diff --git a/.github/scripts/approve-sync-push.cjs b/.github/scripts/approve-sync-push.cjs index 7f4ea85..ecb94db 100644 --- a/.github/scripts/approve-sync-push.cjs +++ b/.github/scripts/approve-sync-push.cjs @@ -6,9 +6,10 @@ const BOT = 'github-actions[bot]'; // resulting pull_request runs for approval and, unlike a person's push, // creates no pull_request_target run, so approve-pr.yml never sees it. The // sync workflow therefore releases the runs for the commit it just pushed, -// under the same rule approve-pr.yml applies: only while a maintainer's -// build-approved label is on the PR. It acts only on its own bot-authored, -// same-repository PR for the branch and commit it pushed. +// under the same rule approve-pr.yml applies: only while build-approved is +// on the PR. The sync applies that label itself, so its pushes build without +// a maintainer. It acts only on its own bot-authored, same-repository PR for +// the branch and commit it pushed. module.exports = async function approveSyncPush({ github, context, core, number, branch, headSha, since, approve = approvePrWorkflows, ...options }) { if (!Number.isInteger(number) || !branch || !headSha || !since) { @@ -25,7 +26,7 @@ module.exports = async function approveSyncPush({ github, context, core, return; } if (!pr.labels.some(label => label.name === 'build-approved')) { - core.info(`PR #${number} has no build-approved label; its runs wait for a maintainer.`); + core.info(`PR #${number} has no build-approved label; its runs stay held.`); return; } await approve({ github, context, core, vouchStatus: 'bot', pullRequest: pr, diff --git a/.github/scripts/auto-merge-pr.cjs b/.github/scripts/auto-merge-pr.cjs new file mode 100644 index 0000000..982bf99 --- /dev/null +++ b/.github/scripts/auto-merge-pr.cjs @@ -0,0 +1,84 @@ +// Whether a PR rides to master on its own once the required checks pass. +// +// The rule is the build gate's, from build-pr.yml: a PR trusted to build is +// trusted to ship. Collaborators, vouched authors and bots are trusted; an +// unknown author is trusted while the PR carries build-approved; a +// denouncement is absolute. Two limits on top of it: +// +// - Only package changes. A PR's workflows, scripts and build tooling never +// run in its own build (build-pr.yml overlays only its package directories +// onto base tooling), so green checks say nothing about them, and after +// merge they run with the publish secrets. Allowed: anything under +// pkgbuilds/, plus the test a package PR brings with it, which is a new +// file under tests/ and lines in test.yml that only run new tests/*.sh. +// test.yml runs on PRs only; an existing test may also run on master +// (builder-images.yml runs tests/build-isolation.sh with packages: write), +// so editing one still needs a maintainer. +// - Not the upstream sync. It labels its own PR build-approved to release +// GitHub's hold on its pushes, which is no one's approval; it stays on the +// reviewed lane. +const PACKAGES = 'pkgbuilds/'; +const TESTS = 'tests/'; +const TEST_WORKFLOW = '.github/workflows/test.yml'; +const TEST_LINE = /^\+\s*\.\/tests\/[A-Za-z0-9._-]+\.sh\s*$/; + +// Every changed line adds a ./tests/.sh call; nothing removed. A diff +// too large for the API has no patch and does not qualify. +function onlyRunsTests(patch) { + if (!patch) return false; + const changed = patch.split('\n').filter(line => + /^[+-]/.test(line) && !line.startsWith('+++') && !line.startsWith('---')); + return changed.length > 0 && changed.every(line => TEST_LINE.test(line)); +} + +function packageChange(file) { + if (file.previous_filename && !file.previous_filename.startsWith(PACKAGES)) return false; + if (file.filename.startsWith(PACKAGES)) return true; + if (file.filename.startsWith(TESTS)) return file.status === 'added'; + if (file.filename === TEST_WORKFLOW) return file.status === 'modified' && onlyRunsTests(file.patch); + return false; +} +const REVIEWED_BRANCHES = /^auto\/sync-upstream(\/|$)/; + +function decide({ pr, files, vouchStatus, repository }) { + if (pr.state !== 'open') return { enable: false, reason: 'PR is not open' }; + if (pr.draft) return { enable: false, reason: 'PR is a draft' }; + + const labelled = pr.labels.some(label => label.name === 'build-approved'); + let trusted; + switch (vouchStatus) { + case 'bot': case 'collaborator': case 'vouched': trusted = true; break; + case 'unknown': trusted = labelled; break; + default: trusted = false; // denounced, or a failed lookup + } + if (!trusted) { + return { enable: false, reason: `author not trusted to build (${vouchStatus || 'missing'}${labelled ? ', labelled' : ''})` }; + } + + if (pr.head.repo?.full_name === repository && REVIEWED_BRANCHES.test(pr.head.ref)) { + return { enable: false, reason: `${pr.head.ref} stays on the reviewed lane` }; + } + + if (!files.length) return { enable: false, reason: 'PR changes no files' }; + const outside = files.filter(file => !packageChange(file)); + if (outside.length) { + const names = outside.slice(0, 3).map(file => file.filename).join(', '); + return { enable: false, reason: `changes more than packages and their new tests: ${names}${outside.length > 3 ? ', ...' : ''}` }; + } + return { enable: true, reason: `${vouchStatus === 'unknown' ? 'build-approved' : vouchStatus} author, package changes only` }; +} + +module.exports = async function autoMerge({ github, context, core, number, vouchStatus }) { + const { data: pr } = await github.rest.pulls.get({ ...context.repo, pull_number: number }); + const files = await github.paginate(github.rest.pulls.listFiles, { + ...context.repo, pull_number: number, per_page: 100, + }); + const decision = decide({ + pr, files, vouchStatus, repository: `${context.repo.owner}/${context.repo.repo}`, + }); + core.info(`#${number}: ${decision.enable ? 'auto-merge' : 'leave for a maintainer'} (${decision.reason})`); + core.setOutput('enable', String(decision.enable)); + core.setOutput('head_sha', pr.head.sha); + return decision; +}; +module.exports.decide = decide; diff --git a/.github/workflows/auto-merge-pr.yml b/.github/workflows/auto-merge-pr.yml new file mode 100644 index 0000000..fa3a4f7 --- /dev/null +++ b/.github/workflows/auto-merge-pr.yml @@ -0,0 +1,108 @@ +name: Auto-merge approved package PRs + +# A package PR trusted to build is trusted to ship: once its builds are +# green it should merge and publish without a maintainer pressing the +# button. This enables GitHub's auto-merge on such PRs; branch protection +# still holds the merge until `result`, `self-tests` and `build-isolation` +# pass, and a red build stays an open PR. .github/scripts/auto-merge-pr.cjs +# has the rule. +# +# Auto-merge is enabled with the PAT in PKGS_BOT_TOKEN: a merge made with the +# built-in GITHUB_TOKEN does not start publish.yml. +# +# pull_request_target runs this default-branch code with secrets; the PR's +# code is never checked out here. +on: + pull_request_target: + types: [opened, reopened, synchronize, ready_for_review, labeled, unlabeled] + workflow_dispatch: + inputs: + pr: + description: 'PR number to evaluate' + required: true + +permissions: + contents: read + pull-requests: read + +concurrency: + group: auto-merge-pr-${{ github.event.pull_request.number || github.event.inputs.pr }} + cancel-in-progress: true + +jobs: + auto-merge: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + + - name: Find the PR's author + id: pr + uses: actions/github-script@v7 + env: + NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr }} + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + ...context.repo, pull_number: Number(process.env.NUMBER), + }); + core.setOutput('number', String(pr.number)); + core.setOutput('author', pr.user.login); + + - id: vouch + uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1 + with: + user: ${{ steps.pr.outputs.author }} + allow-fail: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Decide + id: decide + uses: actions/github-script@v7 + env: + NUMBER: ${{ steps.pr.outputs.number }} + VOUCH_STATUS: ${{ steps.vouch.outputs.status }} + with: + script: | + const autoMerge = require('./.github/scripts/auto-merge-pr.cjs'); + await autoMerge({ github, context, core, + number: Number(process.env.NUMBER), vouchStatus: process.env.VOUCH_STATUS }); + + - name: Enable auto-merge + if: steps.decide.outputs.enable == 'true' + env: + GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }} + PR: ${{ steps.pr.outputs.number }} + HEAD_SHA: ${{ steps.decide.outputs.head_sha }} + run: | + if [[ -z "$GH_TOKEN" ]]; then + echo "::error::Set PKGS_BOT_TOKEN; a GITHUB_TOKEN merge would not publish." + exit 1 + fi + # Idempotent: enabling twice errors. Bot lanes enable their own. + if [[ "$(gh pr view "$PR" -R "$GITHUB_REPOSITORY" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; then + echo "auto-merge already enabled on #$PR" + exit 0 + fi + # --match-head-commit: never arm a merge for a commit newer than + # the one just judged. + gh pr merge --auto --squash --match-head-commit "$HEAD_SHA" "$PR" -R "$GITHUB_REPOSITORY" + + # Removing build-approved withdraws the approval, so withdraw the + # auto-merge it armed too. Only on that event: auto-merge a maintainer + # enabled by hand on any other PR is theirs to keep. + - name: Withdraw auto-merge + if: >- + steps.decide.outputs.enable == 'false' && + github.event.action == 'unlabeled' && github.event.label.name == 'build-approved' + env: + GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }} + PR: ${{ steps.pr.outputs.number }} + run: | + if [[ "$(gh pr view "$PR" -R "$GITHUB_REPOSITORY" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; then + gh pr merge --disable-auto "$PR" -R "$GITHUB_REPOSITORY" + fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ab69575..52bbc62 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,11 +24,10 @@ on: 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 +# Only the publish job serializes (see its concurrency group): two publishes +# into one channel at once would race on the database. Builds run outside the +# lock, so a merge waits behind another merge's signing and upload, seconds, +# never behind its kernel build. jobs: changes: @@ -58,11 +57,10 @@ jobs: echo "matrix=$matrix" >> "$GITHUB_OUTPUT" echo "count=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT" jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix" - # Reuse or rebuild, decided per entry and said out loud. An aarch64 - # tree with no build artifact (PR artifacts last 7 days; a dispatch - # may name any package) goes to the rebuild job, which builds it - # natively on GitHub's arm64 runner. x86_64 builds inside the - # publish job on the droplet, as before. + # Reuse or rebuild, decided per entry and said out loud. A tree with + # no build artifact (PR artifacts last 7 days; a dispatch may name + # any package) goes to the rebuild job: aarch64 natively on GitHub's + # arm64 runner, x86_64 on a builder droplet, one runner per entry. rebuild=() echo "| package | arch | tree | decision |" >> "$GITHUB_STEP_SUMMARY" echo "| --- | --- | --- | --- |" >> "$GITHUB_STEP_SUMMARY" @@ -73,13 +71,23 @@ jobs: 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 | select(. != null) | "run \(.workflow_run.id), expires \(.expires_at)"') + # The plan the publish job makes, made here before a runner is + # asked for: a tree the channel already holds at master's version + # (a re-run, or a dispatch naming a package that is fine) needs + # no build, and an x86 rebuild would wait minutes for a droplet + # to find that out in seconds. + plan="" + [[ -n "$found" ]] || plan=$(bin/build --dry-run --mirror edge --arch "$arch" --package "$package" 2>&1 | grep -E '^==> Plan complete' || true) if [[ -n "$found" ]]; then decision="reuse the build artifact ($found)" + elif [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then + decision="already published at master's version, nothing to build" elif [[ $arch == aarch64 ]]; then decision="no build artifact: rebuild natively on ubuntu-24.04-arm" - rebuild+=("$entry") + rebuild+=("$(jq -c '. + {runner: "[\"ubuntu-24.04-arm\"]"}' <<<"$entry")") else - decision="no build artifact: build in the publish job on the self-hosted builder" + decision="no build artifact: rebuild on a builder droplet" + rebuild+=("$(jq -c '. + {runner: "[\"self-hosted\",\"omarchy-builder\"]"}' <<<"$entry")") fi echo "==> $label: $decision" echo "| $package | $arch | ${hash:0:12} | $decision |" >> "$GITHUB_STEP_SUMMARY" @@ -87,16 +95,19 @@ jobs: echo "rebuild=$(printf '%s\n' "${rebuild[@]}" | jq -sc '{include: .}')" >> "$GITHUB_OUTPUT" echo "rebuild_count=${#rebuild[@]}" >> "$GITHUB_OUTPUT" - # The aarch64 half of "build it now when there is none". It builds exactly - # as build-pr.yml's aarch64 path does (same runner, same builder image, - # same bin/build call) and uploads under the same label, so the publish - # job collects this run's artifact the way it collects a PR's. No secret - # reaches this runner; signing and upload stay on the self-hosted builder. + # "Build it now when there is none". Each entry builds exactly as + # build-pr.yml builds it (same runner kind, same builder image, same + # bin/build call) and uploads under the same label, so the publish job + # collects this run's artifact the way it collects a PR's. No secret + # reaches these runners, and they hold no lock: entries build in parallel, + # and other merges publish while they do. rebuild: needs: changes if: needs.changes.outputs.rebuild_count != '0' - runs-on: ubuntu-24.04-arm - timeout-minutes: 180 + runs-on: ${{ fromJSON(matrix.runner) }} + # The droplets are x86, so aarch64 never builds there. omarchy-mac-boot + # under QEMU took 2h47m; native arm64 and x86 kernels fit easily. + timeout-minutes: 240 permissions: contents: read strategy: @@ -109,7 +120,7 @@ jobs: # The same check the publish job makes before building: a re-run for a # package the channel already holds at master's version builds # nothing, and uploads nothing that could shadow the published file. - - name: Build ${{ matrix.package }} (${{ matrix.arch }}, native) + - name: Build ${{ matrix.package }} (${{ matrix.arch }}) id: build env: CONTAINER_ENGINE: docker @@ -140,23 +151,26 @@ jobs: if-no-files-found: error retention-days: 7 - # One job for the whole merge. It collects every PR artifact for the - # merged tree (building only what has none; aarch64 comes from the - # rebuild job above), then walks each channel and + # One job for the whole merge. It collects every artifact for the merged + # tree (PR builds, or the rebuild job above), 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. + # is no race between packages; the concurrency group keeps one merge's + # publish from overlapping the next. It builds nothing, so it runs on a + # hosted runner in about a minute instead of waiting for a droplet. # It waits for the rebuild job and runs whatever that job's result: a # failed rebuild leaves its package without an artifact, and the collect # step below records that and stops before any publish. publish: needs: [changes, rebuild] if: ${{ !cancelled() && needs.changes.result == 'success' && needs.changes.outputs.count != '0' }} - runs-on: [self-hosted, omarchy-builder] + runs-on: ubuntu-latest environment: publish - timeout-minutes: 240 + timeout-minutes: 30 + concurrency: + group: publish + cancel-in-progress: false steps: - uses: actions/checkout@v4 with: @@ -172,8 +186,8 @@ jobs: 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 + # Fetch each package's artifact into build-output/edge//: the PR's, + # or the rebuild job's when the PR's had expired. 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). @@ -199,8 +213,8 @@ jobs: mkdir -p "build-output/edge/$arch" if [[ -n "$found" ]]; then if [[ $from_run == "${{ github.run_id }}" ]]; then - kind=native-rebuild - echo "==> $label: artifact from this run's native $arch rebuild" + kind=rebuild + echo "==> $label: artifact from this run's $arch rebuild" else kind=pr-artifact echo "==> $label: reusing the build artifact from run $from_run" @@ -225,20 +239,11 @@ jobs: jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"already-published"}' >> sources.jsonl continue fi - # aarch64 never builds here: this droplet is x86 and would - # emulate it. No artifact means the native rebuild failed (see - # the rebuild job), or an artifact expired between planning - # and now (re-run all jobs). - if [[ $arch == aarch64 ]]; then - echo "::error::$label: no artifact from the native rebuild; not building aarch64 under emulation" - jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"native-build-failed"}' >> sources.jsonl; failed=1; break - 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 + # Nothing builds here. No artifact means the rebuild failed (see + # the rebuild job), or an artifact expired between planning and + # now (re-run all jobs). + echo "::error::$label: no artifact from the rebuild job" + jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"build-failed"}' >> sources.jsonl; failed=1; break fi done < plan.txt ls -1 build-output/edge/*/*.pkg.tar.zst 2>/dev/null || true @@ -280,8 +285,26 @@ jobs: 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 + # A fresh runner has no images, and building this one here cost + # every publish about 100 s (and 20 s more to start a container + # from it) for the 14 s of signing and upload it is needed for. + # builder-images.yml already publishes the tested image for exactly + # these build inputs under their key; pull that. Build only when no + # image carries the key: a merge that changed build/ publishes + # before the refresh it triggered has finished. + builder=omarchy-pkg-builder:latest-x86_64-edge + if ! docker image inspect "$builder" >/dev/null 2>&1; then + key=$(bin/builder-image key --arch x86_64 --mirror edge) + published="ghcr.io/omacom/omarchy-pkg-builder:$key" + if docker pull --quiet "$published" && + [[ $(docker image inspect "$published" --format '{{index .Config.Labels "org.omarchy.builder.key"}}') == "$key" ]]; then + docker tag "$published" "$builder" + echo "==> Builder image: pulled $published" + else + echo "==> Builder image: none published for $key, building it" + docker buildx build --load -t "$builder" --build-arg MIRROR=edge build + fi + fi # Group the merge's files by the (channel, architecture) slot each # belongs to. A package's files live under build-output/edge/> "$GITHUB_OUTPUT" fi - # Runs created by this push are newer than this; the approve job - # waits for them. A minute's slack absorbs runner clock skew. - - name: Record push time - if: steps.changes.outputs.has_changes == 'true' - id: pushed - run: echo "at=$(date -u -d '-1 minute' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" - - name: Create Pull Request if: steps.changes.outputs.has_changes == 'true' id: cpr uses: peter-evans/create-pull-request@v7 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PKGS_BOT_TOKEN }} commit-message: 'chore: rebuild against updated dependencies' title: "chore: rebuild against updated dependencies${{ steps.branch.outputs.scope && format(' ({0})', steps.branch.outputs.scope) || '' }}" body: | @@ -109,10 +114,27 @@ jobs: bump is what makes the rebuilt package an upgrade pacman will offer; without it the build produces the version already published and no one receives it. + + This PR auto-merges once the build checks pass. A failing rebuild + leaves it open for a maintainer. branch: ${{ steps.branch.outputs.branch }} delete-branch: true labels: automated - reviewers: ryanrhughes + + # Auto-merge, not a direct merge: branch protection still has to see + # the build checks green before the rebuild lands. + - name: Enable auto-merge + if: steps.cpr.outputs.pull-request-number != '' + env: + GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }} + PR: ${{ steps.cpr.outputs.pull-request-number }} + run: | + # Idempotent across re-runs of an updated PR: enabling twice errors. + if [[ "$(gh pr view "$PR" -R "${{ github.repository }}" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; then + echo "auto-merge already enabled on #$PR" + exit 0 + fi + gh pr merge --auto --merge "$PR" -R "${{ github.repository }}" - name: Notify Basecamp on failure if: failure() && env.BASECAMP_CHATBOT_URL != '' @@ -125,35 +147,3 @@ jobs: "🔴 Rebuild trigger sync failed
View run" \ '{content: $content}')" \ "$BASECAMP_CHATBOT_URL" - - # GitHub holds pull_request runs from a GITHUB_TOKEN push for approval and - # creates no pull_request_target run for it, so approve-pr.yml never sees - # the sync's own pushes. Once a maintainer has labelled the PR - # build-approved, release the held runs for the commit just pushed. A - # separate job, so the sync container's token never holds actions: write. - approve: - needs: sync - if: ${{ !cancelled() && needs.sync.outputs.operation == 'updated' }} - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read - pull-requests: read - actions: write - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Release held build and test runs if build-approved - uses: actions/github-script@v7 - env: - NUMBER: ${{ needs.sync.outputs.number }} - BRANCH: ${{ needs.sync.outputs.branch }} - HEAD_SHA: ${{ needs.sync.outputs.head_sha }} - SINCE: ${{ needs.sync.outputs.pushed_at }} - with: - script: | - const approve = require('./.github/scripts/approve-sync-push.cjs'); - const { NUMBER, BRANCH, HEAD_SHA, SINCE } = process.env; - await approve({ github, context, core, number: Number(NUMBER), - branch: BRANCH, headSha: HEAD_SHA, since: SINCE }); diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 0c5ccac..7bfb34a 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -113,7 +113,11 @@ jobs: are left untouched; check the workflow result for outstanding failures. branch: ${{ steps.branch.outputs.branch }} delete-branch: true - labels: automated + # The bot is trusted; build-approved lets the approve job below + # release GitHub's hold on its pushes without a maintainer. + labels: | + automated + build-approved reviewers: ryanrhughes - name: Notify Basecamp on failure @@ -130,12 +134,13 @@ jobs: # GitHub holds pull_request runs from a GITHUB_TOKEN push for approval and # creates no pull_request_target run for it, so approve-pr.yml never sees - # the sync's own pushes. Once a maintainer has labelled the PR - # build-approved, release the held runs for the commit just pushed. A - # separate job, so the sync container's token never holds actions: write. + # the sync's own pushes. The sync labels its PR build-approved, so release + # the held runs for the commit just pushed, whether it opened the PR or + # updated it. A separate job, so the sync container's token never holds + # actions: write. approve: needs: sync - if: ${{ !cancelled() && needs.sync.outputs.operation == 'updated' }} + if: ${{ !cancelled() && (needs.sync.outputs.operation == 'created' || needs.sync.outputs.operation == 'updated') }} runs-on: ubuntu-latest timeout-minutes: 5 permissions: @@ -146,7 +151,7 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - name: Release held build and test runs if build-approved + - name: Release held build and test runs uses: actions/github-script@v7 env: NUMBER: ${{ needs.sync.outputs.number }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80a59cd..92af7c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,6 +67,7 @@ jobs: ./tests/artifact-helpers.sh ./tests/limine-mkinitcpio-hook.sh ./tests/voxtype-bin-install.sh + ./tests/superwhisper-bin-install.sh pacman -S --noconfirm --quiet rclone >/dev/null ./tests/publish-artifact.sh ' diff --git a/.github/workflows/unpublish.yml b/.github/workflows/unpublish.yml new file mode 100644 index 0000000..2f9a6d2 --- /dev/null +++ b/.github/workflows/unpublish.yml @@ -0,0 +1,79 @@ +name: Unpublish retired packages + +# Takes packages out of the channel databases after their recipes are gone +# from master. Deleting a recipe stops it building; this stops pacman +# offering it. Files stay in the bucket (see bin/unpublish-packages). +# +# Only packages with no recipe on master: one that still has a recipe would +# come back on its next publish, and refusing it keeps a typo from pulling a +# live package out of every channel. + +on: + workflow_dispatch: + inputs: + packages: + description: "Space-separated pkgbases whose recipes were removed from master" + required: true + channels: + description: "Channels to remove them from" + required: true + default: "edge rc stable" + +jobs: + unpublish: + runs-on: ubuntu-latest + environment: publish + timeout-minutes: 15 + # The publish job's group: one writer per channel database at a time. + concurrency: + group: publish + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Refuse packages that still have a recipe + env: + PACKAGES: ${{ github.event.inputs.packages }} + CHANNELS: ${{ github.event.inputs.channels }} + run: | + set -euo pipefail + for c in $CHANNELS; do + [[ $c == edge || $c == rc || $c == stable ]] || { echo "::error::Unknown channel: $c"; exit 1; } + done + for p in $PACKAGES; do + [[ $p =~ ^[a-z0-9@._+-]+$ ]] || { echo "::error::Not a package name: $p"; exit 1; } + if [[ -e pkgbuilds/$p ]]; then + echo "::error::pkgbuilds/$p still exists on master; remove the recipe first" + exit 1 + fi + done + + - name: Unpublish + env: + PACKAGES: ${{ github.event.inputs.packages }} + CHANNELS: ${{ github.event.inputs.channels }} + 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 }} + OMARCHY_PUBLISH_PREFIX: ${{ vars.OMARCHY_PUBLISH_PREFIX }} + run: | + set -euo pipefail + # repo-remove and bsdtar are Arch tools: run in the tested builder + # image, as the publish job does. + builder=ghcr.io/omacom/omarchy-pkg-builder:$(bin/builder-image key --arch x86_64 --mirror edge) + docker pull --quiet "$builder" + for mirror in $CHANNELS; do + for arch in x86_64 aarch64; do + docker run --rm \ + -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 "$builder" \ + bin/unpublish-packages --remote R2:omarchy-pkgs --mirror "$mirror" --arch "$arch" $PACKAGES + done + done diff --git a/README.md b/README.md index 9cec261..46ec160 100644 --- a/README.md +++ b/README.md @@ -565,12 +565,26 @@ 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 Omarchy-owned package with metadata bin/package-worktree # Inspect historical AUR provenance in a scratch workspace -bin/repo remove # Remove package +bin/repo remove # Remove package (host workflow; CI uses unpublish.yml) bin/sync-upstream # Update packages that track a vendor release feed bin/sync-rebuilds # Bump pkgrel for packages whose dependencies moved bin/clean-docker # Clear Docker images/cache (forces fresh rebuild) ``` +### Retiring a package + +Delete its recipe directory in a PR. Once that merges, take it out of the +channel databases with the **Unpublish retired packages** workflow: + +``` +gh workflow run unpublish.yml -f packages=" ..." -f channels="edge rc stable" +``` + +It removes every entry built from those pkgbases (split outputs and `-debug` +included) from both architectures' databases, and refuses any package that +still has a recipe on master. Package files stay in the bucket: published +filenames are immutable, and nothing references them once the entries are gone. + ### Package Metadata Tools ```bash @@ -893,15 +907,15 @@ The repository includes GitHub workflows and systemd services for automated rele #### GitHub Workflows 1. **sync-upstream.yml** (Every 6 hours): Watches direct upstream feeds and updates owned recipes on the reviewed lane. 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. +2. **sync-rebuilds.yml** (Every 6 hours): Bumps pkgrel for packages whose `rebuild_on` dependencies have moved in the official repositories, opens a PR, and enables auto-merge. The PR lands once its build checks pass; a rebuild that fails stays an open red PR for a maintainer. 3. **track-branches.yml** (Every 2 hours): The unattended lane. Pins every `"auto_merge": true` package to the tip of its watched branch once its commit timestamp clears `min_release_age`, opens one PR for all of them, and enables auto-merge. Packages pinned from the same branch move together or not at all, including targeted syncs. The PR builds like any other; a tip that fails to build stays an open red PR until the next tick supersedes it. -The tracking PR and auto-merge use the PAT stored in `PKGS_BOT_TOKEN`, with +The tracking and rebuild PRs and their auto-merge use the PAT stored in `PKGS_BOT_TOKEN`, with Contents and Pull requests write access to this repository and an owner trusted to trigger builds. The existing controller PAT can be reused. No GitHub App is required. The built-in Actions `GITHUB_TOKEN` cannot drive the unattended -build-and-publish chain, so the tracker requires this secret before it runs. -The reviewed sync workflows continue to use `GITHUB_TOKEN` and require +build-and-publish chain, so both workflows require this secret before they run. +The reviewed upstream sync continues to use `GITHUB_TOKEN` and requires maintainer approval as before. See [setup instructions](docs/upstream-sources.md#enable-unattended-branch-updates). Scheduled runs regenerate one shared PR (`auto/sync-upstream`, `auto/sync-rebuilds`) @@ -912,13 +926,26 @@ pending updates. The next scheduled run still picks the same update up in the shared PR if it has not merged by then; identical package trees reuse the same build artifacts. -Sync PRs are pushed with `GITHUB_TOKEN`, so GitHub holds their build and test +Upstream sync PRs are pushed with `GITHUB_TOKEN`, so GitHub holds their build and test runs for approval on every push and starts no `pull_request_target` workflow -for them. Once **`build-approved`** is on a sync PR, the sync workflow's own -`approve` job releases the held runs for each commit it pushes. A push to an -`auto/sync-*` branch does not cancel the PR's in-flight build: the new build -waits for it and then reuses its artifacts, so a long aarch64 build is not -restarted by every sync. +for them. The upstream sync labels its own PRs **`build-approved`**, and +its `approve` job releases the held runs for each commit they push, including +the push that opens the PR. A push to an `auto/sync-*` branch does not cancel +the PR's in-flight build: the new build waits for it and then reuses its +artifacts, so a long aarch64 build is not restarted by every sync. + +Package PRs that are trusted to build also merge themselves. +`auto-merge-pr.yml` enables auto-merge (with `PKGS_BOT_TOKEN`, so the merge +publishes) on any open, non-draft PR whose author is a collaborator, vouched, +or a bot, or that carries **`build-approved`**, and that changes only +packages: anything under `pkgbuilds/`, plus the test a package PR brings with +it (a new file under `tests/`, and `test.yml` lines that only run new +`./tests/*.sh`). The PR lands once `result`, `self-tests` and +`build-isolation` pass; a red build stays open. PRs that also touch +workflows, scripts, build tooling or existing tests still need a maintainer, as does +the upstream sync (`auto/sync-upstream`), which labels itself. Removing +`build-approved` withdraws the auto-merge it armed. For a PR opened before +the workflow existed, run it by hand: `gh workflow run auto-merge-pr.yml -f pr=`. To approve builds for an unvouched contributor's PR, apply **`build-approved`**. Until approval, the PR shows **Awaiting build approval** and its required diff --git a/bin/build b/bin/build index 765e071..41e8867 100755 --- a/bin/build +++ b/bin/build @@ -86,7 +86,7 @@ while [[ $# -gt 0 ]]; do echo " $0 --arch aarch64" echo " $0 --mirror stable" echo " $0 --package yay" - echo " $0 --package yay elephant cursor-bin" + echo " $0 --package yay walker cursor-bin" echo "" echo "Environment (for CI and resumed builds; defaults keep today's behaviour):" echo " OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output and reuse packages already there" diff --git a/bin/unpublish-packages b/bin/unpublish-packages new file mode 100755 index 0000000..e5c3313 --- /dev/null +++ b/bin/unpublish-packages @@ -0,0 +1,80 @@ +#!/bin/bash +# Retire packages from one channel of the remote repository. +# +# unpublish-packages --mirror --arch +# +# The counterpart of publish-artifact, with the same steps: +# 1. pull the channel's current database from the remote +# 2. find every entry built from the named pkgbases (so a package's split +# outputs and -debug go with it) +# 3. repo-remove them +# 4. upload the database +# +# Package files stay in the bucket. Published filenames are immutable and the +# R2 cache cannot recover from a rewrite; an unreferenced file costs nothing +# and pacman never sees it. Naming a package the channel does not hold is not +# an error, so a re-run is harmless. +# +# 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:-} +BASES=() +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,19p' "$0"; exit 0 ;; + -*) print_error "Unknown option: $1"; exit 1 ;; + *) BASES+=("$1"); shift ;; + esac +done +(( ${#BASES[@]} )) || { print_error "No packages given"; exit 1; } + +DEST="$REMOTE/${PREFIX:+$PREFIX/}$MIRROR/$ARCH" +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT +print_header "Unpublish from $DEST" + +# --- 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 + print_info "No database at $DEST; nothing to remove" + exit 0 +fi +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 +before=$(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') +print_info "Pulled current database ($before entries)" + +# --- 2. entries built from the named pkgbases ----------------------------- +names=$(bsdtar -xOf "$WORK/repo/omarchy.db.tar.zst" --include '*/desc' | + awk -v bases=" ${BASES[*]} " ' + /^%NAME%$/ { getline name } + /^%BASE%$/ { getline base; if (index(bases, " " base " ")) print name }') +if [[ -z "$names" ]]; then + print_info "None of ${BASES[*]} is in $MIRROR/$ARCH; nothing to remove" + exit 0 +fi +mapfile -t NAMES <<<"$names" +for n in "${NAMES[@]}"; do print_step "removing $n"; done + +# --- 3. repo-remove --------------------------------------------------------- +( cd "$WORK/repo" && repo-remove --quiet omarchy.db.tar.zst "${NAMES[@]}" ) +ln -sf omarchy.db.tar.zst "$WORK/repo/omarchy.db" +ln -sf omarchy.files.tar.zst "$WORK/repo/omarchy.files" +after=$(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') +(( before - after == ${#NAMES[@]} )) || { + print_error "Expected to remove ${#NAMES[@]} entries, removed $((before - after))"; exit 1; } +print_info "Database now has $after entries" + +# --- 4. upload the database ------------------------------------------------- +rclone copy "$WORK/repo" "$DEST" --s3-no-head --checksum --copy-links --filter '+ omarchy.db*' --filter '+ omarchy.files*' --filter '- *' +print_success "Removed ${#NAMES[@]} package(s) from $DEST" diff --git a/ci/README.md b/ci/README.md index 416f8d3..489862b 100644 --- a/ci/README.md +++ b/ci/README.md @@ -12,9 +12,12 @@ signing on merge exactly as before. - `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 + Polls for queued jobs with our label, creates one g5-32vcpu-64gb-50gb droplet 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 + `MAX_AGE_MINUTES`, or still provisioning after `MAX_BOOT_MINUTES`. Builders go in any region DigitalOcean lists the size in + stock in (`REGIONS` only sets which to try first); a refused create, logged + with DigitalOcean's message, falls back to the next region, then the next + of `SIZES`. 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. @@ -31,6 +34,13 @@ 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. +Each tick pulls the box's checkout first, so a merged `controller.sh` is live +within a minute. The unit and timer are copies made at creation; after +changing them, on the box: + + cp /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.{service,timer} /etc/systemd/system/ + systemctl daemon-reload + ## What the spike proved (2026-09-17, fork ryanrhughes/omarchy-pkgs) - `bin/build` works from a bare clone: with no local published tree it @@ -70,8 +80,11 @@ Watch it with `journalctl -u omarchy-controller -f` on the box. packages then signatures then the db. - aarch64 under QEMU with credential-preserving binfmt. PR builds now run aarch64 natively on `ubuntu-24.04-arm` (QEMU was up to ~15x slower). When a - merged aarch64 tree has no artifact, publish.yml rebuilds it there too, in its - own job, and signs and uploads it on the droplet like a PR artifact. + merged tree has no artifact, publish.yml rebuilds it in its own job, aarch64 + there and x86_64 on a droplet, outside the publish lock. +- Publish itself builds nothing: it signs and uploads on `ubuntu-latest` in the + tested builder image pulled from GHCR, and only that job holds the `publish` + concurrency group, so a merge waits for seconds of signing, not for builds. - 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 diff --git a/ci/controller-box/controller.env.example b/ci/controller-box/controller.env.example index 37ae372..c2b5979 100644 --- a/ci/controller-box/controller.env.example +++ b/ci/controller-box/controller.env.example @@ -5,10 +5,14 @@ GITHUB_TOKEN=github_pat_... REPO=omacom/omarchy-pkgs LABEL=omarchy-builder TAG=omarchy-builder -REGION=ric1 -SIZE=g5-32vcpu-64gb-50gb +# Builder sizes, tried in order in any region that has them in stock. +SIZES="g5-32vcpu-64gb-50gb g5-32vcpu-128gb-50gb" +# Optional: regions to try first, e.g. "ric1". Empty means any. +REGIONS= MAX_DROPLETS=6 MAX_AGE_MINUTES=200 +# Delete a droplet DigitalOcean still reports as provisioning after this long. +MAX_BOOT_MINUTES=10 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. diff --git a/ci/controller-box/omarchy-controller.service b/ci/controller-box/omarchy-controller.service index 12d2e9c..e8848b8 100644 --- a/ci/controller-box/omarchy-controller.service +++ b/ci/controller-box/omarchy-controller.service @@ -7,6 +7,9 @@ Wants=network-online.target Type=oneshot User=controller EnvironmentFile=/etc/omarchy-controller.env +# Run the branch's current controller, not the one cloned when the box was +# built. As root (the checkout's owner); a failed pull keeps the last one. +ExecStartPre=-+/usr/bin/git -C /opt/omarchy-pkgs pull --ff-only --quiet 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.sh b/ci/controller.sh index cc60950..d69f4e3 100755 --- a/ci/controller.sh +++ b/ci/controller.sh @@ -4,7 +4,8 @@ # 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 +# that have powered off, exceeded MAX_AGE_MINUTES, or are still provisioning +# after MAX_BOOT_MINUTES. The reaper does not # trust its own bookkeeping: it lists by tag and acts on what DigitalOcean # reports. # @@ -21,11 +22,19 @@ 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} +# Sizes to try, in order, in any region DigitalOcean lists them in stock. A +# size can sell out in a region for hours; the create is then refused with +# 422 and the next region, then the next size, is tried. REGIONS only orders +# the regions tried first. SIZE and REGION, if set, are one-item lists. +SIZES=${SIZES:-${SIZE:-g5-32vcpu-64gb-50gb g5-32vcpu-128gb-50gb}} +REGIONS=${REGIONS:-${REGION:-}} IMAGE=${IMAGE:-ubuntu-24-04-x64} MAX_DROPLETS=${MAX_DROPLETS:-4} MAX_AGE_MINUTES=${MAX_AGE_MINUTES:-200} +# A droplet DigitalOcean still reports as "new" this long after creation is +# stuck provisioning. Left alone it counts as a runner booting, and holds a +# queued job until MAX_AGE_MINUTES. +MAX_BOOT_MINUTES=${MAX_BOOT_MINUTES:-10} 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). @@ -39,7 +48,8 @@ log() { echo "$(date '+%F %T') $*"; } # 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" \ + # --fail-with-body: a refused create still prints why. + curl -sS --fail-with-body -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ -H "Content-Type: application/json" "https://api.digitalocean.com/v2/$path" "$@" } gh_api() { # gh_api [curl args...] @@ -55,7 +65,8 @@ reap() { 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 + if [[ $status == off ]] || (( age > MAX_AGE_MINUTES )) || + { [[ $status == new ]] && (( age > MAX_BOOT_MINUTES )); }; then log "deleting droplet $id (status=$status age=${age}m)" do_api "droplets/$id" -X DELETE fi @@ -64,18 +75,40 @@ reap() { } # --- demand ---------------------------------------------------------------- +# Emit every item, including later pages of large build matrices. Keep API +# failures fatal so a failed query cannot look like an empty queue. +gh_items() { + local path=$1 key=$2 page=1 response count separator="?" + [[ $path != *"?"* ]] || separator="&" + while :; do + response=$(gh_api "${path}${separator}per_page=100&page=$page") || return 1 + count=$(jq -er --arg key "$key" '.[$key] | arrays | length' <<< "$response") || return 1 + jq -c --arg key "$key" '.[$key][]' <<< "$response" || return 1 + (( count == 100 )) || break + ((page += 1)) + done +} + queued_jobs() { - local run - gh_api "repos/$REPO/actions/runs?status=queued&per_page=50" --get \ - | jq -r '.workflow_runs[].id' | + local status runs run + # A workflow can be in progress while most of its matrix is still queued. + runs=$( + for status in queued in_progress; do + gh_items "repos/$REPO/actions/runs?status=$status" workflow_runs || exit 1 + done + ) || return 1 + jq -r '.id' <<< "$runs" | sort -u | 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 + gh_items "repos/$REPO/actions/runs/$run/jobs" jobs | + jq -r --arg l "$LABEL" 'select(.status=="queued") | select(.labels | index($l)) | .id' || return 1 + done | sort -u | wc -l } live_droplets() { - do_api "droplets?tag_name=$TAG&per_page=200" | jq '[.droplets[] | select(.status != "off")] | length' + # Not the ones reap() just deleted: DigitalOcean can list them for a while. + do_api "droplets?tag_name=$TAG&per_page=200" | jq --argjson boot "$MAX_BOOT_MINUTES" ' + [.droplets[] | select(.status != "off") + | select(.status != "new" or (now - (.created_at | fromdateiso8601)) / 60 <= $boot)] | length' } busy_runners() { @@ -83,22 +116,59 @@ busy_runners() { | jq --arg l "$LABEL" '[.runners[] | select(.busy) | select(any(.labels[]; .name == $l))] | length' } +# --- capacity -------------------------------------------------------------- +# "size region" lines to try, best first: SIZES order, then REGIONS order, +# then every other region where DigitalOcean lists the size in stock. +candidates() { + local page=1 response count catalog="" + while :; do + response=$(do_api "sizes?per_page=200&page=$page") || return 1 + count=$(jq -er '.sizes | arrays | length' <<< "$response") || return 1 + catalog+=$(jq -c '.sizes[]' <<< "$response")$'\n' + (( count == 200 )) || break + ((page += 1)) + done + jq -rs --arg sizes "$SIZES" --arg regions "$REGIONS" ' + ($regions | split(" ") | map(select(length > 0))) as $pref + | INDEX(.slug) as $by + | $sizes | split(" ") | map(select(length > 0)) | .[] + | . as $size | $by[$size] // {} | select(.available == true) + | .regions as $in + | (($pref | map(select(. as $r | $in | index($r)))) + ($in - $pref))[] + | "\($size) \(.)"' <<< "$catalog" +} + # --- create ---------------------------------------------------------------- +# Built once per tick by the first create; a refused pair is dropped from it. +CANDIDATES="" create_droplet() { - local token userdata name body + local token userdata name size region body response + [[ -n $CANDIDATES ]] || CANDIDATES=$(candidates) || return 1 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)"' + while read -r size region; do + [[ -n $size ]] || continue + 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 in $region)" + if response=$(do_api droplets -X POST -d "$body"); then + jq -r '"created droplet \(.droplet.id)"' <<< "$response" + return 0 + fi + log "$size in $region refused: $(jq -r .message <<< "$response" 2>/dev/null || echo "$response")" + CANDIDATES=$(grep -Fvx "$size $region" <<< "$CANDIDATES" || true) + done <<< "$CANDIDATES" + # Every size refused everywhere: the rest of this tick's creates would be too. + log "no size in '$SIZES' can be created in any region" + return 1 } controller_tick() { + CANDIDATES="" reap local queued live busy available need room queued=$(queued_jobs) diff --git a/ci/runner-cloud-init.yaml b/ci/runner-cloud-init.yaml index c05e32e..1cb409e 100644 --- a/ci/runner-cloud-init.yaml +++ b/ci/runner-cloud-init.yaml @@ -45,6 +45,10 @@ write_files: content: | #!/bin/bash set -euo pipefail + # Power off after the one job, and also when the download or the + # registration fails: the controller deletes powered-off droplets, but + # counts a running one as a runner still booting until MAX_AGE_MINUTES. + trap 'sudo poweroff' EXIT cd /home/runner mkdir -p actions-runner && cd actions-runner arch=$(uname -m); [[ $arch == x86_64 ]] && arch=x64 @@ -58,8 +62,6 @@ write_files: --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 diff --git a/docs/upstream-sources.md b/docs/upstream-sources.md index 2e30f07..2971cb9 100644 --- a/docs/upstream-sources.md +++ b/docs/upstream-sources.md @@ -151,7 +151,6 @@ in `origin` and has no effect on release selection. | `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) | @@ -162,21 +161,6 @@ in `origin` and has no effect on release selection. | `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/) | @@ -213,7 +197,6 @@ in `origin` and has no effect on release selection. | `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) | @@ -221,12 +204,10 @@ in `origin` and has no effect on release selection. ## 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`. +`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. `cua-driver-bin` is a deliberate hold: Omarchy bumps it by hand, so a Cua release ships only when a maintainer has verified it. It keeps its `.omarchy/upstream.sh` hook and `min_release_age`, so lifting the hold means removing `"sync": false`. `cua-hyprland-plugin` declares no upstream source, so no automation updates it either. diff --git a/pkgbuilds/1password-beta/PKGBUILD b/pkgbuilds/1password-beta/PKGBUILD index a2cef4c..1ada719 100644 --- a/pkgbuilds/1password-beta/PKGBUILD +++ b/pkgbuilds/1password-beta/PKGBUILD @@ -10,7 +10,7 @@ case "${CARCH}" in ;; esac pkgver=8.12.40_27.BETA -pkgrel=1 +pkgrel=2 conflicts=('1password' '1password-beta-bin') pkgdesc="Password manager and secure wallet" arch=('x86_64' 'aarch64') diff --git a/pkgbuilds/1password-cli/PKGBUILD b/pkgbuilds/1password-cli/PKGBUILD index 5686a0b..eaeb90b 100644 --- a/pkgbuilds/1password-cli/PKGBUILD +++ b/pkgbuilds/1password-cli/PKGBUILD @@ -3,7 +3,7 @@ pkgname=1password-cli pkgver=2.40.0 -pkgrel=1 +pkgrel=2 pkgdesc="1Password command line tool" arch=('x86_64' 'i686' 'arm' 'armv6h' 'aarch64') url="https://app-updates.agilebits.com/product_history/CLI2" diff --git a/pkgbuilds/1password/PKGBUILD b/pkgbuilds/1password/PKGBUILD index 706bdd0..4da39b3 100644 --- a/pkgbuilds/1password/PKGBUILD +++ b/pkgbuilds/1password/PKGBUILD @@ -1,6 +1,6 @@ pkgname=1password -pkgver=8.12.38 -pkgrel=1 +pkgver=8.12.40 +pkgrel=2 conflicts=('1password-beta' '1password-beta-bin') pkgdesc="Password manager and secure wallet" arch=('x86_64' 'aarch64') @@ -17,12 +17,12 @@ source_aarch64=( "https://downloads.1password.com/linux/tar/stable/aarch64/1password-${pkgver}.arm64.tar.gz.sig" ) sha256sums_x86_64=( - '8b9767276ad6795a8f1b79306aa8282f7d79617c7ca08beda7846ff79a1bb4f2' - '5e22ccd53626d814b84de65f9637c2e6994f9f42d4df29f611bc1e4f7e3057f8' + '0ae9645d31be78a8fb57d15f49e5fa4f0b67a0b3608797a410e8fd36f0206266' + '2f777820dc2eb6e7b7b38f4557a897f97fb3259443261fdfb2008127bd975817' ) sha256sums_aarch64=( - 'cb8b3667a0f51705e40a4e1e4ab83d440c57324cfab3f3bfacb713e40e63129d' - '2e92ec19393c7bdaf84e62f2b9ed493f2871b043ae875b302b049f4e2f2ec8e1' + '7476c0fc8215338cd9f304b14822688010fd8ed54d5ea4367c0bbbf72845be1e' + '80412176560a3f76180f7c05ae7ebafe21fac764349cfeed71fe498ee25564ee' ) validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22') diff --git a/pkgbuilds/aether/PKGBUILD b/pkgbuilds/aether/PKGBUILD index e7a3bb3..5091bac 100644 --- a/pkgbuilds/aether/PKGBUILD +++ b/pkgbuilds/aether/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Bjarne Øverli pkgname=aether -pkgver=4.31.1 -pkgrel=1 +pkgver=4.32.0 +pkgrel=2 pkgdesc='Desktop theming application - extract colors from wallpapers and apply cohesive themes' arch=('x86_64' 'aarch64') url='https://github.com/omacom/aether' @@ -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=('bf828f39d4317a1fb52343712dcf6b22824780596850c32426ca19ad825d18d6') -sha256sums_x86_64=('2ac775a33d63d60e4686cf4ab1515edd32041d3ebccca061f736255e4ae1387a') -sha256sums_aarch64=('d74313db4ba62da50c37228ff2ee2d2b1391399e73ac4f4b473a5326edd88b7e') +sha256sums=('3234e5138a46699f8f47330ba582244d5063c8356ffaae1458323e4dda02be82') +sha256sums_x86_64=('c705a4abef734b17ae37cdbff58a9a796517e6aa5f93ef18b68e3ee99d547ff9') +sha256sums_aarch64=('f70195deba008204d58661c3ec1442a16e63f3358b4579969facb0e55dbf63c4') noextract=("aether-linux-amd64-${pkgver}" "aether-linux-arm64-${pkgver}") package() { diff --git a/pkgbuilds/asusctl/.nvchecker.toml b/pkgbuilds/asusctl/.nvchecker.toml deleted file mode 100644 index e98ca30..0000000 --- a/pkgbuilds/asusctl/.nvchecker.toml +++ /dev/null @@ -1,3 +0,0 @@ -[asusctl] -source = "git" -git = "https://github.com/OpenGamingCollective/asusctl.git" diff --git a/pkgbuilds/asusctl/.omarchy/package.json b/pkgbuilds/asusctl/.omarchy/package.json deleted file mode 100644 index 19004c8..0000000 --- a/pkgbuilds/asusctl/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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 deleted file mode 100644 index 97e8b8a..0000000 --- a/pkgbuilds/asusctl/PKGBUILD +++ /dev/null @@ -1,59 +0,0 @@ -# Maintainer: Mahdi Sarikhani -# Contributor: Fabian Bornschein -# Contributor: Static_Rocket - -pkgbase=asusctl -pkgname=(asusctl rog-control-center) -pkgver=6.5.0 -pkgrel=1 -pkgdesc="Daemon and tools to control your ASUS ROG laptop" -arch=('x86_64') -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=('4179e08a60f9480b62e41d84faade1f46407140a213ca4d60c8699837a2486bda8e66b4ca43fa06149667d02e3d060c3efd792348f9a93a675f762d6ea2d05f8') - -prepare() { - cd "${pkgbase}-${pkgver}" - export RUSTUP_TOOLCHAIN=stable - cargo fetch --locked --target host-tuple -} - -build() { - cd "${pkgbase}-${pkgver}" - export RUSTUP_TOOLCHAIN=stable - export CARGO_TARGET_DIR=target - make build -} - -package_asusctl() { - pkgdesc="An utility for Linux to control many aspects of various ASUS laptops" - depends=('glibc' 'libgcc' 'libusb' 'systemd-libs') - optdepends=( - 'acpi_call: fan control' - 'asusctltray: tray profile switcher' - 'rog-control-center: graphical user interface for asusctl' - 'supergfxctl: hybrid GPU control' - ) - install=asusctl.install - - cd "${pkgbase}-${pkgver}" - make DESTDIR="${pkgdir}" \ - install-asusctl \ - install-asusd \ - install-asusd_user \ - install-asus-shutdown \ - install-data-asusd \ - install-data-asusd_user -} - -package_rog-control-center() { - pkgdesc="Graphical user interface for asusctl" - depends=('asusctl' 'fontconfig' 'glibc' 'hicolor-icon-theme' 'libgcc' 'systemd-libs') - - cd "${pkgbase}-${pkgver}" - make DESTDIR="${pkgdir}" \ - install-rog_gui \ - install-data-rog_gui -} diff --git a/pkgbuilds/asusctl/asusctl.install b/pkgbuilds/asusctl/asusctl.install deleted file mode 100644 index c6ccfe7..0000000 --- a/pkgbuilds/asusctl/asusctl.install +++ /dev/null @@ -1,18 +0,0 @@ -post_install() { - printf ":: asusd provides a service that is activated by an udev rule on\n" - printf ":: startup. Please reboot the system or run\n" - printf ":: # systemctl start asusd.service\n" - printf ":: to make it work.\n" - printf ":: See https://github.com/OpenGamingCollective/asusctl#kernel-requirements\n" - printf ":: for latest required kernel patches/versions\n" -} - -post_upgrade() { - if systemctl is-active asusd.service --quiet; then - printf ":: asusd service will be restarted…\n" - systemctl daemon-reload - systemctl restart asusd.service - fi - printf ":: See https://github.com/OpenGamingCollective/asusctl#kernel-requirements\n" - printf ":: for latest required kernel patches/versions\n" -} diff --git a/pkgbuilds/basecamp-cli/PKGBUILD b/pkgbuilds/basecamp-cli/PKGBUILD index b0bd117..ad5ad1b 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=2 +pkgver=0.12.0 +pkgrel=1 pkgdesc="CLI for Basecamp project management" arch=('x86_64' 'aarch64') url="https://github.com/basecamp/basecamp-cli" @@ -15,8 +15,8 @@ optdepends=( ) 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') +sha256sums_x86_64=('25d61c38de5660e97855661a2bf7329264912fcd46e2b3fb893fbc70bb467b5c') +sha256sums_aarch64=('1f692d2a8cc733ef95232eeb107414e32f1dd3228a0dde6c9039538661e0f2ca') package() { install -Dm755 "basecamp" "${pkgdir}/usr/bin/basecamp" diff --git a/pkgbuilds/box64/.omarchy/package.json b/pkgbuilds/box64/.omarchy/package.json new file mode 100644 index 0000000..db153c3 --- /dev/null +++ b/pkgbuilds/box64/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "release_ring": "fast" +} diff --git a/pkgbuilds/box64/PKGBUILD b/pkgbuilds/box64/PKGBUILD new file mode 100644 index 0000000..d5d8423 --- /dev/null +++ b/pkgbuilds/box64/PKGBUILD @@ -0,0 +1,48 @@ +# Maintainer: Ryan Hughes +# Contributor: Bart De Vries +# Contributor: Dan Johansen +# +# Runs x86_64-only Linux programs (dropbox) on AArch64. Pinned past v0.4.5-1: +# that release crashes in its glib wrapper as soon as Dropbox starts its tray +# icon. Move to a release tag once one includes the pinned commit. + +pkgname=box64 +pkgver=0.4.5.1.r309.gd58d619 +pkgrel=1 +_commit=d58d619e84e03282326e8a26c2cbfe749931b5f8 +pkgdesc='Linux userspace x86_64 emulator with native library wrapping' +arch=('aarch64') +url='https://github.com/ptitSeb/box64' +license=('MIT') +depends=('gcc-libs' 'glibc') +makedepends=('cmake' 'python') +backup=('etc/box64.box64rc') +options=('!strip' '!emptydirs') +source=("$pkgname-$_commit.tar.gz::$url/archive/$_commit.tar.gz") +sha256sums=('f7615a3c4ac5412a983a3fd26be79311e6e2702884d8bdfde954b23bad39fff8') + +build() { + # ARM64 is box64's generic AArch64 profile, so the binary does not depend + # on the CPU of the machine that built it. + cmake -S "$pkgname-$_commit" -B build \ + -DARM64=ON \ + -DARM_DYNAREC=ON \ + -DNOGIT=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=/usr + cmake --build build --parallel +} + +package() { + DESTDIR="$pkgdir" cmake --install build + + # Packages call box64 explicitly. A binfmt handler would compete with + # qemu-user-static-binfmt for every x86_64 executable on the system. + rm -r "$pkgdir/etc/binfmt.d" + # The Qt rcfile editor needs PySide, and its launcher entry would show up + # on every system that installs box64 only as a dependency. + rm "$pkgdir/usr/bin/box64-configurator" \ + "$pkgdir/usr/share/applications/box64-configurator.desktop" + + install -Dm644 "$pkgname-$_commit/LICENSE" -t "$pkgdir/usr/share/licenses/$pkgname" +} diff --git a/pkgbuilds/brave-bin/PKGBUILD b/pkgbuilds/brave-bin/PKGBUILD index 5ff2fd5..f42ed06 100644 --- a/pkgbuilds/brave-bin/PKGBUILD +++ b/pkgbuilds/brave-bin/PKGBUILD @@ -11,7 +11,7 @@ pkgname=brave-bin pkgver=1.96.61 -pkgrel=1 +pkgrel=2 epoch=1 pkgdesc='Web browser that blocks ads and trackers by default (binary release)' arch=(x86_64 aarch64) diff --git a/pkgbuilds/brave-origin-bin/PKGBUILD b/pkgbuilds/brave-origin-bin/PKGBUILD index bc385d3..f4d8bd3 100644 --- a/pkgbuilds/brave-origin-bin/PKGBUILD +++ b/pkgbuilds/brave-origin-bin/PKGBUILD @@ -2,7 +2,7 @@ pkgname=brave-origin-bin pkgver=1.96.61 -pkgrel=1 +pkgrel=2 epoch=1 pkgdesc='The minimalist browser from the makers of Brave (binary release).' arch=(x86_64 aarch64) diff --git a/pkgbuilds/claude-code/PKGBUILD b/pkgbuilds/claude-code/PKGBUILD index dac4532..ba7bd17 100644 --- a/pkgbuilds/claude-code/PKGBUILD +++ b/pkgbuilds/claude-code/PKGBUILD @@ -4,8 +4,8 @@ # Automation repository: https://github.com/fabifont/claude-code-aur pkgname=claude-code -pkgver=2.1.289 -pkgrel=1 +pkgver=2.1.291 +pkgrel=2 pkgdesc="An agentic coding tool that lives in your terminal" arch=('x86_64' 'aarch64') url="https://github.com/anthropics/claude-code" @@ -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=('a186b99e4a9c88366cd49df2f7dad56c61fc306ef0140b19ee64b7c42a8d1348') -sha256sums_aarch64=('d100d5e41dcbee220c80d3a3099292e4b5a508b57cafd181856efedf29f84f28') +sha256sums_x86_64=('078fad28d0297c9a25d306b635b2d8816c6839347520f29eb54ffea5d56142fb') +sha256sums_aarch64=('c18473a04cc4f077435d5d9081f09ebea46e699eb2825cea64741c4bccb87647') 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 f4161bc..956eb86 100644 --- a/pkgbuilds/claude-desktop/PKGBUILD +++ b/pkgbuilds/claude-desktop/PKGBUILD @@ -6,8 +6,8 @@ # repository's package index. pkgname=claude-desktop -pkgver=2.9939.4 -pkgrel=1 +pkgver=2.19675.1 +pkgrel=2 pkgdesc="Official Claude desktop app with Claude Code" arch=('x86_64' 'aarch64') url="https://claude.ai" @@ -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=('3cfddb23bf2911e05e27b4ed3856b8e795df94643b2c35b59deb317cf995bca0') -sha256sums_aarch64=('108ed79ea164b08c4fa0bb4387deef4779957b3f0f9b2d9898e359f363ebf1bc') +sha256sums_x86_64=('9ba127eeccf270f6e60d35f5c5333654053bf0540c88fc82a009d01711b106fc') +sha256sums_aarch64=('681d122ae97d0eb302f0e6d92c7f232847064bb01746458dc50a2c095a40ed12') package() { cd "${srcdir}" diff --git a/pkgbuilds/cliamp/PKGBUILD b/pkgbuilds/cliamp/PKGBUILD index 40c20e1..df09b8f 100644 --- a/pkgbuilds/cliamp/PKGBUILD +++ b/pkgbuilds/cliamp/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: bjarneo pkgname=cliamp pkgver=2.3.0 -pkgrel=1 +pkgrel=2 pkgdesc='A retro terminal music player inspired by Winamp 2.x' arch=('x86_64' 'aarch64') url='https://github.com/bjarneo/cliamp' diff --git a/pkgbuilds/crush-bin/PKGBUILD b/pkgbuilds/crush-bin/PKGBUILD index 5fe548e..718ce87 100644 --- a/pkgbuilds/crush-bin/PKGBUILD +++ b/pkgbuilds/crush-bin/PKGBUILD @@ -4,7 +4,7 @@ pkgname='crush-bin' pkgver=0.97.1 -pkgrel=1 +pkgrel=2 pkgdesc='A powerful terminal-based AI assistant for developers, providing intelligent coding assistance directly in your terminal.' url='https://charm.sh/crush' arch=('aarch64' 'armv7h' 'i686' 'x86_64') diff --git a/pkgbuilds/cua-driver-bin/PKGBUILD b/pkgbuilds/cua-driver-bin/PKGBUILD index 75a9b0a..f8b0846 100644 --- a/pkgbuilds/cua-driver-bin/PKGBUILD +++ b/pkgbuilds/cua-driver-bin/PKGBUILD @@ -19,10 +19,8 @@ # binary to point at pm.sh, a stand-in that declines and names pacman instead. pkgname=cua-driver-bin -# Held at 0.28.2: 0.28.3 and newer break screenshots. Bump by hand once a -# fixed release is verified. -pkgver=0.28.2 -pkgrel=3 +pkgver=0.33.4 +pkgrel=1 pkgdesc="Computer-use driver for native GUI apps: accessibility-tree snapshots and input injection" arch=('x86_64' 'aarch64') url="https://github.com/trycua/cua" @@ -49,8 +47,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=('8f3e5b669e2bcd98d0eecc64f40640aac77f358b6332a06abc6ee79991620f7d') -sha256sums_aarch64=('cadd7e6b757c3ce50f2b5f6e273c154ea48450fb5fcaff744209b382915eddf5') +sha256sums_x86_64=('a4759cc41c00691a345f08abfc28ece79aef41447d27a463ddd8662939fa3378') +sha256sums_aarch64=('63a1e858a1a407c44ceb407ad133909b8cddedd713aea3b56d7ccb6db784eb48') case "${CARCH}" in x86_64) _platform="linux-x86_64" ;; @@ -72,8 +70,8 @@ prepare() { return 1 fi - # 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 + # Through 0.33.4 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 @@ -113,4 +111,5 @@ package() { ln -s ../lib/cua-driver/cua-driver "${pkgdir}/usr/bin/cua-driver" install -Dm644 "${srcdir}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" + install -Dm644 THIRD_PARTY_NOTICES.md "${pkgdir}/usr/share/licenses/${pkgname}/THIRD_PARTY_NOTICES.md" } diff --git a/pkgbuilds/cua-hyprland-plugin/README.md b/pkgbuilds/cua-hyprland-plugin/README.md index 1263264..40cd3e7 100644 --- a/pkgbuilds/cua-hyprland-plugin/README.md +++ b/pkgbuilds/cua-hyprland-plugin/README.md @@ -1,6 +1,6 @@ # Optional Cua Hyprland plugin -This package targets **Omarchy x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Release `0.32.0-3` is an edge candidate built from the plugin source published with Driver `0.32.0`, paired with `cua-driver-bin` `0.28.2`. That source carries the independent agent keymaps and compatible Num Lock handling that releases `0.26.1-5` through `0.28.2-2` applied as an Omarchy patch; this release applies a smaller one, `downstream.patch`, so foreground typing keeps working with modifier and Compose remaps (see *Keyboard behavior*) and so that, with its guard active, restarting fcitx5 does not crash Hyprland (see *Input-method popups*). The upstream native qualification below covers older, unpatched source. 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. +This package targets **Omarchy x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Release `0.32.0-3` is an edge candidate built from the plugin source published with Driver `0.32.0`, paired with `cua-driver-bin` `0.33.4`; the plugin sources are unchanged from 0.32.0 through 0.33.4. That source carries the independent agent keymaps and compatible Num Lock handling that releases `0.26.1-5` through `0.28.2-2` applied as an Omarchy patch; this release applies a smaller one, `downstream.patch`, so foreground typing keeps working with modifier and Compose remaps (see *Keyboard behavior*) and so that, with its guard active, restarting fcitx5 does not crash Hyprland (see *Input-method popups*). The upstream native qualification below covers older, unpatched source. 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. @@ -8,7 +8,7 @@ The plugin is optional. Cua Driver works independently, and installation does no The package uses the [Driver 0.32.0 plugin source](https://github.com/trycua/cua/releases/tag/cua-driver-rs-v0.32.0). Against the Driver 0.26.1 source the profile kit was qualified with, it adds the keyboard behaviour below ([Cua #3970](https://github.com/trycua/cua/pull/3970), adapted from [#473](https://github.com/omacom/omarchy-pkgs/pull/473)) and gives agent keyboards the user seat's key repeat rate instead of zero ([Cua #4358](https://github.com/trycua/cua/pull/4358)), which crashed single-seat clients such as imv that bound an agent seat ([Cua #4257](https://github.com/trycua/cua/issues/4257)). The input protocol header is unchanged. -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. This package pairs with `cua-driver-bin 0.28.2`, which speaks the same input protocol v3 to this source; Driver finds the plugin only through its versioned input socket, not through the plugin's provenance. Discovery protocol v2 is separate. `cua-driver-bin` stays at 0.28.2 because Driver 0.28.3 through 0.32.0 refuse desktop capture on Hyprland with more than one output or with one output away from the origin ([Cua #4161](https://github.com/trycua/cua/issues/4161)), where 0.28.2 captures. This pairing is a changed pairing and requires affected replay before promotion. +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. This package pairs with `cua-driver-bin 0.33.4`, which speaks the same input protocol v3 to this source; Driver finds the plugin only through its versioned input socket, not through the plugin's provenance. Discovery protocol v2 is separate. Driver 0.28.3 through 0.33.3 refuse desktop capture on Hyprland with more than one output or with one output away from the origin ([Cua #4161](https://github.com/trycua/cua/issues/4161)); 0.33.4 captures there again ([Cua #4305](https://github.com/trycua/cua/pull/4305)). This pairing is a changed pairing and requires affected replay before promotion. Profile `omarchy-hyprland-0562r4-remaps`, kit tooling `1.1.0`, and package release `3` pin: @@ -19,7 +19,7 @@ Profile `omarchy-hyprland-0562r4-remaps`, kit tooling `1.1.0`, and package relea This profile derives from Cua's `omarchy-stable-20260910` profile. Arch's Hyprland `0.56.2-4` is a rebuild of the same 0.56.2 release against vulkan-sdk 1.4.363 and glslang, built with the same GCC `16.2.1 20260810` and linked to the same `libstdc++`. Its executable (SHA-256 `55da553be71222566ee73b973d2f56dbb9939044d8f22f81aa54b66c83f2f6d1`) and two of its 497 headers differ from `-3`: `src/version.h` now names the Aquamarine `0.15.1` and hyprutils `0.14.2` it was built against, and `protocols/hyprland-input-capture-v1.hpp` gains a destroy handler. The header inventory is re-measured the way `profile_verify.py` measures it, giving `1fdefe6ac027a159d04a5dfee4928ec7ebd15544a9a25b5f66d2f5a46fcf364a`; that method reproduces the kit's `-2`/`-3` values exactly. The checked-in `PROFILE.json` changes only the profile name, package release, source release, exact Hyprland, Aquamarine and glibc package versions, and the re-measured compositor executable and header hashes. Compiler and libstdc++ runtime identities remain Cua's; the separately recorded patch changes the build source. Cua publishes the profile tooling only in the 0.26.1 kit, and the Driver 0.32.0 source manifest has exactly the fields that kit's `profile_verify.py` checks. The download wrapper verifies the original kit and the Driver 0.32.0 source archive before substituting that archive and its manifest into the kit, deriving the updated profile and provenance, and rendering the recipe from the kit's own `PROFILE-PKGBUILD.in`. It then verifies every derived member against its recorded digest, so the result is the kit Cua's `profile_verify.py` accepts as complete for this profile. -The native qualification below was recorded with package `0.26.1-2` and Hyprland `-2`. The 0.32.0 source and its Omarchy patch, the Aquamarine and glibc updates, and the Driver 0.28.2 pairing need their own application and Driver replay before promotion. Hyprland `0.56.2-4`, Aquamarine `0.15.1-1` and glibc `2.44+r50+g1848099f063e-1` must all reach a destination channel before this artifact can be installed there; publication still follows edge → RC → stable. +The native qualification below was recorded with package `0.26.1-2` and Hyprland `-2`. The 0.32.0 source and its Omarchy patch, the Aquamarine and glibc updates, and the Driver 0.33.4 pairing need their own application and Driver replay before promotion. Hyprland `0.56.2-4`, Aquamarine `0.15.1-1` and glibc `2.44+r50+g1848099f063e-1` must all 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, diff --git a/pkgbuilds/cursor-bin/PKGBUILD b/pkgbuilds/cursor-bin/PKGBUILD index 382ca23..2347496 100644 --- a/pkgbuilds/cursor-bin/PKGBUILD +++ b/pkgbuilds/cursor-bin/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: Gunther Schulz pkgname=cursor-bin -pkgver=3.23.12 -pkgrel=1 +pkgver=3.23.23 +pkgrel=2 pkgdesc='AI-first coding environment' arch=('x86_64' 'aarch64') url="https://www.cursor.com" @@ -18,13 +18,13 @@ depends_aarch64=( libxkbcommon libxrandr mesa nspr nss pango systemd-libs which ) options=(!strip !debug) # Don't break ext of VSCode -_commit=2d29876d567da1607532b23bbf2cd5ddbca496fe +_commit=2dac2428994fe34f12658d9ecad1541b98db2c04 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_x86_64=('45f25a65d42018fef96c67b555401c579f4354ea5a66489f24c92817b598702eb8a6aa904b52222eed229903c960130ed1312f222525549dcd8cc91f6ab72ecd' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') +sha512sums_x86_64=('acdf6f2677cd817c41fe91181075f8fa2cbf6fe21211c937a750769f1b036911a05c2e48c3f5186cc5e3d71a1b56aac8e6fbfead84a3c86902c39b77f8776328' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') source_aarch64=("https://downloads.cursor.com/production/${_commit}/linux/arm64/deb/arm64/deb/cursor_${pkgver}_arm64.deb") -sha512sums_aarch64=('9ca4455198d2d629b52f71db92969d03c943f3c20a89e08cdfa8a2744f2c6e3f1f13c8657661713a380e30075d3372ef7f27dbb922f37c8170d3a409f02f4397') +sha512sums_aarch64=('28ae4e335d48a8c9aa189645e75e26a2384462c8d2c910a7a1340442259eda55beb065210f049f895eeb809c4cdc331823e0c6b539a79bab209a94594777d5c0') noextract=(cursor_${pkgver}_amd64.deb cursor_${pkgver}_arm64.deb) # avoid double tarball _app=usr/share/cursor/resources/app package() { diff --git a/pkgbuilds/cursor-cli/PKGBUILD b/pkgbuilds/cursor-cli/PKGBUILD index 9eab023..44b7132 100644 --- a/pkgbuilds/cursor-cli/PKGBUILD +++ b/pkgbuilds/cursor-cli/PKGBUILD @@ -8,7 +8,7 @@ pkgver=2026.10.01.1.e373342 # Derive the upstream version (YYYY.MM.DD-) from that pkgver. _upstream_ver="${pkgver%.*}" _upstream_ver="${_upstream_ver%.*}-${pkgver##*.}" -pkgrel=1 +pkgrel=2 # epoch=1: bumped when switching from the original `20250808.0.` scheme # to the current `YYYY.MM.DD..` scheme (2025-08-09). Never decrease. epoch=1 diff --git a/pkgbuilds/disktree-bin/PKGBUILD b/pkgbuilds/disktree-bin/PKGBUILD index f568969..f122411 100644 --- a/pkgbuilds/disktree-bin/PKGBUILD +++ b/pkgbuilds/disktree-bin/PKGBUILD @@ -2,8 +2,8 @@ pkgname=disktree-bin _name=disktree -pkgver=0.10.1 -pkgrel=1 +pkgver=0.11.0 +pkgrel=2 pkgdesc="Disk space treemap for Omarchy: see what fills a disk by kind, mark what should go, and remove it" arch=('x86_64' 'aarch64') url="https://github.com/tobi/disktree" @@ -16,8 +16,8 @@ conflicts=("$_name") options=('!debug') source_x86_64=("$url/releases/download/v$pkgver/$_name-$pkgver-x86_64-linux.tar.gz") source_aarch64=("$url/releases/download/v$pkgver/$_name-$pkgver-aarch64-linux.tar.gz") -sha256sums_x86_64=('838a5b7085dfd5532070cadade68ca5cfa4e61809188211a5c890412e201a113') -sha256sums_aarch64=('e4927a4935736ffba9ba034ea63ecf3e2bac74bbf60721759fdf7446f6aa1d3d') +sha256sums_x86_64=('7e75afa2283c8b4f9290d52eadbfc00d9f14c13aed2d224ccdf0f7826fcc05b2') +sha256sums_aarch64=('3c75f9c82069e40310c821357a52aee2f11a9c3e2b45452c9893dbf7cb5e454a') package() { cd "$_name-$pkgver-$CARCH-linux" diff --git a/pkgbuilds/dropbox-cli/PKGBUILD b/pkgbuilds/dropbox-cli/PKGBUILD index 61b6e5d..16dec84 100644 --- a/pkgbuilds/dropbox-cli/PKGBUILD +++ b/pkgbuilds/dropbox-cli/PKGBUILD @@ -7,9 +7,9 @@ pkgname=dropbox-cli pkgver=2026.09.28 -pkgrel=1 +pkgrel=3 pkgdesc="Command line interface for Dropbox" -arch=("x86_64") +arch=("any") url="https://www.dropbox.com" license=("GPL-3.0-or-later") makedepends=("gdk-pixbuf2") diff --git a/pkgbuilds/dropbox/PKGBUILD b/pkgbuilds/dropbox/PKGBUILD index e589002..86198ab 100644 --- a/pkgbuilds/dropbox/PKGBUILD +++ b/pkgbuilds/dropbox/PKGBUILD @@ -7,10 +7,13 @@ pkgname=dropbox pkgver=272.4.3798 pkgrel=1 pkgdesc="A free service that lets you bring your photos, docs, and videos anywhere and share them easily." -arch=("x86_64") +arch=("x86_64" "aarch64") url="https://www.dropbox.com" license=(custom:Dropbox) depends=("libsm" "libxslt" "libxmu" "libxdamage" "libxrender" "libxxf86vm" "libxcomposite" "fontconfig" "dbus") +# Dropbox ships Linux builds for x86_64 only. AArch64 runs the same client +# under box64, which maps its glibc and GTK calls onto the native libraries. +depends_aarch64=("box64") makedepends=("gendesk") optdepends=( 'ufw-extras: ufw rules for dropbox' @@ -43,7 +46,15 @@ package() { chmod 755 "$pkgdir"/opt/dropbox/*.so install -d "$pkgdir"/usr/bin - ln -s ../../opt/dropbox/dropbox "$pkgdir"/usr/bin/dropbox + if [[ $CARCH == aarch64 ]]; then + # `dropbox-cli start` runs dropboxd, so pointing dropboxd at box64 and + # /usr/bin/dropbox at dropboxd sends every launch through the emulator. + sed -i 's|^exec "$PAR"/dropbox "$@"$|exec /usr/bin/box64 "$PAR"/dropbox "$@"|' "$pkgdir"/opt/dropbox/dropboxd + grep -qxF 'exec /usr/bin/box64 "$PAR"/dropbox "$@"' "$pkgdir"/opt/dropbox/dropboxd + ln -s ../../opt/dropbox/dropboxd "$pkgdir"/usr/bin/dropbox + else + ln -s ../../opt/dropbox/dropbox "$pkgdir"/usr/bin/dropbox + fi install -Dm644 "$srcdir"/dropbox.desktop -t "$pkgdir"/usr/share/applications install -Dm644 "$srcdir"/DropboxGlyph_Blue.svg "$pkgdir"/usr/share/pixmaps/dropbox.svg diff --git a/pkgbuilds/elephant-all/.omarchy/package.json b/pkgbuilds/elephant-all/.omarchy/package.json deleted file mode 100644 index 0b3c0a1..0000000 --- a/pkgbuilds/elephant-all/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-all/PKGBUILD b/pkgbuilds/elephant-all/PKGBUILD deleted file mode 100644 index a53d168..0000000 --- a/pkgbuilds/elephant-all/PKGBUILD +++ /dev/null @@ -1,47 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-all -pkgver=2.22.1 -pkgrel=1 -pkgdesc='elephant + all official elephant providers' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -depends=('libqalculate' 'wl-clipboard' 'imagemagick' 'fd' 'wtype' 'jq' 'sqlite3') -makedepends=('go') -conflicts=('elephant' 'elephant-playerctl' 'elephant-wireplumber' 'elephant-bitwarden' 'elephant-dnfpackages' 'elephant-1password' 'elephant-bookmarks' 'elephant-nirisessions' 'elephant-niriactions' 'elephant-archlinuxpkgs' 'elephant-bluetooth' 'elephant-calc' 'elephant-clipboard' 'elephant-desktopapplications' 'elephant-files' 'elephant-menus' 'elephant-providerlist' 'elephant-runner' 'elephant-snippets' 'elephant-symbols' 'elephant-todo' 'elephant-unicode' 'elephant-websearch' 'elephant-windows') -provides=('elephant' 'elephant-playerctl' 'elephant-wireplumber' 'elephant-nirisessions' 'elephant-niriactions' 'elephant-archlinuxpkgs' 'elephant-bluetooth' 'elephant-calc' 'elephant-clipboard' 'elephant-desktopapplications' 'elephant-files' 'elephant-menus' 'elephant-providerlist' 'elephant-runner' 'elephant-snippets' 'elephant-symbols' 'elephant-todo' 'elephant-unicode' 'elephant-websearch' 'elephant-windows') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - # Build main elephant binary - cd elephant-${pkgver}/cmd/elephant - go build -ldflags="-s -w" -buildvcs=false -x -o elephant -trimpath - - # Build all provider plugins - cd ../../internal/providers - - # Build each provider - for provider in playerctl wireplumber archlinuxpkgs bitwarden dnfpackages 1password bookmarks bluetooth nirisessions niriactions calc clipboard desktopapplications files menus providerlist runner snippets symbols todo unicode websearch windows; do - cd $provider - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath - cd .. - done -} - -package() { - # Install main elephant binary - cd elephant-${pkgver}/cmd/elephant - install -Dm 755 elephant -t "${pkgdir}/usr/bin" - - # Install all provider plugins - cd ../../internal/providers - for provider in playerctl wireplumber archlinuxpkgs bitwarden dnfpackages bookmarks 1password nirisessions niriactions bluetooth calc clipboard desktopapplications files menus providerlist runner snippets symbols todo unicode websearch windows; do - install -Dm 755 $provider/$provider.so -t "${pkgdir}/usr/lib/elephant" - done - - # Install license - cd ../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-archlinuxpkgs/.omarchy/package.json b/pkgbuilds/elephant-archlinuxpkgs/.omarchy/package.json deleted file mode 100644 index a020358..0000000 --- a/pkgbuilds/elephant-archlinuxpkgs/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-archlinuxpkgs/PKGBUILD b/pkgbuilds/elephant-archlinuxpkgs/PKGBUILD deleted file mode 100644 index 3916f18..0000000 --- a/pkgbuilds/elephant-archlinuxpkgs/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-archlinuxpkgs -pkgver=2.22.1 -pkgrel=1 -pkgdesc='archlinuxpkgs provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-archlinuxpkgs') -provides=('elephant-archlinuxpkgs') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/archlinuxpkgs - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/archlinuxpkgs - install -Dm 755 archlinuxpkgs.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-bluetooth/.omarchy/package.json b/pkgbuilds/elephant-bluetooth/.omarchy/package.json deleted file mode 100644 index d08f83f..0000000 --- a/pkgbuilds/elephant-bluetooth/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-bluetooth/PKGBUILD b/pkgbuilds/elephant-bluetooth/PKGBUILD deleted file mode 100644 index 01819a0..0000000 --- a/pkgbuilds/elephant-bluetooth/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-bluetooth -pkgver=2.22.1 -pkgrel=1 -pkgdesc='bluetooth provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-bluetooth') -provides=('elephant-bluetooth') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/bluetooth - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/bluetooth - install -Dm 755 bluetooth.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-calc/.omarchy/package.json b/pkgbuilds/elephant-calc/.omarchy/package.json deleted file mode 100644 index 94f4a46..0000000 --- a/pkgbuilds/elephant-calc/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-calc/PKGBUILD b/pkgbuilds/elephant-calc/PKGBUILD deleted file mode 100644 index ba7822d..0000000 --- a/pkgbuilds/elephant-calc/PKGBUILD +++ /dev/null @@ -1,28 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-calc -pkgver=2.22.1 -pkgrel=1 -pkgdesc='calc provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -depends=('libqalculate') -makedepends=('go') -conflicts=('elephant-calc') -provides=('elephant-calc') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/calc - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/calc - install -Dm 755 calc.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-clipboard/.omarchy/package.json b/pkgbuilds/elephant-clipboard/.omarchy/package.json deleted file mode 100644 index 1634055..0000000 --- a/pkgbuilds/elephant-clipboard/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-clipboard/PKGBUILD b/pkgbuilds/elephant-clipboard/PKGBUILD deleted file mode 100644 index 16c93c7..0000000 --- a/pkgbuilds/elephant-clipboard/PKGBUILD +++ /dev/null @@ -1,28 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-clipboard -pkgver=2.22.1 -pkgrel=1 -pkgdesc='clipboard provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -depends=('wl-clipboard' 'imagemagick') -makedepends=('go') -conflicts=('elephant-clipboard') -provides=('elephant-clipboard') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/clipboard - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/clipboard - install -Dm 755 clipboard.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-desktopapplications/.omarchy/package.json b/pkgbuilds/elephant-desktopapplications/.omarchy/package.json deleted file mode 100644 index e83c756..0000000 --- a/pkgbuilds/elephant-desktopapplications/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-desktopapplications/PKGBUILD b/pkgbuilds/elephant-desktopapplications/PKGBUILD deleted file mode 100644 index eda46cf..0000000 --- a/pkgbuilds/elephant-desktopapplications/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-desktopapplications -pkgver=2.22.1 -pkgrel=1 -pkgdesc='desktopapplications provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-desktopapplications') -provides=('elephant-desktopapplications') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/desktopapplications - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/desktopapplications - install -Dm 755 desktopapplications.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-files/.omarchy/package.json b/pkgbuilds/elephant-files/.omarchy/package.json deleted file mode 100644 index b7f4bb9..0000000 --- a/pkgbuilds/elephant-files/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-files/PKGBUILD b/pkgbuilds/elephant-files/PKGBUILD deleted file mode 100644 index e0dee0a..0000000 --- a/pkgbuilds/elephant-files/PKGBUILD +++ /dev/null @@ -1,28 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-files -pkgver=2.22.1 -pkgrel=1 -pkgdesc='files provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -depends=('fd') -makedepends=('go') -conflicts=('elephant-files') -provides=('elephant-files') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/files - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/files - install -Dm 755 files.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-menus/.omarchy/package.json b/pkgbuilds/elephant-menus/.omarchy/package.json deleted file mode 100644 index 81b6353..0000000 --- a/pkgbuilds/elephant-menus/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-menus/PKGBUILD b/pkgbuilds/elephant-menus/PKGBUILD deleted file mode 100644 index 08cc273..0000000 --- a/pkgbuilds/elephant-menus/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-menus -pkgver=2.22.1 -pkgrel=1 -pkgdesc='menus provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-menus') -provides=('elephant-menus') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/menus - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/menus - install -Dm 755 menus.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-providerlist/.omarchy/package.json b/pkgbuilds/elephant-providerlist/.omarchy/package.json deleted file mode 100644 index 5646aba..0000000 --- a/pkgbuilds/elephant-providerlist/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-providerlist/PKGBUILD b/pkgbuilds/elephant-providerlist/PKGBUILD deleted file mode 100644 index 7633fef..0000000 --- a/pkgbuilds/elephant-providerlist/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-providerlist -pkgver=2.22.1 -pkgrel=1 -pkgdesc='providerlist provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-providerlist') -provides=('elephant-providerlist') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/providerlist - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/providerlist - install -Dm 755 providerlist.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-runner/.omarchy/package.json b/pkgbuilds/elephant-runner/.omarchy/package.json deleted file mode 100644 index f8d45c4..0000000 --- a/pkgbuilds/elephant-runner/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-runner/PKGBUILD b/pkgbuilds/elephant-runner/PKGBUILD deleted file mode 100644 index 486aedb..0000000 --- a/pkgbuilds/elephant-runner/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-runner -pkgver=2.22.1 -pkgrel=1 -pkgdesc='runner provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-runner') -provides=('elephant-runner') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/runner - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/runner - install -Dm 755 runner.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-symbols/.omarchy/package.json b/pkgbuilds/elephant-symbols/.omarchy/package.json deleted file mode 100644 index 2b2eeec..0000000 --- a/pkgbuilds/elephant-symbols/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-symbols/PKGBUILD b/pkgbuilds/elephant-symbols/PKGBUILD deleted file mode 100644 index 586bb15..0000000 --- a/pkgbuilds/elephant-symbols/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-symbols -pkgver=2.22.1 -pkgrel=1 -pkgdesc='symbols provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-symbols') -provides=('elephant-symbols') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/symbols - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/symbols - install -Dm 755 symbols.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-todo/.omarchy/package.json b/pkgbuilds/elephant-todo/.omarchy/package.json deleted file mode 100644 index 4a63e04..0000000 --- a/pkgbuilds/elephant-todo/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-todo/PKGBUILD b/pkgbuilds/elephant-todo/PKGBUILD deleted file mode 100644 index e1f64d8..0000000 --- a/pkgbuilds/elephant-todo/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-todo -pkgver=2.22.1 -pkgrel=1 -pkgdesc='todo provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-todo') -provides=('elephant-todo') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/todo - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/todo - install -Dm 755 todo.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-unicode/.omarchy/package.json b/pkgbuilds/elephant-unicode/.omarchy/package.json deleted file mode 100644 index 8b9efa1..0000000 --- a/pkgbuilds/elephant-unicode/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-unicode/PKGBUILD b/pkgbuilds/elephant-unicode/PKGBUILD deleted file mode 100644 index 7a126d3..0000000 --- a/pkgbuilds/elephant-unicode/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-unicode -pkgver=2.22.1 -pkgrel=1 -pkgdesc='unicode provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-unicode') -provides=('elephant-unicode') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/unicode - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/unicode - install -Dm 755 unicode.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant-websearch/.omarchy/package.json b/pkgbuilds/elephant-websearch/.omarchy/package.json deleted file mode 100644 index e5f3fe6..0000000 --- a/pkgbuilds/elephant-websearch/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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-websearch/PKGBUILD b/pkgbuilds/elephant-websearch/PKGBUILD deleted file mode 100644 index d003e6b..0000000 --- a/pkgbuilds/elephant-websearch/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant-websearch -pkgver=2.22.1 -pkgrel=1 -pkgdesc='websearch provider for elephant' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant-websearch') -provides=('elephant-websearch') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd elephant-${pkgver}/internal/providers/websearch - go build -ldflags="-s -w" -buildvcs=false -buildmode=plugin -trimpath -} - -package() { - cd elephant-${pkgver}/internal/providers/websearch - install -Dm 755 websearch.so -t "${pkgdir}/usr/lib/elephant" - - cd ../../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/elephant/.omarchy/package.json b/pkgbuilds/elephant/.omarchy/package.json deleted file mode 100644 index 13506dc..0000000 --- a/pkgbuilds/elephant/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "source": "local", - "upstream": { - "watch": { - "github": "abenz1267/elephant", - "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" - } - }, - "origin": { - "aur": "elephant", - "commit": "6dd02e6987a7a91be6a33e9600147523efa7fa5a" - } -} diff --git a/pkgbuilds/elephant/PKGBUILD b/pkgbuilds/elephant/PKGBUILD deleted file mode 100644 index 0c0b58d..0000000 --- a/pkgbuilds/elephant/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: Andrej Benz - -pkgname=elephant -pkgver=2.22.1 -pkgrel=1 -pkgdesc='general purpose datasource and executor' -url='https://github.com/abenz1267/elephant' -arch=('x86_64' 'aarch64') -license=('GPL') -makedepends=('go') -conflicts=('elephant') -provides=('elephant') -source=("${url}/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('3d1d0d4c55ae531fa3f06406b96504b5165a0d7b53523d1f8351d9d93e457f44') - -build() { - cd ${pkgname}-${pkgver}/cmd/elephant - go build -ldflags="-s -w" -buildvcs=false -x -o elephant -trimpath -} - -package() { - cd ${pkgname}-${pkgver}/cmd/elephant - install -Dm 755 elephant -t "${pkgdir}/usr/bin" - - cd ../../ - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index 51b15aa..6c9155f 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -2,7 +2,7 @@ pkgname=flea pkgver=0.3.7 -pkgrel=1 +pkgrel=2 pkgdesc='Fast, keyboard-first file manager for Omarchy' arch=('x86_64' 'aarch64') url='https://github.com/thisisgm/flea' diff --git a/pkgbuilds/github-copilot-cli/PKGBUILD b/pkgbuilds/github-copilot-cli/PKGBUILD index 404827f..8acb786 100644 --- a/pkgbuilds/github-copilot-cli/PKGBUILD +++ b/pkgbuilds/github-copilot-cli/PKGBUILD @@ -6,8 +6,8 @@ _npmmodule=@github/copilot pkgname=github-copilot-cli _pkgexec=copilot -pkgver=1.0.91 -pkgrel=1 +pkgver=1.0.92 +pkgrel=2 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=( - '4f305d718a1e6cb994e926d0f570883eb63bfb79ef091d00d5ff9c67d9d83781' - '7785011a8c97314f0800b76e02bdac7951bae7e6bfde49416c142ae4fca08345' + '4711a32a1917bf4c64308818a76efb1fb633384ac895312d6e9d12f26b78f95a' + '242fb78a1c6e4ff05acb6b9135fc6e3cb0714ecbe87cddbd8bf69d2fa2559c37' ) # Document: https://wiki.archlinux.org/title/Node.js_package_guidelines diff --git a/pkgbuilds/google-chrome/PKGBUILD b/pkgbuilds/google-chrome/PKGBUILD index b0083cf..20f2e70 100644 --- a/pkgbuilds/google-chrome/PKGBUILD +++ b/pkgbuilds/google-chrome/PKGBUILD @@ -7,8 +7,8 @@ # or use: $ curl -sSf https://dl.google.com/linux/chrome/deb/dists/stable/main/binary-amd64/Packages | grep -A1 "Package: google-chrome-stable" | awk '/Version/{print $2}' | cut -d '-' -f1 pkgname=google-chrome -pkgver=154.0.8037.97 -pkgrel=1 +pkgver=155.0.8059.39 +pkgrel=2 pkgdesc="The popular web browser by Google (Stable Channel)" arch=( 'x86_64' @@ -39,8 +39,8 @@ source=('eula_text.html' "google-chrome-$_channel.sh") sha512sums=('a225555c06b7c32f9f2657004558e3f996c981481dbb0d3cd79b1d59fa3f05d591af88399422d3ab29d9446c103e98d567aeafe061d9550817ab6e7eb0498396' 'de02b498a4b5b93e21622c8dba57befe795d733a04656be911cc38e28bfef0e20470450f44be523bbde8d4de28f79c10434846ca01fc2a2f4e67707b79332f94') -sha512sums_x86_64=('990f133f1091d0aea48cc0bfbcc8f303fee80d128a93f02f7910c0e0bb46c45ee96971879037cde100bb35899284fff4fe5b172ace9b9d5e6465d45f311d1ae0') -sha512sums_aarch64=('7955d74192d8364ba22b25cf1de1c7a7b58428a04a04ac095b773f838224b506dc4c74dca340d1a80b54f012a339c01fcba752b5c63a55a4ea6a9886fa36fd8e') +sha512sums_x86_64=('42b4ec9ec61fee532a848fdf35973d3aba6c083028ee45c98572f1bbd9e51e865cbd5bf9fbbe695b6c9a76936472aaccd58f860ecb8bcce380afa6fd57bcb472') +sha512sums_aarch64=('bcb58757677389245952e4fba354c71c586228abe1e91cf7563895dda3702dcddd031838dbde8e246525e1b15fbfdf6b13c59ed7fc1f4cb52c088d499857adb7') source_x86_64=("https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-${_channel}/google-chrome-${_channel}_${pkgver}-1_amd64.deb") source_aarch64=("https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-${_channel}/google-chrome-${_channel}_${pkgver}-1_arm64.deb") diff --git a/pkgbuilds/grok-bot/.omarchy/package.json b/pkgbuilds/grok-bot/.omarchy/package.json index 2e8b089..5a61bc6 100644 --- a/pkgbuilds/grok-bot/.omarchy/package.json +++ b/pkgbuilds/grok-bot/.omarchy/package.json @@ -1,6 +1,6 @@ { "source": "local", - "sync": false, + "release_ring": "fast", "origin": { "aur": "grok-bot", "commit": "05eb78fca06b482affda28b26223cbf249d4bbcd" diff --git a/pkgbuilds/grok-bot/.omarchy/upstream.sh b/pkgbuilds/grok-bot/.omarchy/upstream.sh new file mode 100755 index 0000000..12b6249 --- /dev/null +++ b/pkgbuilds/grok-bot/.omarchy/upstream.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Cursor publishes Grok Bot from its own Debian repository, one index per +# architecture. Each index carries the version and the SHA256, so an update +# is two small HTTP requests instead of downloading the debs, and the pool +# URL is keyed by version. bin/sync-upstream can rewrite pkgver and the +# checksums; it cannot rewrite a commit id embedded in the old +# downloads.cursor.com/grokbot/stable// URL. +set -euo pipefail + +BASE_URL="https://downloads.cursor.com/aptrepo" +declare -A DEB_ARCHES=([x86_64]=amd64 [aarch64]=arm64) + +# Print " " for the newest grok-bot stanza. Newest is +# vercmp's opinion, which is the one bin/sync-upstream and pacman both use; +# sort -V disagrees with it over versions like 1.0a. The winner's Filename +# must be the versioned pool path the PKGBUILD downloads from. +newest_release() { + local index="$1" debarch="$2" + local version sha256 filename best_version="" best_sha256="" best_filename="" + + while read -r version sha256 filename; do + [[ -n "$version" && -n "$sha256" ]] || continue + if [[ -z "$best_version" ]] || [[ "$(vercmp "$version" "$best_version")" -gt 0 ]]; then + best_version="$version" + best_sha256="$sha256" + best_filename="$filename" + fi + done < <(awk ' + { sub(/\r$/, "") } + /^Package:/ { package = $2 } + /^Version:/ { version = $2 } + /^SHA256:/ { sha256 = $2 } + /^Filename:/ { filename = $2 } + /^$/ { + if (package == "grok-bot" && version && sha256) print version, sha256, filename + package = version = sha256 = filename = "" + } + END { + if (package == "grok-bot" && version && sha256) print version, sha256, filename + } + ' <<<"$index") + + [[ -n "$best_version" ]] || return 1 + + local expected="pool/grok-bot/g/gr/grok-bot_${best_version}_${debarch}.deb" + if [[ "$best_filename" != "$expected" ]]; then + echo "Unexpected Grok Bot $best_version $debarch Filename: '${best_filename}' (expected $expected)" >&2 + return 1 + fi + + echo "$best_version $best_sha256" +} + +versions=() +declare -A checksums=() + +for arch in "${!DEB_ARCHES[@]}"; do + index=$(curl -fsSL "$BASE_URL/dists/grok-bot/main/binary-${DEB_ARCHES[$arch]}/Packages") + + read -r version sha256 <<<"$(newest_release "$index" "${DEB_ARCHES[$arch]}")" + if [[ -z "${version:-}" || -z "${sha256:-}" ]]; then + echo "No usable Grok Bot release found for $arch" >&2 + exit 1 + fi + + versions+=("$version") + checksums[$arch]="$sha256" +done + +# A release can land one architecture at a time. Wait until both agree so one +# pkgver always describes both artifacts. +for version in "${versions[@]}"; do + if [[ "$version" != "${versions[0]}" ]]; then + echo "Upstream architectures are mid-release (${versions[*]}); skipping" >&2 + echo '{}' + exit 0 + fi +done + +jq -n \ + --arg pkgver "${versions[0]}" \ + --arg x86_64 "${checksums[x86_64]}" \ + --arg aarch64 "${checksums[aarch64]}" \ + '{pkgver: $pkgver, sha256sums: {x86_64: [$x86_64], aarch64: [$aarch64]}}' diff --git a/pkgbuilds/grok-bot/PKGBUILD b/pkgbuilds/grok-bot/PKGBUILD index bcc5797..5a82f65 100644 --- a/pkgbuilds/grok-bot/PKGBUILD +++ b/pkgbuilds/grok-bot/PKGBUILD @@ -2,9 +2,8 @@ # Contributor: Omarchy pkgname=grok-bot -pkgver=0.47.0 +pkgver=0.68.1 pkgrel=1 -_commit=c1e7d7a46549956d25f53e9c0b9f59666e03aa3a pkgdesc='Grok Bot desktop agent' arch=('x86_64' 'aarch64') url='https://x.ai/bot' @@ -30,20 +29,23 @@ install=grok-bot.install _deb_x86_64="grok-bot_${pkgver}_amd64.deb" _deb_aarch64="grok-bot_${pkgver}_arm64.deb" +# Versioned pool paths — Packages indexes carry SHA256 so upstream.sh +# can bump pkgver without embedding a Cursor commit id. +_pool="https://downloads.cursor.com/aptrepo/pool/grok-bot/g/gr" source=( 'grok-bot.sh' 'grok-bot.desktop' ) source_x86_64=( - "${_deb_x86_64}::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/x64/${_deb_x86_64}" + "${_deb_x86_64}::${_pool}/${_deb_x86_64}" ) source_aarch64=( - "${_deb_aarch64}::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/arm64/${_deb_aarch64}" + "${_deb_aarch64}::${_pool}/${_deb_aarch64}" ) sha256sums=('6dfa6c305941afa6cbaefbeaae06d05ab5a88f31630005d25a819a160c20c7a3' '3e2a2461ea58d17ac1777616be9ba660f7cb9ceefa9292016e36c55758bf78dd') -sha256sums_x86_64=('11ca0f51a535b97af51a352adf9c0f9ecd2e1b0430a69ae9451b688a7a065808') -sha256sums_aarch64=('836f8d19d3826c6573c31ac45c7a9b797abc73381ae0d2b1e7a8dae5410e7e46') +sha256sums_x86_64=('b2be8106d2b3eae07d983d5f1ca77b657accde666dc440db2a409421ecff3359') +sha256sums_aarch64=('3f85fbe2ba3c1d122aa16f8be672076bc19146e07e9d431f37a93b85fa75a93f') noextract=("${_deb_x86_64}" "${_deb_aarch64}") package() { diff --git a/pkgbuilds/grok-bot/update-pkgver.sh b/pkgbuilds/grok-bot/update-pkgver.sh deleted file mode 100755 index 40e9474..0000000 --- a/pkgbuilds/grok-bot/update-pkgver.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -# Resolve current Grok Bot stable from Cursor's update feed and pin PKGBUILD. -# Linux has no latest alias (linux-x64 feed returns 204). The darwin-arm64 -# sand feed publishes version + commit; the Linux .deb lives at the same commit. -# Darwin can ship first — HEAD-check the Linux URL and fail loudly if 404. -set -euo pipefail - -PKGBUILD_PATH="${1:-PKGBUILD}" -[[ -f "${PKGBUILD_PATH}" ]] || { echo "Error: PKGBUILD not found at '${PKGBUILD_PATH}'" >&2; exit 1; } - -FEED='https://api2.cursor.sh/updates/api/update/darwin-arm64/sand/0.0.0/00000000-0000-0000-0000-000000000000/stable' -json="$(curl -fsSL -H 'cache-control: no-cache' "${FEED}")" - -ver="$(jq -er '.name // .version' <<<"${json}")" -feed_url="$(jq -er '.url' <<<"${json}")" -commit="$(sed -nE 's@.*/(grokbot|sand)/stable/([0-9a-f]{40})/.*@\2@p' <<<"${feed_url}")" - -[[ -n "${ver}" && -n "${commit}" ]] || { - echo "Error: could not parse version/commit from feed: ${json}" >&2 - exit 1 -} - -deb_url="https://downloads.cursor.com/grokbot/stable/${commit}/linux/x64/Grok_Bot_${ver}.deb" -code="$(curl -fsSIL -o /dev/null -w '%{http_code}' "${deb_url}")" -[[ "${code}" == "200" ]] || { - echo "Error: Linux deb not fetchable (${code}): ${deb_url}" >&2 - exit 1 -} - -tmp="$(mktemp)" -trap 'rm -f "${tmp}"' EXIT -curl -fL --retry 3 -o "${tmp}" "${deb_url}" -sum="$(sha256sum "${tmp}" | awk '{print $1}')" - -current_ver="$(sed -nE 's/^pkgver=([^[:space:]#]+).*/\1/p' "${PKGBUILD_PATH}" | head -n1)" - -sed -i -E \ - -e "s/^_commit=.*/_commit=${commit}/" \ - -e "s/^pkgver=.*/pkgver=${ver}/" \ - -e "0,/^[[:space:]]*'[0-9a-f]{64}'/s// '${sum}'/" \ - "${PKGBUILD_PATH}" - -if [[ "${ver}" != "${current_ver}" ]]; then - sed -i -E 's/^pkgrel=.*/pkgrel=1/' "${PKGBUILD_PATH}" -fi - -echo "${ver} ${commit}" -echo "${deb_url}" -echo "${sum}" diff --git a/pkgbuilds/gtk2/.nvchecker.toml b/pkgbuilds/gtk2/.nvchecker.toml new file mode 100644 index 0000000..d6510d8 --- /dev/null +++ b/pkgbuilds/gtk2/.nvchecker.toml @@ -0,0 +1,4 @@ +[gtk2] +source = "git" +git = "https://gitlab.gnome.org/GNOME/gtk.git" +include_regex = '2\.24\.\d+' diff --git a/pkgbuilds/gtk2/.omarchy/package.json b/pkgbuilds/gtk2/.omarchy/package.json new file mode 100644 index 0000000..d034b5e --- /dev/null +++ b/pkgbuilds/gtk2/.omarchy/package.json @@ -0,0 +1,7 @@ +{ + "source": "local", + "origin": { + "aur": "gtk2", + "commit": "19829b43450b1a7e838896f9b2dd391a227eabd1" + } +} diff --git a/pkgbuilds/gtk2/0001-Lower-severity-of-XID-collision-warnings.patch b/pkgbuilds/gtk2/0001-Lower-severity-of-XID-collision-warnings.patch new file mode 100644 index 0000000..5392eeb --- /dev/null +++ b/pkgbuilds/gtk2/0001-Lower-severity-of-XID-collision-warnings.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Jan Alexander Steffens (heftig)" +Date: Sat, 22 Jun 2024 22:08:43 +0200 +Subject: [PATCH] Lower severity of XID collision warnings + +--- + gdk/x11/gdkxid.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/gdk/x11/gdkxid.c b/gdk/x11/gdkxid.c +index 1005f9e40c0d..1523fa70b2d3 100644 +--- a/gdk/x11/gdkxid.c ++++ b/gdk/x11/gdkxid.c +@@ -58,7 +58,7 @@ _gdk_xid_table_insert (GdkDisplay *display, + (GEqualFunc) gdk_xid_equal); + + if (g_hash_table_lookup (display_x11->xid_ht, xid)) +- g_warning ("XID collision, trouble ahead"); ++ g_debug ("XID collision, trouble ahead"); + + g_hash_table_insert (display_x11->xid_ht, xid, data); + } diff --git a/pkgbuilds/gtk2/0002-Stop-looking-for-modules-in-cwd.patch b/pkgbuilds/gtk2/0002-Stop-looking-for-modules-in-cwd.patch new file mode 100644 index 0000000..db2a2fc --- /dev/null +++ b/pkgbuilds/gtk2/0002-Stop-looking-for-modules-in-cwd.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthias Clasen +Date: Sat, 15 Jun 2024 14:18:01 -0400 +Subject: [PATCH] Stop looking for modules in cwd + +This is just not a good idea. It is surprising, and can be misused. + +Fixes: #6786 +--- + gtk/gtkmodules.c | 9 ++------- + 1 file changed, 2 insertions(+), 7 deletions(-) + +diff --git a/gtk/gtkmodules.c b/gtk/gtkmodules.c +index 50729b61a590..c0f0c30a2148 100644 +--- a/gtk/gtkmodules.c ++++ b/gtk/gtkmodules.c +@@ -229,13 +229,8 @@ find_module (const gchar *name) + gchar *module_name; + + module_name = _gtk_find_module (name, "modules"); +- if (!module_name) +- { +- /* As last resort, try loading without an absolute path (using system +- * library path) +- */ +- module_name = g_module_build_path (NULL, name); +- } ++ if (module_name == NULL) ++ return NULL; + + module = g_module_open (module_name, G_MODULE_BIND_LOCAL | G_MODULE_BIND_LAZY); + diff --git a/pkgbuilds/asusctl/LICENSE b/pkgbuilds/gtk2/LICENSE similarity index 100% rename from pkgbuilds/asusctl/LICENSE rename to pkgbuilds/gtk2/LICENSE diff --git a/pkgbuilds/asusctl/LICENSES/0BSD.txt b/pkgbuilds/gtk2/LICENSES/0BSD.txt similarity index 100% rename from pkgbuilds/asusctl/LICENSES/0BSD.txt rename to pkgbuilds/gtk2/LICENSES/0BSD.txt diff --git a/pkgbuilds/gtk2/LICENSES/LGPL-2.1-or-later.txt b/pkgbuilds/gtk2/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 0000000..c6487f4 --- /dev/null +++ b/pkgbuilds/gtk2/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,176 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +GNU LESSER GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That's all there is to it! diff --git a/pkgbuilds/gtk2/PKGBUILD b/pkgbuilds/gtk2/PKGBUILD new file mode 100644 index 0000000..4d71166 --- /dev/null +++ b/pkgbuilds/gtk2/PKGBUILD @@ -0,0 +1,112 @@ +# Maintainer: Jan Alexander Steffens (heftig) +# Maintainer: Jan de Groot + +pkgname=gtk2 +pkgver=2.24.33 +pkgrel=5 +pkgdesc="GObject-based multi-platform GUI toolkit (legacy)" +url="https://www.gtk.org/" +# Built for aarch64 only: Valve's native arm64 Steam client loads GTK 2 from +# the host, which Arch Linux ARM no longer ships. On x86_64 Steam brings its +# own copy in its runtime. +arch=(aarch64) +license=(LGPL-2.1-or-later) +depends=( + atk + cairo + desktop-file-utils + fontconfig + gdk-pixbuf2 + glib2 + glibc + gtk-update-icon-cache + libcups + librsvg + libx11 + libxcomposite + libxcursor + libxdamage + libxext + libxfixes + libxi + libxinerama + libxrandr + libxrender + pango + shared-mime-info +) +makedepends=( + git + glib2-devel + gobject-introspection + gtk-doc +) +source=( + "git+https://gitlab.gnome.org/GNOME/gtk.git#tag=$pkgver" + gtk-query-immodules-2.0.hook + 0001-Lower-severity-of-XID-collision-warnings.patch + 0002-Stop-looking-for-modules-in-cwd.patch +) +b2sums=('1b18d1cfef55466209cf93be45af15dc058a8b74d13ab590cfc7f0b09b0584adc62d4330aaed65185c0142cc8c326e4274c8e75e0af94bec5be3cfcca105c1e6' + '9c531f9f605e1739e13c39c1cac22daddd9574f3082f18bcf0b9dfaa4c41f2485d55be03a9ed12fb4504d509f0d5ac63980a9d9349e3f80a06595c6430c78096' + '45ecc976d9eb9d990fc204230aa052a6d1b2bdfdc94788be37d576ab262a1da49855eb46ecd4bfce4efde6e2f817a1660c6d1fa756be3b372f7f8d13b0ef0fd0' + '06ca1c6f0e8f6a7c7a3cc08ce3d358af978d28fc4aa8d9e981883e3ad5adf7d821bcb27bc8b93bf65171a92396ac8f7ad62c90db501a492cca7c30b6081e957f') + +prepare() { + cd gtk + git apply -3 ../0001-Lower-severity-of-XID-collision-warnings.patch + + # CVE-2024-6655: https://www.openwall.com/lists/oss-security/2024/09/09/1 + # https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/7361 + git apply -3 ../0002-Stop-looking-for-modules-in-cwd.patch + + sed -i '/AM_INIT_AUTOMAKE/s/]/ foreign]/' configure.ac + autoreconf -fvi +} + +build() { + local configure_options=( + --prefix=/usr + --sysconfdir=/etc + --localstatedir=/var + --with-xinput=yes + --disable-gtk-doc + ) + + CFLAGS+=" -Wno-error=implicit-int -Wno-error=incompatible-pointer-types" + + cd gtk + ./configure "${configure_options[@]}" + sed -i -e 's/ -shared / -Wl,-O1,--as-needed\0/g' libtool + make +} + +package() { + optdepends=( + 'adwaita-fonts: Default font' + 'adwaita-icon-theme: Default icon theme' + 'gnome-themes-extra-gtk2: Default widget theme' + 'python: gtk-builder-convert' + ) + provides=( + libgailutil.so + libgdk-x11-2.0.so + libgtk-x11-2.0.so + ) + install=gtk2.install + + make -C gtk DESTDIR="$pkgdir" install + + install -Dm644 /dev/stdin "$pkgdir/usr/share/gtk-2.0/gtkrc" < - -pkgname=hyprland-guiutils -pkgver=0.2.2 -pkgrel=3 -pkgdesc='Hyprland GUI utilities' -arch=(aarch64) -url="https://github.com/hyprwm/$pkgname" -license=(BSD-3-Clause) -depends=( - libgcc - libstdc++ - glibc # libc.so libm.so - hyprlang - hyprtoolkit libhyprtoolkit.so - hyprutils libhyprutils.so - libdrm - pixman -) -makedepends=(cmake) -replaces=(hyprland-qtutils) -_archive="$pkgname-$pkgver" -source=("$url/archive/v$pkgver/$_archive.tar.gz") -sha256sums=('16f92a6c5a22ac58e1fc313f6b202c188da45e804e1f21ff57dfd0da5c1a01b7') - -build() { - cd "$_archive" - local cmake_flags=( - -D CMAKE_BUILD_TYPE=Release - -D CMAKE_INSTALL_PREFIX=/usr - ) - cmake -B build ${cmake_flags[@]} - cmake --build build -} - -package() { - cd "$_archive" - DESTDIR="$pkgdir" cmake --install build - install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname" LICENSE -} diff --git a/pkgbuilds/hyprland-preview-share-picker/PKGBUILD b/pkgbuilds/hyprland-preview-share-picker/PKGBUILD index 0862780..e3b059e 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=2 +pkgrel=3 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" diff --git a/pkgbuilds/hyprland/.omarchy/package.json b/pkgbuilds/hyprland/.omarchy/package.json index 5ee99df..56f3b83 100644 --- a/pkgbuilds/hyprland/.omarchy/package.json +++ b/pkgbuilds/hyprland/.omarchy/package.json @@ -16,7 +16,7 @@ ], "rebuilt_against": { "aarch64": { - "aquamarine": "0.15.0-2" + "aquamarine": "0.15.1-1.1" } } } diff --git a/pkgbuilds/hyprland/PKGBUILD b/pkgbuilds/hyprland/PKGBUILD index c03e578..0e41ae7 100644 --- a/pkgbuilds/hyprland/PKGBUILD +++ b/pkgbuilds/hyprland/PKGBUILD @@ -5,7 +5,7 @@ pkgname=(hyprland hyprpm) pkgver=0.56.2 -pkgrel=4 +pkgrel=5 pkgdesc='a highly customizable dynamic tiling Wayland compositor' arch=(aarch64) url="https://github.com/hyprwm/${pkgname^}" diff --git a/pkgbuilds/hyprtoolkit/.omarchy/package.json b/pkgbuilds/hyprtoolkit/.omarchy/package.json deleted file mode 100644 index ee5e6cb..0000000 --- a/pkgbuilds/hyprtoolkit/.omarchy/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "source": "local", - "release_ring": "fast", - "upstream": { - "git_tags": "https://github.com/hyprwm/hyprtoolkit.git", - "tag_pattern": "v{pkgver}", - "sources": { - "any": [ - "https://github.com/hyprwm/hyprtoolkit/archive/v{pkgver}/hyprtoolkit-{pkgver}.tar.gz" - ] - } - }, - "rebuild_on": [ - "aquamarine" - ], - "rebuilt_against": { - "aarch64": { - "aquamarine": "0.15.0-2" - } - } -} diff --git a/pkgbuilds/hyprtoolkit/PKGBUILD b/pkgbuilds/hyprtoolkit/PKGBUILD deleted file mode 100644 index 2427e27..0000000 --- a/pkgbuilds/hyprtoolkit/PKGBUILD +++ /dev/null @@ -1,50 +0,0 @@ -# Maintainer: Caleb Maclennan - -pkgname=hyprtoolkit -pkgver=0.6.0 -pkgrel=1 -pkgdesc='A modern C++ Wayland-native GUI toolkit' -arch=(aarch64) -url="https://github.com/hyprwm/$pkgname" -license=(BSD-3-Clause) -depends=( - libgcc - libstdc++ - aquamarine libaquamarine.so - cairo libcairo.so - glib2 libglib-2.0.so - glibc # libc.so libm.so - hyprgraphics libhyprgraphics.so - hyprlang libhyprlang.so - hyprutils libhyprutils.so - iniparser libiniparser.so - libdrm # libdrm.so - libglvnd libEGL.so libOpenGL.so - libxkbcommon libxkbcommon.so - mesa # libgbm.so - pango libpango-1.0.so # libpango.so - pixman libpixman-1.so - wayland libwayland-client.so -) -makedepends=(cmake - hyprwayland-scanner) -provides=(libhyprtoolkit.so) -_archive="$pkgname-$pkgver" -source=("$url/archive/v$pkgver/$_archive.tar.gz") -sha256sums=('53c41be72af97d9ede274a63c9c1034c58726d862905763ed6e5a564ae42ba6b') - -build() { - cd "$_archive" - local cmake_options=( - -D CMAKE_BUILD_TYPE=None - -D CMAKE_INSTALL_PREFIX=/usr - ) - cmake -B build -W no-dev ${cmake_options[@]} - cmake --build build -} - -package() { - cd "$_archive" - DESTDIR="$pkgdir" cmake --install build - install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE -} diff --git a/pkgbuilds/learn-omarchy/PKGBUILD b/pkgbuilds/learn-omarchy/PKGBUILD index 4dd2934..b440c6a 100644 --- a/pkgbuilds/learn-omarchy/PKGBUILD +++ b/pkgbuilds/learn-omarchy/PKGBUILD @@ -2,7 +2,7 @@ pkgname=learn-omarchy pkgver=0.2.5 -pkgrel=1 +pkgrel=2 pkgdesc="Interactive, theme-aware courses for learning Omarchy" arch=('any') url="https://github.com/DanWahlin/learn-omarchy" diff --git a/pkgbuilds/libfprint-git/PKGBUILD b/pkgbuilds/libfprint-git/PKGBUILD index 4406635..19c1375 100644 --- a/pkgbuilds/libfprint-git/PKGBUILD +++ b/pkgbuilds/libfprint-git/PKGBUILD @@ -16,7 +16,7 @@ pkgname=libfprint-git pkgver=1.94.100.r10.g6f9479c -pkgrel=2 +pkgrel=3 # Retain the epoch used to supersede the previously published untested build. epoch=1 pkgdesc="Library for fingerprint readers (pinned upstream snapshot with FocalTech and Synaptics 06cb:010b support, and Apple Touch ID on aarch64)" diff --git a/pkgbuilds/libva-v4l2_request-avd/.omarchy/package.json b/pkgbuilds/libva-v4l2_request-avd/.omarchy/package.json index 5dc4867..5943457 100644 --- a/pkgbuilds/libva-v4l2_request-avd/.omarchy/package.json +++ b/pkgbuilds/libva-v4l2_request-avd/.omarchy/package.json @@ -8,5 +8,10 @@ }, "rebuild_on": [ "libva" - ] + ], + "rebuilt_against": { + "aarch64": { + "libva": "2.24.1-1" + } + } } diff --git a/pkgbuilds/libva-v4l2_request-avd/PKGBUILD b/pkgbuilds/libva-v4l2_request-avd/PKGBUILD index 255f542..5256b66 100644 --- a/pkgbuilds/libva-v4l2_request-avd/PKGBUILD +++ b/pkgbuilds/libva-v4l2_request-avd/PKGBUILD @@ -4,7 +4,7 @@ _fork=sofus13 pkgname=libva-v4l2_request-avd pkgver=1.3 -pkgrel=1 +pkgrel=2 pkgdesc='VA-API driver for V4L2 stateless decoders, with Apple Video Decoder (AVD) support' arch=('aarch64') url="https://github.com/$_fork/libva-v4l2_request" diff --git a/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD b/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD index 72db547..9fd3695 100644 --- a/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD +++ b/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD @@ -3,7 +3,7 @@ _pkgname="limine-entry-tool" pkgname="limine-mkinitcpio-hook" _gradle_version=9.7.1 pkgver=1.40.0 -pkgrel=1 +pkgrel=2 pkgdesc="Install kernels for the Limine bootloader." arch=('x86_64' 'aarch64') url="https://gitlab.com/Zesko/limine-entry-tool" diff --git a/pkgbuilds/limine-snapper-sync/PKGBUILD b/pkgbuilds/limine-snapper-sync/PKGBUILD index 327a152..ddbf2b8 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.32.1 -pkgrel=1 +pkgrel=2 _gradle_version=9.7.1 pkgdesc="Integrates Limine boot entries with Snapper snapshots." arch=('x86_64' 'aarch64') diff --git a/pkgbuilds/linux-firmware-cirrus/.omarchy/package.json b/pkgbuilds/linux-firmware-cirrus-dx13260/.omarchy/package.json similarity index 100% rename from pkgbuilds/linux-firmware-cirrus/.omarchy/package.json rename to pkgbuilds/linux-firmware-cirrus-dx13260/.omarchy/package.json diff --git a/pkgbuilds/linux-firmware-cirrus-dx13260/PKGBUILD b/pkgbuilds/linux-firmware-cirrus-dx13260/PKGBUILD new file mode 100644 index 0000000..5fe103c --- /dev/null +++ b/pkgbuilds/linux-firmware-cirrus-dx13260/PKGBUILD @@ -0,0 +1,45 @@ +# Maintainer: Spencer Bull + +pkgname=linux-firmware-cirrus-dx13260 +pkgver=20260810 +pkgrel=2 +pkgdesc="Cirrus speaker firmware aliases for Dell XPS 13 DX13260 Panther Lake" +arch=('x86_64') +url="https://gitlab.com/kernel-firmware/linux-firmware" +license=('LicenseRef-cirrus') +depends=('linux-firmware-cirrus>=20260810-2') +options=('!strip' '!debug') +_cirrus=linux-firmware-cirrus-20260910-2-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" + "dell-xps13-ptl-firmware.conf" +) +noextract=("${_cirrus}") +sha256sums=('70100c551b079bd8abec3d9c96a16defd04766b2a4afc6d7be9128d37e9840f7' + 'SKIP' + '760385ad796818f9d60af3bc62c9670882c32050063f1d6086fb12f8817ed3fc') +validpgpkeys=('83BC8889351B5DEBBB68416EB8AC08600F108CDF') + +package() { + local firmware_dir="${pkgdir}/usr/lib/firmware/updates/cirrus" + install -d "$firmware_dir" + + # The updates directory avoids ownership conflicts with native Arch aliases. + local speaker + for speaker in 1 2 3; do + ln -s ../../cirrus/cs35l56/CS35L56_Rev4.5.9.wmfw.zst \ + "$firmware_dir/cs35l56-b2-dsp1-misc-10280e54-spkid${speaker}.wmfw.zst" + ln -s "../../cirrus/cs35l56-b2-dsp1-misc-10280e53-spkid${speaker}-ampl.bin.zst" \ + "$firmware_dir/cs35l56-b2-dsp1-misc-10280e54-spkid${speaker}-ampl.bin.zst" + ln -s "../../cirrus/cs35l56-b2-dsp1-misc-10280e53-spkid${speaker}-ampr.bin.zst" \ + "$firmware_dir/cs35l56-b2-dsp1-misc-10280e54-spkid${speaker}-ampr.bin.zst" + done + + install -Dm644 dell-xps13-ptl-firmware.conf \ + "${pkgdir}/etc/mkinitcpio.conf.d/dell-xps13-ptl-firmware.conf" + + install -d "${pkgdir}/usr/share/licenses/${pkgname}" + bsdtar -xOf "${srcdir}/${_cirrus}" usr/share/licenses/linux-firmware-cirrus/LICENSE.cirrus \ + >"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.cirrus" +} diff --git a/pkgbuilds/linux-firmware-cirrus-dx13260/README.package.md b/pkgbuilds/linux-firmware-cirrus-dx13260/README.package.md new file mode 100644 index 0000000..1d0ddf1 --- /dev/null +++ b/pkgbuilds/linux-firmware-cirrus-dx13260/README.package.md @@ -0,0 +1,7 @@ +This package supplies the nine Dell XPS 13 DX13260 Panther Lake `10280e54` firmware aliases, under `/usr/lib/firmware/updates/cirrus`. The kernel and mkinitcpio search this directory before the stock firmware directory. A package-owned mkinitcpio drop-in explicitly includes all nine links and their targets in the boot image, because module firmware globs alone do not include every speaker ID. Each link targets the exact existing Wildcat Lake tuning or Rev4.5.9 firmware target used by Arch’s native aliases. Stock `linux-firmware-cirrus` continues to own and update every firmware binary; this package neither provides nor conflicts with it. + +Omarchy integration should install this package on every matching XPS 13 DX13260 Panther Lake machine, including machines currently using native firmware. Keeping its package identity installed lets a channel downgrade restore the overlay through pacman alone, even after migrations have completed. The initial links exactly match the current native aliases; a later native-firmware channel transition removes the overlay payload. Listing it in `omarchy-other.packages` makes it available offline without installing it on other hardware. It can coexist with both the old same-name Omarchy shim and native Arch firmware; a stable refresh may remove the old shim’s aliases without removing these separately owned links. + +When a channel’s core snapshot supplies native aliases, publish a higher-version empty transitional package depending on `linux-firmware-cirrus>=20260910-2`, with no firmware links or mkinitcpio drop-in. The existing Omarchy firmware path hook rebuilds the UKIs when the links are removed; no new hook is needed. That transaction removes the overlay while upgrading stock firmware, so the native aliases take over. Publish the transition only after the channel supplies the required stock version, and retain it for late updaters. Keep the earlier overlay package in older channels: a downgrade from the native transition back to an older snapshot must downgrade both packages together and restore the links. Do not publish the higher native dependency to an older channel. Removing this recipe or its repository entry alone leaves installed overlays in place and could mask future native PTL tuning. + +This package does not replace the separate Panther Lake display workaround. diff --git a/pkgbuilds/linux-firmware-cirrus-dx13260/dell-xps13-ptl-firmware.conf b/pkgbuilds/linux-firmware-cirrus-dx13260/dell-xps13-ptl-firmware.conf new file mode 100644 index 0000000..356e601 --- /dev/null +++ b/pkgbuilds/linux-firmware-cirrus-dx13260/dell-xps13-ptl-firmware.conf @@ -0,0 +1,12 @@ +# Include every speaker ID and amplifier alias before the encrypted root is available. +FILES+=( + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid1-ampl.bin.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid1-ampr.bin.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid1.wmfw.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid2-ampl.bin.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid2-ampr.bin.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid2.wmfw.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid3-ampl.bin.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid3-ampr.bin.zst + /usr/lib/firmware/updates/cirrus/cs35l56-b2-dsp1-misc-10280e54-spkid3.wmfw.zst +) diff --git a/pkgbuilds/linux-firmware-cirrus/keys/pgp/83BC8889351B5DEBBB68416EB8AC08600F108CDF.asc b/pkgbuilds/linux-firmware-cirrus-dx13260/keys/pgp/83BC8889351B5DEBBB68416EB8AC08600F108CDF.asc similarity index 100% rename from pkgbuilds/linux-firmware-cirrus/keys/pgp/83BC8889351B5DEBBB68416EB8AC08600F108CDF.asc rename to pkgbuilds/linux-firmware-cirrus-dx13260/keys/pgp/83BC8889351B5DEBBB68416EB8AC08600F108CDF.asc diff --git a/pkgbuilds/linux-firmware-cirrus/PKGBUILD b/pkgbuilds/linux-firmware-cirrus/PKGBUILD deleted file mode 100644 index 0c46f9b..0000000 --- a/pkgbuilds/linux-firmware-cirrus/PKGBUILD +++ /dev/null @@ -1,63 +0,0 @@ -# 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-omarchy-bore/0010-archlinux-base.patch b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch index 55e9379..497422b 100644 --- a/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch +++ b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch @@ -1,5 +1,4 @@ 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 @@ @@ -15,7 +14,7 @@ index f0e2e131a9a5..7b611da9a27a 100644 /* * Minimum number of threads to boot the kernel */ -@@ -2093,6 +2099,11 @@ __latent_entropy struct task_struct *copy_process( +@@ -2092,6 +2098,11 @@ __latent_entropy struct task_struct *copy_process( return ERR_PTR(-EPERM); } @@ -27,7 +26,7 @@ index f0e2e131a9a5..7b611da9a27a 100644 /* * 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) +@@ -3165,6 +3176,10 @@ static int check_unshare_flags(unsigned long unshare_flags) if (!current_is_single_threaded()) return -EINVAL; } @@ -38,7 +37,7 @@ index f0e2e131a9a5..7b611da9a27a 100644 return 0; } -@@ -3398,6 +3413,15 @@ static const struct ctl_table fork_sysctl_table[] = { +@@ -3400,6 +3415,15 @@ static const struct ctl_table fork_sysctl_table[] = { .mode = 0644, .proc_handler = sysctl_max_threads, }, diff --git a/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch.sig b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch.sig index a7eaa24..586266d 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch.sig and b/pkgbuilds/linux-omarchy-bore/0010-archlinux-base.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch index 1d049bd..c126c5a 100644 --- a/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch +++ b/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch @@ -177,7 +177,7 @@ diff --git a/kernel/fork.c b/kernel/fork.c #include #define CREATE_TRACE_POINTS -@@ -2441,6 +2445,11 @@ __latent_entropy struct task_struct *copy_process( +@@ -2443,6 +2447,11 @@ __latent_entropy struct task_struct *copy_process( p->start_time = ktime_get_ns(); p->start_boottime = ktime_get_boottime_ns(); @@ -189,7 +189,7 @@ diff --git a/kernel/fork.c b/kernel/fork.c /* * 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( +@@ -2528,7 +2537,11 @@ __latent_entropy struct task_struct *copy_process( p->real_parent->signal->is_child_subreaper; if (clone_flags & CLONE_AUTOREAP) p->signal->autoreap = 1; @@ -749,7 +749,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c struct load_weight lw; if (task_has_idle_policy(p)) { -@@ -8975,6 +8983,10 @@ void __init sched_init(void) +@@ -8989,6 +8997,10 @@ void __init sched_init(void) BUG_ON(!sched_class_above(&ext_sched_class, &idle_sched_class)); #endif @@ -934,7 +934,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c void __init sched_init_granularity(void) { -@@ -1083,7 +1108,11 @@ struct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq) +@@ -1093,7 +1118,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) { @@ -946,7 +946,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c 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) +@@ -1170,8 +1199,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; @@ -966,7 +966,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* 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) +@@ -1227,6 +1265,7 @@ struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) /************************************************************** * Scheduling class statistics methods: */ @@ -974,7 +974,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c int sched_update_scaling(void) { unsigned int factor = get_update_sysctl_factor(); -@@ -1228,6 +1267,7 @@ int sched_update_scaling(void) +@@ -1238,6 +1277,7 @@ int sched_update_scaling(void) return 0; } @@ -982,7 +982,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c 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) {} +@@ -1979,6 +2019,38 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) {} #endif /* CONFIG_SCHED_CACHE */ @@ -1021,7 +1021,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * Used by other classes to account runtime. */ -@@ -2006,6 +2078,11 @@ static void update_curr(struct cfs_rq *cfs_rq) +@@ -2014,6 +2086,11 @@ static void update_curr(struct cfs_rq *cfs_rq) resched = update_deadline(cfs_rq, curr); if (entity_is_task(curr)) { @@ -1033,7 +1033,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * 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) +@@ -4671,8 +4748,8 @@ rescale_entity(struct sched_entity *se, unsigned long weight, bool rel_vprot) se->vprot = div64_long(se->vprot * old_weight, weight); } @@ -1044,7 +1044,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c { 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) +@@ -5976,13 +6053,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) { @@ -1059,7 +1059,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * 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) +@@ -6094,7 +6170,18 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) se->rel_deadline = 0; return; } @@ -1079,7 +1079,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * 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) +@@ -6103,6 +6190,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; @@ -1089,7 +1089,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * EEVDF: vd_i = ve_i + r_i/w_i */ -@@ -6104,7 +6194,7 @@ static void check_enqueue_throttle(struct cfs_rq *cfs_rq); +@@ -6113,7 +6203,7 @@ static void check_enqueue_throttle(struct cfs_rq *cfs_rq); static inline int cfs_rq_throttled(struct cfs_rq *cfs_rq); static void @@ -1098,7 +1098,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c 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) +@@ -7776,7 +7866,7 @@ static int choose_idle_cpu(int cpu, struct task_struct *p) } static void @@ -1107,7 +1107,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c { struct cfs_rq *cfs_rq = cfs_rq_of(se); -@@ -7785,7 +7875,13 @@ requeue_delayed_entity(struct sched_entity *se) +@@ -7794,7 +7884,13 @@ requeue_delayed_entity(struct sched_entity *se) cfs_rq->nr_queued--; if (se != cfs_rq->curr) __dequeue_entity(cfs_rq, se); @@ -1122,7 +1122,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c 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) +@@ -7833,7 +7929,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) util_est_enqueue(&rq->cfs, p); if (flags & ENQUEUE_DELAYED) { @@ -1131,7 +1131,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c return; } -@@ -7842,7 +7938,7 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) +@@ -7851,7 +7947,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) @@ -1140,7 +1140,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c 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) +@@ -8054,6 +8150,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); @@ -1156,7 +1156,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c 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 +@@ -9860,6 +9965,18 @@ static void wakeup_preempt_fair(struct rq *rq, struct task_struct *p, int wake_f goto pick; } @@ -1175,7 +1175,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * 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) +@@ -10030,16 +10147,25 @@ static void yield_task_fair(struct rq *rq) /* * Are we the only task in the tree? */ @@ -1201,7 +1201,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * 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) +@@ -15022,6 +15148,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); 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 index 6256900..1efa5e3 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0110-bore-6.8.0.patch.sig 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 index 712b9b7..14e2d46 100644 --- a/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch +++ b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch @@ -22,19 +22,6 @@ diff --git a/arch/x86/include/asm/pgtable_types.h b/arch/x86/include/asm/pgtable 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 @@ -168,35 +155,7 @@ diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c 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]; +@@ -88,9 +88,8 @@ static unsigned long direct_pages_count[PG_LEVEL_NUM]; void update_page_count(int level, unsigned long pages) { /* Protect against CPA */ @@ -207,16 +166,37 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c } static void split_page_count(int level) -@@ -418,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) +@@ -420,7 +419,7 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) int collapsed = 0; int i; +- spin_lock(&cpa_lock); + guard(spinlock)(&cpa_lock); -+ + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { for (i = 0; i < cpa->numpages; i++) - 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) +@@ -435,10 +434,8 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + collapsed += collapse_large_pages(addr, &pgtables); + } + +- if (!collapsed) { +- spin_unlock(&cpa_lock); ++ if (!collapsed) + return; +- } + + flush_tlb_all(); + +@@ -454,8 +451,6 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + else + pagetable_free(ptdesc); + } +- +- spin_unlock(&cpa_lock); + } + + static void cpa_collapse_large_pages(struct cpa_data *cpa) +@@ -916,24 +911,23 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) { /* change init_mm */ set_pte_atomic(kpte, pte); @@ -246,7 +226,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c } 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, +@@ -1103,16 +1097,11 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address, static int should_split_large_page(pte_t *kpte, unsigned long address, struct cpa_data *cpa) { @@ -265,7 +245,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c } 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, +@@ -1162,16 +1151,14 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, bool nx, rw; pte_t *tmp; @@ -284,7 +264,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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, +@@ -1204,7 +1191,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, break; default: @@ -292,7 +272,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c return 1; } -@@ -1225,7 +1217,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, +@@ -1252,7 +1238,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, * just split large page entry. */ flush_tlb_all(); @@ -300,21 +280,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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, +@@ -1327,11 +1312,11 @@ static int collapse_pmd_page(pmd_t *pmd, unsigned long addr, list_add(&page_ptdesc(pmd_page(old_pmd))->pt_list, pgtables); if (IS_ENABLED(CONFIG_X86_32)) { @@ -329,7 +295,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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, +@@ -1405,7 +1390,7 @@ static int collapse_pud_page(pud_t *pud, unsigned long addr, */ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) { @@ -338,7 +304,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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) +@@ -1413,26 +1398,24 @@ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) addr &= PMD_MASK; @@ -370,153 +336,6 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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 diff --git a/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch.sig b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch.sig index 54e6137..766d275 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0120-tlbpull.patch.sig and b/pkgbuilds/linux-omarchy-bore/0120-tlbpull.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 index 9c155d1..2e16e5f 100644 --- a/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch +++ b/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch @@ -1,7 +1,7 @@ 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, +@@ -13476,12 +13476,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. */ diff --git a/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch.sig b/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch.sig index 3e089bd..dd5fd2d 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0130-sched-detach-tasks.patch.sig 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 deleted file mode 100644 index cdb96f8..0000000 --- a/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 3407a4e..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0131-sched-avg-idle.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch index 8754792..745455c 100644 --- a/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch +++ b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch @@ -212,7 +212,7 @@ diff --git a/include/linux/vtime.h b/include/linux/vtime.h 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) +@@ -5106,7 +5106,7 @@ static inline void prepare_task(struct task_struct *next) WRITE_ONCE(next->on_cpu, 1); } @@ -221,7 +221,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c { /* * 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) +@@ -5150,7 +5150,7 @@ static void zap_balance_callbacks(struct rq *rq) rq->balance_callback = found ? &balance_push_callback : NULL; } @@ -230,7 +230,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c { void (*func)(struct rq *rq); struct balance_callback *next; -@@ -5178,7 +5178,7 @@ struct balance_callback balance_push_callback = { +@@ -5185,7 +5185,7 @@ struct balance_callback balance_push_callback = { .func = balance_push, }; @@ -239,7 +239,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c __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 +@@ -5259,7 +5259,7 @@ prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf __acquire(__rq_lockp(this_rq())); } @@ -248,7 +248,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c __releases(__rq_lockp(rq)) { /* -@@ -5286,7 +5286,7 @@ static inline void kmap_local_sched_out(void) +@@ -5293,7 +5293,7 @@ static inline void kmap_local_sched_out(void) #endif } @@ -257,7 +257,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c { #ifdef CONFIG_KMAP_LOCAL if (unlikely(current->kmap_ctrl.idx)) -@@ -5340,7 +5340,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, +@@ -5347,7 +5347,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. */ diff --git a/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch.sig b/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch.sig index 290ce7c..b867df1 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0140-sched-always-inline.patch.sig 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 index e59e1f1..cddb0d9 100644 --- a/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch +++ b/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch @@ -1,21 +1,7 @@ -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, +@@ -10701,17 +10701,40 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu, return mig_llc; } @@ -58,7 +44,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c mm = p->mm; if (!mm) return mig_unrestricted; -@@ -10758,6 +10781,14 @@ alb_break_llc(struct lb_env *env) +@@ -10768,6 +10791,14 @@ alb_break_llc(struct lb_env *env) unsigned long util = 0; struct task_struct *cur; @@ -73,7 +59,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c if (env->src_rq->nr_running <= 1) return true; -@@ -10765,7 +10796,8 @@ alb_break_llc(struct lb_env *env) +@@ -10775,7 +10806,8 @@ alb_break_llc(struct lb_env *env) if (cur && cur->sched_class == &fair_sched_class) util = task_util(cur); @@ -83,7 +69,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c util, false) == mig_forbid) return true; } -@@ -10805,8 +10837,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) +@@ -10815,8 +10847,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; @@ -93,7 +79,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c return false; return true; -@@ -11869,6 +11900,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, +@@ -11879,6 +11910,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, if (env->sd->flags & SD_SHARE_LLC) return false; @@ -109,17 +95,3 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * 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 index 89bb69e..81f23cb 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0141-sched-urgent-fixes.patch.sig 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 deleted file mode 100644 index a0f39ea..0000000 --- a/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index d64847c..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0142-sched-itmt-no-debugfs-dependency.patch.sig and /dev/null 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 index be80f96..2131bc4 100644 --- a/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch +++ b/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch @@ -14,22 +14,22 @@ diff --git a/include/linux/sched/sd_flags.h b/include/linux/sched/sd_flags.h 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, +@@ -12038,12 +12038,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; + if (balancing_at_rd) + *sg_overloaded = 1; + + /* + * Only account misfit load if @dst_cpu can @@ -44,43 +44,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c } } 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, +@@ -13156,9 +13169,24 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env, * average load. */ if (env->sd->flags & SD_ASYM_CPUCAPACITY && 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 index 91abc02..7ca830c 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0143-sched-hybrid-cluster-balancing.patch.sig 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 index f3af36b..7a22780 100644 --- a/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch +++ b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch @@ -1,7 +1,7 @@ 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) +@@ -14060,29 +14060,62 @@ static inline int on_null_domain(struct rq *rq) */ static inline int find_new_ilb(void) { 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 index a6bbb0d..d956e49 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch.sig and b/pkgbuilds/linux-omarchy-bore/0144-sched-nohz-idle-core.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0200-idle.patch b/pkgbuilds/linux-omarchy-bore/0200-idle.patch deleted file mode 100644 index dc499e5..0000000 --- a/pkgbuilds/linux-omarchy-bore/0200-idle.patch +++ /dev/null @@ -1,61 +0,0 @@ -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 deleted file mode 100644 index 20c9157..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0200-idle.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0210-pm-updates.patch b/pkgbuilds/linux-omarchy-bore/0210-pm-updates.patch new file mode 100644 index 0000000..5110d06 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0210-pm-updates.patch @@ -0,0 +1,2365 @@ +diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c +--- a/drivers/acpi/acpi_processor.c ++++ b/drivers/acpi/acpi_processor.c +@@ -994,3 +994,357 @@ end: + } + EXPORT_SYMBOL_NS_GPL(acpi_processor_evaluate_cst, "ACPI_PROCESSOR_IDLE"); + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ ++ ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++struct acpi_lpi_states_array { ++ unsigned int size; ++ unsigned int composite_states_size; ++ struct acpi_lpi_state *entries; ++ struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; ++}; ++ ++static int obj_get_integer(union acpi_object *obj, u32 *value) ++{ ++ if (obj->type != ACPI_TYPE_INTEGER) ++ return -EINVAL; ++ ++ *value = obj->integer.value; ++ return 0; ++} ++ ++#define lpi_state_debug(handle, message, state_idx) \ ++ acpi_handle_debug(handle, message " for _LPI state %u\n", state_idx) ++ ++static void process_lpi_state_package(union acpi_object *lpi_pkg, ++ struct acpi_lpi_state *lpi_state, ++ acpi_handle handle, ++ unsigned int state_idx, bool strict) ++{ ++ union acpi_object *lpi_pkg_elem, *obj; ++ ++ if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) ++ return; ++ ++ lpi_pkg_elem = lpi_pkg->package.elements; ++ ++ /* Get the entry method first and skip the state if that fails. */ ++ obj = &lpi_pkg_elem[6]; ++ if (obj->type == ACPI_TYPE_BUFFER) { ++ struct acpi_power_register *reg; ++ ++ if (obj->buffer.length < sizeof(*reg)) { ++ lpi_state_debug(handle, "Invalid register data", state_idx); ++ return; ++ } ++ ++ reg = (struct acpi_power_register *)obj->buffer.pointer; ++ if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { ++ lpi_state_debug(handle, "Unsupported entry method", state_idx); ++ return; ++ } ++ ++ lpi_state->entry_method = ACPI_CSTATE_FFH; ++ lpi_state->address = reg->address; ++ } else if (obj->type == ACPI_TYPE_INTEGER) { ++ lpi_state->entry_method = ACPI_CSTATE_INTEGER; ++ lpi_state->address = obj->integer.value; ++ } else { ++ lpi_state_debug(handle, "Invalid entry method", state_idx); ++ return; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No min. residency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); ++ lpi_state->min_residency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No wake latency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); ++ lpi_state->wake_latency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) ++ lpi_state->flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) ++ lpi_state->arch_flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) ++ lpi_state->res_cnt_freq = 1; ++ ++ if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) ++ lpi_state->enable_parent_state = 0; ++ ++ /* Skip elements [7-8] i.e. Residency/Usage counters. */ ++ ++ /* ++ * Avoid out-of-bounds access if the size of the package is less than ++ * expected. ++ */ ++ if (lpi_pkg->package.count < 10) ++ return; ++ ++ obj = &lpi_pkg_elem[9]; ++ if (obj->type == ACPI_TYPE_STRING) ++ strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); ++} ++ ++static int acpi_processor_evaluate_lpi(acpi_handle handle, ++ struct acpi_lpi_states_array *info, ++ bool strict) ++{ ++ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object *lpi_data, *lpi_pkg; ++ unsigned int lpi_pkg_count, state_idx; ++ struct acpi_lpi_state *lpi_state; ++ acpi_status status; ++ int ret = 0; ++ ++ status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); ++ if (ACPI_FAILURE(status)) { ++ acpi_handle_debug(handle, "No _LPI, giving up\n"); ++ return -ENODEV; ++ } ++ ++ lpi_data = buffer.pointer; ++ ++ /* There must be at least 4 elements = 3 elements + 1 package */ ++ if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || ++ lpi_data->package.count < 4) { ++ acpi_handle_debug(handle, "Not enough elements in _LPI\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_pkg_count = lpi_data->package.elements[2].integer.value; ++ ++ /* Validate number of power states. */ ++ if (!lpi_pkg_count || lpi_pkg_count != lpi_data->package.count - 3) { ++ acpi_handle_debug(handle, "Invalid _LPI state count\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_state = kzalloc_objs(*lpi_state, lpi_pkg_count); ++ if (!lpi_state) { ++ ret = -ENOMEM; ++ goto end; ++ } ++ ++ info->size = lpi_pkg_count; ++ info->entries = lpi_state; ++ ++ /* _LPI State packages start at index 3. */ ++ lpi_pkg = &lpi_data->package.elements[3]; ++ ++ for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { ++ lpi_state->index = state_idx; ++ process_lpi_state_package(lpi_pkg++, lpi_state++, handle, ++ state_idx, strict); ++ } ++ ++ acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); ++end: ++ kfree(buffer.pointer); ++ return ret; ++} ++ ++/** ++ * combine_lpi_states - combine local and parent LPI states to form a composite LPI state ++ * ++ * @local: local LPI state ++ * @parent: parent LPI state ++ * @result: composite LPI state ++ */ ++static bool combine_lpi_states(struct acpi_lpi_state *local, ++ struct acpi_lpi_state *parent, ++ struct acpi_lpi_state *result) ++{ ++ if (parent->entry_method == ACPI_CSTATE_INTEGER) { ++ if (!parent->address) /* 0 means autopromotable */ ++ return false; ++ result->address = local->address + parent->address; ++ } else { ++ result->address = parent->address; ++ } ++ ++ result->min_residency = max(local->min_residency, parent->min_residency); ++ result->wake_latency = local->wake_latency + parent->wake_latency; ++ result->enable_parent_state = parent->enable_parent_state; ++ result->entry_method = local->entry_method; ++ ++ result->flags = parent->flags; ++ result->arch_flags = parent->arch_flags; ++ result->index = parent->index; ++ ++ scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); ++ return true; ++} ++ ++#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) ++ ++static void stash_composite_state(struct acpi_lpi_states_array *curr_level, ++ struct acpi_lpi_state *t) ++{ ++ curr_level->composite_states[curr_level->composite_states_size++] = t; ++} ++ ++static bool too_many_states(acpi_handle handle, unsigned int state_count) ++{ ++ if (state_count < ACPI_PROCESSOR_MAX_POWER) ++ return false; ++ ++ acpi_handle_info(handle, "No space for more _LPI states than %d\n", ++ ACPI_PROCESSOR_MAX_POWER); ++ return true; ++} ++ ++static unsigned int flatten_lpi_states(acpi_handle handle, ++ struct acpi_lpi_state *lpi_states, ++ unsigned int state_count, ++ struct acpi_lpi_states_array *curr, ++ struct acpi_lpi_states_array *prev) ++{ ++ struct acpi_lpi_state *parent_lpi = curr->entries; ++ unsigned int j; ++ ++ /* ++ * Combine each of the "raw" _LPI states from the current (processor ++ * container) level with all of the composite _LPI states from the ++ * previous (processor or processor container) level. ++ */ ++ for (j = 0; j < curr->size; j++, parent_lpi++) { ++ struct acpi_lpi_state *flpi; ++ int i; ++ ++ if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) ++ continue; ++ ++ if (too_many_states(handle, state_count)) ++ break; ++ ++ flpi = &lpi_states[state_count]; ++ ++ for (i = 0; i < prev->composite_states_size; i++) { ++ struct acpi_lpi_state *local_lpi = prev->composite_states[i]; ++ ++ if (parent_lpi->index > local_lpi->enable_parent_state) ++ continue; ++ ++ if (!combine_lpi_states(local_lpi, parent_lpi, flpi)) ++ continue; ++ ++ stash_composite_state(curr, flpi); ++ state_count++; ++ flpi++; ++ ++ if (state_count >= ACPI_PROCESSOR_MAX_POWER) ++ break; ++ } ++ } ++ ++ return state_count; ++} ++ ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ struct acpi_lpi_states_array info[2], *prev, *curr; ++ acpi_handle handle = pr_handle; ++ unsigned int state_count = 0; ++ unsigned int i; ++ int ret; ++ ++ if (!osc_pc_lpi_support_confirmed) ++ return -EOPNOTSUPP; ++ ++ curr = &info[0]; ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ return ret; ++ ++ /* Copy all of the usable first-level states to power.lpi_states[]. */ ++ for (i = 0; i < curr->size; i++) { ++ struct acpi_lpi_state *lpi = &curr->entries[i]; ++ struct acpi_lpi_state *flpi; ++ ++ /* ++ * Skip states that are not enabled or have an inadequate entry ++ * method for this level. ++ */ ++ if (!(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) || ++ lpi->entry_method == ACPI_CSTATE_INTEGER) ++ continue; ++ ++ if (too_many_states(pr_handle, state_count)) ++ break; ++ ++ flpi = &pr_power->lpi_states[state_count++]; ++ memcpy(flpi, lpi, sizeof(*lpi)); ++ stash_composite_state(curr, flpi); ++ } ++ ++ kfree(curr->entries); ++ ++ /* ++ * If there are no _LPI states at the first level, there are no _LPI ++ * states at all. ++ */ ++ if (!state_count) ++ return -ENODATA; ++ ++ prev = curr; ++ curr = &info[1]; ++ ++ for (;;) { ++ struct acpi_lpi_states_array *tmp; ++ struct acpi_device *d; ++ ++ if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) ++ break; ++ ++ d = acpi_fetch_acpi_dev(handle); ++ if (!d) ++ break; ++ ++ if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) ++ break; ++ ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ break; ++ ++ /* flatten all the LPI states in this level of hierarchy */ ++ state_count = flatten_lpi_states(pr_handle, pr_power->lpi_states, ++ state_count, curr, prev); ++ ++ kfree(curr->entries); ++ ++ tmp = prev, prev = curr, curr = tmp; ++ } ++ ++ /* reset the index after flattening */ ++ for (i = 0; i < state_count; i++) ++ pr_power->lpi_states[i].index = i; ++ ++ pr_power->count = state_count; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_processor_extract_lpi_info, "ACPI_PROCESSOR_IDLE"); ++#endif /* CONFIG_ACPI_PROCESSOR_IDLE */ +diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c +--- a/drivers/acpi/processor_idle.c ++++ b/drivers/acpi/processor_idle.c +@@ -853,237 +853,6 @@ static int acpi_processor_setup_cstates(struct acpi_processor *pr) + + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ + +-struct acpi_lpi_states_array { +- unsigned int size; +- unsigned int composite_states_size; +- struct acpi_lpi_state *entries; +- struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; +-}; +- +-static int obj_get_integer(union acpi_object *obj, u32 *value) +-{ +- if (obj->type != ACPI_TYPE_INTEGER) +- return -EINVAL; +- +- *value = obj->integer.value; +- return 0; +-} +- +-static int acpi_processor_evaluate_lpi(acpi_handle handle, +- struct acpi_lpi_states_array *info) +-{ +- acpi_status status; +- int ret = 0; +- int pkg_count, state_idx = 1, loop; +- struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; +- union acpi_object *lpi_data; +- struct acpi_lpi_state *lpi_state; +- +- status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); +- if (ACPI_FAILURE(status)) { +- acpi_handle_debug(handle, "No _LPI, giving up\n"); +- return -ENODEV; +- } +- +- lpi_data = buffer.pointer; +- +- /* There must be at least 4 elements = 3 elements + 1 package */ +- if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || +- lpi_data->package.count < 4) { +- pr_debug("not enough elements in _LPI\n"); +- ret = -ENODATA; +- goto end; +- } +- +- pkg_count = lpi_data->package.elements[2].integer.value; +- +- /* Validate number of power states. */ +- if (pkg_count < 1 || pkg_count != lpi_data->package.count - 3) { +- pr_debug("count given by _LPI is not valid\n"); +- ret = -ENODATA; +- goto end; +- } +- +- lpi_state = kzalloc_objs(*lpi_state, pkg_count); +- if (!lpi_state) { +- ret = -ENOMEM; +- goto end; +- } +- +- info->size = pkg_count; +- info->entries = lpi_state; +- +- /* LPI States start at index 3 */ +- for (loop = 3; state_idx <= pkg_count; loop++, state_idx++, lpi_state++) { +- union acpi_object *element, *pkg_elem, *obj; +- +- element = &lpi_data->package.elements[loop]; +- if (element->type != ACPI_TYPE_PACKAGE || element->package.count < 7) +- continue; +- +- pkg_elem = element->package.elements; +- +- obj = pkg_elem + 6; +- if (obj->type == ACPI_TYPE_BUFFER) { +- struct acpi_power_register *reg; +- +- if (obj->buffer.length < sizeof(*reg)) { +- acpi_handle_debug(handle, +- "Invalid register data for _LPI state %d\n", +- state_idx); +- continue; +- } +- +- reg = (struct acpi_power_register *)obj->buffer.pointer; +- if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_IO && +- reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) +- continue; +- +- lpi_state->address = reg->address; +- lpi_state->entry_method = +- reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE ? +- ACPI_CSTATE_FFH : ACPI_CSTATE_SYSTEMIO; +- } else if (obj->type == ACPI_TYPE_INTEGER) { +- lpi_state->entry_method = ACPI_CSTATE_INTEGER; +- lpi_state->address = obj->integer.value; +- } else { +- pr_debug("Entry method of state-%d is invalid, disable it.\n", +- state_idx); +- continue; +- } +- +- lpi_state->index = state_idx; +- if (obj_get_integer(pkg_elem + 0, &lpi_state->min_residency)) { +- pr_debug("No min. residency found, assuming 10 us\n"); +- lpi_state->min_residency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 1, &lpi_state->wake_latency)) { +- pr_debug("No wakeup residency found, assuming 10 us\n"); +- lpi_state->wake_latency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 2, &lpi_state->flags)) +- lpi_state->flags = 0; +- +- if (obj_get_integer(pkg_elem + 3, &lpi_state->arch_flags)) +- lpi_state->arch_flags = 0; +- +- if (obj_get_integer(pkg_elem + 4, &lpi_state->res_cnt_freq)) +- lpi_state->res_cnt_freq = 1; +- +- if (obj_get_integer(pkg_elem + 5, &lpi_state->enable_parent_state)) +- lpi_state->enable_parent_state = 0; +- +- /* Skip elements [7-8] i.e. Residency/Usage counters. */ +- +- /* +- * Avoid out-of-bounds access if the size of the package is less +- * than expected. +- */ +- if (element->package.count < 10) +- continue; +- +- obj = pkg_elem + 9; +- if (obj->type == ACPI_TYPE_STRING) +- strscpy(lpi_state->desc, obj->string.pointer, +- ACPI_CX_DESC_LEN); +- } +- +- acpi_handle_debug(handle, "Found %d power states\n", state_idx); +-end: +- kfree(buffer.pointer); +- return ret; +-} +- +-/** +- * combine_lpi_states - combine local and parent LPI states to form a composite LPI state +- * +- * @local: local LPI state +- * @parent: parent LPI state +- * @result: composite LPI state +- */ +-static bool combine_lpi_states(struct acpi_lpi_state *local, +- struct acpi_lpi_state *parent, +- struct acpi_lpi_state *result) +-{ +- if (parent->entry_method == ACPI_CSTATE_INTEGER) { +- if (!parent->address) /* 0 means autopromotable */ +- return false; +- result->address = local->address + parent->address; +- } else { +- result->address = parent->address; +- } +- +- result->min_residency = max(local->min_residency, parent->min_residency); +- result->wake_latency = local->wake_latency + parent->wake_latency; +- result->enable_parent_state = parent->enable_parent_state; +- result->entry_method = local->entry_method; +- +- result->flags = parent->flags; +- result->arch_flags = parent->arch_flags; +- result->index = parent->index; +- +- scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); +- return true; +-} +- +-#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) +- +-static void stash_composite_state(struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_state *t) +-{ +- curr_level->composite_states[curr_level->composite_states_size++] = t; +-} +- +-static unsigned int flatten_lpi_states(struct acpi_processor *pr, +- unsigned int flat_state_cnt, +- struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_states_array *prev_level) +-{ +- int i, j, state_count = curr_level->size; +- struct acpi_lpi_state *p, *t = curr_level->entries; +- +- curr_level->composite_states_size = 0; +- for (j = 0; j < state_count; j++, t++) { +- struct acpi_lpi_state *flpi; +- +- if (!(t->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) +- continue; +- +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) { +- pr_warn("Limiting number of LPI states to max (%d)\n", +- ACPI_PROCESSOR_MAX_POWER); +- pr_warn("Please increase ACPI_PROCESSOR_MAX_POWER if needed.\n"); +- break; +- } +- +- flpi = &pr->power.lpi_states[flat_state_cnt]; +- +- if (!prev_level) { /* leaf/processor node */ +- memcpy(flpi, t, sizeof(*t)); +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- continue; +- } +- +- for (i = 0; i < prev_level->composite_states_size; i++) { +- p = prev_level->composite_states[i]; +- if (t->index <= p->enable_parent_state && +- combine_lpi_states(p, t, flpi)) { +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- flpi++; +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) +- break; +- } +- } +- } +- +- kfree(curr_level->entries); +- return flat_state_cnt; +-} +- + int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + { + return -EOPNOTSUPP; +@@ -1091,64 +860,16 @@ int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + + static int acpi_processor_get_lpi_info(struct acpi_processor *pr) + { +- int ret, i; +- acpi_status status; +- acpi_handle handle = pr->handle, pr_ahandle; +- struct acpi_device *d = NULL; +- struct acpi_lpi_states_array info[2], *tmp, *prev, *curr; +- unsigned int state_count; ++ int ret; + + /* make sure our architecture has support */ + ret = acpi_processor_ffh_lpi_probe(pr->id); + if (ret == -EOPNOTSUPP) + return ret; + +- if (!osc_pc_lpi_support_confirmed) +- return -EOPNOTSUPP; +- +- if (!acpi_has_method(handle, "_LPI")) +- return -EINVAL; +- +- prev = &info[0]; +- curr = &info[1]; +- handle = pr->handle; +- ret = acpi_processor_evaluate_lpi(handle, prev); ++ ret = acpi_processor_extract_lpi_info(pr->handle, &pr->power, false); + if (ret) + return ret; +- state_count = flatten_lpi_states(pr, 0, prev, NULL); +- +- status = acpi_get_parent(handle, &pr_ahandle); +- while (ACPI_SUCCESS(status)) { +- d = acpi_fetch_acpi_dev(pr_ahandle); +- if (!d) +- break; +- +- handle = pr_ahandle; +- +- if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) +- break; +- +- /* can be optional ? */ +- if (!acpi_has_method(handle, "_LPI")) +- break; +- +- ret = acpi_processor_evaluate_lpi(handle, curr); +- if (ret) +- break; +- +- /* flatten all the LPI states in this level of hierarchy */ +- state_count = flatten_lpi_states(pr, state_count, curr, prev); +- +- tmp = prev, prev = curr, curr = tmp; +- +- status = acpi_get_parent(handle, &pr_ahandle); +- } +- +- /* reset the index after flattening */ +- for (i = 0; i < state_count; i++) +- pr->power.lpi_states[i].index = i; +- +- pr->power.count = state_count; + + /* Tell driver that _LPI is supported. */ + pr->flags.has_lpi = 1; +diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c +--- a/drivers/base/power/main.c ++++ b/drivers/base/power/main.c +@@ -41,6 +41,9 @@ + #include "../base.h" + #include "power.h" + ++#undef MODULE_PARAM_PREFIX ++#define MODULE_PARAM_PREFIX "pm." ++ + typedef int (*pm_callback_t)(struct device *); + + /* +@@ -532,6 +535,11 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644); + MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace, + "Backtrace all CPUs on DPM watchdog timeout"); + ++static bool __read_mostly dpm_watchdog_enabled = ++ IS_ENABLED(CONFIG_DPM_WATCHDOG_ENABLED); ++module_param(dpm_watchdog_enabled, bool, 0644); ++MODULE_PARM_DESC(dpm_watchdog_enabled, "Enable DPM watchdog"); ++ + static unsigned int __read_mostly dpm_watchdog_timeout = CONFIG_DPM_WATCHDOG_TIMEOUT; + static unsigned int __read_mostly dpm_watchdog_warning_timeout = + CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT; +@@ -627,6 +635,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + wd->dev = dev; + wd->tsk = current; + wd->fatal = dpm_watchdog_timeout == dpm_watchdog_warning_timeout; +@@ -645,6 +656,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + timer_delete_sync(timer); + timer_destroy_on_stack(timer); + } +diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c +--- a/drivers/base/power/runtime.c ++++ b/drivers/base/power/runtime.c +@@ -469,9 +469,6 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) + if (retval == -EACCES) + retval = -EAGAIN; + +- if (retval != -EAGAIN && retval != -EBUSY) +- dev->power.runtime_error = retval; +- + return retval; + } + +@@ -751,6 +748,9 @@ static int rpm_suspend(struct device *dev, int rpmflags) + dev->power.deferred_resume = false; + wake_up_all(&dev->power.wait_queue); + ++ if (retval != -EAGAIN && retval != -EBUSY) ++ dev->power.runtime_error = retval; ++ + /* + * On transient errors, if the callback routine failed an autosuspend, + * and if the last_busy time has been updated so that there is a new +diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +--- a/drivers/cpufreq/amd-pstate-ut.c ++++ b/drivers/cpufreq/amd-pstate-ut.c +@@ -59,6 +59,7 @@ static int amd_pstate_ut_check_freq(u32 index); + static int amd_pstate_ut_epp(u32 index); + static int amd_pstate_ut_check_driver(u32 index); + static int amd_pstate_ut_check_freq_attrs(u32 index); ++static int amd_pstate_ut_check_floor_freq(u32 index); + + static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid }, +@@ -68,6 +69,7 @@ static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_epp", amd_pstate_ut_epp }, + {"amd_pstate_ut_check_driver", amd_pstate_ut_check_driver }, + {"amd_pstate_ut_check_freq_attrs", amd_pstate_ut_check_freq_attrs }, ++ {"amd_pstate_ut_check_floor_freq", amd_pstate_ut_check_floor_freq }, + }; + + static bool test_in_list(const char *list, const char *name) +@@ -275,6 +277,7 @@ static int amd_pstate_set_mode(enum amd_pstate_mode mode) + static int amd_pstate_ut_epp(u32 index) + { + static const char * const epp_strings[] = { ++ "dynamic", + "power", + "balance_power", + "balance_performance", +@@ -282,10 +285,10 @@ static int amd_pstate_ut_epp(u32 index) + }; + char *buf __free(cleanup_page) = NULL; + struct cpufreq_policy *policy = NULL; ++ unsigned long orig_dynamic_epp = 0; + enum amd_pstate_mode orig_mode; + struct amd_cpudata *cpudata; + unsigned long orig_policy; +- bool orig_dynamic_epp; + int ret, cpu = 0; + u16 epp; + int i; +@@ -294,9 +297,11 @@ static int amd_pstate_ut_epp(u32 index) + if (!policy) + return -ENODEV; + +- cpudata = policy->driver_data; + orig_mode = amd_pstate_get_status(); +- orig_dynamic_epp = cpudata->dynamic_epp; ++ if (policy->driver_data) { ++ cpudata = policy->driver_data; ++ orig_dynamic_epp = cpudata->dynamic_epp; ++ } + + /* Drop reference before potential driver change. */ + cpufreq_cpu_put(policy); +@@ -321,16 +326,6 @@ static int amd_pstate_ut_epp(u32 index) + orig_policy = cpudata->policy; + cpudata->policy = CPUFREQ_POLICY_POWERSAVE; + +- /* +- * Disable dynamic EPP before running test. If "orig_dynamic_epp" is +- * true, the driver will do a redundant switch at the end and there +- * is no need for enabling it again at the end of the test. +- */ +- if (cpudata->dynamic_epp) { +- pr_debug("Dynamic EPP is enabled, disabling it\n"); +- amd_pstate_clear_dynamic_epp(policy); +- } +- + for (epp = 0; epp <= U8_MAX; epp++) { + u8 val; + +@@ -367,6 +362,11 @@ static int amd_pstate_ut_epp(u32 index) + if (ret < 0) + goto out; + strreplace(buf, '\n', '\0'); ++ /* ++ * "dynamic" mode reports the EPP as "dynamic(profile:X)" ++ * Trim at "(" and just compare tie the epp string. ++ */ ++ strreplace(buf, '(', '\0'); + + if (strcmp(buf, epp_strings[i])) { + pr_err("String EPP value mismatch: %s != %s\n", buf, epp_strings[i]); +@@ -380,18 +380,23 @@ static int amd_pstate_ut_epp(u32 index) + out: + if (policy) { + cpudata->policy = orig_policy; ++ /* ++ * If the driver had enabled dynamic_epp to begin with, ++ * restore it here before dropping policy reference. ++ */ ++ if (orig_dynamic_epp) { ++ int ret2; ++ ++ ret2 = store_energy_performance_preference(policy, ++ epp_strings[0], ++ strlen(epp_strings[0])); ++ if (!ret && (ret2 < 0)) ++ ret = ret2; ++ } + up_write(&policy->rwsem); + cpufreq_cpu_put(policy); + } + +- if (orig_dynamic_epp) { +- int ret2; +- +- ret2 = amd_pstate_set_mode(AMD_PSTATE_DISABLE); +- if (!ret && ret2) +- ret = ret2; +- } +- + if (orig_mode != amd_pstate_get_status()) { + int ret2; + +@@ -557,6 +562,80 @@ out: + return ret; + } + ++static int amd_pstate_ut_check_floor_freq(u32 index) ++{ ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL; ++ char *buf __free(cleanup_page) = NULL; ++ unsigned int orig_floor_freq; ++ unsigned int floor_freq; ++ int ret, cpu = 0; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO)) ++ return -EOPNOTSUPP; ++ ++ policy = cpufreq_cpu_get(cpu); ++ if (!policy) ++ return -ENODEV; ++ ++ buf = (char *)__get_free_page(GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ guard(rwsem_write)(&policy->rwsem); ++ ++ if (!policy->driver_data) ++ return -ENODEV; ++ ++ /* Retrieve original floor frequency */ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ ret = kstrtou32(buf, 0, &orig_floor_freq); ++ if (ret) ++ return ret; ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", policy->cpuinfo.min_freq); ++ ++ /* Set floor frequency to cpuinfo.min_freq */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to set floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ strreplace(buf, '\n', '\0'); ++ ret = kstrtou32(buf, 0, &floor_freq); ++ if (ret) ++ return ret; ++ ++ /* Confirm sysfs reflects the change correctly. */ ++ if (floor_freq != policy->cpuinfo.min_freq) { ++ pr_err("Floor frequency value mismatch: %u != %u\n", ++ floor_freq, policy->cpuinfo.min_freq); ++ return -EINVAL; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", orig_floor_freq); ++ ++ /* Restore the original value. */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to restore floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ return 0; ++} ++ + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); +@@ -575,10 +654,16 @@ static int __init amd_pstate_ut_init(void) + + ret = amd_pstate_ut_cases[i].func(i); + +- if (ret) ++ if (ret) { ++ /* Platform does not support the feature being tested. */ ++ if (ret == -EOPNOTSUPP) { ++ pr_err("%-4d %-20s\t skipped!\n", i+1, amd_pstate_ut_cases[i].name); ++ continue; ++ } + pr_err("%-4d %-20s\t fail: %d!\n", i+1, amd_pstate_ut_cases[i].name, ret); +- else ++ } else { + pr_info("%-4d %-20s\t success!\n", i+1, amd_pstate_ut_cases[i].name); ++ } + } + + return 0; +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -75,7 +75,7 @@ const char *amd_pstate_get_mode_string(enum amd_pstate_mode mode) + mode = AMD_PSTATE_UNDEFINED; + return amd_pstate_mode_string[mode]; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_mode_string); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_mode_string); + + struct quirk_entry { + u32 nominal_freq; +@@ -87,7 +87,6 @@ static struct cpufreq_driver amd_pstate_driver; + static struct cpufreq_driver amd_pstate_epp_driver; + static int cppc_state = AMD_PSTATE_UNDEFINED; + static bool amd_pstate_prefcore = true; +-static bool dynamic_epp; + static struct quirk_entry *quirks; + + /* +@@ -106,6 +105,7 @@ static struct quirk_entry *quirks; + * 3 balance_power + * 4 power + * 5 custom (for raw EPP values) ++ * 6 dynamic (platform profile driven selection) + */ + enum energy_perf_value_index { + EPP_INDEX_DEFAULT = 0, +@@ -114,6 +114,7 @@ enum energy_perf_value_index { + EPP_INDEX_BALANCE_POWERSAVE, + EPP_INDEX_POWERSAVE, + EPP_INDEX_CUSTOM, ++ EPP_INDEX_DYNAMIC, + EPP_INDEX_MAX, + }; + +@@ -124,6 +125,7 @@ static const char * const energy_perf_strings[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = "balance_power", + [EPP_INDEX_POWERSAVE] = "power", + [EPP_INDEX_CUSTOM] = "custom", ++ [EPP_INDEX_DYNAMIC] = "dynamic", + }; + static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); + +@@ -134,7 +136,7 @@ static unsigned int epp_values[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_BALANCE_POWERSAVE, + [EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_POWERSAVE, + }; +-static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 1); ++static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 2); + + typedef int (*cppc_mode_transition_fn)(int); + +@@ -462,7 +464,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + { + union perf_cached perf = READ_ONCE(cpudata->perf); + u64 cap1, numerator, cppc_req; +- u8 min_perf; + + int ret = rdmsrq_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, + &cap1); +@@ -478,16 +479,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + return ret; + + WRITE_ONCE(cpudata->cppc_req_cached, cppc_req); +- min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); +- +- /* +- * Clear out the min_perf part to check if the rest of the MSR is 0, if yes, this is an +- * indication that the min_perf value is the one specified through the BIOS option +- */ +- cppc_req &= ~(AMD_CPPC_MIN_PERF_MASK); +- +- if (!cppc_req) +- perf.bios_min_perf = min_perf; + + perf.highest_perf = numerator; + perf.max_limit_perf = numerator; +@@ -495,6 +486,7 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + perf.nominal_perf = FIELD_GET(AMD_CPPC_NOMINAL_PERF_MASK, cap1); + perf.lowest_nonlinear_perf = FIELD_GET(AMD_CPPC_LOWNONLIN_PERF_MASK, cap1); + perf.lowest_perf = FIELD_GET(AMD_CPPC_LOWEST_PERF_MASK, cap1); ++ perf.bios_min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, FIELD_GET(AMD_CPPC_HIGHEST_PERF_MASK, cap1)); + WRITE_ONCE(cpudata->floor_perf_cnt, FIELD_GET(AMD_CPPC_FLOOR_PERF_CNT_MASK, cap1)); +@@ -789,6 +781,7 @@ static unsigned int amd_pstate_fast_switch(struct cpufreq_policy *policy, + static void amd_pstate_adjust_perf(struct cpufreq_policy *policy, + unsigned long _min_perf, + unsigned long target_perf, ++ unsigned long _max_perf, + unsigned long capacity) + { + u8 max_perf, min_perf, des_perf, cap_perf; +@@ -1044,6 +1037,13 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) + return -EINVAL; + } + ++ if (perf.bios_min_perf) { ++ u32 bios_min_freq = perf_to_freq(perf, cpudata->nominal_freq, perf.bios_min_perf); ++ ++ pr_debug("Found Requested CPU Min Frequency of %uKHz on CPU%d\n", ++ bios_min_freq, cpudata->cpu); ++ } ++ + return 0; + } + +@@ -1192,6 +1192,24 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + return NOTIFY_OK; + } + ++static int amd_pstate_get_epp_from_platform_profile(struct cpufreq_policy *policy, ++ enum platform_profile_option profile) ++{ ++ switch (profile) { ++ case PLATFORM_PROFILE_PERFORMANCE: ++ return AMD_CPPC_EPP_PERFORMANCE; ++ case PLATFORM_PROFILE_BALANCED: ++ return amd_pstate_get_balanced_epp(policy); ++ case PLATFORM_PROFILE_LOW_POWER: ++ return AMD_CPPC_EPP_POWERSAVE; ++ default: ++ break; ++ } ++ ++ pr_err("Unknown Platform Profile %d\n", profile); ++ return -EOPNOTSUPP; ++} ++ + static int amd_pstate_profile_probe(void *drvdata, unsigned long *choices) + { + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); +@@ -1217,31 +1235,19 @@ static int amd_pstate_profile_set(struct device *dev, + struct amd_cpudata *cpudata = dev_get_drvdata(dev); + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; ++ u8 epp; + + if (!policy) + return -ENODEV; + +- switch (profile) { +- case PLATFORM_PROFILE_LOW_POWER: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_BALANCED: +- ret = amd_pstate_set_epp(policy, +- amd_pstate_get_balanced_epp(policy)); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_PERFORMANCE: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_PERFORMANCE); +- if (ret) +- return ret; +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; ++ ret = amd_pstate_set_epp(policy, epp); ++ if (ret) ++ return ret; + + cpudata->current_profile = profile; + +@@ -1267,28 +1273,20 @@ void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy) + kfree(cpudata->profile_name); + cpudata->dynamic_epp = false; + } +-EXPORT_SYMBOL_GPL(amd_pstate_clear_dynamic_epp); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_clear_dynamic_epp); + + static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; ++ u64 prev = READ_ONCE(cpudata->cppc_req_cached); + int ret; + u8 epp; + +- switch (cpudata->current_profile) { +- case PLATFORM_PROFILE_PERFORMANCE: +- epp = AMD_CPPC_EPP_PERFORMANCE; +- break; +- case PLATFORM_PROFILE_LOW_POWER: +- epp = AMD_CPPC_EPP_POWERSAVE; +- break; +- case PLATFORM_PROFILE_BALANCED: +- epp = amd_pstate_get_balanced_epp(policy); +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", cpudata->current_profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, cpudata->current_profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1321,6 +1319,9 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + cleanup: + amd_pstate_clear_dynamic_epp(policy); + ++ epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, prev); ++ /* Restore previous EPP if toggling Dynamic EPP failed. */ ++ amd_pstate_set_epp(policy, epp); + return ret; + } + +@@ -1391,7 +1392,7 @@ static ssize_t show_amd_pstate_hw_prefcore(struct cpufreq_policy *policy, + static ssize_t show_energy_performance_available_preferences( + struct cpufreq_policy *policy, char *buf) + { +- int offset = 0, i; ++ int i, offset = 0; + struct amd_cpudata *cpudata = policy->driver_data; + + if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) +@@ -1414,11 +1415,6 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + bool raw_epp = false; + u8 epp; + +- if (cpudata->dynamic_epp) { +- pr_debug("EPP cannot be set when dynamic EPP is enabled\n"); +- return -EBUSY; +- } +- + /* + * if the value matches a number, use that, otherwise see if + * matches an index in the energy_perf_strings array +@@ -1429,6 +1425,25 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + ret = sysfs_match_string(energy_perf_strings, buf); + if (ret < 0 || ret == EPP_INDEX_CUSTOM) + return -EINVAL; ++ ++ if (ret == EPP_INDEX_DYNAMIC) { ++ if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) ++ return -EBUSY; ++ /* ++ * Dynamic EPP was already enabled for this CPU. ++ * Nothing to do. ++ */ ++ if (cpudata->dynamic_epp) ++ return count; ++ ++ cpudata->current_profile = PLATFORM_PROFILE_BALANCED; ++ ret = amd_pstate_set_dynamic_epp(policy); ++ if (ret) ++ return ret; ++ ++ return count; ++ } ++ + if (ret) + epp = epp_values[ret]; + else +@@ -1440,6 +1455,13 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + return -EBUSY; + } + ++ /* ++ * Dynamic EPP was enabled previously! ++ * Switch back to the static EPP mode. ++ */ ++ if (cpudata->dynamic_epp) ++ amd_pstate_clear_dynamic_epp(policy); ++ + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1448,7 +1470,7 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + + return count; + } +-EXPORT_SYMBOL_GPL(store_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_energy_performance_preference); + + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf) + { +@@ -1457,7 +1479,7 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + + epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached); + +- if (cpudata->raw_epp) ++ if (!cpudata->dynamic_epp && cpudata->raw_epp) + return sysfs_emit(buf, "%u\n", epp); + + switch (epp) { +@@ -1477,12 +1499,14 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + return -EINVAL; + } + ++ if (cpudata->dynamic_epp) ++ return sysfs_emit(buf, "dynamic(profile:%s)\n", energy_perf_strings[preference]); ++ + return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]); + } +-EXPORT_SYMBOL_GPL(show_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_energy_performance_preference); + +-static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, +- const char *buf, size_t count) ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count) + { + struct amd_cpudata *cpudata = policy->driver_data; + union perf_cached perf = READ_ONCE(cpudata->perf); +@@ -1505,13 +1529,15 @@ static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, + + return ret ?: count; + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_amd_pstate_floor_freq); + +-static ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) + { + struct amd_cpudata *cpudata = policy->driver_data; + + return sysfs_emit(buf, "%u\n", cpudata->floor_freq); + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_amd_pstate_floor_freq); + + static ssize_t show_amd_pstate_floor_count(struct cpufreq_policy *policy, char *buf) + { +@@ -1579,7 +1605,7 @@ struct freq_attr **amd_pstate_get_current_attrs(void) + return NULL; + return current_pstate_driver->attr; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_current_attrs); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_current_attrs); + + static struct freq_attr **get_freq_attrs(void) + { +@@ -1764,7 +1790,7 @@ int amd_pstate_get_status(void) + { + return cppc_state; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status); + + int amd_pstate_update_status(const char *buf, size_t size) + { +@@ -1784,7 +1810,7 @@ int amd_pstate_update_status(const char *buf, size_t size) + + return 0; + } +-EXPORT_SYMBOL_GPL(amd_pstate_update_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_update_status); + + static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -1812,50 +1838,12 @@ static ssize_t prefcore_show(struct device *dev, + return sysfs_emit(buf, "%s\n", str_enabled_disabled(amd_pstate_prefcore)); + } + +-static ssize_t dynamic_epp_show(struct device *dev, +- struct device_attribute *attr, char *buf) +-{ +- return sysfs_emit(buf, "%s\n", str_enabled_disabled(dynamic_epp)); +-} +- +-static ssize_t dynamic_epp_store(struct device *a, struct device_attribute *b, +- const char *buf, size_t count) +-{ +- bool enabled; +- int ret; +- +- ret = kstrtobool(buf, &enabled); +- if (ret) +- return ret; +- +- guard(mutex)(&amd_pstate_driver_lock); +- +- if (cppc_state != AMD_PSTATE_ACTIVE) { +- pr_debug("dynamic_epp can only be toggled in active mode\n"); +- return -EINVAL; +- } +- +- /* Nothing to do */ +- if (dynamic_epp == enabled) +- return count; +- +- /* reinitialize with desired dynamic EPP value */ +- dynamic_epp = enabled; +- ret = amd_pstate_change_driver_mode(cppc_state); +- if (ret) +- dynamic_epp = false; +- +- return ret ? ret : count; +-} +- + static DEVICE_ATTR_RW(status); + static DEVICE_ATTR_RO(prefcore); +-static DEVICE_ATTR_RW(dynamic_epp); + + static struct attribute *pstate_global_attributes[] = { + &dev_attr_status.attr, + &dev_attr_prefcore.attr, +- &dev_attr_dynamic_epp.attr, + NULL + }; + +@@ -1938,12 +1926,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + +- /* Fetch the firmware programmed default EPP value */ ++ /* Cache the firmware programmed EPP */ + default_epp = amd_pstate_get_epp(cpudata); + if (default_epp < 0) { + ret = default_epp; + goto free_cpudata1; + } ++ FIELD_MODIFY(AMD_CPPC_EPP_PERF_MASK, &cpudata->cppc_req_cached, default_epp); + + /* + * Set the policy to provide a valid fallback value in case +@@ -1961,10 +1950,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + cpudata->current_profile = PLATFORM_PROFILE_BALANCED; + } + +- if (dynamic_epp) +- ret = amd_pstate_set_dynamic_epp(policy); +- else +- ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); ++ ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); + if (ret) + goto free_cpudata1; + +@@ -2036,6 +2022,18 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy) + if (!policy->cpuinfo.max_freq) + return -ENODEV; + ++ /* Must be a switch between PERFORMANCE and POWERSAVE */ ++ if (cpudata->policy != policy->policy) { ++ /* ++ * Disable dynamic_epp when switching ++ * out of CPUFREQ_POLICY_POWERSAVE. ++ */ ++ if (cpudata->dynamic_epp) { ++ WARN_ON_ONCE(cpudata->policy != CPUFREQ_POLICY_POWERSAVE); ++ amd_pstate_clear_dynamic_epp(policy); ++ } ++ } ++ + cpudata->policy = policy->policy; + + ret = amd_pstate_epp_update_limit(policy, true); +@@ -2363,19 +2361,8 @@ static int __init amd_prefcore_param(char *str) + return 0; + } + +-static int __init amd_dynamic_epp_param(char *str) +-{ +- if (!strcmp(str, "disable")) +- dynamic_epp = false; +- if (!strcmp(str, "enable")) +- dynamic_epp = true; +- +- return 0; +-} +- + early_param("amd_pstate", amd_pstate_param); + early_param("amd_prefcore", amd_prefcore_param); +-early_param("amd_dynamic_epp", amd_dynamic_epp_param); + + MODULE_AUTHOR("Huang Rui "); + MODULE_DESCRIPTION("AMD Processor P-state Frequency Driver"); +diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h +--- a/drivers/cpufreq/amd-pstate.h ++++ b/drivers/cpufreq/amd-pstate.h +@@ -11,6 +11,13 @@ + #include + #include + ++#if IS_MODULE(CONFIG_X86_AMD_PSTATE_UT) ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) \ ++ EXPORT_SYMBOL_FOR_MODULES(symbol, "amd-pstate-ut") ++#else ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) ++#endif ++ + /********************************************************************* + * AMD P-state INTERFACE * + *********************************************************************/ +@@ -153,6 +160,8 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + const char *buf, size_t count); + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf); + void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy); ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count); ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf); + + struct freq_attr; + +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2225,14 +2225,17 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + * @policy: cpufreq policy object of the target CPU. + * @min_perf: Minimum (required) performance level (units of @capacity). + * @target_perf: Target (desired) performance level (units of @capacity). ++ * @max_perf: Maximum (allowed) performance level (units of @capacity). + * @capacity: Capacity of the target CPU. + * +- * Carry out a fast performance level switch of @cpu without sleeping. ++ * Carry out a fast performance level adjustment for the CPU represented by ++ * @policy without sleeping. + * + * The driver's ->adjust_perf() callback invoked by this function must be +- * suitable for being called from within RCU-sched read-side critical sections +- * and it is expected to select a suitable performance level equal to or above +- * @min_perf and preferably equal to or below @target_perf. ++ * suitable for calling from within RCU-sched read-side critical sections and ++ * it is expected to program the processor to select suitable performance ++ * levels between @min_perf and @max_perf inclusive and preferably close to ++ * @target_perf going forward for the CPU represented by @policy. + * + * This function must not be called if policy->fast_switch_enabled is unset. + * +@@ -2244,9 +2247,10 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { +- cpufreq_driver->adjust_perf(policy, min_perf, target_perf, capacity); ++ cpufreq_driver->adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /** +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -299,7 +299,6 @@ struct pstate_funcs { + static struct pstate_funcs pstate_funcs __read_mostly; + + static bool hwp_active __ro_after_init; +-static int hwp_mode_bdw __ro_after_init; + static bool per_cpu_limits __ro_after_init; + static bool hwp_forced __ro_after_init; + static bool hwp_boost __read_mostly; +@@ -587,21 +586,14 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) + + hwp_is_hybrid = true; + +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_pstate * scaling, +- perf_ctl_scaling); +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_pstate * scaling, +- perf_ctl_scaling); +- + freq = perf_ctl_max_phys * perf_ctl_scaling; + cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); + +- freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.min_freq = freq; + /* + * Cast the min P-state value retrieved via pstate_funcs.get_min() to + * the effective range of HWP performance levels. + */ +- cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); ++ cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); + } + + static bool turbo_is_disabled(void) +@@ -979,12 +971,10 @@ static int hybrid_get_cost(struct device *dev, unsigned long freq, + * capacity. Similarly, P-cores start to be populated when E-cores are + * utilized above 60% of the capacity. + */ +- if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_ATOM) { +- if (hybrid_has_l3(dev->id)) /* E-core */ +- *cost += 1; +- } else { /* P-core */ ++ if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_CORE) /* P-core */ + *cost += 2; +- } ++ else if (hybrid_has_l3(dev->id)) /* E-core */ ++ *cost += 1; + + return 0; + } +@@ -1185,6 +1175,22 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static void intel_pstate_update_freq_limits(struct cpudata *cpu) ++{ ++ int scaling = cpu->pstate.scaling; ++ unsigned int turbo_freq = cpu->pstate.turbo_pstate * scaling; ++ unsigned int max_freq = cpu->pstate.max_pstate * scaling; ++ int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; ++ ++ if (scaling != perf_ctl_scaling) { ++ turbo_freq = rounddown(turbo_freq, perf_ctl_scaling); ++ max_freq = rounddown(max_freq, perf_ctl_scaling); ++ } ++ ++ cpu->pstate.turbo_freq = turbo_freq; ++ cpu->pstate.max_freq = max_freq; ++} ++ + static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + { + u64 cap; +@@ -1197,20 +1203,8 @@ static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + + static void intel_pstate_get_hwp_cap(struct cpudata *cpu) + { +- int scaling = cpu->pstate.scaling; +- + __intel_pstate_get_hwp_cap(cpu); +- +- cpu->pstate.max_freq = cpu->pstate.max_pstate * scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * scaling; +- if (scaling != cpu->pstate.perf_ctl_scaling) { +- int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; +- +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_freq, +- perf_ctl_scaling); +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_freq, +- perf_ctl_scaling); +- } ++ intel_pstate_update_freq_limits(cpu); + } + + static void hybrid_update_capacity(struct cpudata *cpu) +@@ -2299,33 +2293,16 @@ static int hwp_get_cpu_scaling(int cpu) + return intel_pstate_cppc_get_scaling(cpu); + } + +-static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) +-{ +- trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); +- cpu->pstate.current_pstate = pstate; +- /* +- * Generally, there is no guarantee that this code will always run on +- * the CPU being updated, so force the register update to run on the +- * right CPU. +- */ +- wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, +- pstate_funcs.get_val(cpu, pstate)); +-} +- +-static void intel_pstate_set_min_pstate(struct cpudata *cpu) +-{ +- intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); +-} +- + static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + { + int perf_ctl_scaling = pstate_funcs.get_scaling(); + + cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); + cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); ++ cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; + cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; + +- if (hwp_active && !hwp_mode_bdw) { ++ if (hwp_active) { + __intel_pstate_get_hwp_cap(cpu); + + if (pstate_funcs.get_cpu_scaling) { +@@ -2345,11 +2322,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); + } + +- if (cpu->pstate.scaling == perf_ctl_scaling) { +- cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.max_freq = cpu->pstate.max_pstate * perf_ctl_scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * perf_ctl_scaling; +- } ++ intel_pstate_update_freq_limits(cpu); + + if (pstate_funcs.get_aperf_mperf_shift) + cpu->aperf_mperf_shift = pstate_funcs.get_aperf_mperf_shift(); +@@ -2882,8 +2855,22 @@ static void intel_pstate_update_perf_limits(struct cpudata *cpu, + cpu->min_perf_ratio); + } + ++static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) ++{ ++ trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); ++ cpu->pstate.current_pstate = pstate; ++ /* ++ * Generally, there is no guarantee that this code will always run on ++ * the CPU being updated, so force the register update to run on the ++ * right CPU. ++ */ ++ wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, ++ pstate_funcs.get_val(cpu, pstate)); ++} ++ + static int intel_pstate_set_policy(struct cpufreq_policy *policy) + { ++ unsigned int freq = policy->min; + struct cpudata *cpu; + + if (!policy->cpuinfo.max_freq) +@@ -2899,7 +2886,23 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + + intel_pstate_update_perf_limits(cpu, policy->min, policy->max); + +- if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ if (hwp_active) { ++ /* ++ * The active mode only requires an update util hook if HWP ++ * boost is used and the policy is not "performance". ++ */ ++ if (hwp_boost && cpu->policy != CPUFREQ_POLICY_PERFORMANCE) { ++ intel_pstate_set_update_util_hook(policy->cpu); ++ } else { ++ intel_pstate_clear_update_util_hook(policy->cpu); ++ if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ freq = cpu->max_perf_ratio * cpu->pstate.scaling; ++ if (cpu->pstate.scaling != cpu->pstate.perf_ctl_scaling) ++ freq = rounddown(freq, cpu->pstate.perf_ctl_scaling); ++ } ++ } ++ intel_pstate_hwp_set(policy->cpu); ++ } else if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + int pstate = max(cpu->pstate.min_pstate, cpu->max_perf_ratio); + + /* +@@ -2908,25 +2911,17 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + */ + intel_pstate_clear_update_util_hook(policy->cpu); + intel_pstate_set_pstate(cpu, pstate); ++ freq = pstate * cpu->pstate.scaling; + } else { + intel_pstate_set_update_util_hook(policy->cpu); + } +- +- if (hwp_active) { +- /* +- * When hwp_boost was active before and dynamically it +- * was turned off, in that case we need to clear the +- * update util hook. +- */ +- if (!hwp_boost) +- intel_pstate_clear_update_util_hook(policy->cpu); +- intel_pstate_hwp_set(policy->cpu); +- } + /* +- * policy->cur is never updated with the intel_pstate driver, but it +- * is used as a stale frequency value. So, keep it within limits. ++ * policy->cur is never updated in the intel_pstate driver, but it is ++ * used as a stale frequency value, so set it to reflect the actual ++ * requested P-state in the "performance" policy case and to the min ++ * otherwise. + */ +- policy->cur = policy->min; ++ policy->cur = freq; + + mutex_unlock(&intel_pstate_limits_lock); + +@@ -2969,6 +2964,11 @@ static int intel_pstate_verify_policy(struct cpufreq_policy_data *policy) + return 0; + } + ++static void intel_pstate_set_min_pstate(struct cpudata *cpu) ++{ ++ intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); ++} ++ + static int intel_cpufreq_cpu_offline(struct cpufreq_policy *policy) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3242,6 +3242,7 @@ static unsigned int intel_cpufreq_fast_switch(struct cpufreq_policy *policy, + static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3272,7 +3273,13 @@ static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + if (min_pstate > cpu->max_perf_ratio) + min_pstate = cpu->max_perf_ratio; + +- max_pstate = min(cap_pstate, cpu->max_perf_ratio); ++ max_pstate = cap_pstate; ++ if (max_perf < capacity) ++ max_pstate = DIV_ROUND_UP(cap_pstate * max_perf, capacity); ++ ++ if (max_pstate > cpu->max_perf_ratio) ++ max_pstate = cpu->max_perf_ratio; ++ + if (max_pstate < min_pstate) + max_pstate = min_pstate; + +@@ -3679,14 +3686,14 @@ static inline bool intel_pstate_has_acpi_ppc(void) { return false; } + static inline void intel_pstate_request_control_from_smm(void) {} + #endif /* CONFIG_ACPI */ + +-#define INTEL_PSTATE_HWP_BROADWELL 0x01 ++#define INTEL_PSTATE_HWP_NOT_HYBRID 0x01 + + #define X86_MATCH_HWP(vfm, hwp_mode) \ + X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_HWP, hwp_mode) + + static const struct x86_cpu_id hwp_support_ids[] __initconst = { +- X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_BROADWELL), +- X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_BROADWELL), ++ X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_NOT_HYBRID), ++ X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_NOT_HYBRID), + X86_MATCH_HWP(INTEL_ANY, 0), + {} + }; +@@ -3811,7 +3818,6 @@ static int __init intel_pstate_init(void) + + if (!no_hwp) { + hwp_active = true; +- hwp_mode_bdw = id->driver_data; + intel_pstate.attr = hwp_cpufreq_attrs; + intel_cpufreq.attr = hwp_cpufreq_attrs; + intel_cpufreq.flags |= CPUFREQ_NEED_UPDATE_LIMITS; +@@ -3819,7 +3825,8 @@ static int __init intel_pstate_init(void) + if (!default_driver) + default_driver = &intel_pstate; + +- pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; ++ if (!id->driver_data) ++ pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; + + goto hwp_cpu_matched; + } +diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c +--- a/drivers/idle/intel_idle.c ++++ b/drivers/idle/intel_idle.c +@@ -1780,6 +1780,7 @@ module_param_named(no_native, no_native, bool, 0444); + MODULE_PARM_DESC(no_native, "Ignore cpu specific (native) idle states in lieu of ACPI idle states"); + + static struct acpi_processor_power acpi_state_table __initdata; ++static bool acpi_lpi_available __initdata; + + /** + * intel_idle_cst_usable - Check if the _CST information can be used. +@@ -1804,18 +1805,37 @@ static bool __init intel_idle_cst_usable(void) + return true; + } + +-static bool __init intel_idle_acpi_cst_extract(void) ++static bool __init intel_idle_acpi_extract_lpi_cstates(void) + { + unsigned int cpu; + +- if (no_acpi) { +- pr_debug("Not allowed to use ACPI _CST\n"); +- return false; ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); ++ if (!pr) ++ continue; ++ ++ if (acpi_processor_extract_lpi_info(pr->handle, ++ &acpi_state_table, true)) ++ continue; ++ ++ acpi_lpi_available = true; ++ return true; + } + +- for_each_possible_cpu(cpu) { +- struct acpi_processor *pr = per_cpu(processors, cpu); ++ pr_debug("No ACPI _LPI idle states\n"); ++ return false; ++} + ++static bool __init intel_idle_acpi_extract_cst_cstates(void) ++{ ++ unsigned int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); + if (!pr) + continue; + +@@ -1827,18 +1847,96 @@ static bool __init intel_idle_acpi_cst_extract(void) + if (!intel_idle_cst_usable()) + continue; + +- if (!acpi_processor_claim_cst_control()) +- break; +- + return true; + } + +- acpi_state_table.count = 0; + pr_debug("ACPI _CST not found or not usable\n"); + return false; + } + +-static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) ++static bool __init intel_idle_acpi_extract_cstates(void) ++{ ++ if (intel_idle_acpi_extract_lpi_cstates()) ++ return true; ++ ++ if (intel_idle_acpi_extract_cst_cstates()) ++ return true; ++ ++ return false; ++} ++ ++static bool __init intel_idle_acpi_probe(void) ++{ ++ if (no_acpi) { ++ pr_debug("Not allowed to use ACPI for C-states extraction\n"); ++ return false; ++ } ++ ++ if (intel_idle_acpi_extract_cstates() && ++ acpi_processor_claim_cst_control()) ++ return true; ++ ++ acpi_state_table.count = 0; ++ return false; ++} ++ ++static void __init intel_idle_complete_state_init(struct cpuidle_state *state) ++{ ++ if (intel_idle_state_needs_timer_stop(state)) ++ state->flags |= CPUIDLE_FLAG_TIMER_STOP; ++ ++ state->enter = intel_idle; ++ state->enter_dead = intel_idle_enter_dead; ++ state->enter_s2idle = intel_idle_s2idle; ++} ++ ++static void __init intel_idle_init_cstates_acpi_lpi(struct cpuidle_driver *drv) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ struct acpi_lpi_state *lpi_state; ++ struct cpuidle_state *state; ++ ++ if (intel_idle_max_cstate_reached(index)) ++ break; ++ ++ lpi_state = &acpi_state_table.lpi_states[index]; ++ ++ state = &drv->states[drv->state_count++]; ++ ++ scnprintf(state->name, CPUIDLE_NAME_LEN, "C%d_LPI", index + 1); ++ strscpy(state->desc, lpi_state->desc, CPUIDLE_DESC_LEN); ++ state->exit_latency = lpi_state->wake_latency; ++ state->target_residency = lpi_state->min_residency; ++ state->flags = MWAIT2flg(lpi_state->address); ++ /* ++ * Assume that entering any of the idle states extracted from ++ * _LPI except for the first two will cause the TLB to be ++ * flushed and let the core call leave_mm() for them upfront ++ * to avoid unnecessary wakeups due to TLB shootdowns. ++ */ ++ if (index > 1) ++ state->flags |= CPUIDLE_FLAG_TLB_FLUSHED; ++ ++ if (disabled_states_mask & BIT(index + 1)) ++ state->flags |= CPUIDLE_FLAG_OFF; ++ ++ intel_idle_complete_state_init(state); ++ ++ pr_info("%s: MWAIT hint 0x%x\n", state->name, flg2MWAIT(state->flags)); ++ } ++ ++ /* ++ * Assume the first idle state in the table to be C1 and if any deeper ++ * idle states are exposed while X86_FEATURE_NONSTOP_TSC is unset, mark ++ * the TSC as unstable. ++ */ ++ if (index > 1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) ++ mark_tsc_unstable("TSC halts in idle"); ++} ++ ++static void __init intel_idle_init_cstates_acpi_cst(struct cpuidle_driver *drv) + { + int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +@@ -1880,49 +1978,76 @@ static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + if (disabled_states_mask & BIT(cstate)) + state->flags |= CPUIDLE_FLAG_OFF; + +- if (intel_idle_state_needs_timer_stop(state)) +- state->flags |= CPUIDLE_FLAG_TIMER_STOP; +- + if (cx->type > ACPI_STATE_C1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) + mark_tsc_unstable("TSC halts in idle"); + +- state->enter = intel_idle; +- state->enter_dead = intel_idle_enter_dead; +- state->enter_s2idle = intel_idle_s2idle; ++ intel_idle_complete_state_init(state); + } + } + +-static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + { +- int cstate, limit; ++ if (acpi_lpi_available) ++ intel_idle_init_cstates_acpi_lpi(drv); ++ else ++ intel_idle_init_cstates_acpi_cst(drv); ++} + +- /* +- * If there are no _CST C-states, do not disable any C-states by +- * default. +- */ +- if (!acpi_state_table.count) +- return false; ++static bool __init intel_idle_acpi_hint_match(unsigned int flags, u32 acpi_hint, ++ u32 table_hint) ++{ ++ if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { ++ acpi_hint &= ~MWAIT_SUBSTATE_MASK; ++ table_hint &= ~MWAIT_SUBSTATE_MASK; ++ } ++ return acpi_hint == table_hint; ++} ++ ++static bool __init intel_idle_off_by_default_lpi(unsigned int flags, u32 mwait_hint) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ u32 acpi_hint = acpi_state_table.lpi_states[index].address; ++ ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) ++ return false; ++ } ++ return true; ++} ++ ++static bool __init intel_idle_off_by_default_cst(unsigned int flags, u32 mwait_hint) ++{ ++ int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +- limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + /* + * If limit > 0, intel_idle_cst_usable() has returned 'true', so all of + * the interesting states are ACPI_CSTATE_FFH. + */ + for (cstate = 1; cstate < limit; cstate++) { + u32 acpi_hint = acpi_state_table.states[cstate].address; +- u32 table_hint = mwait_hint; + +- if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { +- acpi_hint &= ~MWAIT_SUBSTATE_MASK; +- table_hint &= ~MWAIT_SUBSTATE_MASK; +- } +- +- if (acpi_hint == table_hint) ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) + return false; + } + return true; + } + ++static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++{ ++ /* ++ * If there is no C-states information in the ACPI tables, do not ++ * disable any C-states by default. ++ */ ++ if (!acpi_state_table.count) ++ return false; ++ ++ if (acpi_lpi_available) ++ return intel_idle_off_by_default_lpi(flags, mwait_hint); ++ ++ return intel_idle_off_by_default_cst(flags, mwait_hint); ++} ++ + static inline bool ignore_native(void) + { + return no_native && !no_acpi; +@@ -1930,7 +2055,7 @@ static inline bool ignore_native(void) + #else /* !CONFIG_ACPI_PROCESSOR_CSTATE */ + #define force_use_acpi (false) + +-static inline bool intel_idle_acpi_cst_extract(void) { return false; } ++static inline bool intel_idle_acpi_probe(void) { return false; } + static inline void intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) { } + static inline bool intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) + { +@@ -2745,7 +2870,7 @@ static int __init intel_idle_init(void) + if (icpu) { + if (icpu->state_table) + cpuidle_state_table = icpu->state_table; +- else if (!intel_idle_acpi_cst_extract()) ++ else if (!intel_idle_acpi_probe()) + return -ENODEV; + + auto_demotion_disable_flags = icpu->auto_demotion_disable_flags; +@@ -2754,8 +2879,8 @@ static int __init intel_idle_init(void) + if (icpu->c1_demotion_supported) + c1_demotion_supported = true; + if (icpu->use_acpi || force_use_acpi) +- intel_idle_acpi_cst_extract(); +- } else if (!intel_idle_acpi_cst_extract()) { ++ intel_idle_acpi_probe(); ++ } else if (!intel_idle_acpi_probe()) { + return -ENODEV; + } + +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -1581,8 +1581,6 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { +- int ret; +- + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1591,39 +1589,35 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clks) + return opp_table; + +- /* Find clk for the device */ +- opp_table->clk = clk_get(dev, NULL); ++ /* ++ * There are few platforms which don't want the OPP core to manage ++ * device's clock settings. In such cases neither the platform ++ * provides the clks explicitly to us, nor the DT contains a valid ++ * clk entry. The OPP nodes in DT may still contain "opp-hz" property ++ * though, which we need to parse and allow the platform to find an ++ * OPP based on freq later on. ++ * ++ * This is a simple solution to take care of such corner cases, i.e. ++ * make the clk_count 1, which lets us allocate space for frequency ++ * in opp->rates and also parse the entries in DT. Use ++ * clk_get_optional() instead of clk_get() so opp_table->clk stays ++ * NULL for such devices, instead of holding an ERR_PTR(-ENOENT) that ++ * consumers must remember to special-case. ++ */ ++ opp_table->clk = clk_get_optional(dev, NULL); + +- ret = PTR_ERR_OR_ZERO(opp_table->clk); +- if (!ret) { ++ if (IS_ERR(opp_table->clk)) { ++ dev_pm_opp_put_opp_table(opp_table); ++ dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); ++ return ERR_CAST(opp_table->clk); ++ } ++ ++ if (opp_table->clk) + opp_table->config_clks = _opp_config_clk_single; +- opp_table->clk_count = 1; +- return opp_table; +- } + +- if (ret == -ENOENT) { +- /* +- * There are few platforms which don't want the OPP core to +- * manage device's clock settings. In such cases neither the +- * platform provides the clks explicitly to us, nor the DT +- * contains a valid clk entry. The OPP nodes in DT may still +- * contain "opp-hz" property though, which we need to parse and +- * allow the platform to find an OPP based on freq later on. +- * +- * This is a simple solution to take care of such corner cases, +- * i.e. make the clk_count 1, which lets us allocate space for +- * frequency in opp->rates and also parse the entries in DT. +- */ +- opp_table->clk_count = 1; ++ opp_table->clk_count = 1; + +- dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); +- return opp_table; +- } +- +- dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, ret, "Couldn't find clock\n"); +- +- return ERR_PTR(ret); ++ return opp_table; + } + + /* +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -315,6 +315,19 @@ static inline int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, + } + #endif + ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict); ++#else ++static inline int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ return -ENODEV; ++} ++#endif ++ + #ifdef CONFIG_ACPI_HOTPLUG_CPU + /* Arch dependent functions for cpu hotplug support */ + int acpi_map_cpu(acpi_handle handle, phys_cpuid_t physid, u32 acpi_id, +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -379,6 +379,7 @@ struct cpufreq_driver { + void (*adjust_perf)(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + + /* +@@ -624,6 +625,7 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + bool cpufreq_driver_has_adjust_perf(void); + int cpufreq_driver_target(struct cpufreq_policy *policy, +diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig +--- a/kernel/power/Kconfig ++++ b/kernel/power/Kconfig +@@ -268,6 +268,16 @@ config DPM_WATCHDOG + captured in pstore device for inspection in subsequent + boot session. + ++config DPM_WATCHDOG_ENABLED ++ bool "Enable DPM watchdog by default" ++ depends on DPM_WATCHDOG ++ default y ++ help ++ If you say Y here, the DPM watchdog will be enabled by default. ++ If you say N, it will be compiled in but disabled. It can be ++ enabled at boot time via the "pm.dpm_watchdog_enabled" kernel ++ parameter or at runtime via sysfs. ++ + config DPM_WATCHDOG_TIMEOUT + int "Watchdog timeout to panic in seconds" + range 1 120 +diff --git a/kernel/power/swap.c b/kernel/power/swap.c +--- a/kernel/power/swap.c ++++ b/kernel/power/swap.c +@@ -430,19 +430,22 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + + if (!handle->cur) + return -EINVAL; +- offset = alloc_swapdev_block(root_swap); +- error = write_page(buf, offset, hb); +- if (error) +- return error; +- handle->cur->entries[handle->k++] = offset; ++ ++ /* ++ * If the current map page is full, allocate and link next one first. ++ * Delaying this until here avoids writing an empty swap map page when ++ * the image size is an exact MAP_PAGE_ENTRIES multiple. ++ */ + if (handle->k >= MAP_PAGE_ENTRIES) { + offset = alloc_swapdev_block(root_swap); + if (!offset) + return -ENOSPC; ++ + handle->cur->next_swap = offset; + error = write_page(handle->cur, handle->cur_swap, hb); + if (error) +- goto out; ++ return error; ++ + clear_page(handle->cur); + handle->cur_swap = offset; + handle->k = 0; +@@ -450,7 +453,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + if (hb && low_free_pages() <= handle->reqd_free_pages) { + error = hib_wait_io(hb); + if (error) +- goto out; ++ return error; + /* + * Recalculate the number of required free pages, to + * make sure we never take more than half. +@@ -458,14 +461,21 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + handle->reqd_free_pages = reqd_free_pages(); + } + } +- out: +- return error; ++ ++ offset = alloc_swapdev_block(root_swap); ++ error = write_page(buf, offset, hb); ++ if (error) ++ return error; ++ handle->cur->entries[handle->k++] = offset; ++ return 0; + } + + static int flush_swap_writer(struct swap_map_handle *handle) + { +- if (handle->cur && handle->cur_swap) ++ if (handle->cur && handle->cur_swap && handle->k) + return write_page(handle->cur, handle->cur_swap, NULL); ++ else if (handle->cur && handle->cur_swap) ++ return 0; + else + return -EINVAL; + } +@@ -739,7 +749,7 @@ static int save_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +@@ -1243,7 +1253,7 @@ static int load_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c +--- a/kernel/sched/cpufreq_schedutil.c ++++ b/kernel/sched/cpufreq_schedutil.c +@@ -50,6 +50,7 @@ struct sugov_cpu { + + unsigned long util; + unsigned long bw_min; ++ unsigned long bw_max; + + /* The field below is for single-CPU policies only: */ + #ifdef CONFIG_NO_HZ_COMMON +@@ -243,6 +244,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) + util = effective_cpu_util(sg_cpu->cpu, util, &min, &max); + util = max(util, boost); + sg_cpu->bw_min = min; ++ sg_cpu->bw_max = max; + sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); + } + +@@ -495,7 +497,7 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time, + sg_cpu->util = prev_util; + + cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min, +- sg_cpu->util, max_cap); ++ sg_cpu->util, sg_cpu->bw_max, max_cap); + + sg_policy->need_freq_update = false; + sg_policy->last_freq_update_time = time; +@@ -601,7 +603,7 @@ static ssize_t rate_limit_us_show(struct gov_attr_set *attr_set, char *buf) + { + struct sugov_tunables *tunables = to_sugov_tunables(attr_set); + +- return sprintf(buf, "%u\n", tunables->rate_limit_us); ++ return sysfs_emit(buf, "%u\n", tunables->rate_limit_us); + } + + static ssize_t +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -797,7 +797,13 @@ pub trait Driver { + } + + /// Driver's `adjust_perf` callback. +- fn adjust_perf(_policy: &mut Policy, _min_perf: usize, _target_perf: usize, _capacity: usize) { ++ fn adjust_perf( ++ _policy: &mut Policy, ++ _min_perf: usize, ++ _target_perf: usize, ++ _max_perf: usize, ++ _capacity: usize, ++ ) { + build_error!(VTABLE_DEFAULT_ERROR) + } + +@@ -1270,12 +1276,13 @@ impl Registration { + ptr: *mut bindings::cpufreq_policy, + min_perf: c_ulong, + target_perf: c_ulong, ++ max_perf: c_ulong, + capacity: c_ulong, + ) { + // SAFETY: The `ptr` is guaranteed to be valid by the contract with the C code for the + // lifetime of `policy`. + let policy = unsafe { Policy::from_raw_mut(ptr) }; +- T::adjust_perf(policy, min_perf, target_perf, capacity); ++ T::adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /// Driver's `get_intermediate` callback. diff --git a/pkgbuilds/linux-omarchy-bore/0210-pm-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0210-pm-updates.patch.sig new file mode 100644 index 0000000..4f4c8b3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0210-pm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0210-pstate.patch b/pkgbuilds/linux-omarchy-bore/0210-pstate.patch deleted file mode 100644 index 3d4eefc..0000000 --- a/pkgbuilds/linux-omarchy-bore/0210-pstate.patch +++ /dev/null @@ -1,480 +0,0 @@ -diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c ---- a/drivers/cpufreq/amd-pstate.c -+++ b/drivers/cpufreq/amd-pstate.c -@@ -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 deleted file mode 100644 index 463f061..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0210-pstate.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch b/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch deleted file mode 100644 index 67a6cf4..0000000 --- a/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch +++ /dev/null @@ -1,127 +0,0 @@ -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 deleted file mode 100644 index 018259f..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0211-amd-pstate-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0211-pm-fixes-1.patch b/pkgbuilds/linux-omarchy-bore/0211-pm-fixes-1.patch new file mode 100644 index 0000000..ed2d0f5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0211-pm-fixes-1.patch @@ -0,0 +1,104 @@ +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -453,8 +453,8 @@ int dev_pm_opp_get_opp_count(struct device *dev) + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_dbg(dev, "%s: OPP table not found (%ld)\n", +- __func__, PTR_ERR(opp_table)); ++ dev_dbg(dev, "%s: OPP table not found (%pe)\n", ++ __func__, opp_table); + return PTR_ERR(opp_table); + } + +@@ -611,8 +611,8 @@ _find_key(struct device *dev, unsigned long *key, int index, bool available, + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -722,8 +722,8 @@ struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev, + struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -1036,8 +1036,8 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, + + /* Regulator not available for device */ + if (IS_ERR(reg)) { +- dev_dbg(dev, "%s: regulator not available: %ld\n", __func__, +- PTR_ERR(reg)); ++ dev_dbg(dev, "%s: regulator not available: %pe\n", __func__, ++ reg); + return 0; + } + +@@ -1448,8 +1448,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) + temp_freq = freq; + opp = _find_freq_ceil(opp_table, &temp_freq); + if (IS_ERR(opp)) { +- dev_err(dev, "%s: failed to find OPP for freq %lu (%ld)\n", +- __func__, freq, PTR_ERR(opp)); ++ dev_err(dev, "%s: failed to find OPP for freq %lu (%pe)\n", ++ __func__, freq, opp); + return PTR_ERR(opp); + } + +@@ -1581,6 +1581,8 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { ++ int ret; ++ + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1607,9 +1609,9 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clk = clk_get_optional(dev, NULL); + + if (IS_ERR(opp_table->clk)) { ++ ret = dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); + dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); +- return ERR_CAST(opp_table->clk); ++ return ERR_PTR(ret); + } + + if (opp_table->clk) +@@ -2869,8 +2871,8 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, + struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; + + if (IS_ERR(opp_table)) { +- dev_warn(dev, "%s: Device OPP not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_warn(dev, "%s: Device OPP not found (%pe)\n", __func__, ++ opp_table); + return PTR_ERR(opp_table); + } + +diff --git a/drivers/opp/of.c b/drivers/opp/of.c +--- a/drivers/opp/of.c ++++ b/drivers/opp/of.c +@@ -1345,8 +1345,8 @@ int of_get_required_opp_performance_state(struct device_node *np, int index) + _find_table_of_opp_np(required_np); + + if (IS_ERR(opp_table)) { +- pr_err("%s: Failed to find required OPP table %pOF: %ld\n", +- __func__, np, PTR_ERR(opp_table)); ++ pr_err("%s: Failed to find required OPP table %pOF: %pe\n", ++ __func__, np, opp_table); + return PTR_ERR(opp_table); + } + diff --git a/pkgbuilds/linux-omarchy-bore/0211-pm-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-bore/0211-pm-fixes-1.patch.sig new file mode 100644 index 0000000..3282089 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0211-pm-fixes-1.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 deleted file mode 100644 index 61ea14c..0000000 --- a/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c ---- a/drivers/cpufreq/amd-pstate.c -+++ b/drivers/cpufreq/amd-pstate.c -@@ -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 deleted file mode 100644 index ef0e5da..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0212-amd-pstate-epp-cache.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0212-pm-fixes-2.patch b/pkgbuilds/linux-omarchy-bore/0212-pm-fixes-2.patch new file mode 100644 index 0000000..0e9c073 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0212-pm-fixes-2.patch @@ -0,0 +1,54 @@ +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -408,9 +408,18 @@ int hibernation_snapshot(int platform_mode) + if (error) + goto Close; + ++ error = dpm_prepare(PMSG_FREEZE); ++ if (error) ++ goto Complete; ++ ++ /* Preallocate image memory before freezing kernel threads and shutting down devices. */ ++ error = hibernate_preallocate_memory(); ++ if (error) ++ goto Complete; ++ + error = freeze_kernel_threads(); + if (error) +- goto Close; ++ goto Cleanup; + + if (hibernation_test(TEST_FREEZER)) { + +@@ -422,15 +431,6 @@ int hibernation_snapshot(int platform_mode) + goto Thaw; + } + +- error = dpm_prepare(PMSG_FREEZE); +- if (error) +- goto Complete; +- +- /* Preallocate image memory before shutting down devices. */ +- error = hibernate_preallocate_memory(); +- if (error) +- goto Complete; +- + console_suspend_all(); + pm_restrict_gfp_mask(); + +@@ -464,10 +464,12 @@ int hibernation_snapshot(int platform_mode) + platform_end(platform_mode); + return error; + +- Complete: +- dpm_complete(PMSG_RECOVER); + Thaw: + thaw_kernel_threads(); ++ Cleanup: ++ swsusp_free(); ++ Complete: ++ dpm_complete(PMSG_RECOVER); + goto Close; + } + diff --git a/pkgbuilds/linux-omarchy-bore/0212-pm-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-bore/0212-pm-fixes-2.patch.sig new file mode 100644 index 0000000..679057c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0212-pm-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0213-pm-fixes-3.patch b/pkgbuilds/linux-omarchy-bore/0213-pm-fixes-3.patch new file mode 100644 index 0000000..e8c1523 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0213-pm-fixes-3.patch @@ -0,0 +1,61 @@ +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2590,8 +2590,8 @@ static void cpufreq_update_pressure(struct cpufreq_policy *policy) + + cpu = cpumask_first(policy->related_cpus); + max_freq = arch_scale_freq_ref(cpu); +- if (!max_freq) +- max_freq = policy->cpuinfo.max_freq; ++ if (!max_freq && cpufreq_driver->scale_freq_ref) ++ max_freq = cpufreq_driver->scale_freq_ref(policy); + + capped_freq = policy->max; + +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -1175,6 +1175,14 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static unsigned int intel_pstate_scale_freq_ref(struct cpufreq_policy *policy) ++{ ++ if (READ_ONCE(all_cpu_data[policy->cpu]->capacity_perf)) ++ return policy->cpuinfo.max_freq; ++ ++ return 0; ++} ++ + static void intel_pstate_update_freq_limits(struct cpudata *cpu) + { + int scaling = cpu->pstate.scaling; +@@ -3092,6 +3100,7 @@ static struct cpufreq_driver intel_pstate = { + .offline = intel_pstate_cpu_offline, + .online = intel_pstate_cpu_online, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_pstate", + }; + +@@ -3415,6 +3424,7 @@ static struct cpufreq_driver intel_cpufreq = { + .suspend = intel_cpufreq_suspend, + .resume = intel_pstate_resume, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_cpufreq", + }; + +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -420,6 +420,9 @@ struct cpufreq_driver { + /* Will be called after the driver is fully initialized */ + void (*ready)(struct cpufreq_policy *policy); + ++ /* Return the capacity reference frequency for policy. */ ++ unsigned int (*scale_freq_ref)(struct cpufreq_policy *policy); ++ + struct freq_attr **attr; + + /* platform specific boost support code */ diff --git a/pkgbuilds/linux-omarchy-bore/0213-pm-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-bore/0213-pm-fixes-3.patch.sig new file mode 100644 index 0000000..caad5f0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0213-pm-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch index 46a53c5..e0bf148 100644 --- a/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch +++ b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch @@ -278,25 +278,7 @@ diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c 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); +@@ -952,6 +952,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item); bool memcg_stat_item_valid(int idx); bool memcg_vm_event_item_valid(enum vm_event_item idx); unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx); @@ -305,7 +287,7 @@ diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h 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, +@@ -1404,6 +1406,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec, return node_page_state(lruvec_pgdat(lruvec), idx); } @@ -576,66 +558,6 @@ diff --git a/lib/maple_tree.c b/lib/maple_tree.c 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 @@ -898,24 +820,7 @@ diff --git a/mm/swap.c b/mm/swap.c 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 +@@ -1173,8 +1106,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid)); @@ -1129,20 +1034,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c 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, +@@ -4819,7 +4877,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, struct reclaim_stat stat; struct lru_gen_mm_walk *walk; int scanned, reclaimed; @@ -1152,7 +1044,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c 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, +@@ -4831,6 +4890,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness, &list, &isolated, &type, &type_scanned); @@ -1160,7 +1052,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c /* 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, +@@ -4843,6 +4903,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, retry: reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg); sc->nr_reclaimed += reclaimed; @@ -1168,7 +1060,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c /* 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, +@@ -4892,6 +4953,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, goto retry; } diff --git a/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch.sig index 4e1b338..97d0386 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch.sig and b/pkgbuilds/linux-omarchy-bore/0280-mm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch b/pkgbuilds/linux-omarchy-bore/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch new file mode 100644 index 0000000..e8e9809 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch @@ -0,0 +1,35 @@ +diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c +--- a/mm/pgtable-generic.c ++++ b/mm/pgtable-generic.c +@@ -438,12 +438,30 @@ static void kernel_pgtable_work_func(struct work_struct *work) + __pagetable_free(pt); + } + ++static void schedule_kernel_pgtable_free(void) ++{ ++ schedule_work(&kernel_pgtable_work.work); ++} ++ + void pagetable_free_kernel(struct ptdesc *pt) + { + spin_lock(&kernel_pgtable_work.lock); + list_add(&pt->pt_list, &kernel_pgtable_work.list); + spin_unlock(&kernel_pgtable_work.lock); + +- schedule_work(&kernel_pgtable_work.work); ++ /* ++ * The workqueue may not exist yet while the system is booting. ++ * kernel_pgtable_drain_early() schedules the work once it does. ++ */ ++ if (system_state != SYSTEM_BOOTING) ++ schedule_kernel_pgtable_free(); + } ++ ++static int __init kernel_pgtable_drain_early(void) ++{ ++ /* Free the kernel page tables queued while booting. */ ++ schedule_kernel_pgtable_free(); ++ return 0; ++} ++core_initcall(kernel_pgtable_drain_early); + #endif diff --git a/pkgbuilds/linux-omarchy-bore/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig b/pkgbuilds/linux-omarchy-bore/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig new file mode 100644 index 0000000..d9c680c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0281-mm-no-deferred-kernel-page-table-free-at-boot.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 deleted file mode 100644 index 602ef86..0000000 --- a/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index b5e1f0f..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch index 0cb9f8d..837f7bc 100644 --- a/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch +++ b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch @@ -1,50 +1,7 @@ -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) +@@ -2630,10 +2630,9 @@ static int fill_dummy_bgs(struct btrfs_fs_info *fs_info) /* Fill dummy cache as FULL */ bg->length = map->chunk_len; @@ -56,7 +13,7 @@ diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c 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, +@@ -3922,7 +3921,7 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, old_val += num_bytes; cache->used = old_val; cache->reserved -= num_bytes; @@ -311,43 +268,7 @@ diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c 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, +@@ -1169,7 +1169,7 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, struct defrag_target_range *entry; struct defrag_target_range *tmp; LIST_HEAD(target_list); @@ -356,69 +277,45 @@ diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c 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); +@@ -1196,10 +1196,8 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + * range or the extent lock. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) { +- kfree(folios); + if (ret < 0) -+ return ret; -+ + 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, +@@ -1234,16 +1232,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, if (ret < 0) goto unlock_extent; - list_for_each_entry(entry, &target_list, list) { -- 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) { + defrag_one_locked_target(inode, entry, folios, nr_pages, &cached_state); + if (entry->start > last_defrag_end) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, + entry->start - last_defrag_end, true); + last_defrag_end = entry->start + entry->len; - } - - list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_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, +@@ -1255,7 +1249,6 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, folio_unlock(folios[i]); folio_put(folios[i]); } - kfree(folios); -+ btrfs_delalloc_release_extents(inode, len); -+ if (last_defrag_end < start + len) -+ btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, -+ 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, + btrfs_delalloc_release_extents(inode, len); + if (last_defrag_end < start + len) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, +@@ -1326,10 +1319,8 @@ static int defrag_one_cluster(struct btrfs_inode *inode, inode->root->fs_info->sectorsize_bits; } out: @@ -744,29 +641,7 @@ diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c 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 = { +@@ -591,12 +591,13 @@ static const struct address_space_operations btree_aops = { struct extent_buffer *btrfs_find_create_tree_block( struct btrfs_fs_info *fs_info, @@ -781,7 +656,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c } /* -@@ -608,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( +@@ -609,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, struct btrfs_tree_parent_check *check) { @@ -796,7 +671,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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, +@@ -667,10 +669,7 @@ static struct btrfs_root *btrfs_alloc_root(struct btrfs_fs_info *fs_info, init_waitqueue_head(&root->log_commit_wait[1]); INIT_LIST_HEAD(&root->log_ctxs[0]); INIT_LIST_HEAD(&root->log_ctxs[1]); @@ -807,7 +682,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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, +@@ -2053,7 +2052,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, if (IS_ERR(log_tree_root->node)) { ret = PTR_ERR(log_tree_root->node); log_tree_root->node = NULL; @@ -816,7 +691,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c btrfs_put_root(log_tree_root); return ret; } -@@ -2062,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, +@@ -2063,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, btrfs_put_root(log_tree_root); if (unlikely(ret)) { ASSERT(BTRFS_FS_ERROR(fs_info) != 0); @@ -825,7 +700,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } -@@ -2303,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) +@@ -2304,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) return 0; out: @@ -836,7 +711,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } -@@ -2395,8 +2395,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, +@@ -2400,8 +2399,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, int btrfs_validate_super(const struct btrfs_fs_info *fs_info, const struct btrfs_super_block *sb, int mirror_num) { @@ -847,7 +722,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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, +@@ -2443,24 +2442,24 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info, */ if (unlikely(!is_power_of_2(sectorsize) || sectorsize < BTRFS_MIN_BLOCKSIZE || sectorsize > BTRFS_MAX_METADATA_BLOCKSIZE)) { @@ -876,7 +751,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c le32_to_cpu(sb->__unused_leafsize), nodesize); ret = -EINVAL; } -@@ -2905,7 +2905,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) +@@ -2910,7 +2909,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) fs_info->nodesize = 4096; fs_info->sectorsize = 4096; fs_info->sectorsize_bits = ilog2(4096); @@ -884,7 +759,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c /* 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) +@@ -2984,8 +2982,8 @@ static int btrfs_uuid_rescan_kthread(void *data) ret = btrfs_uuid_tree_iterate(fs_info); if (ret < 0) { if (ret != -EINTR) @@ -895,7 +770,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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) +@@ -3088,7 +3086,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_rebuild_free_space_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -904,7 +779,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } } -@@ -3094,7 +3093,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3099,7 +3097,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_delete_free_space_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -913,7 +788,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } } -@@ -3105,7 +3104,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3110,7 +3108,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) */ ret = btrfs_delete_orphan_free_space_entries(fs_info); if (ret < 0) { @@ -923,7 +798,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } /* -@@ -3139,7 +3139,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3144,7 +3143,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_recover_relocation(fs_info); mutex_unlock(&fs_info->cleaner_mutex); if (ret < 0) { @@ -932,7 +807,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } -@@ -3149,7 +3149,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3154,7 +3153,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_create_free_space_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -941,7 +816,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } } -@@ -3177,7 +3177,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3182,7 +3181,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_create_uuid_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -950,46 +825,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3372,7 +3371,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device { u32 sectorsize; u32 nodesize; @@ -997,7 +833,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3481,7 +3479,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device /* Set up fs_info before parsing mount options */ nodesize = btrfs_super_nodesize(disk_super); sectorsize = btrfs_super_sectorsize(disk_super); @@ -1005,7 +841,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3500,7 +3497,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device else fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; @@ -1013,7 +849,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3566,7 +3562,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_read_sys_array(fs_info); mutex_unlock(&fs_info->chunk_mutex); if (ret) { @@ -1022,7 +858,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_sb_buffer; } -@@ -3568,7 +3577,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3585,7 +3581,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_read_chunk_tree(fs_info); if (ret) { @@ -1031,7 +867,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_tree_roots; } -@@ -3598,7 +3607,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3615,7 +3611,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_get_dev_zone_info_all_devices(fs_info); if (ret) { btrfs_err(fs_info, @@ -1040,7 +876,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_block_groups; } -@@ -3621,72 +3630,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3638,72 +3634,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_verify_dev_extents(fs_info); if (ret) { btrfs_err(fs_info, @@ -1129,7 +965,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_sysfs; } -@@ -3742,7 +3752,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3759,7 +3756,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device fs_info->fs_root = btrfs_get_fs_root(fs_info, BTRFS_FS_TREE_OBJECTID, true); if (IS_ERR(fs_info->fs_root)) { ret = PTR_ERR(fs_info->fs_root); @@ -1138,7 +974,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3780,7 +3777,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device btrfs_info(fs_info, "checking UUID tree"); ret = btrfs_check_uuid_tree(fs_info); if (ret) { @@ -1147,7 +983,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c close_ctree(fs_info); return ret; } -@@ -3879,8 +3889,8 @@ static int write_dev_supers(struct btrfs_device *device, +@@ -3896,8 +3893,8 @@ static int write_dev_supers(struct btrfs_device *device, continue; } else if (ret < 0) { btrfs_err(device->fs_info, @@ -1158,7 +994,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c atomic_inc(&device->sb_write_errors); continue; } -@@ -3898,8 +3908,8 @@ static int write_dev_supers(struct btrfs_device *device, +@@ -3915,8 +3912,8 @@ static int write_dev_supers(struct btrfs_device *device, GFP_NOFS); if (IS_ERR(folio)) { btrfs_err(device->fs_info, @@ -1169,7 +1005,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c atomic_inc(&device->sb_write_errors); continue; } -@@ -4381,6 +4391,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) +@@ -4398,6 +4395,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) */ flush_workqueue(fs_info->fixup_workers); @@ -1191,7 +1027,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c /* * 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) +@@ -4528,7 +4540,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) if (!btrfs_is_shutdown(fs_info)) { ret = btrfs_commit_super(fs_info); if (ret) @@ -1200,7 +1036,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c } } -@@ -4550,6 +4575,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) +@@ -4567,6 +4579,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) free_root_pointers(fs_info, true); btrfs_free_fs_roots(fs_info); @@ -1214,7 +1050,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c /* * 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) +@@ -5015,6 +5034,7 @@ static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info) btrfs_assert_delayed_root_empty(fs_info); btrfs_destroy_all_delalloc_inodes(fs_info); btrfs_drop_all_logs(fs_info); @@ -1461,198 +1297,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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) +@@ -2976,13 +2994,25 @@ void btrfs_readahead(struct readahead_control *rac) struct extent_map *em_cached = NULL; struct fsverity_info *vi = NULL; @@ -1681,123 +1326,8 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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; +@@ -3298,12 +3328,9 @@ void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + trans->inhibited_ebs_hand = 0; } -static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, @@ -1811,7 +1341,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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 +@@ -3316,7 +3343,15 @@ static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info refcount_set(&eb->refs, 1); ASSERT(eb->len <= BTRFS_MAX_METADATA_BLOCKSIZE); @@ -1827,7 +1357,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c return eb; } -@@ -3502,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) +@@ -3613,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) * The caller needs to free the existing folios and retry using the same order. */ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, @@ -1836,7 +1366,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c struct extent_buffer **found_eb_ret) { -@@ -3524,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, +@@ -3635,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, if (!ret) goto finish; @@ -1844,7 +1374,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -3643,7 +3679,27 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, /* For now, we should only have single-page folios for btree inode. */ ASSERT(folio_nr_pages(existing_folio) == 1); @@ -1872,7 +1402,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -3674,8 +3730,10 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, eb->folio_size = folio_size(eb->folios[i]); eb->folio_shift = folio_shift(eb->folios[i]); /* Should not fail, as we have preallocated the memory. */ @@ -1884,7 +1414,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c /* * 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, +@@ -3690,13 +3748,104 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, return 0; } @@ -1990,7 +1520,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -3720,9 +3869,14 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, if (eb) return eb; @@ -2008,7 +1538,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c /* * 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, +@@ -3733,66 +3887,18 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, btrfs_set_buffer_lockdep_class(lockdep_owner, eb, level); @@ -2079,7 +1609,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c attached++; /* -@@ -3758,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, +@@ -3869,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, out: WARN_ON(!refcount_dec_and_test(&eb->refs)); @@ -2090,71 +1620,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c /* * 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) +@@ -4030,6 +4140,32 @@ void free_extent_buffer_stale(struct extent_buffer *eb) release_extent_buffer(eb); } @@ -2187,7 +1653,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -4054,26 +4190,42 @@ void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, return; } @@ -2246,29 +1712,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c } 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, +@@ -4923,6 +5075,8 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, .level = level, .transid = gen }; @@ -2277,7 +1721,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c struct extent_buffer *eb; int ret; -@@ -4797,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, +@@ -4931,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, check.has_first_key = true; } @@ -2347,197 +1791,10 @@ diff --git a/fs/btrfs/fiemap.c b/fs/btrfs/fiemap.c 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) +@@ -1573,8 +1573,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) else btrfs_inode_lock(inode, BTRFS_ILOCK_MMAP); @@ -2546,7 +1803,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c /* * 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) +@@ -1657,8 +1655,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) if (ret) goto out_release_extents; @@ -2555,7 +1812,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c 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) +@@ -2663,8 +2659,8 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len) lockstart = round_up(offset, fs_info->sectorsize); lockend = round_down(offset + len, fs_info->sectorsize) - 1; @@ -2566,7 +1823,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c /* * Only do this if we are in the same block and we aren't doing the * entire block. -@@ -2888,7 +2862,7 @@ enum { +@@ -2868,7 +2864,7 @@ enum { static int btrfs_zero_range_check_range_boundary(struct btrfs_inode *inode, u64 offset) { @@ -2575,7 +1832,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c struct extent_map *em; int ret; -@@ -2918,7 +2892,7 @@ static int btrfs_zero_range(struct inode *inode, +@@ -2898,7 +2894,7 @@ static int btrfs_zero_range(struct inode *inode, struct extent_changeset *data_reserved = NULL; int ret; u64 alloc_hint = 0; @@ -2584,7 +1841,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c 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, +@@ -2947,8 +2943,8 @@ static int btrfs_zero_range(struct inode *inode, } btrfs_free_extent_map(em); @@ -2735,7 +1992,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c return ret; } -@@ -3050,7 +3051,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, +@@ -3065,7 +3066,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, u64 qgroup_reserved) { struct btrfs_root *root = inode->root; @@ -2744,7 +2001,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c 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) +@@ -3926,7 +3927,7 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) out: if (ret) @@ -2753,7 +2010,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c return ret; } -@@ -4210,8 +4211,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path +@@ -4229,8 +4230,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path ret = btrfs_load_inode_props(inode, path); if (ret) btrfs_err(fs_info, @@ -2764,7 +2021,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } /* -@@ -6825,8 +6826,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, +@@ -6844,8 +6845,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, } if (ret) { btrfs_err(fs_info, @@ -2775,7 +2032,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } /* -@@ -10205,6 +10206,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) +@@ -10224,6 +10225,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; struct btrfs_swapfile_pin *sp; struct rb_node *node, *next; @@ -2784,7 +2041,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c 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) +@@ -10233,15 +10236,24 @@ static void btrfs_free_swapfile_pins(struct inode *inode) if (sp->inode == inode) { rb_erase(&sp->node, &fs_info->swapfile_pins); if (sp->is_block_group) { @@ -2811,7 +2068,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } struct btrfs_swap_info { -@@ -10300,8 +10312,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, +@@ -10319,8 +10331,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, struct btrfs_backref_share_check_ctx *backref_ctx = NULL; struct btrfs_path *path = NULL; int ret = 0; @@ -2822,7 +2079,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c /* * 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, +@@ -10570,6 +10584,9 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, ret = 0; else goto out; @@ -2832,7 +2089,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } if (bsi.block_len && -@@ -10598,6 +10615,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, +@@ -10617,6 +10634,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, if (ret) return ret; @@ -3215,43 +2472,6 @@ diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c } } -@@ -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 @@ -3655,87 +2875,7 @@ diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c 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, +@@ -4151,10 +4152,10 @@ static int copy_remapped_data(struct btrfs_fs_info *fs_info, u64 old_addr, u64 new_addr, u64 length) { int ret; @@ -3748,17 +2888,6 @@ diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c 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 @@ -4127,7 +3256,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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) +@@ -1249,20 +1231,9 @@ static void scrub_stripe_read_repair_worker(struct work_struct *work) static void scrub_read_endio(struct btrfs_bio *bbio) { struct scrub_stripe *stripe = bbio->private; @@ -4149,7 +3278,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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) +@@ -1274,7 +1245,7 @@ static void scrub_write_endio(struct btrfs_bio *bbio) { struct scrub_stripe *stripe = bbio->private; struct btrfs_fs_info *fs_info = stripe->bg->fs_info; @@ -4158,7 +3287,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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 +@@ -1353,7 +1324,7 @@ static void scrub_write_sectors(struct scrub_ctx *sctx, struct scrub_stripe *str bbio = NULL; } if (!bbio) @@ -4167,7 +3296,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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) +@@ -1848,7 +1819,7 @@ static void scrub_submit_extent_sector_read(struct scrub_stripe *stripe) continue; } @@ -4176,7 +3305,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c logical, scrub_read_endio, stripe); } -@@ -1869,7 +1840,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, +@@ -1873,7 +1844,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, { struct btrfs_fs_info *fs_info = sctx->fs_info; struct btrfs_bio *bbio; @@ -4184,7 +3313,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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, +@@ -1886,7 +1856,7 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, return; } @@ -4193,7 +3322,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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, +@@ -2142,7 +2112,7 @@ static int scrub_raid56_cached_parity(struct scrub_ctx *sctx, for (int i = 0; i < data_stripes; i++) { struct scrub_stripe *stripe = &sctx->raid56_data_stripes[i]; @@ -4202,7 +3331,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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, +@@ -3095,14 +3065,6 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, /* At mount time we have ensured nodesize is in the range of [4K, 64K]. */ ASSERT(fs_info->nodesize <= BTRFS_STRIPE_LEN); @@ -4436,16 +3565,7 @@ diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c 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) +@@ -1528,12 +1528,8 @@ static noinline int commit_fs_roots(struct btrfs_trans_handle *trans) ASSERT(atomic_read(&root->log_writers) == 0, "atomic_read(&root->log_writers)=%d", atomic_read(&root->log_writers)); @@ -4460,7 +3580,7 @@ diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c 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, +@@ -1651,7 +1647,7 @@ static int qgroup_account_snapshot(struct btrfs_trans_handle *trans, ret = btrfs_write_and_wait_transaction(trans); if (unlikely(ret)) { btrfs_err(fs_info, @@ -4469,16 +3589,16 @@ diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c return ret; } -@@ -2588,7 +2582,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) +@@ -2597,7 +2593,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) ret = btrfs_write_and_wait_transaction(trans); if (unlikely(ret)) { - btrfs_err(fs_info, "error while writing out transaction: %d", ret); + btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); - mutex_unlock(&fs_info->tree_log_mutex); - goto scrub_continue; - } -@@ -2749,8 +2743,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, + /* + * Abort before releasing tree_log_mutex, so a log sync waiting + * on it sees the fs error and skips writing super_for_commit +@@ -2764,8 +2760,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, WRITE_ONCE(trans->transaction->aborted, error); trace_btrfs_transaction_abort(trans); if (first_hit) { @@ -4489,67 +3609,6 @@ diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c 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 @@ -4811,7 +3870,7 @@ diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c 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, +@@ -652,7 +652,7 @@ static int btrfs_end_enable_verity(struct file *filp, const void *desc, rollback_ret = rollback_verity(inode); if (rollback_ret) btrfs_err(inode->root->fs_info, @@ -4820,46 +3879,6 @@ diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c 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 @@ -4914,7 +3933,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c const char *old_name; /* -@@ -4588,7 +4553,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) +@@ -4592,7 +4557,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) if (ret == -ENOSPC) { enospc_errors++; } else if (ret == -ETXTBSY) { @@ -4923,7 +3942,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c "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) +@@ -6053,6 +6018,19 @@ struct btrfs_chunk_map *btrfs_alloc_chunk_map(int num_stripes, gfp_t gfp) return map; } @@ -4943,7 +3962,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c 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, +@@ -6071,11 +6049,10 @@ static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, map->start = start; map->chunk_len = ctl->chunk_size; map->stripe_size = ctl->stripe_size; @@ -4957,7 +3976,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c 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, +@@ -6254,7 +6231,7 @@ int btrfs_chunk_alloc_add_chunk_item(struct btrfs_trans_handle *trans, btrfs_set_stack_chunk_length(chunk, bg->length); btrfs_set_stack_chunk_owner(chunk, BTRFS_EXTENT_TREE_OBJECTID); btrfs_set_stack_chunk_stripe_len(chunk, BTRFS_STRIPE_LEN); @@ -4966,7 +3985,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c 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, +@@ -7636,9 +7613,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, map->start = logical; map->chunk_len = length; map->num_stripes = num_stripes; @@ -4977,7 +3996,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c /* * 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, +@@ -7649,6 +7624,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, */ map->sub_stripes = btrfs_raid_array[index].sub_stripes; map->verified_stripes = 0; diff --git a/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch.sig b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch.sig index 1586085..c992405 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch.sig and b/pkgbuilds/linux-omarchy-bore/0300-btrfs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes-1.patch b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes-1.patch new file mode 100644 index 0000000..8cc1ec5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes-1.patch @@ -0,0 +1,92 @@ +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 +@@ -627,7 +627,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/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags &= ~BTRFS_INODE_COMPRESS; + inode_flags |= BTRFS_INODE_NOCOMPRESS; + } else if (fsflags & FS_COMPR_FL) { ++ enum btrfs_compression_type comp_type; + + if (IS_SWAPFILE(&inode->vfs_inode)) + return -ETXTBSY; +@@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags |= BTRFS_INODE_COMPRESS; + inode_flags &= ~BTRFS_INODE_NOCOMPRESS; + +- comp = btrfs_compress_type2str(fs_info->compress_type); +- if (!comp || comp[0] == 0) +- comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB); ++ /* ++ * Keep the algorithm recorded in the compression property, ++ * otherwise changing an unrelated attribute would reset it to ++ * the mount default, since FS_IOC_SETFLAGS callers write back ++ * the whole flag set they got from FS_IOC_GETFLAGS and that ++ * includes FS_COMPR_FL for any inode carrying the property. ++ * ++ * Inodes with the compress flag set but no property keep using ++ * the mount default, so they behave as before. ++ */ ++ if (inode->prop_compress) ++ comp_type = inode->prop_compress; ++ else if (fs_info->compress_type) ++ comp_type = fs_info->compress_type; ++ else ++ comp_type = BTRFS_COMPRESS_ZLIB; ++ comp = btrfs_compress_type2str(comp_type); + } else { + inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS); + } +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -6417,6 +6417,13 @@ static int process_extent(struct send_ctx *sctx, + + if (S_ISLNK(sctx->cur_inode_mode)) + return 0; ++ if (unlikely(!S_ISREG(sctx->cur_inode_mode))) { ++ btrfs_crit(sctx->send_root->fs_info, ++ "send: extent for non-regular inode %llu root %llu mode 0%llo", ++ key->objectid, btrfs_root_id(sctx->send_root), ++ sctx->cur_inode_mode & S_IFMT); ++ return -EUCLEAN; ++ } + + if (sctx->parent_root && !sctx->cur_inode_new) { + ret = is_extent_unchanged(sctx, path, key); +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2710,6 +2710,7 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + { + struct btrfs_block_group *block_group; + u64 min_alloc_bytes; ++ int ret = 0; + + if (!btrfs_is_zoned(fs_info)) + return 0; +@@ -2729,11 +2730,11 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + block_group->start + block_group->zone_capacity) + goto out; + +- do_zone_finish(block_group, true); ++ ret = do_zone_finish(block_group, true); + + out: + btrfs_put_block_group(block_group); +- return 0; ++ return ret; + } + + static void btrfs_zone_finish_endio_workfn(struct work_struct *work) diff --git a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes-1.patch.sig new file mode 100644 index 0000000..e43d3d6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch b/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch deleted file mode 100644 index c5a4318..0000000 --- a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch +++ /dev/null @@ -1,386 +0,0 @@ -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 deleted file mode 100644 index b319318..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0301-btrfs-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0303-btrfs-fixes-2.patch b/pkgbuilds/linux-omarchy-bore/0303-btrfs-fixes-2.patch new file mode 100644 index 0000000..42654e7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0303-btrfs-fixes-2.patch @@ -0,0 +1,97 @@ +diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c +--- a/fs/btrfs/block-group.c ++++ b/fs/btrfs/block-group.c +@@ -3074,6 +3074,18 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + ++ /* ++ * Ensure the corresponding space_info object is created and ++ * assigned to our block group. We want our bg to be added to the rbtree ++ * with its ->space_info set. ++ * ++ * On a zoned filesystem btrfs_add_new_free_space() ends up in ++ * __btrfs_add_free_space_zoned(), which dereferences ++ * block_group->space_info, so it has to be set beforehand. ++ */ ++ cache->space_info = space_info; ++ ASSERT(cache->space_info); ++ + ret = btrfs_add_new_free_space(cache, chunk_offset, chunk_offset + size, NULL); + btrfs_free_excluded_extents(cache); + if (ret) { +@@ -3081,14 +3093,6 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + +- /* +- * Ensure the corresponding space_info object is created and +- * assigned to our block group. We want our bg to be added to the rbtree +- * with its ->space_info set. +- */ +- cache->space_info = space_info; +- ASSERT(cache->space_info); +- + ret = btrfs_add_block_group_cache(cache); + if (ret) { + btrfs_remove_free_space_cache(cache); +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -1909,6 +1909,16 @@ static int check_inode_ref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ if (unlikely(btrfs_is_fstree(btrfs_header_owner(leaf)) && ++ (key->offset < BTRFS_FIRST_FREE_OBJECTID || ++ key->offset > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid offset for ref key, have %llu expect [%llu, %lld]", ++ key->offset, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + ptr = btrfs_item_ptr_offset(leaf, slot); + end = ptr + btrfs_item_size(leaf, slot); + while (ptr < end) { +@@ -1952,12 +1962,14 @@ static int check_inode_extref(struct extent_buffer *leaf, + { + unsigned long ptr = btrfs_item_ptr_offset(leaf, slot); + unsigned long end = ptr + btrfs_item_size(leaf, slot); ++ const bool is_fstree = btrfs_is_fstree(btrfs_header_owner(leaf)); + + if (unlikely(!check_prev_ino(leaf, key, slot, prev_key))) + return -EUCLEAN; + + while (ptr < end) { + struct btrfs_inode_extref *extref = (struct btrfs_inode_extref *)ptr; ++ u64 parent; + u16 namelen; + + if (unlikely(ptr + sizeof(*extref) > end)) { +@@ -1967,7 +1979,24 @@ static int check_inode_extref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ parent = btrfs_inode_extref_parent(leaf, extref); ++ if (unlikely(is_fstree && (parent < BTRFS_FIRST_FREE_OBJECTID || ++ parent > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid parent for extref key, have %llu expect [%llu, %lld]", ++ parent, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + namelen = btrfs_inode_extref_name_len(leaf, extref); ++ if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) { ++ inode_ref_err(leaf, slot, ++ "invalid inode extref name length, has %u expect [1, %u]", ++ namelen, BTRFS_NAME_LEN); ++ return -EUCLEAN; ++ } ++ + if (unlikely(ptr + sizeof(*extref) + namelen > end)) { + inode_ref_err(leaf, slot, + "inode extref overflow, ptr %lu end %lu namelen %u", diff --git a/pkgbuilds/linux-omarchy-bore/0303-btrfs-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-bore/0303-btrfs-fixes-2.patch.sig new file mode 100644 index 0000000..705b8ba Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0303-btrfs-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0304-btrfs-fixes-3.patch b/pkgbuilds/linux-omarchy-bore/0304-btrfs-fixes-3.patch new file mode 100644 index 0000000..7d1ab48 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0304-btrfs-fixes-3.patch @@ -0,0 +1,77 @@ +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -2306,7 +2306,7 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key + + if (unlikely(btrfs_item_size(leaf, slot) != 0)) { + generic_err(leaf, slot, +- "invalid item size for free space info, has %u expect 0", ++ "invalid item size for free space extent, has %u expect 0", + btrfs_item_size(leaf, slot)); + return -EUCLEAN; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,6 +740,36 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + ++static bool should_rename_device(const struct btrfs_device *dev) ++{ ++ bool ret; ++ const char *old_name; ++ ++ rcu_read_lock(); ++ old_name = rcu_dereference(dev->name); ++ /* ++ * For systems booted without an initramfs, the rootfs has the device ++ * name "/dev/root". ++ * ++ * Although using btrfs without an initramfs is not recommended (if a ++ * new device is added to the rootfs, the system can no longer boot, as ++ * there is no way to register all devices), there is still a minority ++ * of users doing this. ++ * ++ * And after the system is up, a later device scan on the real block ++ * device file will never get this device's name updated, as the ++ * device->devt is still the same. ++ * ++ * Here we add one and only one exception for "/dev/root", to allow the ++ * device name to be updated even if the new path points to the same ++ * block device. ++ */ ++ ret = (strcmp(old_name, "/dev/root") == 0); ++ rcu_read_unlock(); ++ ++ return ret; ++} ++ + /* + * Add new device to list of registered devices + * +@@ -860,7 +890,8 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || device->devt != path_devt) { ++ } else if (!device->name || device->devt != path_devt || ++ should_rename_device(device)) { + const char *old_name; + + /* +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2688,6 +2688,11 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) + + switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { + case 0: /* single */ ++ case BTRFS_BLOCK_GROUP_RAID0: ++ case BTRFS_BLOCK_GROUP_RAID1: ++ case BTRFS_BLOCK_GROUP_RAID1C3: ++ case BTRFS_BLOCK_GROUP_RAID1C4: ++ case BTRFS_BLOCK_GROUP_RAID10: + ret = (atomic_read(&zinfo->active_zones_left) >= (1 + reserved)); + break; + case BTRFS_BLOCK_GROUP_DUP: diff --git a/pkgbuilds/linux-omarchy-bore/0304-btrfs-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-bore/0304-btrfs-fixes-3.patch.sig new file mode 100644 index 0000000..3e983dc Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0304-btrfs-fixes-3.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 index b34a909..38a6427 100644 --- a/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch +++ b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch @@ -1,7 +1,7 @@ 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, +@@ -2289,6 +2289,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) { @@ -28,7 +28,7 @@ diff --git a/fs/fuse/file.c b/fs/fuse/file.c 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, +@@ -2909,6 +2913,11 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, /* we could have extended the file */ if (!(mode & FALLOC_FL_KEEP_SIZE)) { diff --git a/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch.sig b/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch.sig index dc63cd6..79483f9 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0310-fuse-eof-zeroing.patch.sig 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 index 5162077..ca3c02a 100644 --- a/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch +++ b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch @@ -1,7 +1,7 @@ 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); +@@ -213,10 +213,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash); /* * A new request is available, wake fiq->waitq */ @@ -17,7 +17,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c kill_fasync(&fiq->fasync, SIGIO, POLL_IN); spin_unlock(&fiq->lock); } -@@ -236,7 +239,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, +@@ -233,7 +236,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, if (fiq->connected) { fiq->forget_list_tail->next = forget; fiq->forget_list_tail = forget; @@ -26,7 +26,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c } else { kfree(forget); spin_unlock(&fiq->lock); -@@ -258,7 +261,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) +@@ -255,7 +258,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) list_del_init(&req->intr_entry); spin_unlock(&fiq->lock); } else { @@ -35,7 +35,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c } } else { spin_unlock(&fiq->lock); -@@ -288,11 +291,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); +@@ -285,11 +288,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req) { @@ -50,7 +50,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c } else { spin_unlock(&fiq->lock); req->out.h.error = -ENOTCONN; -@@ -755,6 +760,11 @@ static void __fuse_request_send(struct fuse_req *req) +@@ -752,6 +757,11 @@ static void __fuse_request_send(struct fuse_req *req) /* acquire extra reference, since request is still needed after fuse_request_end() */ __fuse_get_request(req); @@ -62,7 +62,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c fuse_send_one(fiq, req); request_wait_answer(req); -@@ -1809,7 +1819,7 @@ void fuse_chan_resend(struct fuse_chan *fch) +@@ -1806,7 +1816,7 @@ void fuse_chan_resend(struct fuse_chan *fch) } /* iq and pq requests are both oldest to newest */ list_splice(&to_queue, &fiq->pending); @@ -94,7 +94,7 @@ diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h 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) +@@ -1420,6 +1420,7 @@ static void process_init_reply(struct fuse_args *args, int error) fm->sb->s_bdi->ra_pages = min(fm->sb->s_bdi->ra_pages, ra_pages); diff --git a/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch.sig b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch.sig index ff6a782..ef5fe91 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.patch.sig and b/pkgbuilds/linux-omarchy-bore/0311-fuse-perf.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 index 77e750a..27fbcdc 100644 --- a/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch +++ b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch @@ -1,7 +1,7 @@ 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) +@@ -406,7 +406,8 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val) fch->max_background = val; fch->blocked = fch->num_background >= fch->max_background; if (!fch->blocked) diff --git a/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch.sig b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch.sig index 8ec069b..a8e53e8 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch.sig and b/pkgbuilds/linux-omarchy-bore/0313-fuse-background-wakeup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0320-vfs-open-consume-path-reference.patch b/pkgbuilds/linux-omarchy-bore/0320-vfs-open-consume-path-reference.patch new file mode 100644 index 0000000..eba1599 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0320-vfs-open-consume-path-reference.patch @@ -0,0 +1,119 @@ +diff --git a/fs/internal.h b/fs/internal.h +--- a/fs/internal.h ++++ b/fs/internal.h +@@ -204,6 +204,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, + int flag); + int chown_common(const struct path *path, uid_t user, gid_t group); + extern int vfs_open(const struct path *, struct file *); ++int vfs_open_consume(struct path *, struct file *); + + /* + * inode.c +diff --git a/fs/namei.c b/fs/namei.c +--- a/fs/namei.c ++++ b/fs/namei.c +@@ -4652,6 +4652,7 @@ static const char *open_last_lookups(struct nameidata *nd, + static int do_open(struct nameidata *nd, + struct file *file, const struct open_flags *op) + { ++ struct vfsmount *mnt; + struct mnt_idmap *idmap; + int open_flag = op->open_flag; + bool do_truncate; +@@ -4693,11 +4694,17 @@ static int do_open(struct nameidata *nd, + error = mnt_want_write(nd->path.mnt); + if (error) + return error; ++ /* ++ * A dedicated reference is needed because after the call to ++ * vfs_open_consume() we no longer own the reference in nd->path.mnt ++ * while we need to undo write acess below. ++ */ ++ mnt = mntget(nd->path.mnt); + do_truncate = true; + } + error = may_open(idmap, &nd->path, acc_mode, open_flag); + if (!error && !(file->f_mode & FMODE_OPENED)) +- error = vfs_open(&nd->path, file); ++ error = vfs_open_consume(&nd->path, file); + if (!error) + error = security_file_post_open(file, op->acc_mode); + if (!error && do_truncate) +@@ -4706,8 +4713,10 @@ static int do_open(struct nameidata *nd, + WARN_ON(1); + error = -EINVAL; + } +- if (do_truncate) +- mnt_drop_write(nd->path.mnt); ++ if (do_truncate) { ++ mnt_drop_write(mnt); ++ mntput(mnt); ++ } + return error; + } + +diff --git a/fs/open.c b/fs/open.c +--- a/fs/open.c ++++ b/fs/open.c +@@ -882,6 +882,11 @@ static inline int file_get_write_access(struct file *f) + return error; + } + ++/* ++ * Populate struct file ++ * ++ * NOTE: it assumes f_path is populated and consumes the caller's reference. ++ */ + static int do_dentry_open(struct file *f, + int (*open)(struct inode *, struct file *)) + { +@@ -889,7 +894,6 @@ static int do_dentry_open(struct file *f, + struct inode *inode = f->f_path.dentry->d_inode; + int error; + +- path_get(&f->f_path); + f->f_inode = inode; + f->f_mapping = inode->i_mapping; + f->f_wb_err = filemap_sample_wb_err(f->f_mapping); +@@ -1006,6 +1010,7 @@ int finish_open(struct file *file, struct dentry *dentry, + BUG_ON(file->f_mode & FMODE_OPENED); /* once it's opened, it's opened */ + + file->__f_path.dentry = dentry; ++ path_get(&file->f_path); + return do_dentry_open(file, open); + } + EXPORT_SYMBOL(finish_open); +@@ -1049,6 +1054,7 @@ int vfs_open(const struct path *path, struct file *file) + int ret; + + file->__f_path = *path; ++ path_get(&file->f_path); + ret = do_dentry_open(file, NULL); + if (!ret) { + /* +@@ -1061,6 +1067,25 @@ int vfs_open(const struct path *path, struct file *file) + return ret; + } + ++/** ++ * vfs_open_consume - open the file at the given path and consume the reference ++ * @path: path to open ++ * @file: newly allocated file with f_flag initialized ++ */ ++int vfs_open_consume(struct path *path, struct file *file) ++{ ++ int ret; ++ ++ file->__f_path = *path; ++ path->mnt = NULL; ++ path->dentry = NULL; ++ ret = do_dentry_open(file, NULL); ++ if (!ret) { ++ fsnotify_open(file); ++ } ++ return ret; ++} ++ + struct file *dentry_open(const struct path *path, int flags, + const struct cred *cred) + { diff --git a/pkgbuilds/linux-omarchy-bore/0320-vfs-open-consume-path-reference.patch.sig b/pkgbuilds/linux-omarchy-bore/0320-vfs-open-consume-path-reference.patch.sig new file mode 100644 index 0000000..74dd333 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0320-vfs-open-consume-path-reference.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 deleted file mode 100644 index 6977a84..0000000 --- a/pkgbuilds/linux-omarchy-bore/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index bd99af4..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch.sig and /dev/null differ 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 deleted file mode 100644 index 6e91496..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch b/pkgbuilds/linux-omarchy-bore/0401-psr2-early-transport-panels.patch similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch rename to pkgbuilds/linux-omarchy-bore/0401-psr2-early-transport-panels.patch diff --git a/pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch.sig b/pkgbuilds/linux-omarchy-bore/0401-psr2-early-transport-panels.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0402-psr2-early-transport-panels.patch.sig rename to pkgbuilds/linux-omarchy-bore/0401-psr2-early-transport-panels.patch.sig diff --git a/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch b/pkgbuilds/linux-omarchy-bore/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch similarity index 64% rename from pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch rename to pkgbuilds/linux-omarchy-bore/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch index 28a6330..27e9b7d 100644 --- a/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch +++ b/pkgbuilds/linux-omarchy-bore/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch @@ -1,5 +1,4 @@ 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 { @@ -11,10 +10,9 @@ index c21e0c0ef0b1..e62788328bfd 100644 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, +@@ -2481,6 +2481,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); @@ -22,7 +20,7 @@ index beaa1d62613d..644ab9b1bee9 100644 } /** -@@ -2506,6 +2507,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) +@@ -2512,6 +2513,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) cancel_work_sync(&psr->work); cancel_delayed_work_sync(&psr->dc3co_work); @@ -30,48 +28,48 @@ index beaa1d62613d..644ab9b1bee9 100644 } /** -@@ -3539,6 +3541,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) +@@ -3545,6 +3547,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); } +#define PANEL_REPLAY_REENABLE_DELAY_MS 50 + -+static bool panel_replay_alpm_cursor_lag_workaround_enabled(struct intel_dp *intel_dp) ++static bool panel_replay_exit_on_flush_enabled(struct intel_dp *intel_dp) +{ + return intel_dp_is_edp(intel_dp) && + intel_dp->psr.panel_replay_enabled && + intel_dp->psr.sel_update_enabled && + intel_has_dpcd_quirk(intel_dp, -+ QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); +} + 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) +@@ -3563,6 +3576,11 @@ static void intel_psr_work(struct work_struct *work) if (intel_dp->psr.pause_counter) goto unlock; -+ /* The dedicated delayed work owns re-entry while the workaround is armed. */ -+ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ /* The dedicated delayed work owns re-entry while the quirk is armed. */ ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && + delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) + goto unlock; + /* * We have to make sure PSR is ready for re-enable * otherwise it keeps disabled until next full enable/disable cycle. -@@ -3566,6 +3584,10 @@ static void intel_psr_work(struct work_struct *work) +@@ -3572,6 +3590,10 @@ static void intel_psr_work(struct work_struct *work) if (!__psr_wait_for_idle_locked(intel_dp)) goto unlock; -+ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && + delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) + goto unlock; + /* * The delayed work can race with an invalidate hence we need to * recheck. Since psr_flush first clears this and then reschedules we -@@ -3579,6 +3601,33 @@ static void intel_psr_work(struct work_struct *work) +@@ -3585,6 +3607,33 @@ static void intel_psr_work(struct work_struct *work) mutex_unlock(&intel_dp->psr.lock); } @@ -84,7 +82,7 @@ index beaa1d62613d..644ab9b1bee9 100644 + mutex_lock(&intel_dp->psr.lock); + + if (!intel_dp->psr.enabled || -+ !panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) || ++ !panel_replay_exit_on_flush_enabled(intel_dp) || + intel_dp->psr.pause_counter || + READ_ONCE(intel_dp->psr.irq_aux_error)) + goto unlock; @@ -105,25 +103,25 @@ index beaa1d62613d..644ab9b1bee9 100644 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, +@@ -3656,8 +3705,11 @@ void intel_psr_invalidate(struct intel_display *display, INTEL_FRONTBUFFER_ALL_MASK(intel_dp->psr.pipe); intel_dp->psr.busy_frontbuffer_bits |= pipe_frontbuffer_bits; - if (pipe_frontbuffer_bits) + if (pipe_frontbuffer_bits) { -+ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) ++ if (panel_replay_exit_on_flush_enabled(intel_dp)) + cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); _psr_invalidate_handle(intel_dp); + } mutex_unlock(&intel_dp->psr.lock); } -@@ -3767,6 +3819,15 @@ void intel_psr_flush(struct intel_display *display, +@@ -3773,6 +3825,15 @@ void intel_psr_flush(struct intel_display *display, if (intel_dp->psr.pause_counter) goto unlock; + if (pipe_frontbuffer_bits && -+ panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) { ++ panel_replay_exit_on_flush_enabled(intel_dp)) { + intel_psr_exit(intel_dp); + mod_delayed_work(display->wq.unordered, + &intel_dp->psr.panel_replay_reenable_work, @@ -134,7 +132,7 @@ index beaa1d62613d..644ab9b1bee9 100644 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) +@@ -3836,6 +3897,8 @@ void intel_psr_init(struct intel_dp *intel_dp) INIT_WORK(&intel_dp->psr.work, intel_psr_work); INIT_DELAYED_WORK(&intel_dp->psr.dc3co_work, tgl_dc3co_disable_work); @@ -144,53 +142,31 @@ index beaa1d62613d..644ab9b1bee9 100644 } 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"); +@@ -94,6 +94,14 @@ static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) + drm_info(display->drm, "Applying disable Panel Replay quirk\n"); } ++static void quirk_panel_replay_exit_on_flush(struct intel_dp *intel_dp) ++{ ++ struct intel_display *display = to_intel_display(intel_dp); ++ ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); ++ drm_info(display->drm, "Applying Panel Replay exit on flush quirk\n"); ++} ++ static void quirk_disable_psr2(struct intel_display *display) -@@ -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, - }, - }; - + { + intel_set_quirk(display, QUIRK_DISABLE_PSR2); diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h -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, ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH, }; void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy-bore/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-bore/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..cb36779 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-bore/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..24c203e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -284,7 +284,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0db9, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + /* Dell XPS 16 DA16260 */ + { diff --git a/pkgbuilds/linux-omarchy-bore/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-bore/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..a828b25 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-bore/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..8eb9b65 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -292,7 +292,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0dba, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + }; + diff --git a/pkgbuilds/linux-omarchy-bore/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-bore/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..0894c4b Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch b/pkgbuilds/linux-omarchy-bore/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch new file mode 100644 index 0000000..1da7f71 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -267,6 +267,9 @@ static struct intel_quirk intel_quirks[] = { + + /* Xiaomi Book Pro 14 2026 */ + { 0xb081, 0x1d72, 0x2424, quirk_disable_psr2 }, ++ ++ /* Dell XPS 13 DX13260 */ ++ { DEVICE_ID_ANY, 0x1028, 0x0e54, quirk_disable_psr2 }, + }; + + static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { +@@ -294,6 +297,14 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_panel_replay_exit_on_flush, + }, ++ /* Dell XPS 13 DX13260 */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e54, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .hook = quirk_disable_edp_panel_replay, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-bore/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig b/pkgbuilds/linux-omarchy-bore/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig new file mode 100644 index 0000000..833b6bd Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-bore/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..94d0d55 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch @@ -0,0 +1,19 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -305,6 +305,15 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_disable_edp_panel_replay, + }, ++ /* Dell XPS 13 DX13260 (Wildcat Lake), sink "Bamboo" */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e53, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .sink_device_id = SINK_DEVICE_ID(0x42, 0x61, 0x6d, 0x62, 0x6f, 0x6f), ++ .hook = quirk_panel_replay_exit_on_flush, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-bore/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-bore/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..086a867 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0430-fbc.patch b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch index 4548866..b64db93 100644 --- a/pkgbuilds/linux-omarchy-bore/0430-fbc.patch +++ b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch @@ -55,7 +55,7 @@ diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/dis 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, +@@ -1932,10 +1932,10 @@ static int intel_plane_min_height(struct intel_plane *plane, return 1; } @@ -70,7 +70,7 @@ diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/dr { 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, +@@ -1943,10 +1943,10 @@ static int intel_plane_max_width(struct intel_plane *plane, return INT_MAX; } diff --git a/pkgbuilds/linux-omarchy-bore/0430-fbc.patch.sig b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch.sig index f0db1f3..7c6d7b4 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0430-fbc.patch.sig and b/pkgbuilds/linux-omarchy-bore/0430-fbc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch b/pkgbuilds/linux-omarchy-bore/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch new file mode 100644 index 0000000..e2e1fac --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch @@ -0,0 +1,95 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c +--- a/drivers/gpu/drm/i915/display/intel_cursor.c ++++ b/drivers/gpu/drm/i915/display/intel_cursor.c +@@ -536,7 +536,8 @@ static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); +@@ -569,8 +570,10 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch) { ++ i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); + return; ++ } + + if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) { + if (crtc_state->enable_psr2_su_region_et) { +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1177,6 +1177,8 @@ struct intel_crtc_state { + bool has_sel_update; + bool enable_psr2_sel_fetch; + bool enable_psr2_su_region_et; ++ /* Drop the stale selective fetch enable bits as selective fetch is turned off */ ++ bool clear_psr2_sel_fetch; + bool req_psr2_sdp_prior_scanline; + bool has_panel_replay; + bool link_off_after_as_sdp_when_pr_active; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2941,6 +2941,8 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) + { + struct intel_display *display = to_intel_display(state); ++ const struct intel_crtc_state *old_crtc_state = ++ intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + struct intel_plane_state *new_plane_state, *old_plane_state; + struct intel_plane *plane; +@@ -2953,6 +2955,19 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + bool full_update = false, su_area_changed; + int i, ret; + ++ /* ++ * Selective fetch is not always usable, for instance it is dropped ++ * while pipe CRC is active. The planes keep their selective fetch ++ * enable bit set in hardware over that, and a plane disabled while ++ * selective fetch is off never gets the bit cleared. Once selective ++ * fetch comes back the hardware would resume fetching for a plane that ++ * is no longer enabled and keep its DDB range reserved, so have the ++ * plane update drop the bit for every plane of the pipe as selective ++ * fetch is turned off. ++ */ ++ crtc_state->clear_psr2_sel_fetch = old_crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->enable_psr2_sel_fetch; ++ + if (!crtc_state->enable_psr2_sel_fetch) + return 0; + +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -885,7 +885,8 @@ static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); +@@ -1634,10 +1635,8 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) +- return; +- +- if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) ++ if (crtc_state->enable_psr2_sel_fetch && ++ drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), + SEL_FETCH_PLANE_CTL_ENABLE); + else diff --git a/pkgbuilds/linux-omarchy-bore/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig b/pkgbuilds/linux-omarchy-bore/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig new file mode 100644 index 0000000..2a891ba Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch b/pkgbuilds/linux-omarchy-bore/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch new file mode 100644 index 0000000..820d377 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dsb.c b/drivers/gpu/drm/i915/display/intel_dsb.c +--- a/drivers/gpu/drm/i915/display/intel_dsb.c ++++ b/drivers/gpu/drm/i915/display/intel_dsb.c +@@ -902,9 +902,17 @@ void intel_dsb_wait_for_delayed_vblank(struct intel_atomic_state *state, + * the hardware itself guarantees that we're SCL lines + * away from the delayed vblank, and we won't be inside + * the vmin safe window so this extra wait does nothing. ++ * ++ * Experimentally, DSB may observe a slightly stale ++ * PIPEDSL value. When the actual scanline has just reached ++ * safe_window_start, WAIT_DSL_OUT may complete immediately ++ * due to the stale value. ++ * ++ * Shift the start back by one scanline to ensure the wait ++ * window is entered reliably. + */ + intel_dsb_wait_scanline_out(state, dsb, +- intel_vrr_safe_window_start(crtc_state), ++ intel_vrr_safe_window_start(crtc_state) - 1, + intel_vrr_vmin_safe_window_end(crtc_state)); + /* + * When the push is sent during vblank it will trigger diff --git a/pkgbuilds/linux-omarchy-bore/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig b/pkgbuilds/linux-omarchy-bore/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig new file mode 100644 index 0000000..f11347d Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0444-drm-i915-vrr-dc-balance-off-by-default.patch b/pkgbuilds/linux-omarchy-bore/0444-drm-i915-vrr-dc-balance-off-by-default.patch new file mode 100644 index 0000000..9efda11 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0444-drm-i915-vrr-dc-balance-off-by-default.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_params.c b/drivers/gpu/drm/i915/display/intel_display_params.c +--- a/drivers/gpu/drm/i915/display/intel_display_params.c ++++ b/drivers/gpu/drm/i915/display/intel_display_params.c +@@ -120,6 +120,10 @@ intel_display_param_named_unsafe(enable_psr, int, 0400, + "(0=disabled, 1=enable up to PSR1, 2=enable up to PSR2) " + "Default: -1 (use per-chip default)"); + ++intel_display_param_named_unsafe(enable_dc_balance, bool, 0400, ++ "Enable VRR DC balance (0=disabled, 1=enabled). " ++ "Default: 0 (disabled)"); ++ + intel_display_param_named_unsafe(enable_panel_replay, int, 0400, + "Enable Panel Replay (0=disabled, 1=enabled). Default: -1 (use per-chip default)"); + +diff --git a/drivers/gpu/drm/i915/display/intel_display_params.h b/drivers/gpu/drm/i915/display/intel_display_params.h +--- a/drivers/gpu/drm/i915/display/intel_display_params.h ++++ b/drivers/gpu/drm/i915/display/intel_display_params.h +@@ -46,6 +46,7 @@ struct drm_printer; + param(bool, enable_dp_mst, true, 0600) \ + param(int, enable_fbc, -1, 0600) \ + param(int, enable_psr, -1, 0600) \ ++ param(bool, enable_dc_balance, false, 0600) \ + param(int, enable_panel_replay, -1, 0600) \ + param(bool, psr_safest_params, false, 0400) \ + param(bool, enable_psr2_sel_fetch, true, 0400) \ +diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c +--- a/drivers/gpu/drm/i915/display/intel_vrr.c ++++ b/drivers/gpu/drm/i915/display/intel_vrr.c +@@ -383,10 +383,12 @@ static bool intel_vrr_dc_balance_possible(const struct intel_crtc_state *crtc_st + static void + intel_vrr_dc_balance_compute_config(struct intel_crtc_state *crtc_state) + { ++ struct intel_display *display = to_intel_display(crtc_state); + int guardband_usec, adjustment_usec; + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + +- if (!intel_vrr_dc_balance_possible(crtc_state) || !crtc_state->vrr.enable) ++ if (!intel_vrr_dc_balance_possible(crtc_state) || ++ !crtc_state->vrr.enable || !display->params.enable_dc_balance) + return; + + crtc_state->vrr.dc_balance.vmax = crtc_state->vrr.vmax; diff --git a/pkgbuilds/linux-omarchy-bore/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig b/pkgbuilds/linux-omarchy-bore/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig new file mode 100644 index 0000000..497cbb1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0444-drm-i915-vrr-dc-balance-off-by-default.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 index ebe9a36..02991bc 100644 --- a/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch +++ b/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch @@ -9,7 +9,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm #include "modules/inc/mod_power.h" #include "modules/power/power_helpers.h" -@@ -10068,6 +10069,9 @@ static void update_freesync_state_on_stream( +@@ -10088,6 +10089,9 @@ static void update_freesync_state_on_stream( &vrr_infopacket, pack_sdp_v1_3); @@ -19,7 +19,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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( +@@ -10099,6 +10103,36 @@ static void update_freesync_state_on_stream( new_stream->vrr_infopacket = vrr_infopacket; new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); @@ -56,7 +56,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -10665,9 +10699,12 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, } if (acrtc_state->stream) { @@ -70,7 +70,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } } -@@ -12049,6 +12086,14 @@ static void get_freesync_config_for_crtc( +@@ -12069,6 +12106,14 @@ static void get_freesync_config_for_crtc( } out: new_crtc_state->freesync_config = config; @@ -85,7 +85,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } static void reset_freesync_config_for_crtc( -@@ -14011,6 +14056,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, +@@ -14031,6 +14076,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) goto update; @@ -101,7 +101,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -14056,7 +14110,9 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; } @@ -112,7 +112,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -14072,6 +14128,59 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; } } @@ -172,7 +172,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } if (amdgpu_dm_connector->dc_link) -@@ -14093,6 +14202,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, +@@ -14113,6 +14222,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (dm_con_state) dm_con_state->freesync_capable = freesync_capable; 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 index ee35537..186828c 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0450-amd-hdmi-vrr-allm.patch.sig 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 index f61c400..5bc46ad 100644 --- a/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch +++ b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch @@ -1,7 +1,7 @@ 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( +@@ -10089,7 +10089,10 @@ static void update_freesync_state_on_stream( &vrr_infopacket, pack_sdp_v1_3); 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 index 7077a0e..ce4d4da 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch.sig and b/pkgbuilds/linux-omarchy-bore/0451-amd-vtem-tmds-links.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0453-amd-vtem-null-sink.patch b/pkgbuilds/linux-omarchy-bore/0453-amd-vtem-null-sink.patch new file mode 100644 index 0000000..0a3c3d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0453-amd-vtem-null-sink.patch @@ -0,0 +1,14 @@ +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 +@@ -10090,8 +10090,8 @@ static void update_freesync_state_on_stream( + pack_sdp_v1_3); + + /* 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 && ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_FRL || ++ (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A && + aconn && aconn->base.display_info.hdmi.vrr_cap.supported)) + mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); + diff --git a/pkgbuilds/linux-omarchy-bore/0453-amd-vtem-null-sink.patch.sig b/pkgbuilds/linux-omarchy-bore/0453-amd-vtem-null-sink.patch.sig new file mode 100644 index 0000000..e8b34ac Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0453-amd-vtem-null-sink.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0454-amd-hdmi-frl-status-polling.patch b/pkgbuilds/linux-omarchy-bore/0454-amd-hdmi-frl-status-polling.patch new file mode 100644 index 0000000..62ada93 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0454-amd-hdmi-frl-status-polling.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -2025,7 +2025,7 @@ static void hdmi_frl_status_polling_work(struct work_struct *work) + if (!dc_is_hdmi_signal(dc_link->connector_signal)) + continue; + +- if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) ++ if (dc_link->frl_link_settings.frl_link_rate == 0) + continue; + + link_update = dc_link_frl_poll_status_flag(dc_link); diff --git a/pkgbuilds/linux-omarchy-bore/0454-amd-hdmi-frl-status-polling.patch.sig b/pkgbuilds/linux-omarchy-bore/0454-amd-hdmi-frl-status-polling.patch.sig new file mode 100644 index 0000000..0f7a5ed Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0454-amd-hdmi-frl-status-polling.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch b/pkgbuilds/linux-omarchy-bore/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch new file mode 100644 index 0000000..26db142 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +--- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c ++++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +@@ -882,7 +882,7 @@ static void verify_link_capability_non_destructive(struct dc_link *link) + else + link->verified_link_cap = dp_get_max_link_cap(link); + } else if (dc_is_hdmi_signal(link->local_sink->sink_signal)) { +- link->verified_link_cap = link->reported_link_cap; ++ link->frl_verified_link_cap = link->frl_reported_link_cap; + + if (is_hdmi_frl_in_use(link)) { + link->local_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; diff --git a/pkgbuilds/linux-omarchy-bore/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig b/pkgbuilds/linux-omarchy-bore/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig new file mode 100644 index 0000000..757e147 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0456-amd-dml-frl-compressed-cap-check.patch b/pkgbuilds/linux-omarchy-bore/0456-amd-dml-frl-compressed-cap-check.patch new file mode 100644 index 0000000..4531604 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0456-amd-dml-frl-compressed-cap-check.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +--- a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c ++++ b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +@@ -630,17 +630,17 @@ enum frl_cap_chk_result dml1_frl_cap_chk(struct frl_cap_chk_params *params) + { + struct frl_cap_chk_intermediates inter; + +-#if defined (CONFIG_DRM_AMD_DC_FP) +- if (params->compressed) +- return dml1_frl_cap_chk_compressed(params, &inter); +-#endif +- + return dml1_frl_cap_chk_inter(params, &inter); + } + + enum frl_cap_chk_result dml1_frl_cap_chk_inter(struct frl_cap_chk_params *params, + struct frl_cap_chk_intermediates *inter) + { ++#if defined (CONFIG_DRM_AMD_DC_FP) ++ if (params->compressed) ++ return dml1_frl_cap_chk_compressed(params, inter); ++#endif ++ + return dml1_frl_cap_chk_uncompressed(params, inter); + } + diff --git a/pkgbuilds/linux-omarchy-bore/0456-amd-dml-frl-compressed-cap-check.patch.sig b/pkgbuilds/linux-omarchy-bore/0456-amd-dml-frl-compressed-cap-check.patch.sig new file mode 100644 index 0000000..3ebb7c2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0456-amd-dml-frl-compressed-cap-check.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 index 1ba6ec7..7597723 100644 --- a/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch +++ b/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch @@ -1,7 +1,7 @@ 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( +@@ -7116,6 +7116,11 @@ static void fill_stream_properties_from_drm_display_mode( stream->output_color_space = get_output_color_space(timing_out, connector_state); stream->content_type = get_output_content_type(connector_state); @@ -13,7 +13,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } static void fill_audio_info(struct audio_info *audio_info, -@@ -7616,6 +7621,7 @@ create_stream_for_sink(struct drm_connector *connector, +@@ -7636,6 +7641,7 @@ create_stream_for_sink(struct drm_connector *connector, struct drm_display_mode mode; struct drm_display_mode saved_mode; struct drm_display_mode *freesync_mode = NULL; @@ -21,7 +21,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -7727,6 +7733,16 @@ create_stream_for_sink(struct drm_connector *connector, } } 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 index f76e652..51e6458 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0460-vesa-displayid-dsc-bpp.patch.sig 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 index e27b493..b826d02 100644 --- 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 @@ -1,7 +1,7 @@ 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, +@@ -7642,6 +7642,7 @@ create_stream_for_sink(struct drm_connector *connector, struct drm_display_mode saved_mode; struct drm_display_mode *freesync_mode = NULL; struct drm_display_mode *dsc_passthru_mode = NULL; @@ -9,7 +9,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -7708,6 +7709,22 @@ create_stream_for_sink(struct drm_connector *connector, mode_refresh = drm_mode_vrefresh(&mode); @@ -32,7 +32,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -7733,16 +7750,6 @@ create_stream_for_sink(struct drm_connector *connector, } } 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 index d303e52..09c45c3 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.patch.sig and b/pkgbuilds/linux-omarchy-bore/0461-vesa-dsc-passthru-mode-match-fix.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 deleted file mode 100644 index 08d39ec..0000000 --- a/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 1048bf7..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0473-i915-ptl-cdclk-sanitize.patch.sig and /dev/null 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 index 590d8a0..035758b 100644 --- a/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch +++ b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch @@ -1,7 +1,7 @@ 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) +@@ -4176,8 +4176,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; 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 index 14aa526..3f0768a 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch.sig and b/pkgbuilds/linux-omarchy-bore/0474-amd-display-oled-vesa-backlight.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0476-amd-display-ycbcr420-only-modes.patch b/pkgbuilds/linux-omarchy-bore/0476-amd-display-ycbcr420-only-modes.patch new file mode 100644 index 0000000..8d6c8a4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0476-amd-display-ycbcr420-only-modes.patch @@ -0,0 +1,25 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7020,16 +7020,11 @@ static void fill_stream_properties_from_drm_display_mode( + timing_out->v_border_top = 0; + timing_out->v_border_bottom = 0; + /* TODO: un-hardcode */ +- if (drm_mode_is_420_only(info, mode_in) +- && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || +- stream->signal == SIGNAL_TYPE_HDMI_FRL) +- && aconnector +- && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) +- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; +- else if (drm_mode_is_420_also(info, mode_in) +- && aconnector +- && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 +- || aconnector->force_yuv420_output)) ++ if (drm_mode_is_420_only(info, mode_in) || ++ (aconnector && ++ (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 || ++ aconnector->force_yuv420_output) && ++ drm_mode_is_420_also(info, mode_in))) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + && aconnector diff --git a/pkgbuilds/linux-omarchy-bore/0476-amd-display-ycbcr420-only-modes.patch.sig b/pkgbuilds/linux-omarchy-bore/0476-amd-display-ycbcr420-only-modes.patch.sig new file mode 100644 index 0000000..bf85c11 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0476-amd-display-ycbcr420-only-modes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0477-amd-display-irq-atomic-rmw.patch b/pkgbuilds/linux-omarchy-bore/0477-amd-display-irq-atomic-rmw.patch new file mode 100644 index 0000000..1b0eb75 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0477-amd-display-irq-atomic-rmw.patch @@ -0,0 +1,303 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -3345,7 +3345,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + if (acrtc && state->stream_status[i].plane_count != 0 && + amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + if (rc) + drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", + enable ? "enable" : "disable"); +@@ -3369,7 +3369,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + /* During gpu-reset we disable and then enable vblank irq, so + * don't use amdgpu_irq_get/put() to avoid refcount change. + */ +- if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) ++ if (!amdgpu_dm_irq_set(adev, irq_source, enable)) + drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); + + } else if (acrtc && state->stream_status[i].plane_count != 0) { +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +@@ -550,6 +550,18 @@ struct amdgpu_display_manager { + struct common_irq_params + vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + ++ /** ++ * @irq_reg_lock: ++ * ++ * Serializes the read-modify-writes of the HW interrupt control ++ * registers. Several interrupt sources share one register - e.g. the ++ * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK ++ * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must ++ * not race with acking another. Held only across amdgpu_dm_irq_set() ++ * and amdgpu_dm_irq_ack(). ++ */ ++ spinlock_t irq_reg_lock; ++ + /** + * @dmub_trace_params: + * +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +@@ -31,6 +31,7 @@ + #include "amdgpu_dm_psr.h" + #include "amdgpu_dm_replay.h" + #include "amdgpu_dm_crtc.h" ++#include "amdgpu_dm_irq.h" + #include "amdgpu_dm_plane.h" + #include "amdgpu_dm_trace.h" + #include "amdgpu_dm_debugfs.h" +@@ -87,7 +88,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) + + irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; + +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + + DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", + acrtc->crtc_id, enable ? "en" : "dis", rc); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +@@ -1378,12 +1378,13 @@ void dm_helpers_free_gpu_mem( + + bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) + { ++ struct amdgpu_device *adev = ctx->driver_context; + enum dc_irq_source irq_source; + bool ret; + + irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + +- ret = dc_interrupt_set(ctx->dc, irq_source, enable); ++ ret = amdgpu_dm_irq_set(adev, irq_source, enable); + + DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", + enable ? "en" : "dis", ret); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +@@ -424,6 +424,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) + DRM_DEBUG_KMS("DM_IRQ\n"); + + spin_lock_init(&adev->dm.irq_handler_list_table_lock); ++ spin_lock_init(&adev->dm.irq_reg_lock); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + /* low context handler list init */ +@@ -496,7 +497,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, false); ++ amdgpu_dm_irq_set(adev, src, false); + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + +@@ -533,7 +534,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -558,7 +559,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -645,6 +646,21 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + } + ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ return dc_interrupt_set(adev->dm.dc, src, enable); ++} ++ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ dc_interrupt_ack(adev->dm.dc, src); ++} ++ + /** + * amdgpu_dm_irq_handler - Generic DM IRQ handler + * @adev: amdgpu base driver device containing the DM device +@@ -665,7 +681,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + entry->src_id, + entry->src_data[0]); + +- dc_interrupt_ack(adev->dm.dc, src); ++ amdgpu_dm_irq_ack(adev, src); + + /* Call high irq work immediately */ + amdgpu_dm_irq_immediate_work(adev, src); +@@ -703,7 +719,7 @@ static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, src, st); ++ amdgpu_dm_irq_set(adev, src, st); + return 0; + } + +@@ -737,7 +753,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, + if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) + dc_allow_idle_optimizations(dc, false); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -791,7 +807,7 @@ static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -817,7 +833,7 @@ static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -881,9 +897,7 @@ void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) + } + void amdgpu_dm_outbox_init(struct amdgpu_device *adev) + { +- dc_interrupt_set(adev->dm.dc, +- DC_IRQ_SOURCE_DMCUB_OUTBOX, +- true); ++ amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); + } + + /** +@@ -905,7 +919,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + + /* First, clear all hpd and hpdrx interrupts */ + for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { +- if (!dc_interrupt_set(adev->dm.dc, i, false)) ++ if (!amdgpu_dm_irq_set(adev, i, false)) + drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", + i); + } +@@ -934,7 +948,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * of dm. Note that only hpd interrupt types are registered with + * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on + * hpd_rx isn't available. DM currently controls hpd_rx +- * explicitly with dc_interrupt_set() ++ * explicitly with amdgpu_dm_irq_set() + */ + if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { + irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; +@@ -943,23 +957,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * and what bios reports as the # of connectors with hpd + * sources. Since the # of hpd source types registered + * with base driver == mode_info.num_hpd, we have to +- * fallback to dc_interrupt_set for the remaining types. ++ * fallback to amdgpu_dm_irq_set for the remaining types. + */ + if (irq_type < adev->mode_info.num_hpd) { + if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) + drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ true); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ true); + } + } + drm_connector_list_iter_end(&iter); +@@ -1003,16 +1015,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) + drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ false); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ false); + } + } + drm_connector_list_iter_end(&iter); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +@@ -81,6 +81,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, + enum dc_irq_source irq_source, + void *ih_index); + ++/** ++ * amdgpu_dm_irq_set - enable or disable a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to toggle ++ * @enable: true to enable the source, false to disable it ++ * ++ * DM-wide replacement for dc_interrupt_set(). As locking is DM's ++ * responsibility, this is a thin wrapper serializes the underlying ++ * read-modify-write against the other interrupt sources sharing HW control ++ * registers with @src, so DM must never call dc_interrupt_set() directly. ++ * ++ * Returns: true if the source was toggled. ++ */ ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable); ++ ++/** ++ * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to acknowledge ++ * ++ * DM-wide replacement for dc_interrupt_ack(), serialized the same way as ++ * amdgpu_dm_irq_set(). ++ */ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); ++ + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); + + void amdgpu_dm_outbox_init(struct amdgpu_device *adev); diff --git a/pkgbuilds/linux-omarchy-bore/0477-amd-display-irq-atomic-rmw.patch.sig b/pkgbuilds/linux-omarchy-bore/0477-amd-display-irq-atomic-rmw.patch.sig new file mode 100644 index 0000000..ffb08a4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0477-amd-display-irq-atomic-rmw.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch b/pkgbuilds/linux-omarchy-bore/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch new file mode 100644 index 0000000..dcdb282 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -14232,6 +14232,12 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + } + } + ++ /* HDMI/PCON without an EDID FreeSync VCP code is not FreeSync-capable. */ ++ if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && ++ !sink->edid_caps.freesync_vcp_code) ++ freesync_capable = false; ++ + /* Handle MCCS */ + if (do_mccs) { + dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); diff --git a/pkgbuilds/linux-omarchy-bore/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig b/pkgbuilds/linux-omarchy-bore/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig new file mode 100644 index 0000000..55dd043 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch b/pkgbuilds/linux-omarchy-bore/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch new file mode 100644 index 0000000..13b9043 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch @@ -0,0 +1,40 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -10135,20 +10135,19 @@ static void update_freesync_state_on_stream( + */ + if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { +- struct dc_info_packet hf_vsif = {0}; ++ struct dc_info_packet vsp_infopacket = {0}; + bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; + bool allm = sink_allm && + (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || + vrr_params.state == VRR_STATE_ACTIVE_FIXED); + bool allm_changed; + +- if (allm) +- mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ mod_build_hf_vsif_infopacket(new_stream, &vsp_infopacket, allm, allm); + +- allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, +- sizeof(hf_vsif)) != 0; ++ allm_changed = memcmp(&new_stream->vsp_infopacket, &vsp_infopacket, ++ sizeof(vsp_infopacket)) != 0; + new_crtc_state->freesync_vrr_info_changed |= allm_changed; +- new_stream->hfvsif_infopacket = hf_vsif; ++ new_stream->vsp_infopacket = vsp_infopacket; + + if (allm_changed) + drm_dbg_driver(adev_to_drm(adev), +@@ -10727,8 +10726,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; +- bundle->stream_update.hfvsif_infopacket = +- &acrtc_state->stream->hfvsif_infopacket; ++ bundle->stream_update.vsp_infopacket = ++ &acrtc_state->stream->vsp_infopacket; + } + } + } diff --git a/pkgbuilds/linux-omarchy-bore/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig b/pkgbuilds/linux-omarchy-bore/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig new file mode 100644 index 0000000..8cd5401 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch index 29d72a3..1368f53 100644 --- a/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch +++ b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch @@ -1,7 +1,7 @@ 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) +@@ -1757,6 +1757,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) * by the drivers/platform/x86/serial-multi-instantiate.c driver, which * knows which client device id to use for each resource. */ @@ -9,18 +9,6 @@ diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c {"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 @@ -48,132 +36,6 @@ diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/ { "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 @@ -868,88 +730,6 @@ diff --git a/include/sound/core.h b/include/sound/core.h 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 @@ -1319,196 +1099,6 @@ diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h /* 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 @@ -1520,571 +1110,6 @@ diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h 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 @@ -2445,649 +1470,6 @@ diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h * 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 @@ -3567,15 +1949,7 @@ diff --git a/sound/core/init.c b/sound/core/init.c }; /** -@@ -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) +@@ -592,6 +591,9 @@ static int snd_card_do_free(struct snd_card *card) snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); #endif snd_device_free_all(card); @@ -3585,16 +1959,7 @@ diff --git a/sound/core/init.c b/sound/core/init.c 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, +@@ -725,7 +727,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, int len, loops; bool is_default = false; char *id; @@ -3603,7 +1968,7 @@ diff --git a/sound/core/init.c b/sound/core/init.c copy_valid_id_string(card, src, nid); id = card->id; -@@ -1031,21 +1035,44 @@ int __init snd_card_info_init(void) +@@ -1034,21 +1036,44 @@ int __init snd_card_info_init(void) * * Return: Zero otherwise a negative error code. */ @@ -4227,7 +2592,7 @@ diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c 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, +@@ -4256,29 +4260,29 @@ static unsigned long snd_pcm_get_unmapped_area(struct file *file, const struct file_operations snd_pcm_f_ops[2] = { { @@ -4512,41 +2877,7 @@ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c * 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, +@@ -724,10 +736,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, /* lock list */ if (atomic) @@ -4560,7 +2891,7 @@ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c /* 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, +@@ -748,7 +761,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, memcpy(event, &event_saved, saved_size); } if (atomic) @@ -4569,7 +2900,7 @@ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c 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) +@@ -1945,7 +1958,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) { struct snd_seq_query_subs *subs = arg; struct snd_seq_port_subs_info *group; @@ -4578,7 +2909,7 @@ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c 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) +@@ -1972,15 +1985,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) /* search for the subscriber */ subs->num_subs = group->count; i = 0; @@ -4597,7 +2928,7 @@ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c 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, +@@ -2536,19 +2549,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, struct snd_seq_port_subs_info *group, int is_src, char *msg) { @@ -4622,7 +2953,7 @@ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c 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, +@@ -2670,16 +2683,15 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, * REGISTRATION PART */ @@ -4659,159 +2990,6 @@ diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h 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 @@ -7149,24 +5327,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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 { +@@ -3939,6 +3994,7 @@ enum { ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, ALC269_FIXUP_ACER_AC700, ALC269_FIXUP_LIMIT_INT_MIC_BOOST, @@ -7174,7 +5335,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC269VB_FIXUP_ASUS_ZENBOOK, ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, -@@ -3997,6 +4063,7 @@ enum { +@@ -4007,6 +4063,7 @@ enum { ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, ALC295_FIXUP_DISABLE_DAC3, ALC285_FIXUP_SPEAKER2_TO_DAC1, @@ -7182,7 +5343,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1, ALC285_FIXUP_ASUS_HEADSET_MIC, ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS, -@@ -4066,7 +4133,6 @@ enum { +@@ -4076,7 +4133,6 @@ enum { ALC294_FIXUP_ASUS_ALLY, ALC294_FIXUP_ASUS_ALLY_PINS, ALC294_FIXUP_ASUS_ALLY_VERBS, @@ -7190,7 +5351,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC294_FIXUP_ASUS_HPE, ALC294_FIXUP_ASUS_COEF_1B, ALC294_FIXUP_ASUS_GX502_HP, -@@ -4091,6 +4157,8 @@ enum { +@@ -4101,6 +4157,8 @@ enum { ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, ALC298_FIXUP_LG_GRAM_STYLE_14, @@ -7199,7 +5360,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, -@@ -4108,7 +4176,6 @@ enum { +@@ -4118,7 +4176,6 @@ enum { ALC269_FIXUP_LEMOTE_A190X, ALC256_FIXUP_INTEL_NUC8_RUGGED, ALC233_FIXUP_INTEL_NUC8_DMIC, @@ -7207,7 +5368,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC256_FIXUP_INTEL_NUC10, ALC255_FIXUP_XIAOMI_HEADSET_MIC, ALC274_FIXUP_HP_MIC, -@@ -4139,6 +4206,7 @@ enum { +@@ -4149,6 +4206,7 @@ enum { ALC298_FIXUP_LENOVO_C940_DUET7, ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, ALC287_FIXUP_LENOVO_YOGA_PRO7, @@ -7215,7 +5376,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC287_FIXUP_13S_GEN2_SPEAKERS, ALC256_FIXUP_SET_COEF_DEFAULTS, ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, -@@ -4183,7 +4251,6 @@ enum { +@@ -4193,7 +4251,6 @@ enum { ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, ALC2XX_FIXUP_HEADSET_MIC, ALC289_FIXUP_DELL_CS35L41_SPI_2, @@ -7223,7 +5384,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, ALC256_FIXUP_HEADPHONE_AMP_VOL, ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, -@@ -4197,6 +4264,7 @@ enum { +@@ -4207,6 +4264,7 @@ enum { ALC287_FIXUP_LENOVO_THKPAD_WH_ALC1318, ALC256_FIXUP_CHROME_BOOK, ALC245_FIXUP_CLEVO_NOISY_MIC, @@ -7231,7 +5392,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE, ALC233_FIXUP_MEDION_MTL_SPK, ALC233_FIXUP_STARLABS_STARFIGHTER, -@@ -4206,7 +4274,6 @@ enum { +@@ -4216,7 +4274,6 @@ enum { ALC274_FIXUP_HP_AIO_BIND_DACS, ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2, ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC, @@ -7239,7 +5400,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC, ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK, ALC256_FIXUP_VAIO_RPL_MIC_NO_PRESENCE, -@@ -4226,6 +4293,10 @@ enum { +@@ -4236,6 +4293,10 @@ enum { ALC236_FIXUP_DELL_HP_POP_NOISE, ALC274_FIXUP_HP_89E9_GPIO, ALC274_FIXUP_HP_VERBS, @@ -7250,7 +5411,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 }; /* 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, +@@ -4273,6 +4334,20 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, } static const struct hda_fixup alc269_fixups[] = { @@ -7271,7 +5432,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 [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[] = { +@@ -4713,6 +4788,10 @@ static const struct hda_fixup alc269_fixups[] = { .chain_id = ALC271_FIXUP_DMIC, }, [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = { @@ -7282,7 +5443,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 .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[] = { +@@ -5088,11 +5167,7 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_inv_dmic, .chained = true, @@ -7295,7 +5456,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 }, [ALC255_FIXUP_DELL_SPK_NOISE] = { .type = HDA_FIXUP_FUNC, -@@ -5143,6 +5228,10 @@ static const struct hda_fixup alc269_fixups[] = { +@@ -5153,6 +5228,10 @@ static const struct hda_fixup alc269_fixups[] = { [ALC285_FIXUP_SPEAKER2_TO_DAC1] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_speaker2_to_dac1, @@ -7306,7 +5467,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 .chained = true, .chain_id = ALC269_FIXUP_THINKPAD_ACPI }, -@@ -5662,11 +5751,7 @@ static const struct hda_fixup alc269_fixups[] = { +@@ -5672,11 +5751,7 @@ static const struct hda_fixup alc269_fixups[] = { { } }, .chained = true, @@ -7319,13 +5480,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 }, [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, +@@ -6223,6 +6298,16 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_LENOVO_XPAD_ACPI, }, @@ -7342,7 +5497,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 [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[] = { +@@ -6662,10 +6747,6 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC289_FIXUP_DUAL_SPK }, @@ -7353,7 +5508,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 [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[] = { +@@ -6738,6 +6819,10 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, }, @@ -7364,7 +5519,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 [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[] = { +@@ -6758,6 +6843,8 @@ static const struct hda_fixup alc269_fixups[] = { [ALC233_FIXUP_STARLABS_STARFIGHTER] = { .type = HDA_FIXUP_FUNC, .v.func = alc233_fixup_starlabs_starfighter, @@ -7373,7 +5528,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 }, [ALC294_FIXUP_BASS_SPEAKER_15] = { .type = HDA_FIXUP_FUNC, -@@ -6776,11 +6872,7 @@ static const struct hda_fixup alc269_fixups[] = { +@@ -6785,11 +6872,7 @@ static const struct hda_fixup alc269_fixups[] = { { } }, .chained = true, @@ -7386,7 +5541,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 }, [ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC] = { .type = HDA_FIXUP_FUNC, -@@ -6898,6 +6990,26 @@ static const struct hda_fixup alc269_fixups[] = { +@@ -6907,6 +6990,26 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC274_FIXUP_HP_89E9_GPIO, }, @@ -7413,7 +5568,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 }; static const struct hda_quirk alc269_fixup_tbl[] = { -@@ -6951,8 +7063,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -6960,8 +7063,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), @@ -7424,7 +5579,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -6970,6 +7075,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), @@ -7432,7 +5587,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7154,10 +7260,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), @@ -7445,7 +5600,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7268,6 +7376,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), @@ -7453,7 +5608,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7359,10 +7468,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), @@ -7466,7 +5621,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7511,6 +7621,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), @@ -7474,7 +5629,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7518,6 +7629,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1034, "ASUS GU605C", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), @@ -7482,7 +5637,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7525,16 +7637,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), @@ -7502,7 +5657,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7583,7 +7694,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1863, "ASUS UX6404VI/VV", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), @@ -7511,7 +5666,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7594,7 +7705,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x19f4, "ASUS UM3405GA", ALC294_FIXUP_ASUS_I2C_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), SND_PCI_QUIRK(0x1043, 0x1a63, "ASUS UX3405MA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), @@ -7520,7 +5675,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7623,6 +7734,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1264, "ASUS UM5606KA", ALC294_FIXUP_BASS_SPEAKER_15), SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x1e10, "ASUS VivoBook X507UAR", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), @@ -7528,7 +5683,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7634,8 +7746,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE), SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X), SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), @@ -7538,7 +5693,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7846,7 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK), SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK), SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK), @@ -7547,7 +5702,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7862,7 +7973,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), @@ -7557,7 +5712,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7937,7 +8049,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), @@ -7566,7 +5721,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7972,6 +8084,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C), @@ -7579,7 +5734,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -7989,7 +8107,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), @@ -7587,7 +5742,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 /* 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[] = { +@@ -7999,7 +8116,9 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38fc, "Lenovo Yoga Pro 7 15ASH11", ALC287_FIXUP_LENOVO_YOGA_PRO7), SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), @@ -7598,7 +5753,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -8007,14 +8126,19 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), @@ -7620,7 +5775,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -8027,7 +8151,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), @@ -7629,7 +5784,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -8041,9 +8165,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1854, 0x0489, "LG gram 16 (16Z90R-A)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x1854, 0x048a, "LG gram 17 (17ZD90R)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), @@ -7644,7 +5799,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -8078,13 +8207,17 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x1e39, 0xca14, "MEDION NM14LNL", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), @@ -7662,7 +5817,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -8095,6 +8228,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), @@ -7670,7 +5825,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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[] = { +@@ -8168,6 +8302,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = { SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI), SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), @@ -7678,7 +5833,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 {} }; -@@ -8298,6 +8442,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { +@@ -8307,6 +8442,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC2XX_FIXUP_HEADSET_MIC, .name = "alc2xx-fixup-headset-mic"}, {.id = ALC245_FIXUP_BASS_HP_DAC, .name = "alc245-fixup-bass-hp-dac"}, {.id = ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, .name = "alc256-honor-mrb-xxx-m1020-audio"}, @@ -7686,7 +5841,7 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 {} }; #define ALC225_STANDARD_PINS \ -@@ -8903,6 +9048,7 @@ static int alc269_probe(struct hda_codec *codec, const struct hda_device_id *id) +@@ -8912,6 +9048,7 @@ static int alc269_probe(struct hda_codec *codec, const struct hda_device_id *id) spec->init_hook = alc256_init; spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */ if (codec->core.vendor_id == 0x10ec0236 && @@ -8476,49 +6631,6 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/s 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 @@ -8556,56 +6668,6 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/s 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 @@ -8762,7 +6824,7 @@ diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c 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) +@@ -2969,8 +2975,11 @@ static void hda_codec_pm_complete(struct device *dev) dev->power.power_state = PMSG_RESUME; if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || @@ -8775,7 +6837,7 @@ diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c } static int hda_codec_pm_suspend(struct device *dev) -@@ -3370,7 +3379,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, +@@ -3372,7 +3381,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, for (; knew->name; knew++) { struct snd_kcontrol *kctl; int addr = 0, idx = 0; @@ -8827,29 +6889,6 @@ diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c 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 @@ -9012,7 +7051,7 @@ diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c 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[] = { +@@ -664,6 +664,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { { 0x1af4, "QEMU" }, { 0x1fa8, "Senarytech" }, { 0x434d, "C-Media" }, @@ -9020,7 +7059,7 @@ diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c { 0x8086, "Intel" }, { 0x8384, "SigmaTel" }, {} /* terminator */ -@@ -765,7 +766,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor +@@ -766,7 +767,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor params_set_format(¶ms, snd_hdac_format_normalize(format)); snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), @@ -9304,21 +7343,6 @@ diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c 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 @@ -9715,44 +7739,6 @@ diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c } 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 @@ -9775,1351 +7761,10 @@ diff --git a/sound/sh/aica.c b/sound/sh/aica.c 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 +@@ -726,6 +726,10 @@ config SND_SOC_AW87390 sound quality, which is a new high efficiency, low noise, constant large volume, 6th Smart K audio amplifier. @@ -11130,7 +7775,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig config SND_SOC_AW88399 tristate "Soc Audio for awinic aw88399" depends on I2C -@@ -733,6 +739,7 @@ config SND_SOC_AW88399 +@@ -733,6 +737,7 @@ config SND_SOC_AW88399 select REGMAP_I2C select GPIOLIB select SND_SOC_AW88395_LIB @@ -11138,127 +7783,6 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig 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 @@ -11270,23 +7794,7 @@ diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile 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 +@@ -496,6 +497,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o @@ -11294,1216 +7802,11 @@ diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile 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 @@ +@@ -0,0 +1,1411 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// aw88399-lib.c -- AW88399 Common functions for ASoC and HDA audio drivers @@ -13788,7 +9091,7 @@ new file mode 100644 + +int aw88399_request_firmware_file(struct aw88399 *aw88399) +{ -+ const struct firmware *cont __free(firmware) = NULL; ++ const struct firmware *cont = NULL; + int ret; + + aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; @@ -13804,11 +9107,13 @@ new file mode 100644 + + aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, + struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); -+ if (!aw88399->aw_cfg) ++ 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) { @@ -13916,11 +9221,7 @@ new file mode 100644 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 +@@ -10,1217 +10,12 @@ #include #include #include @@ -15138,29 +10439,7 @@ diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c 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, +@@ -1869,86 +664,6 @@ static int aw88399_calib_set(struct snd_kcontrol *kcontrol, return 0; } @@ -15247,24 +10526,7 @@ diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c 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 = { +@@ -2040,70 +755,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88399 = { .num_controls = ARRAY_SIZE(aw88399_controls), }; @@ -15934,11848 +11196,6 @@ diff --git a/sound/soc/codecs/aw88399.h b/sound/soc/codecs/aw88399.h - -#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 @@ -27808,6973 +11228,10 @@ diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib } 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) +@@ -1118,7 +1118,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) } ret = of_property_read_u32(asrc_np, "fsl,asrc-format", &asrc_fmt); @@ -34783,129 +11240,10 @@ diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c 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) +@@ -1358,7 +1358,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) } ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); @@ -34914,50 +11252,6 @@ diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c 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 @@ -34988,307 +11282,10 @@ diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c 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) +@@ -2227,7 +2227,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) } ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); @@ -35297,134 +11294,6 @@ diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c 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 @@ -35450,318 +11319,6 @@ diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c 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 @@ -35783,2271 +11340,6 @@ diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c 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 @@ -38060,1561 +11352,6 @@ diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c 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 @@ -39681,18 +11418,6 @@ diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt81 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 @@ -39717,52 +11442,6 @@ diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc 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 @@ -39787,16 +11466,7 @@ diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359.c b/sound/soc/mediatek/mt81 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, +@@ -180,7 +180,7 @@ static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, /* fix BE i2s format to 32bit, clean param mask first */ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), @@ -39805,1491 +11475,6 @@ diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8 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 @@ -41302,8834 +11487,6 @@ diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-gl 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 @@ -50398,7 +11755,7 @@ diff --git a/sound/usb/clock.c b/sound/usb/clock.c 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) +@@ -1237,7 +1237,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) /* try to use enough URBs to contain an entire ALSA buffer */ max_urbs = min((unsigned) MAX_URBS, MAX_QUEUE * packs_per_ms / urb_packs); @@ -50409,8 +11766,8 @@ diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c + 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) + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) +@@ -1262,6 +1265,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) goto out_of_memory; u->urb->pipe = ep->pipe; u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; diff --git a/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch.sig index cfb31fc..95975e7 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch.sig and b/pkgbuilds/linux-omarchy-bore/0510-sound-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0510-sound-updates.patch b/pkgbuilds/linux-omarchy-bore/0511-asoc-updates.patch similarity index 72% rename from pkgbuilds/linux-omarchy/0510-sound-updates.patch rename to pkgbuilds/linux-omarchy-bore/0511-asoc-updates.patch index 29d72a3..75c49b4 100644 --- a/pkgbuilds/linux-omarchy/0510-sound-updates.patch +++ b/pkgbuilds/linux-omarchy-bore/0511-asoc-updates.patch @@ -1,14 +1,3 @@ -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 @@ -21,33 +10,6 @@ diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c b/drivers/firmwar { 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 @@ -174,700 +136,6 @@ deleted file mode 100644 -}; - -#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 @@ -950,375 +218,6 @@ diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h 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 @@ -1357,20 +256,6 @@ diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h + 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 @@ -1509,17 +394,6 @@ diff --git a/include/sound/sdca_hid.h b/include/sound/sdca_hid.h 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 @@ -1708,74 +582,6 @@ diff --git a/include/sound/soc.h b/include/sound/soc.h 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 @@ -2085,366 +891,6 @@ diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h * 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 @@ -3088,5395 +1534,10 @@ diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h /* 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) +@@ -171,13 +171,13 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) case HDA_GEN_PCM_ACT_OPEN: pm_runtime_get_sync(dev); scoped_guard(mutex, &tas_hda->priv->codec_lock) { @@ -8492,7 +1553,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/s tas_hda->priv->playback_started = false; } -@@ -722,7 +720,7 @@ static int tas2781_runtime_suspend(struct device *dev) +@@ -720,7 +720,7 @@ static int tas2781_runtime_suspend(struct device *dev) * Stop the playback if it's unused. */ if (tas_hda->priv->playback_started) { @@ -8501,7 +1562,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/s tas_hda->priv->playback_started = false; } -@@ -752,7 +750,7 @@ static int tas2781_system_suspend(struct device *dev) +@@ -750,7 +750,7 @@ static int tas2781_system_suspend(struct device *dev) /* Shutdown chip before system suspend */ if (tas_hda->priv->playback_started) @@ -8510,7 +1571,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/s /* * Reset GPIO may be shared, so cannot reset here. -@@ -785,7 +783,7 @@ static int tas2781_system_resume(struct device *dev) +@@ -783,7 +783,7 @@ static int tas2781_system_resume(struct device *dev) TASDEVICE_BIN_BLK_PRE_POWER_UP); if (tas_hda->priv->playback_started) @@ -8522,41 +1583,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/s 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) +@@ -399,11 +399,11 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) pm_runtime_get_sync(dev); guard(mutex)(&tas_priv->codec_lock); if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) @@ -8570,7 +1597,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/s pm_runtime_put_autosuspend(dev); } } -@@ -848,7 +847,7 @@ static int tas2781_runtime_suspend(struct device *dev) +@@ -847,7 +847,7 @@ static int tas2781_runtime_suspend(struct device *dev) if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK && tas_priv->playback_started) @@ -8579,7 +1606,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/s 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) +@@ -864,7 +864,7 @@ static int tas2781_runtime_resume(struct device *dev) if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK && tas_priv->playback_started) @@ -8588,7 +1615,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/s return 0; } -@@ -883,7 +882,7 @@ static int tas2781_system_suspend(struct device *dev) +@@ -882,7 +882,7 @@ static int tas2781_system_suspend(struct device *dev) /* Shutdown chip before system suspend */ if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK && tas_priv->playback_started) @@ -8597,7 +1624,7 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/s return 0; } -@@ -918,7 +917,7 @@ static int tas2781_system_resume(struct device *dev) +@@ -917,7 +917,7 @@ static int tas2781_system_resume(struct device *dev) tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; if (tas_priv->playback_started) @@ -8606,1175 +1633,6 @@ diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/s } 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 @@ -9951,16 +1809,7 @@ diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c 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) +@@ -531,15 +531,15 @@ static int mc_probe(struct platform_device *pdev) dmi_check_system(soc_sdw_quirk_table); if (quirk_override != -1) { @@ -9979,7 +1828,7 @@ diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw /* 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) +@@ -556,12 +556,12 @@ static int mc_probe(struct platform_device *pdev) for (i = 0; i < ctx->codec_info_list_count; i++) amp_num += codec_info_list[i].amp_num; @@ -9994,7 +1843,7 @@ diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw "%s mic:acp-dmic cfg-mics:%d", card->components, 1); -@@ -560,9 +560,9 @@ static int mc_probe(struct platform_device *pdev) +@@ -570,9 +570,9 @@ static int mc_probe(struct platform_device *pdev) } /* Register the card */ @@ -10006,7 +1855,7 @@ diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw asoc_sdw_mc_dailink_exit_loop(card); return ret; } -@@ -580,8 +580,8 @@ static void mc_remove(struct platform_device *pdev) +@@ -590,8 +590,8 @@ static void mc_remove(struct platform_device *pdev) } static const struct platform_device_id mc_id_table[] = { @@ -10020,16 +1869,16 @@ diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw 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) +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) if (!sof_aux) return -ENOMEM; -- ret = asoc_sdw_parse_sdw_endpoints(card, sof_aux, sof_dais, sof_ends, &num_devs); +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); if (ret < 0) return ret; -@@ -388,12 +388,12 @@ static int mc_probe(struct platform_device *pdev) +@@ -399,12 +399,12 @@ static int mc_probe(struct platform_device *pdev) dmi_check_system(sof_sdw_quirk_table); if (quirk_override != -1) { @@ -10044,7 +1893,7 @@ diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-so 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) +@@ -423,15 +423,15 @@ static int mc_probe(struct platform_device *pdev) for (i = 0; i < ctx->codec_info_list_count; i++) amp_num += codec_info_list[i].amp_num; @@ -10063,7 +1912,7 @@ diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-so asoc_sdw_mc_dailink_exit_loop(card); return ret; } -@@ -438,8 +438,8 @@ static void mc_remove(struct platform_device *pdev) +@@ -449,8 +449,8 @@ static void mc_remove(struct platform_device *pdev) } static const struct platform_device_id mc_id_table[] = { @@ -10296,20 +2145,7 @@ diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c 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, +@@ -220,6 +225,12 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, FIELD_PREP(SERDES_CONF_SYNC_SEL, 0)); mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1)); @@ -11119,26 +2955,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig 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 +@@ -894,6 +896,7 @@ config SND_SOC_CS35L56_SDW tristate "Cirrus Logic CS35L56 CODEC (SDW)" depends on SOUNDWIRE select REGMAP_SOUNDWIRE @@ -11146,7 +2963,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig select SND_SOC_CS35L56 select SND_SOC_CS35L56_SHARED help -@@ -1201,16 +1209,16 @@ config SND_SOC_JZ4725B_CODEC +@@ -1206,16 +1209,16 @@ config SND_SOC_JZ4725B_CODEC will be called snd-soc-jz4725b-codec. config SND_SOC_JZ4760_CODEC @@ -11172,7 +2989,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig config SND_SOC_JZ4770_CODEC depends on MACH_INGENIC || COMPILE_TEST -@@ -1888,7 +1896,7 @@ config SND_SOC_RT5670 +@@ -1893,7 +1896,7 @@ config SND_SOC_RT5670 depends on I2C config SND_SOC_RT5677 @@ -11181,7 +2998,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig depends on I2C select REGMAP_I2C select REGMAP_IRQ -@@ -1978,6 +1986,15 @@ config SND_SOC_RT715_SDCA_SDW +@@ -1983,6 +1986,15 @@ config SND_SOC_RT715_SDCA_SDW select REGMAP_SOUNDWIRE select REGMAP_SOUNDWIRE_MBQ @@ -11197,7 +3014,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig config SND_SOC_RT9120 tristate "Richtek RT9120 Stereo Class-D Amplifier" depends on I2C -@@ -2297,8 +2314,8 @@ config SND_SOC_TLV320ADC3XXX +@@ -2302,8 +2314,8 @@ config SND_SOC_TLV320ADC3XXX depends on I2C depends on GPIOLIB help @@ -11208,7 +3025,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig config SND_SOC_TLV320AIC23 tristate -@@ -2771,6 +2788,16 @@ config SND_SOC_WSA884X +@@ -2776,6 +2788,16 @@ config SND_SOC_WSA884X This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D Smart Speaker Amplifier. @@ -11225,7 +3042,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig config SND_SOC_ZL38060 tristate "Microsemi ZL38060 Connected Home Audio Processor" depends on SPI_MASTER -@@ -2894,28 +2921,32 @@ config SND_SOC_TPA6130A2 +@@ -2899,28 +2921,32 @@ config SND_SOC_TPA6130A2 depends on I2C config SND_SOC_LPASS_MACRO_COMMON @@ -11262,15 +3079,7 @@ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig 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 +@@ -285,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o @@ -11278,7 +3087,7 @@ diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile 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 +@@ -427,6 +428,7 @@ snd-soc-wm-hubs-y := wm_hubs.o snd-soc-wsa881x-y := wsa881x.o snd-soc-wsa883x-y := wsa883x.o snd-soc-wsa884x-y := wsa884x.o @@ -11286,15 +3095,7 @@ diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile 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 +@@ -726,6 +728,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o @@ -11302,7 +3103,7 @@ diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile 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 +@@ -878,6 +881,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o @@ -11313,7 +3114,7 @@ diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile 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 @@ +@@ -14,14 +14,17 @@ * for ST-Ericsson. */ @@ -11321,46 +3122,195 @@ diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c #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); + #include + #include + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -51,9 +54,32 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ -- mutex_lock(&drvdata->ctrl_lock); ++/* Nr of FIR/IIR-coeff banks in ANC-block */ ++#define AB8500_NR_OF_ANC_COEFF_BANKS 2 ++ ++/* Minimum duration to keep ANC IIR Init bit high or ++low before proceeding with the configuration sequence */ ++#define AB8500_ANC_SM_DELAY 2000 ++ ++/* Sidetone states */ ++static const char * const enum_sid_state[] = { ++ "Unconfigured", ++ "Apply FIR", ++ "FIR is configured", ++}; ++enum sid_state { ++ SID_UNCONFIGURED = 0, ++ SID_APPLY_FIR = 1, ++ SID_FIR_CONFIGURED = 2, ++}; ++ + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; ++ struct mutex ctrl_lock; ++ ++ /* Sidetone */ ++ enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -616,6 +642,9 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + ++ SND_SOC_DAPM_INPUT("ANC Configure Input"), ++ SND_SOC_DAPM_OUTPUT("ANC Configure Output"), ++ + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -658,6 +687,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + ++ /* ANC Configure */ ++ {"ANC Configure Input", NULL, "Main Supply"}, ++ {"ANC Configure Output", NULL, "ANC Configure Input"}, ++ + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -939,6 +972,75 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + ++/* ++ * Control-events ++ */ ++ ++static int sid_status_control_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ + guard(mutex)(&drvdata->ctrl_lock); - ucontrol->value.enumerated.item[0] = drvdata->sid_status; -- 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); ++ ucontrol->value.enumerated.item[0] = drvdata->sid_status; ++ ++ return 0; ++} ++ ++/* Write sidetone FIR-coefficients configuration sequence */ ++static int sid_status_control_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ unsigned int param, sidconf; ++ int status = 1; ++ ++ dev_dbg(component->dev, "%s: Enter\n", __func__); ++ ++ if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { ++ dev_err(component->dev, ++ "%s: ERROR: This control supports '%s' only!\n", ++ __func__, enum_sid_state[SID_APPLY_FIR]); ++ return -EIO; ++ } ++ + guard(mutex)(&drvdata->ctrl_lock); - - sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); - if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { -@@ -1025,7 +1025,8 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, - } else { - status = -EBUSY; - } -- goto out; ++ ++ sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); ++ if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { ++ if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { ++ dev_err(component->dev, "%s: Sidetone busy while off!\n", ++ __func__); ++ status = -EPERM; ++ } else { ++ status = -EBUSY; ++ } + dev_dbg(component->dev, "%s: Exit\n", __func__); + return status; - } ++ } ++ ++ snd_soc_component_write(component, AB8500_SIDFIRADR, 0); ++ ++ for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); ++ } ++ ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), ++ BIT(AB8500_SIDFIRADR_FIRSIDSET)); ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); ++ ++ drvdata->sid_status = SID_FIR_CONFIGURED; ++ ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ ++ return status; ++} ++ + /* + * Controls - Non-DAPM ASoC + */ +@@ -1222,6 +1324,9 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); - snd_soc_component_write(component, AB8500_SIDFIRADR, 0); -@@ -1043,9 +1044,6 @@ static int sid_status_control_put(struct snd_kcontrol *kcontrol, ++/* Sidetone */ ++static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); ++ + /* ANC */ - drvdata->sid_status = SID_FIR_CONFIGURED; + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1512,6 +1617,8 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), --out: -- mutex_unlock(&drvdata->ctrl_lock); -- - dev_dbg(component->dev, "%s: Exit\n", __func__); + /* Sidetone */ ++ SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, ++ sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2041,8 +2148,10 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + static int ab8500_codec_probe(struct snd_soc_component *component) + { ++ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2075,6 +2184,10 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + ++ snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); ++ ++ mutex_init(&drvdata->ctrl_lock); ++ return status; + } + +@@ -2103,6 +2216,7 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; ++ drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -12500,1423 +4450,37 @@ diff --git a/sound/soc/codecs/aw88395/aw88395_device.c b/sound/soc/codecs/aw8839 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 +--- a/sound/soc/codecs/aw88399-lib.c +++ 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) -+{ +@@ -1282,7 +1282,7 @@ static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg + + int aw88399_request_firmware_file(struct aw88399 *aw88399) + { +- const struct firmware *cont = NULL; + const struct firmware *cont __free(firmware) = NULL; -+ int ret; -+ -+ aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; -+ -+ 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); + int ret; + + aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +@@ -1298,13 +1298,11 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399) + + aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, + struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); + if (!aw88399->aw_cfg) -+ return -ENOMEM; + 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"); + aw88399->aw_cfg->len = (int)cont->size; + memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); + if (ret) { diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c --- a/sound/soc/codecs/aw88399.c +++ b/sound/soc/codecs/aw88399.c -@@ -7,1220 +7,16 @@ +@@ -7,6 +7,7 @@ // Author: Weidong Wang // @@ -13924,1221 +4488,7 @@ diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c #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, +@@ -497,11 +498,10 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, struct aw88399 *aw88399 = snd_soc_component_get_drvdata(codec); int ret; @@ -15151,7 +4501,7 @@ diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c return 0; } -@@ -1715,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, +@@ -510,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, aw88399_start(aw88399, AW88399_SYNC_START); } @@ -15160,94 +4510,7 @@ diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c 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, +@@ -690,7 +688,7 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct aw88399 *aw88399 = snd_soc_component_get_drvdata(component); @@ -15256,7 +4519,7 @@ diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c 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, +@@ -701,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, default: break; } @@ -15264,676 +4527,6 @@ diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c 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 @@ -16258,26 +4851,6 @@ diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c 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 @@ -16400,15 +4973,11 @@ diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c regmap_config = &cs35l63_regmap_sdw; break; default: -@@ -483,25 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi +@@ -483,21 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi /* Start in cache-only until device is enumerated */ regcache_cache_only(cs35l56->base.regmap, true); -- ret = cs35l56_common_probe(cs35l56); -- if (ret != 0) -- return ret; -- -- return 0; +- return cs35l56_common_probe(cs35l56, -EINVAL); + return cs35l56_common_probe(cs35l56, peripheral->irq); } @@ -16429,7 +4998,7 @@ diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c 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 = { +@@ -505,6 +467,7 @@ static const struct dev_pm_ops cs35l56_sdw_pm = { static const struct sdw_device_id cs35l56_sdw_id[] = { SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0x3556), SDW_SLAVE_ENTRY(0x01FA, 0x3557, 0x3557), @@ -16589,33 +5158,32 @@ diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared 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 @@ +@@ -18,11 +18,9 @@ + #include + #include + #include +-#include + #include + #include + #include +-#include + #include + #include + #include +@@ -39,55 +37,49 @@ #include "wm_adsp.h" #include "cs35l56.h" +-/* +- * snd_soc_register_component() can call component_probe() on all instances +- * in a card, so deferred registration must be protected across all instances. +- */ +-static DEFINE_MUTEX(cs35l56_component_register_lock); +-static bool cs35l56_shutting_down; +- -void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral) +void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) { @@ -16681,7 +5249,7 @@ diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c 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 +@@ -837,11 +829,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing { int ret; @@ -16694,7 +5262,7 @@ diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c 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) +@@ -1405,6 +1393,7 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) ARRAY_SIZE(cs35l56_controls)); break; case 0x63: @@ -16702,45 +5270,68 @@ diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c 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) - { +@@ -1968,11 +1957,6 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); 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); +- guard(mutex)(&cs35l56_component_register_lock); +- +- if (cs35l56_shutting_down) +- return; +- + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2233,37 +2217,6 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif +-static int cs35l56_reboot_notify(struct notifier_block *nb, +- unsigned long action, void *data) +-{ +- guard(mutex)(&cs35l56_component_register_lock); +- cs35l56_shutting_down = true; +- +- return NOTIFY_DONE; +-} +- +-static struct notifier_block cs35l56_reboot_notifier = { +- .notifier_call = cs35l56_reboot_notify, +-}; +- +-static int __init cs35l56_modinit(void) +-{ +- /* +- * Use reboot notifier to prevent race between shutdown and +- * snd_soc_register_component(). Driver shutdown() callback would +- * run too late, after device_shutdown() is already walking the +- * device list that component registration can modify. +- */ +- return register_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_init(cs35l56_modinit); +- +-static void __exit cs35l56_modexit(void) +-{ +- unregister_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_exit(cs35l56_modexit); +- + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h --- a/sound/soc/codecs/cs35l56.h +++ b/sound/soc/codecs/cs35l56.h -@@ -39,8 +39,6 @@ struct cs35l56_private { +@@ -32,7 +32,6 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; +- struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -40,11 +39,8 @@ struct cs35l56_private { struct sdw_slave *sdw_peripheral; struct regmap *sdw_bus_regmap; const char *fallback_fw_suffix; @@ -16748,8 +5339,11 @@ diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h - bool sdw_irq_no_unmask; bool soft_resetting; bool sdw_attached; +- bool component_registered; struct completion init_completion; -@@ -65,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b + + int speaker_id; +@@ -67,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; @@ -16762,17 +5356,14 @@ diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h 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); +@@ -78,7 +72,6 @@ int cs35l56_system_suspend_no_irq(struct device *dev); int cs35l56_system_resume_no_irq(struct device *dev); int cs35l56_system_resume_early(struct device *dev); int cs35l56_system_resume(struct device *dev); -irqreturn_t cs35l56_irq(int irq, void *data); int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); --int cs35l56_common_probe(struct cs35l56_private *cs35l56); -+int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + 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 @@ -19309,7 +7900,7 @@ diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c #include #include #include -@@ -452,31 +453,30 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, +@@ -456,31 +457,30 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, if (!((has_playback && tx) || (has_capture && !tx))) return 0; @@ -19345,7 +7936,7 @@ diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c /* Select chmap supported */ hdmi_codec_eld_chmap(hcp); -@@ -484,8 +484,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, +@@ -488,8 +488,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, hcp->busy = true; @@ -19354,7 +7945,7 @@ diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c return ret; } -@@ -503,9 +501,8 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, +@@ -507,9 +505,8 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); @@ -24556,20 +13147,7 @@ diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c 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) +@@ -499,11 +499,11 @@ static int rt700_dev_system_suspend(struct device *dev) * deferred work completes and before the parent disables * interrupts on the link */ @@ -24870,7 +13448,7 @@ diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c 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) +@@ -912,10 +912,23 @@ static int rt712_sdca_dmic_dev_resume(struct device *dev) } regcache_cache_only(rt712->regmap, false); @@ -27779,36 +16357,7 @@ diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-co 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, +@@ -2253,7 +2253,7 @@ int tas2781_load_calibration(void *context, char *file_name, { struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); @@ -27817,7 +16366,7 @@ diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib struct tasdevice_fw *tas_fmw; struct firmware fmw; int offset = 0; -@@ -2253,60 +2263,50 @@ int tas2781_load_calibration(void *context, char *file_name, +@@ -2263,60 +2263,50 @@ int tas2781_load_calibration(void *context, char *file_name, if (ret) { dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", __func__, file_name); @@ -27889,7 +16438,7 @@ diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib } EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, "SND_SOC_TAS2781_FMWLIB"); -@@ -2399,7 +2399,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, +@@ -2409,7 +2399,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, int tasdevice_dsp_parser(void *context) { struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; @@ -27898,7 +16447,7 @@ diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib int ret; ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, -@@ -2407,15 +2407,10 @@ int tasdevice_dsp_parser(void *context) +@@ -2417,15 +2407,10 @@ int tasdevice_dsp_parser(void *context) if (ret) { dev_err(tas_priv->dev, "%s: load %s error\n", __func__, tas_priv->coef_binaryname); @@ -27916,7 +16465,7 @@ diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib } EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, "SND_SOC_TAS2781_FMWLIB"); -@@ -2797,11 +2792,12 @@ int tasdevice_prmg_load(void *context, int prm_no) +@@ -2807,11 +2792,12 @@ int tasdevice_prmg_load(void *context, int prm_no) } EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_load, "SND_SOC_TAS2781_FMWLIB"); @@ -27931,7 +16480,7 @@ diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib /* * Only RCA-based Playback can still work with no dsp program running -@@ -2818,7 +2814,6 @@ void tasdevice_tuning_switch(void *context, int state) +@@ -2828,7 +2814,6 @@ void tasdevice_tuning_switch(void *context, int state) if (state == 0) { if (tas_fmw && tas_priv->cur_prog < tas_fmw->nr_programs) { /* dsp mode or tuning mode */ @@ -28478,48 +17027,7 @@ diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c } 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, +@@ -1349,7 +1398,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, function_data->desc = &peripheral->sdca_data.function[i]; /* Parse the function */ @@ -28665,41 +17173,6 @@ diff --git a/sound/soc/codecs/tas5805m.c b/sound/soc/codecs/tas5805m.c /* 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 @@ -34504,8 +22977,8 @@ diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c - 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; +- ret = dev_err_probe(&pdev->dev, -EPROBE_DEFER, +- "failed to find CPU DAI device\n"); - goto fail; - } - @@ -34774,15 +23247,6 @@ diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c 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; } @@ -34905,15 +23369,6 @@ diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c } /** -@@ -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 @@ -34958,36 +23413,6 @@ diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c 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 @@ -35020,69 +23445,6 @@ diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c 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 @@ -35288,15 +23650,6 @@ diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c } /* -@@ -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); @@ -35425,31 +23778,6 @@ diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c 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 @@ -35762,27 +24090,6 @@ diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c 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 @@ -37694,15 +26001,42 @@ diff --git a/sound/soc/intel/avs/debug.h b/sound/soc/intel/avs/debug.h 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 @@ +@@ -6,9 +6,9 @@ // Amadeusz Slawinski // +#include #include #include +-#include #include -@@ -251,24 +252,22 @@ static int strace_release(struct inode *inode, struct file *file) + #include + #include +@@ -236,20 +236,15 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) { +- ret = -EBUSY; +- goto err; +- } ++ if (kfifo_initialized(&adev->trace_fifo)) ++ return -EBUSY; + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- goto err; ++ return ret; + + file->private_data = adev; + return 0; +-err: +- module_put(adev->dev->driver->owner); +- return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +@@ -257,24 +252,22 @@ static int strace_release(struct inode *inode, struct file *file) union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); struct avs_dev *adev = file->private_data; unsigned long resource_mask; @@ -37919,7 +26253,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c return NULL; } -@@ -1305,7 +1303,7 @@ void avs_path_free(struct avs_path *path) +@@ -1300,7 +1298,7 @@ void avs_path_free(struct avs_path *path) struct avs_path *cpath, *csave; struct avs_dev *adev = path->owner; @@ -37928,7 +26262,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c /* 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) +@@ -1309,8 +1307,6 @@ void avs_path_free(struct avs_path *path) avs_condpath_free(path->owner, cpath); avs_path_free_unlocked(path); @@ -37937,7 +26271,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c } 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, +@@ -1329,13 +1325,13 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, } /* Serialize path and its components creation. */ @@ -37954,7 +26288,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c 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, +@@ -1343,10 +1339,6 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, path = ERR_PTR(ret); } @@ -37965,7 +26299,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c return path; } -@@ -1496,15 +1488,13 @@ static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) +@@ -1491,15 +1483,13 @@ static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) { struct avs_path *cpath; @@ -37982,7 +26316,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c } 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 +@@ -1555,7 +1545,7 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t { struct avs_path *cpath; @@ -37991,7 +26325,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c /* 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 +@@ -1567,8 +1557,6 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t if (cpath->source->state == AVS_PPL_STATE_RUNNING && cpath->sink->state == AVS_PPL_STATE_RUNNING) avs_condpath_run(adev, cpath, trigger); @@ -38003,7 +26337,7 @@ diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c 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 = { +@@ -959,7 +959,7 @@ static const struct file_operations topology_name_fops = { static int avs_component_load_libraries(struct avs_soc_component *acomp) { struct avs_tplg *tplg = acomp->tplg; @@ -38012,7 +26346,7 @@ diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c int ret; if (!tplg->num_libs) -@@ -1387,25 +1387,28 @@ int avs_register_component(struct device *dev, const char *name, +@@ -1393,25 +1393,28 @@ int avs_register_component(struct device *dev, const char *name, struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) { struct avs_soc_component *acomp; @@ -38051,15 +26385,6 @@ diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c 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) { @@ -38091,7 +26416,7 @@ diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c 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) +@@ -2229,7 +2229,7 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) int avs_load_topology(struct snd_soc_component *comp, const char *filename) { @@ -38100,7 +26425,7 @@ diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c 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) +@@ -2242,7 +2242,6 @@ int avs_load_topology(struct snd_soc_component *comp, const char *filename) if (ret < 0) dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); @@ -38248,15 +26573,6 @@ diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c 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); @@ -39615,72 +27931,6 @@ diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt81 __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 @@ -39693,30 +27943,9 @@ diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c b/sound/soc/mediatek/mt 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), }; @@ -39763,27 +27992,6 @@ diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc 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 @@ -39796,15 +28004,6 @@ diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8 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 @@ -41174,39 +29373,7 @@ new file mode 100644 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, +@@ -71,6 +71,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, struct device_node *node, struct snd_soc_dai_link_component *dlc) { @@ -41214,7 +29381,7 @@ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-uti int ret; if (!dlc || !node) -@@ -83,7 +79,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, +@@ -78,7 +79,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, ret = snd_soc_of_get_dlc(node, NULL, dlc, 0); if (ret) @@ -41223,7 +29390,7 @@ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-uti return ret; } -@@ -94,7 +90,8 @@ static int meson_card_set_link_name(struct snd_soc_card *card, +@@ -89,7 +90,8 @@ static int meson_card_set_link_name(struct snd_soc_card *card, struct device_node *node, const char *prefix) { @@ -41233,7 +29400,7 @@ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-uti prefix, node->full_name); if (!name) return -ENOMEM; -@@ -137,16 +134,17 @@ int meson_card_set_be_link(struct snd_soc_card *card, +@@ -132,16 +134,17 @@ int meson_card_set_be_link(struct snd_soc_card *card, struct device_node *node) { struct snd_soc_dai_link_component *codec; @@ -41253,7 +29420,7 @@ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-uti if (!codec) return -ENOMEM; -@@ -163,7 +161,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, +@@ -158,7 +161,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, ret = meson_card_set_link_name(card, link, node, "be"); if (ret) @@ -41262,7 +29429,7 @@ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-uti return ret; } -@@ -194,12 +192,13 @@ EXPORT_SYMBOL_GPL(meson_card_set_fe_link); +@@ -189,12 +192,13 @@ EXPORT_SYMBOL_GPL(meson_card_set_fe_link); static int meson_card_add_links(struct snd_soc_card *card) { struct meson_card *priv = snd_soc_card_get_drvdata(card); @@ -41278,7 +29445,7 @@ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-uti return -EINVAL; } -@@ -224,8 +223,10 @@ static int meson_card_parse_of_optional(struct snd_soc_card *card, +@@ -219,8 +223,10 @@ static int meson_card_parse_of_optional(struct snd_soc_card *card, int (*func)(struct snd_soc_card *c, const char *p)) { @@ -41290,18 +29457,6 @@ diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-uti 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 @@ -41395,25 +29550,6 @@ diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c 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 @@ -42506,40 +30642,6 @@ diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6ap 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 @@ -43997,98 +32099,7 @@ diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_s 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[] = { +@@ -1116,15 +1116,31 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { { "Playback Mixer", NULL, "Primary Playback" }, @@ -44122,7 +32133,7 @@ diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c .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) +@@ -1441,10 +1457,10 @@ static int samsung_i2s_probe(struct platform_device *pdev) regs_base = res->start; priv->clk = devm_clk_get(&pdev->dev, "iis"); @@ -44137,7 +32148,7 @@ diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c 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 = { +@@ -1650,8 +1666,7 @@ static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = { }; static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = { @@ -44147,7 +32158,7 @@ diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c .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[] = { +@@ -1675,7 +1690,7 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { .name = "samsung-i2s", .driver_data = (kernel_ulong_t)&i2sv3_dai_type, }, @@ -44815,22 +32826,6 @@ diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c 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 @@ -44960,7 +32955,7 @@ diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_u } } -@@ -1969,21 +1990,16 @@ static int is_sdca_endpoint_present(struct device *dev, +@@ -1969,11 +1990,7 @@ static int is_sdca_endpoint_present(struct device *dev, "SDCA device function for DAI type %d not supported, skip endpoint\n", dai_info->dai_type); @@ -44972,28 +32967,7 @@ diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_u + 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; - + int asoc_sdw_parse_sdw_endpoints(struct device *dev, diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -47416,23 +35390,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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 * +@@ -1371,6 +1412,22 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * pipeline = pipe_widget->private; pipeline->mem_usage += total; @@ -47455,7 +35413,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c /* 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) +@@ -1688,6 +1745,8 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private; pipeline->mem_usage = 0; @@ -47464,7 +35422,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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 +@@ -2065,10 +2124,57 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget } static int @@ -47526,7 +35484,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c { 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, +@@ -2540,6 +2646,21 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, return 0; } @@ -47548,7 +35506,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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, +@@ -2553,6 +2674,10 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; int input_fmt_index, output_fmt_index; @@ -47559,7 +35517,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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, +@@ -2598,6 +2723,10 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; int input_fmt_index, output_fmt_index; @@ -47570,7 +35528,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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, +@@ -2644,6 +2773,10 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; int output_fmt_index, input_fmt_index; @@ -47581,7 +35539,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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, +@@ -2810,6 +2943,18 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, int ret; if (available_fmt->num_input_formats) { @@ -47600,7 +35558,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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, +@@ -2822,39 +2967,69 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, if (available_fmt->num_output_formats) { struct sof_ipc4_audio_format *in_fmt; struct sof_ipc4_pin_format *pin_fmt; @@ -47686,7 +35644,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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, +@@ -2868,9 +3043,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, /* modify the pipeline params with the output format */ ret = sof_ipc4_update_hw_params(sdev, pipeline_params, &process->output_format, @@ -47697,7 +35655,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c if (ret) return ret; } -@@ -3083,27 +3258,47 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr +@@ -3085,27 +3258,47 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr return 0; } @@ -47758,7 +35716,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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, +@@ -3113,27 +3306,26 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, /* Add ext_init first and set objects array flag to 1 */ ext_init = (struct sof_ipc4_module_init_ext_init *)payload; @@ -47800,7 +35758,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c /* 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, +@@ -3156,6 +3348,69 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, return new_size; } @@ -47870,7 +35828,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c 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 +@@ -3342,8 +3597,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget swidget->widget->name, swidget->pipeline_id, module_id, swidget->instance_id, swidget->core); @@ -47881,7 +35839,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c if (ret < 0) goto fail; -@@ -3353,6 +3610,17 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget +@@ -3355,6 +3610,17 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget dev_dbg(sdev->dev, "Create pipeline %s (pipe %d) - instance %d, core %d\n", swidget->widget->name, swidget->pipeline_id, swidget->instance_id, swidget->core); @@ -47899,7 +35857,7 @@ diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c } msg->data_size = ipc_size; -@@ -3410,6 +3678,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget +@@ -3412,6 +3678,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget swidget->widget->name); pipeline->mem_usage = 0; @@ -48182,62 +36140,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c }; 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, +@@ -1108,7 +1108,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, full = NULL; partial = NULL; @@ -48246,7 +36149,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c /* 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, +@@ -1173,7 +1173,7 @@ static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, else return; @@ -48255,7 +36158,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c /* 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_ +@@ -1949,8 +1949,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ private->array, le32_to_cpu(private->size)); if (ret < 0) { dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); @@ -48265,7 +36168,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c } 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_ +@@ -2001,6 +2000,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ case SOF_DAI_AMD_HS: case SOF_DAI_AMD_SP_VIRTUAL: case SOF_DAI_AMD_HS_VIRTUAL: @@ -48273,7 +36176,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c 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_ +@@ -2019,8 +2019,8 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ /* allocate memory for tuples array */ slink->tuples = kzalloc_objs(*slink->tuples, num_tuples); if (!slink->tuples) { @@ -48284,7 +36187,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c } 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_ +@@ -2076,6 +2076,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ err: kfree(slink->tuples); @@ -48292,7 +36195,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c kfree(slink); return ret; -@@ -2505,13 +2512,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) +@@ -2511,13 +2512,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_pdata *sof_pdata = sdev->pdata; const char *tplg_filename_prefix = sof_pdata->tplg_filename_prefix; @@ -48308,7 +36211,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c if (!tplg_files) return -ENOMEM; -@@ -2537,10 +2543,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) +@@ -2543,10 +2543,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) tplg_filename_prefix, &tplg_files, no_fallback); @@ -48320,7 +36223,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c } /* -@@ -2551,8 +2555,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) +@@ -2557,8 +2555,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) if (strstr(file, "dummy")) { dev_err(scomp->dev, "Function topology is required, please upgrade sof-firmware\n"); @@ -48329,7 +36232,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c return -EINVAL; } tplg_files[0] = file; -@@ -2567,6 +2569,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) +@@ -2573,6 +2569,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) if (tplg_files[0] != file) dev_info(scomp->dev, "loading topology %d: %s\n", i, tplg_files[i]); @@ -48337,7 +36240,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c 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) +@@ -2591,8 +2588,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) else ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); @@ -48346,7 +36249,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c 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) +@@ -2611,6 +2606,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) goto out; } dev_info(scomp->dev, "loading feature topology %d: %s\n", i, feature_topology); @@ -48355,7 +36258,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c 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) +@@ -2635,8 +2632,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) else ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); @@ -48364,7 +36267,7 @@ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c 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) +@@ -2655,8 +2650,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) if (ret >= 0 && sdev->led_present) ret = snd_ctl_led_request(); @@ -48421,24 +36324,6 @@ diff --git a/sound/soc/sophgo/cv1800b-tdm.c b/sound/soc/sophgo/cv1800b-tdm.c 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; } @@ -48451,7 +36336,7 @@ diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c 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 = { +@@ -343,6 +348,8 @@ static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { .set_sysclk = spacemit_i2s_set_sysclk, .set_fmt = spacemit_i2s_set_fmt, .trigger = spacemit_i2s_trigger, @@ -48460,66 +36345,7 @@ diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c }; 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) +@@ -459,7 +466,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(i2s->base)) @@ -48528,7 +36354,7 @@ diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c 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) +@@ -476,7 +483,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) &spacemit_i2s_component, dai, 1); if (ret) @@ -49249,32 +37075,7 @@ diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c 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) +@@ -870,10 +870,9 @@ static int j721e_soc_probe(struct platform_device *pdev) card->num_dapm_routes = ARRAY_SIZE(j721e_cpb_dapm_routes); card->fully_routed = 1; @@ -49822,48 +37623,6 @@ diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_forma #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 @@ -50130,655 +37889,3 @@ diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c } 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/0511-asoc-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0511-asoc-updates.patch.sig new file mode 100644 index 0000000..a7f9893 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0511-asoc-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch.sig deleted file mode 100644 index 09b8d69..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes-1.patch b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes-1.patch new file mode 100644 index 0000000..7de337b --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes-1.patch @@ -0,0 +1,661 @@ +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info + int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, + int mode, struct snd_rawmidi_file *rfile, + int depth); +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); + int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, +@@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, + return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); + } + ++static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_release_nested(rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c +--- a/sound/aoa/soundbus/i2sbus/core.c ++++ b/sound/aoa/soundbus/i2sbus/core.c +@@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, + } + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + int irq = irq_of_parse_and_map(np, i); ++ if (!irq) ++ goto err; + if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = irq; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; +- guard(mutex)(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; +@@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + } + + /* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth) + { + struct snd_rawmidi *rmidi; + +@@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) + return -ENXIO; + + rmidi = rfile->rmidi; ++ mutex_lock_nested(&rmidi->open_mutex, depth); + rawmidi_release_priv(rfile); ++ mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + return 0; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_release); ++EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); + + static int snd_rawmidi_release(struct inode *inode, struct file *file) + { +@@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) + + rfile = file->private_data; + rmidi = rfile->rmidi; +- rawmidi_release_priv(rfile); ++ scoped_guard(mutex, &rmidi->open_mutex) ++ rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) + ump->legacy_substreams[dir][group] = NULL; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) +- snd_rawmidi_kernel_release(&ump->legacy_out_rfile); ++ snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + } + return 0; + } +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) + snd_hda_gen_remove(codec); + } + +-static void cx_process_headset_plugin(struct hda_codec *codec) ++static void cx_process_headset_detect_plug_type(struct hda_codec *codec) + { + unsigned int val; + unsigned int count = 0; +@@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) + count++; + } while (count < 3); + val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); +- if (val & 0x800) { +- codec_dbg(codec, "headset plugin, type is CTIA\n"); +- snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); +- } else if (val & 0x400) { +- codec_dbg(codec, "headset plugin, type is OMTP\n"); ++ if (val & 0xc00) { ++ codec_dbg(codec, "headset plugin, type is %s\n", ++ val & 0x800 ? "CTIA" : "OMTP"); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } else { + codec_dbg(codec, "headphone plugin\n"); +@@ -243,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + * Check hp&mic tag to process headset plugin & plugout. + */ + mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); +- if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ ++ if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); +- else +- cx_process_headset_plugin(codec); ++ } else { ++ cx_process_headset_detect_plug_type(codec); ++ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); ++ } + } + + static int cx_suspend(struct hda_codec *codec) +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -1370,6 +1370,61 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); + } + ++/* ++ * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix ++ * ++ * The speaker mute LED is controlled via VREF100 on NID 0x1a. ++ * This pin must remain powered (D0) even during suspend, otherwise ++ * the LED state is lost and the pin defaults to Hi-Z on resume, ++ * causing the LED to stop responding to mute toggles. ++ * Windows keeps this pin powered unconditionally, so matching that ++ * behavior ensures consistent LED operation across suspend/resume. ++ * The mic-mute LED is controlled via GPIO 0 with active-low polarity. ++ */ ++static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, ++ hda_nid_t nid, ++ unsigned int power_state) ++{ ++ if (nid == 0x1a) ++ return AC_PWRST_D0; ++ return snd_hda_gen_path_power_filter(codec, nid, power_state); ++} ++ ++static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); ++ unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; ++ ++ snd_hda_set_pin_ctl_cache(codec, 0x1a, val); ++ return 0; ++} ++ ++static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->micmute_led_polarity = 1; ++ alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); ++ ++ spec->mute_led_polarity = 0; ++ snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); ++ ++ codec->power_filter = hp_8bb6_power_filter; ++ spec->no_shutup_pins = 1; ++ break; ++ ++ case HDA_FIXUP_ACT_INIT: ++ if (spec->gen.vmaster_mute.hook) ++ snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); ++ break; ++ } ++} ++ + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -4089,6 +4144,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, + ALC225_FIXUP_S3_POP_NOISE, + ALC700_FIXUP_INTEL_REFERENCE, ++ ALC700_FIXUP_INTEL_HADES_CANYON, + ALC274_FIXUP_DELL_BIND_DACS, + ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + ALC298_FIXUP_TPT470_DOCK_FIX, +@@ -4152,6 +4208,7 @@ enum { + ALC236_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ++ ALC236_FIXUP_HP_15_FD0XXX, + ALC236_FIXUP_LENOVO_INV_DMIC, + ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, +@@ -4297,6 +4354,7 @@ enum { + ALC287_FIXUP_LENOVO_LEGION_AW88399, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, ++ ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4333,6 +4391,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + __snd_hda_apply_fixup(codec, id, action, 0); + } + ++static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ /* ++ * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken ++ * analog playback after rebooting into Linux. Restore the bit ++ * during codec initialization. ++ */ ++ if (action == HDA_FIXUP_ACT_INIT) ++ alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); ++} ++ + static const struct hda_fixup alc269_fixups[] = { + [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { + .type = HDA_FIXUP_PINS, +@@ -5411,6 +5482,10 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC700_FIXUP_INTEL_HADES_CANYON] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc700_fixup_intel_hades_canyon, ++ }, + [ALC274_FIXUP_DELL_BIND_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_bind_dacs, +@@ -5913,6 +5988,10 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_gpio, + }, ++ [ALC236_FIXUP_HP_15_FD0XXX] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc236_fixup_hp_15_fd0xxx, ++ }, + [ALC236_FIXUP_LENOVO_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, +@@ -7010,6 +7089,12 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_lenovo_dac_rename, + }, ++ [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_TXNW2781_I2C, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7448,6 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), +@@ -8031,6 +8117,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), ++ SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8044,14 +8131,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), ++ SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), +- HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ ++ HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8231,6 +8319,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ++ ALC700_FIXUP_INTEL_HADES_CANYON), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), + SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), +diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c +--- a/sound/usb/mixer.c ++++ b/sound/usb/mixer.c +@@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) + } + + /* +- * Additional checks for sticky mixers ++ * Additional checks for sticky GET_CUR + * +- * Some devices' volume control mixers are sticky, which accept SET_CUR but +- * do absolutely nothing. ++ * Some devices' volume control mixers have sticky GET_CUR, which implies either ++ * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts ++ * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only ++ * way to control the volume. For the latter case, the mixer has effective ++ * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we ++ * always provide mixer value from the ceche. + * + * Check the return values of GET_CUR with different SET_CUR values. Consider +- * the mixer as sticky if GET_CUR always returns a constant value. ++ * GET_CUR as sticky if GET_CUR always returns a constant value. + * +- * Some devices have effective SET_CUR despite GET_CUR being constant. Do not +- * consider the mixer as sticky if a quirk flag indicates that. ++ * Unfortunately, we can't distinguish between stubbed SET_CUR and broken ++ * GET_CUR with simple read-back tests. Disabling the mixer regardless and ++ * forcing userspace to use soft mixer instead can lead to audible distortion at ++ * low volume on some wireless headphones, probably due to their poorly- ++ * performed lossy codec. + * +- * Gate the registration of sticky mixers to prevent confusing userspace, so +- * that they won't cause ineffective volume control. However, for mixers with +- * effective SET_CUR but broken GET_CUR, the registration can continue normally +- * but further GET_CUR requests will be gated. ++ * Instead, mark GET_CUR as broken regardless and only provide mixer value from ++ * the cache. Users may opt into soft mixer in userspace audio stack if they ++ * need it. + */ + static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +@@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + return 0; + } + +- if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { +- usb_audio_info(cval->head.mixer->chip, +- "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); +- +- cval->get_cur_broken = 1; +- return -ENXIO; +- } +- +- usb_audio_err(cval->head.mixer->chip, +- "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); + usb_audio_info(cval->head.mixer->chip, +- "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); ++ "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", ++ cval->head.id, mixer_ctrl_intf(cval->head.mixer), ++ cval->min, cval->max, cval->res, saved); + +- return -ENODEV; ++ cval->get_cur_broken = 1; ++ return -ENXIO; + } + + /* +@@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + goto no_checks; + + ret = check_sticky_volume_control(cval, minchn, saved); +- if (ret == -ENODEV) +- goto sticky; + if (ret) + goto no_checks; + +@@ -1454,34 +1447,15 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + } + } + +- return 0; +- +-sticky: + /* +- * It makes no sense to restore the saved value for a sticky mixer, +- * since setting any value is a no-op. +- * +- * However, in some rare cases, SET_CUR is effective despite GET_CUR +- * always returns a constant value. These mixers are not sticky, but +- * there's no way to distinguish them. Without any additional +- * information, the best thing we can do is to set the mixer value to +- * the maximum before bailing out, so that a soft mixer can still reach +- * the maximum hardware volume if the mixer turns out to be non-sticky. +- * Meanwhile, all channels must be synchronized to prevent imbalance +- * volume. ++ * When GET_CUR is sticky, the saved value is bogus, so mixer values set ++ * by the sanity checks must be discarded through init_cur_mix_raw(). ++ * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ +- if (!cval->cmask) { +- snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); +- } else { +- idx = 0; +- for (i = 0; i < MAX_CHANNELS; i++) { +- if (cval->cmask & BIT(i)) { +- snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); +- idx++; +- } +- } +- } +- return ret; ++ if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) ++ cval->get_cur_broken = 0; ++ ++ return 0; + } + + #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + +-/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels +- * and sits on the monitor mixer branch, but it is traced through to the +- * Speaker output terminal and gets named "Speaker Playback Volume". Userspace +- * then adopts it as the stream's hardware volume, and any setting below 0 dB +- * attenuates some channels but not others (20 dB imbalance at 80%). Give it a +- * non-standard name so that it is no longer taken for the stream's master +- * volume, while remaining reachable for anyone who wants the monitor gain. ++/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is ++ * traced through to the Speaker output terminal, so it is named "Speaker ++ * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI ++ * testing found asymmetric attenuation within the main stereo pair. Userspace ++ * adopts this control as the stream's hardware volume, causing imbalance below ++ * 0 dB. Give it a non-standard name so that userspace no longer treats it as ++ * the stream master, while keeping the monitor gain reachable. + */ + static const struct usbmix_name_map audient_id14_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ +@@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .map = maya44_map, + }, + { +- /* Audient iD14 */ ++ /* Audient iD14 MkI */ ++ .id = USB_ID(0x2708, 0x0002), ++ .map = audient_id14_map, ++ }, ++ { ++ /* Audient iD14 MkII */ + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * M-Audio Venom ++ * ++ * The AudioControl interface times out on every GET_CUR request, ++ * which adds around 47 seconds to the card registration and ++ * freezes the device, blocking streaming. ++ * Using an explicit composite quirk to skip the mixer entirely. ++ */ ++ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), ++ QUIRK_DRIVER_INFO { ++ .vendor_name = "M-Audio", ++ .product_name = "Venom", ++ QUIRK_DATA_COMPOSITE { ++ { QUIRK_DATA_IGNORE(0) }, ++ { QUIRK_DATA_STANDARD_AUDIO(1) }, ++ { QUIRK_DATA_STANDARD_AUDIO(2) }, ++ { QUIRK_DATA_STANDARD_MIDI(3) }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* Casio devices */ + { +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2215,10 +2215,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ +@@ -2256,8 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), ++ DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ ++ QUIRK_FLAG_MIXER_GET_CUR_OK | ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ +@@ -2329,9 +2330,11 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ ++ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ +@@ -2370,8 +2373,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ +@@ -2379,8 +2380,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ +@@ -2393,6 +2392,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ ++ QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | ++ QUIRK_FLAG_SKIP_IFACE_SETUP), + DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ + 0), /* Doesn't have the vendor quirk which would otherwise apply */ + DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ +@@ -2505,8 +2507,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ + QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ +@@ -2647,7 +2647,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), +- QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), + QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), + QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -243,24 +243,18 @@ extern bool snd_usb_skip_validation; + * from snd_usb_handle_sync_urb. Instead fall through and enqueue a + * packet_info containing only size-0 packets, so the OUT ring keeps + * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). +- * QUIRK_FLAG_MIXER_GET_CUR_BROKEN +- * Some mixers are sticky, which means that setting their current volume is a +- * no-op, and reading the current volume returns a constant value. The sticky +- * check disables these mixers to prevent confusing userspace. However, some +- * devices do have a tunable volume despite the reported current volume being +- * constant. As the sticky check can't distinguish between the two categories, +- * setting this flag tells that the device should fall into the second +- * category when GET_CUR returns a constant value, resulting in the sticky +- * check being non-fatal and only disabling GET_CUR instead of the whole mixer. +- * The current volume will then be provided by the internal cache that stores +- * the last set volume ++ * QUIRK_FLAG_MIXER_GET_CUR_OK ++ * On some devices, whether their GET_CUR being sticky depends on whether ++ * hotpluggable components are present. When the hotpluggable components are ++ * missing on probe, their GET_CUR behavior is classified as broken. Set the ++ * flag to prevent the heuristics from gating GET_CUR. + * QUIRK_FLAG_PLAYBACK_URB_FIXUP + * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. + * This is needed for devices that exhibit boot-time audio stuttering due + * to insufficient buffer depth combined with xHCI scheduling variability. + * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling + * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. +- * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * QUIRK_FLAG_ALWAYS_SET_RATE + * Issue SET_CUR for the sample rate even when the clock already reports the + * requested rate. A device advertising a single rate is otherwise never sent + * the request at all, and some require it before streaming will start. +@@ -297,10 +291,10 @@ enum { + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, +- QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_MIXER_GET_CUR_OK = 30, + QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, + QUIRK_TYPE_ALWAYS_SET_RATE = 32, +-/* Please also edit snd_usb_audio_quirk_flag_names */ ++/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ + }; + + #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) +@@ -335,7 +329,7 @@ enum { + #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) +-#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) + #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) + #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + diff --git a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes-1.patch.sig new file mode 100644 index 0000000..6645ee8 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch.sig deleted file mode 100644 index c02c1df..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch b/pkgbuilds/linux-omarchy-bore/0513-asoc-fixes-1.patch similarity index 51% rename from pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch rename to pkgbuilds/linux-omarchy-bore/0513-asoc-fixes-1.patch index c32b5c3..49c1eb3 100644 --- a/pkgbuilds/linux-omarchy-bore/0511-sound-updates-fixes.patch +++ b/pkgbuilds/linux-omarchy-bore/0513-asoc-fixes-1.patch @@ -64,400 +64,9 @@ diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bin #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"), }, @@ -816,51 +425,6 @@ diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c 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 @@ -1275,18 +839,6 @@ diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_u 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 @@ -1317,339 +869,3 @@ diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaen 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/0513-asoc-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-bore/0513-asoc-fixes-1.patch.sig new file mode 100644 index 0000000..2c6d29a Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0513-asoc-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0512-sound-fixes.patch b/pkgbuilds/linux-omarchy-bore/0514-sound-fixes-2.patch similarity index 74% rename from pkgbuilds/linux-omarchy/0512-sound-fixes.patch rename to pkgbuilds/linux-omarchy-bore/0514-sound-fixes-2.patch index 9566376..043c34e 100644 --- a/pkgbuilds/linux-omarchy/0512-sound-fixes.patch +++ b/pkgbuilds/linux-omarchy-bore/0514-sound-fixes-2.patch @@ -1,92 +1,3 @@ -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 @@ -233,62 +144,6 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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 diff --git a/pkgbuilds/linux-omarchy-bore/0514-sound-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-bore/0514-sound-fixes-2.patch.sig new file mode 100644 index 0000000..fce1ac9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0514-sound-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy-bore/0515-xps13-sof-quirk.patch similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch rename to pkgbuilds/linux-omarchy-bore/0515-xps13-sof-quirk.patch diff --git a/pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch.sig b/pkgbuilds/linux-omarchy-bore/0515-xps13-sof-quirk.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0513-xps13-sof-quirk.patch.sig rename to pkgbuilds/linux-omarchy-bore/0515-xps13-sof-quirk.patch.sig diff --git a/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch b/pkgbuilds/linux-omarchy-bore/0516-rt766-stream-config-type.patch similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch rename to pkgbuilds/linux-omarchy-bore/0516-rt766-stream-config-type.patch diff --git a/pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch.sig b/pkgbuilds/linux-omarchy-bore/0516-rt766-stream-config-type.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0514-rt766-stream-config-type.patch.sig rename to pkgbuilds/linux-omarchy-bore/0516-rt766-stream-config-type.patch.sig diff --git a/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy-bore/0517-hda-realtek-rog-strix-g733zw-speakers.patch similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch rename to pkgbuilds/linux-omarchy-bore/0517-hda-realtek-rog-strix-g733zw-speakers.patch diff --git a/pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig b/pkgbuilds/linux-omarchy-bore/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig rename to pkgbuilds/linux-omarchy-bore/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig diff --git a/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy-bore/0518-asoc-amd-yc-acer-aspire-a314-23p.patch similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch rename to pkgbuilds/linux-omarchy-bore/0518-asoc-amd-yc-acer-aspire-a314-23p.patch diff --git a/pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig b/pkgbuilds/linux-omarchy-bore/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy-bore/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig rename to pkgbuilds/linux-omarchy-bore/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig diff --git a/pkgbuilds/linux-omarchy-bore/0522-sound-fixes-3.patch b/pkgbuilds/linux-omarchy-bore/0522-sound-fixes-3.patch new file mode 100644 index 0000000..48c9872 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0522-sound-fixes-3.patch @@ -0,0 +1,432 @@ +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 +@@ -7465,6 +7465,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8890, "HP Elite Dragonfly Max G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8896, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8898, "HP EliteBook 845 G8 Notebook PC", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), +@@ -7580,6 +7581,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), ++ SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 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), +diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c +--- a/sound/pci/rme9652/hdspm.c ++++ b/sound/pci/rme9652/hdspm.c +@@ -1071,14 +1071,8 @@ struct hdspm { + + + static const struct pci_device_id snd_hdspm_ids[] = { +- { +- .vendor = PCI_VENDOR_ID_XILINX, +- .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, +- .subvendor = PCI_ANY_ID, +- .subdevice = PCI_ANY_ID, +- .class = 0, +- .class_mask = 0, +- .driver_data = 0}, ++ { PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI) }, ++ { PCI_DEVICE(0x1d18, 0x3fc6) }, /* RME HDSPe AIO PCI express audio */ + {0,} + }; + +diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c +--- a/sound/usb/hiface/pcm.c ++++ b/sound/usb/hiface/pcm.c +@@ -24,7 +24,7 @@ + struct pcm_urb { + struct hiface_chip *chip; + +- struct urb instance; ++ struct urb *instance; + struct usb_anchor submitted; + u8 *buffer; + }; +@@ -193,7 +193,7 @@ static void hiface_pcm_stream_stop(struct pcm_runtime *rt) + if (!time) + usb_kill_anchored_urbs( + &rt->out_urbs[i].submitted); +- usb_kill_urb(&rt->out_urbs[i].instance); ++ usb_kill_urb(rt->out_urbs[i].instance); + } + + rt->stream_state = STREAM_DISABLED; +@@ -215,9 +215,9 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) + rt->stream_state = STREAM_STARTING; + for (i = 0; i < PCM_N_URBS; i++) { + memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE); +- usb_anchor_urb(&rt->out_urbs[i].instance, ++ usb_anchor_urb(rt->out_urbs[i].instance, + &rt->out_urbs[i].submitted); +- ret = usb_submit_urb(&rt->out_urbs[i].instance, ++ ret = usb_submit_urb(rt->out_urbs[i].instance, + GFP_ATOMIC); + if (ret) { + hiface_pcm_stream_stop(rt); +@@ -334,7 +334,7 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) + if (do_period_elapsed) + snd_pcm_period_elapsed(sub->instance); + +- ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC); ++ ret = usb_submit_urb(out_urb->instance, GFP_ATOMIC); + if (ret < 0) + goto out_fail; + +@@ -492,16 +492,18 @@ static int hiface_pcm_init_urb(struct pcm_urb *urb, + void (*handler)(struct urb *)) + { + urb->chip = chip; +- usb_init_urb(&urb->instance); ++ urb->instance = usb_alloc_urb(0, GFP_KERNEL); ++ if (!urb->instance) ++ return -ENOMEM; + + urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL); + if (!urb->buffer) + return -ENOMEM; + +- usb_fill_bulk_urb(&urb->instance, chip->dev, ++ usb_fill_bulk_urb(urb->instance, chip->dev, + usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer, + PCM_PACKET_SIZE, handler, urb); +- if (usb_urb_ep_type_check(&urb->instance)) ++ if (usb_urb_ep_type_check(urb->instance)) + return -EINVAL; + init_usb_anchor(&urb->submitted); + +@@ -520,24 +522,26 @@ void hiface_pcm_abort(struct hiface_chip *chip) + } + } + +-static void hiface_pcm_destroy(struct hiface_chip *chip) ++static void hiface_pcm_destroy(struct pcm_runtime *rt) + { +- struct pcm_runtime *rt = chip->pcm; + int i; + +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); ++ if (!rt) ++ return; + +- kfree(chip->pcm); +- chip->pcm = NULL; ++ if (rt->chip) ++ rt->chip->pcm = NULL; ++ ++ for (i = 0; i < PCM_N_URBS; i++) { ++ usb_free_urb(rt->out_urbs[i].instance); ++ kfree(rt->out_urbs[i].buffer); ++ } ++ kfree(rt); + } + + static void hiface_pcm_free(struct snd_pcm *pcm) + { +- struct pcm_runtime *rt = pcm->private_data; +- +- if (rt) +- hiface_pcm_destroy(rt->chip); ++ hiface_pcm_destroy(pcm->private_data); + } + + int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) +@@ -587,8 +591,6 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) + return 0; + + error: +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); +- kfree(rt); ++ hiface_pcm_destroy(rt); + return ret; + } +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -109,10 +109,10 @@ struct ua101 { + unsigned int buffer_pos; + unsigned int queue_length; + struct ua101_urb { +- struct urb urb; +- struct usb_iso_packet_descriptor iso_frame_desc[1]; ++ struct urb *urb; + struct list_head ready_list; +- } *urbs[MAX_QUEUE_LENGTH]; ++ struct ua101 *ua; ++ } urbs[MAX_QUEUE_LENGTH]; + struct { + unsigned int size; + void *addr; +@@ -167,15 +167,15 @@ static void abort_usb_playback(struct ua101 *ua) + wake_up(&ua->alsa_playback_wait); + } + +-static void playback_urb_complete(struct urb *usb_urb) ++static void playback_urb_complete(struct urb *urb) + { +- struct ua101_urb *urb = (struct ua101_urb *)usb_urb; +- struct ua101 *ua = urb->urb.context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + +- if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ +- urb->urb.status == -ENODEV || /* device removed */ +- urb->urb.status == -ECONNRESET || /* unlinked */ +- urb->urb.status == -ESHUTDOWN)) { /* device disabled */ ++ if (unlikely(urb->status == -ENOENT || /* unlinked */ ++ urb->status == -ENODEV || /* device removed */ ++ urb->status == -ECONNRESET || /* unlinked */ ++ urb->status == -ESHUTDOWN)) { /* device disabled */ + abort_usb_playback(ua); + abort_alsa_playback(ua); + return; +@@ -184,18 +184,19 @@ static void playback_urb_complete(struct urb *usb_urb) + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { + /* append URB to FIFO */ + guard(spinlock_irqsave)(&ua->lock); +- list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); ++ list_add_tail(&ua_urb->ready_list, &ua->ready_playback_urbs); + if (ua->rate_feedback_count > 0) + queue_work(system_highpri_wq, &ua->playback_work); + ua->playback.substream->runtime->delay -= +- urb->urb.iso_frame_desc[0].length / ++ urb->iso_frame_desc[0].length / + ua->playback.frame_bytes; + } + } + + static void first_playback_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = playback_urb_complete; + playback_urb_complete(urb); +@@ -248,7 +249,8 @@ static void playback_work(struct work_struct *work) + { + struct ua101 *ua = container_of(work, struct ua101, playback_work); + unsigned int frames; +- struct ua101_urb *urb; ++ struct ua101_urb *ua_urb; ++ struct urb *urb; + bool do_period_elapsed = false; + int err; + +@@ -275,23 +277,24 @@ static void playback_work(struct work_struct *work) + ua->rate_feedback_count--; + + /* take URB out of FIFO */ +- urb = list_first_entry(&ua->ready_playback_urbs, +- struct ua101_urb, ready_list); +- list_del(&urb->ready_list); ++ ua_urb = list_first_entry(&ua->ready_playback_urbs, ++ struct ua101_urb, ready_list); ++ list_del(&ua_urb->ready_list); ++ urb = ua_urb->urb; + + /* fill packet with data or silence */ +- urb->urb.iso_frame_desc[0].length = ++ urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, +- &urb->urb, ++ urb, + frames); + else +- memset(urb->urb.transfer_buffer, 0, +- urb->urb.iso_frame_desc[0].length); ++ memset(urb->transfer_buffer, 0, ++ urb->iso_frame_desc[0].length); + + /* and off you go ... */ +- err = usb_submit_urb(&urb->urb, GFP_ATOMIC); ++ err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + abort_usb_playback(ua); + abort_alsa_playback(ua); +@@ -342,7 +345,8 @@ static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, + + static void capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + struct ua101_stream *stream = &ua->capture; + unsigned int frames, write_ptr; + bool do_period_elapsed; +@@ -413,7 +417,8 @@ static void capture_urb_complete(struct urb *urb) + + static void first_capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = capture_urb_complete; + capture_urb_complete(urb); +@@ -427,7 +432,7 @@ static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); ++ int err = usb_submit_urb(stream->urbs[i].urb, GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); +@@ -442,8 +447,8 @@ static void kill_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) +- if (stream->urbs[i]) +- usb_kill_urb(&stream->urbs[i]->urb); ++ if (stream->urbs[i].urb) ++ usb_kill_urb(stream->urbs[i].urb); + } + + static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) +@@ -508,7 +513,7 @@ static int start_usb_capture(struct ua101 *ua) + return err; + + clear_bit(CAPTURE_URB_COMPLETED, &ua->states); +- ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; ++ ua->capture.urbs[0].urb->complete = first_capture_urb_complete; + ua->rate_feedback_start = 0; + ua->rate_feedback_count = 0; + +@@ -550,7 +555,7 @@ static int start_usb_playback(struct ua101 *ua) + return err; + + clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); +- ua->playback.urbs[0]->urb.complete = ++ ua->playback.urbs[0].urb->complete = + first_playback_urb_complete; + scoped_guard(spinlock_irq, &ua->lock) { + INIT_LIST_HEAD(&ua->ready_playback_urbs); +@@ -580,7 +585,7 @@ static int start_usb_playback(struct ua101 *ua) + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + } +- urb = &ua->playback.urbs[i]->urb; ++ urb = ua->playback.urbs[i].urb; + urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + memset(urb->transfer_buffer, 0, +@@ -1059,7 +1064,7 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + void (*urb_complete)(struct urb *)) + { + unsigned max_packet_size = stream->max_packet_bytes; +- struct ua101_urb *urb; ++ struct urb *urb; + unsigned int b, u = 0; + + for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { +@@ -1070,23 +1075,24 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + while (size >= max_packet_size) { + if (u >= stream->queue_length) + goto bufsize_error; +- urb = kmalloc_obj(*urb); ++ urb = usb_alloc_urb(1, GFP_KERNEL); + if (!urb) + return -ENOMEM; +- usb_init_urb(&urb->urb); +- urb->urb.dev = ua->dev; +- urb->urb.pipe = stream->usb_pipe; +- urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP; +- urb->urb.transfer_buffer = addr; +- urb->urb.transfer_dma = dma; +- urb->urb.transfer_buffer_length = max_packet_size; +- urb->urb.number_of_packets = 1; +- urb->urb.interval = 1; +- urb->urb.context = ua; +- urb->urb.complete = urb_complete; +- urb->urb.iso_frame_desc[0].offset = 0; +- urb->urb.iso_frame_desc[0].length = max_packet_size; +- stream->urbs[u++] = urb; ++ urb->dev = ua->dev; ++ urb->pipe = stream->usb_pipe; ++ urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ urb->transfer_buffer = addr; ++ urb->transfer_dma = dma; ++ urb->transfer_buffer_length = max_packet_size; ++ urb->number_of_packets = 1; ++ urb->interval = 1; ++ urb->context = &stream->urbs[u]; ++ urb->complete = urb_complete; ++ urb->iso_frame_desc[0].offset = 0; ++ urb->iso_frame_desc[0].length = max_packet_size; ++ stream->urbs[u].ua = ua; ++ stream->urbs[u].urb = urb; ++ u++; + size -= max_packet_size; + addr += max_packet_size; + dma += max_packet_size; +@@ -1104,8 +1110,8 @@ static void free_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- kfree(stream->urbs[i]); +- stream->urbs[i] = NULL; ++ usb_free_urb(stream->urbs[i].urb); ++ stream->urbs[i].urb = NULL; + } + } + +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -849,6 +849,27 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) + return 0; + } + ++/* ++ * A post configuration device descriptor read is needed to make the CM1A ++ * operational after reenumeration. ++ */ ++static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) ++{ ++ struct usb_device_descriptor *desc __free(kfree) = kmalloc_obj(*desc); ++ int err; ++ ++ if (!desc) ++ return -ENOMEM; ++ ++ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); ++ if (err < 0) { ++ dev_err(&dev->dev, "failed to read device descriptor: %d\n", err); ++ return err; ++ } ++ ++ return 0; ++} ++ + /* + * Some sound cards from Native Instruments are in fact compliant to the USB + * audio standard of version 2 and other approved USB standards, even though +@@ -1681,6 +1702,8 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); ++ case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ ++ return snd_usb_cm1a_boot_quirk(dev); + } + + return 0; +@@ -2390,6 +2413,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x050c, /* Behringer Flow 8 */ + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), ++ DEVICE_FLG(0x1397, 0x0510, /* Behringer UV1 */ ++ QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ diff --git a/pkgbuilds/linux-omarchy-bore/0522-sound-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-bore/0522-sound-fixes-3.patch.sig new file mode 100644 index 0000000..2f7746c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0522-sound-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0523-asoc-fixes-2.patch b/pkgbuilds/linux-omarchy-bore/0523-asoc-fixes-2.patch new file mode 100644 index 0000000..b65c5d4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0523-asoc-fixes-2.patch @@ -0,0 +1,1040 @@ +diff --git a/sound/hda/core/ext/controller.c b/sound/hda/core/ext/controller.c +--- a/sound/hda/core/ext/controller.c ++++ b/sound/hda/core/ext/controller.c +@@ -90,8 +90,10 @@ int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus) + + for (idx = 0; idx < link_count; idx++) { + hlink = kzalloc_obj(*hlink); +- if (!hlink) ++ if (!hlink) { ++ snd_hdac_ext_link_free_all(bus); + return -ENOMEM; ++ } + hlink->index = idx; + hlink->bus = bus; + hlink->ml_addr = bus->mlcap + AZX_ML_BASE + +diff --git a/sound/hda/core/ext/stream.c b/sound/hda/core/ext/stream.c +--- a/sound/hda/core/ext/stream.c ++++ b/sound/hda/core/ext/stream.c +@@ -102,8 +102,10 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + + for (i = 0; i < num_stream; i++) { + struct hdac_ext_stream *hext_stream = kzalloc_obj(*hext_stream); +- if (!hext_stream) ++ if (!hext_stream) { ++ snd_hdac_ext_stream_free_all(bus); + return -ENOMEM; ++ } + tag = ++stream_tag; + snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag); + idx++; +@@ -111,7 +113,6 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + } + + return 0; +- + } + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "RB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN17-41"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -899,6 +906,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "TM2423"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "HP 255R 15.6 inch G10 Notebook PC"), ++ } ++ }, + {} + }; + +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,17 +14,14 @@ + * for ST-Ericsson. + */ + +-#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include +-#include + #include + #include + #include +@@ -54,32 +51,9 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + +-/* Nr of FIR/IIR-coeff banks in ANC-block */ +-#define AB8500_NR_OF_ANC_COEFF_BANKS 2 +- +-/* Minimum duration to keep ANC IIR Init bit high or +-low before proceeding with the configuration sequence */ +-#define AB8500_ANC_SM_DELAY 2000 +- +-/* Sidetone states */ +-static const char * const enum_sid_state[] = { +- "Unconfigured", +- "Apply FIR", +- "FIR is configured", +-}; +-enum sid_state { +- SID_UNCONFIGURED = 0, +- SID_APPLY_FIR = 1, +- SID_FIR_CONFIGURED = 2, +-}; +- + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; +- struct mutex ctrl_lock; +- +- /* Sidetone */ +- enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -642,9 +616,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + +- SND_SOC_DAPM_INPUT("ANC Configure Input"), +- SND_SOC_DAPM_OUTPUT("ANC Configure Output"), +- + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -687,10 +658,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + +- /* ANC Configure */ +- {"ANC Configure Input", NULL, "Main Supply"}, +- {"ANC Configure Output", NULL, "ANC Configure Input"}, +- + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -972,75 +939,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + +-/* +- * Control-events +- */ +- +-static int sid_status_control_get(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- +- guard(mutex)(&drvdata->ctrl_lock); +- ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- +- return 0; +-} +- +-/* Write sidetone FIR-coefficients configuration sequence */ +-static int sid_status_control_put(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- unsigned int param, sidconf; +- int status = 1; +- +- dev_dbg(component->dev, "%s: Enter\n", __func__); +- +- if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { +- dev_err(component->dev, +- "%s: ERROR: This control supports '%s' only!\n", +- __func__, enum_sid_state[SID_APPLY_FIR]); +- return -EIO; +- } +- +- guard(mutex)(&drvdata->ctrl_lock); +- +- sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); +- if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +- if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { +- dev_err(component->dev, "%s: Sidetone busy while off!\n", +- __func__); +- status = -EPERM; +- } else { +- status = -EBUSY; +- } +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- return status; +- } +- +- snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +- +- for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { +- snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); +- snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); +- } +- +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), +- BIT(AB8500_SIDFIRADR_FIRSIDSET)); +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); +- +- drvdata->sid_status = SID_FIR_CONFIGURED; +- +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- +- return status; +-} +- + /* + * Controls - Non-DAPM ASoC + */ +@@ -1324,9 +1222,6 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + +-/* Sidetone */ +-static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); +- + /* ANC */ + + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1617,8 +1512,6 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ +- SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, +- sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2148,10 +2041,8 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + + static int ab8500_codec_probe(struct snd_soc_component *component) + { +- struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2184,10 +2075,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + +- snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); +- +- mutex_init(&drvdata->ctrl_lock); +- + return status; + } + +@@ -2216,7 +2103,6 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; +- drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -386,11 +386,8 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); + +- if (!cs35l56->base.init_done) +- return 0; +- +- /* runtime_resume unmasks the interrupt */ +- cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->sdw_attached) ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -18,9 +18,11 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include + #include + #include +@@ -37,6 +39,13 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + ++/* ++ * snd_soc_register_component() can call component_probe() on all instances ++ * in a card, so deferred registration must be protected across all instances. ++ */ ++static DEFINE_MUTEX(cs35l56_component_register_lock); ++static bool cs35l56_shutting_down; ++ + void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* +@@ -1957,6 +1966,11 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); + int ret; + ++ guard(mutex)(&cs35l56_component_register_lock); ++ ++ if (cs35l56_shutting_down) ++ return; ++ + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2217,6 +2231,37 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif + ++static int cs35l56_reboot_notify(struct notifier_block *nb, ++ unsigned long action, void *data) ++{ ++ guard(mutex)(&cs35l56_component_register_lock); ++ cs35l56_shutting_down = true; ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block cs35l56_reboot_notifier = { ++ .notifier_call = cs35l56_reboot_notify, ++}; ++ ++static int __init cs35l56_modinit(void) ++{ ++ /* ++ * Use reboot notifier to prevent race between shutdown and ++ * snd_soc_register_component(). Driver shutdown() callback would ++ * run too late, after device_shutdown() is already walking the ++ * device list that component registration can modify. ++ */ ++ return register_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_init(cs35l56_modinit); ++ ++static void __exit cs35l56_modexit(void) ++{ ++ unregister_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_exit(cs35l56_modexit); ++ + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -32,6 +32,7 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; ++ struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -41,6 +42,7 @@ struct cs35l56_private { + const char *fallback_fw_suffix; + bool soft_resetting; + bool sdw_attached; ++ bool component_registered; + struct completion init_completion; + + int speaker_id; +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -26,6 +26,7 @@ struct es8326_priv { + struct snd_soc_component *component; + struct delayed_work jack_detect_work; + struct delayed_work button_press_work; ++ struct delayed_work capture_pop_work; + struct snd_soc_jack *jack; + int irq; + /* The lock protects the situation that an irq is generated +@@ -628,6 +629,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, + 0x30, 0x00); + } else { ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, + 0x0F, 0x0F); + if (es8326->version > ES8326_VERSION_B) { +@@ -666,8 +668,9 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x70); + regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x00); + } +- regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, +- 0x0F, 0x00); ++ ++ queue_delayed_work(system_dfl_wq, &es8326->capture_pop_work, ++ msecs_to_jiffies(40)); + } + } + return 0; +@@ -773,6 +776,15 @@ static void es8326_disable_micbias(struct snd_soc_component *component) + snd_soc_dapm_mutex_unlock(dapm); + } + ++static void es8326_capture_pop_handler(struct work_struct *work) ++{ ++ struct es8326_priv *es8326 = ++ container_of(work, struct es8326_priv, capture_pop_work.work); ++ ++ regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, ++ 0x0F, 0x00); ++} ++ + /* + * For button detection, set the following in soundcard + * snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); +@@ -1140,6 +1152,7 @@ static int es8326_suspend(struct snd_soc_component *component) + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&es8326->jack_detect_work); ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + es8326_disable_micbias(component); + es8326->calibrated = false; + regmap_write(es8326->regmap, ES8326_CLK_MUX, 0x2d); +@@ -1291,6 +1304,8 @@ static int es8326_i2c_probe(struct i2c_client *i2c) + es8326_jack_detect_handler); + INIT_DELAYED_WORK(&es8326->button_press_work, + es8326_jack_button_handler); ++ INIT_DELAYED_WORK(&es8326->capture_pop_work, ++ es8326_capture_pop_handler); + /* ES8316 is level-based while ES8326 is edge-based */ + ret = devm_request_threaded_irq(&i2c->dev, es8326->irq, NULL, es8326_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -812,6 +812,23 @@ static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static void es8389_standby(struct snd_soc_component *component) ++{ ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); ++ regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); ++ usleep_range(70000, 72000); ++ regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); ++ regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); ++ regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); ++ regmap_write(es8389->regmap, ES8389_RESET, 0x3E); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); ++ usleep_range(8000, 8500); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); ++} ++ + static int es8389_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) + { +@@ -834,18 +851,7 @@ static int es8389_set_bias_level(struct snd_soc_component *component, + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); +- regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); +- usleep_range(70000, 72000); +- regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); +- regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); +- regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); +- regmap_write(es8389->regmap, ES8389_RESET, 0x3E); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); +- usleep_range(8000, 8500); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); +- ++ es8389_standby(component); + clk_disable_unprepare(es8389->mclk); + break; + case SND_SOC_BIAS_OFF: +@@ -1015,7 +1021,7 @@ static int es8389_suspend(struct snd_soc_component *component) + { + struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); + +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + regcache_cache_only(es8389->regmap, true); + regcache_mark_dirty(es8389->regmap); + +@@ -1084,7 +1090,7 @@ static int es8389_probe(struct snd_soc_component *component) + + es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include "rt1318-sdw.h" + +@@ -564,11 +565,10 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1318_sdw_priv *rt1318 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -582,23 +582,13 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt1318->sdw_slave, &stream_config, +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -3246,7 +3246,7 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1320_sdw_priv *rt1320 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + struct sdw_port_config dmic_port_config[2]; + struct sdw_stream_runtime *sdw_stream; +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include "rt712-sdca.h" + +@@ -1449,11 +1450,10 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); +@@ -1471,7 +1471,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT712_AIF1) + port = 1; + else if (dai->id == RT712_AIF2) +@@ -1479,7 +1478,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT712_AIF1) + port = 4; + else if (dai->id == RT712_AIF3) +@@ -1488,13 +1486,8 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt712->slave, &stream_config, +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + #include + + #include "rt721-sdca.h" +@@ -206,6 +207,7 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); ++ regmap_write(rt721->regmap, 0x2f5d, 0x1); + } + + static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) +@@ -1268,11 +1270,10 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt721_sdca_priv *rt721 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1291,7 +1292,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT721_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT721_AIF1) + port = 1; + else if (dai->id == RT721_AIF2) +@@ -1299,7 +1299,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT721_AIF1) + port = 2; + else if (dai->id == RT721_AIF3) +@@ -1307,13 +1306,9 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt721->slave, &stream_config, +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #include + + #include "rt722-sdca.h" +@@ -1442,11 +1443,10 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1465,7 +1465,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT722_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT722_AIF1) + port = 1; + else if (dai->id == RT722_AIF2) +@@ -1473,7 +1472,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT722_AIF1) + port = 2; + else if (dai->id == RT722_AIF3) +@@ -1481,13 +1479,9 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt722->slave, &stream_config, +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1001,6 +1001,31 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, + &stream_config, &port_config); ++ ++ /* ++ * The two mono amps each render one channel of the stereo stream: ++ * snd_sdw_params_to_config() hands every codec the full mask for ++ * playback, which leaves the pair in mirror mode and one channel ++ * unreproduced. Claim a single channel instead, keyed off the ++ * machine-assigned component prefix rather than the SoundWire ++ * address, which is board-specific: soc_sdw_ti_amp.c names the amps ++ * tas2783-1..4. ++ * ++ * Which side an amp then renders does not follow from the bit that ++ * is set - sdw_compute_slave_ports() advances the payload offset by ++ * the popcount of ch_mask and never looks at which bit it is - but ++ * from the amp's position in the codec order of the DAI link, which ++ * on these boards matches the prefix numbering. ++ */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && ++ params_channels(params) == 2 && component->name_prefix) { ++ const char *idx_str = strrchr(component->name_prefix, '-'); ++ unsigned long idx; ++ ++ if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) ++ port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); ++ } ++ + /* port 1 for playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port_config.num = 1; +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -15,6 +15,22 @@ + #include "../../../codecs/hda.h" + #include "../utils.h" + ++static int avs_link_startup(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ const struct snd_soc_pcm_stream *stream_info; ++ struct snd_soc_dai *codec_dai; ++ ++ codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ stream_info = snd_soc_dai_get_pcm_stream(codec_dai, substream->stream); ++ ++ return snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, stream_info->sig_bits); ++} ++ ++static const struct snd_soc_ops avs_link_ops = { ++ .startup = avs_link_startup, ++}; ++ + static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, + struct snd_soc_dai_link **links) + { +@@ -43,6 +59,7 @@ static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int + dl[i].platforms = platform; + dl[i].num_platforms = 1; + dl[i].ignore_pmdown_time = 1; ++ dl[i].ops = &avs_link_ops; + + dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -92,16 +92,28 @@ static int avs_hdac_bus_init_streams(struct hdac_bus *bus) + { + unsigned int cp_streams, pb_streams; + unsigned int gcap; ++ int ret; + + gcap = snd_hdac_chip_readw(bus, GCAP); + cp_streams = (gcap >> 8) & 0x0F; + pb_streams = (gcap >> 12) & 0x0F; + bus->num_streams = cp_streams + pb_streams; + +- snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); +- snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ ret = snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); ++ if (ret) ++ return ret; ++ ret = snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ if (ret) ++ goto err; + +- return snd_hdac_bus_alloc_stream_pages(bus); ++ ret = snd_hdac_bus_alloc_stream_pages(bus); ++ if (ret) ++ goto err; ++ ++ return 0; ++err: ++ snd_hdac_ext_stream_free_all(bus); ++ return ret; + } + + static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) +@@ -383,6 +395,18 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + struct device *dev = &pci->dev; + int ret; + ++ ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); ++ if (!ipc) ++ return -ENOMEM; ++ ++ adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); ++ if (!adev->modcfg_buf) ++ return -ENOMEM; ++ ++ ret = avs_ipc_init(ipc, dev); ++ if (ret < 0) ++ return ret; ++ + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); + if (ret < 0) + return ret; +@@ -394,17 +418,6 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + bus->mixer_assigned = -1; + mutex_init(&bus->prepare_mutex); + +- ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); +- if (!ipc) +- return -ENOMEM; +- ret = avs_ipc_init(ipc, dev); +- if (ret < 0) +- return ret; +- +- adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); +- if (!adev->modcfg_buf) +- return -ENOMEM; +- + adev->dev = dev; + adev->spec = (const struct avs_spec *)id->driver_data; + adev->ipc = ipc; +@@ -456,13 +469,14 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + + ret = pcim_request_all_regions(pci, "AVS HDAudio"); + if (ret < 0) +- return ret; ++ goto err_request_regions; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (!bus->remap_addr) { + dev_err(bus->dev, "ioremap error\n"); +- return -ENXIO; ++ ret = -ENXIO; ++ goto err_request_regions; + } + + adev->dsp_ba = pci_ioremap_bar(pci, 4); +@@ -526,6 +540,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + iounmap(adev->dsp_ba); + err_remap_bar4: + iounmap(bus->remap_addr); ++err_request_regions: ++ snd_hdac_ext_bus_exit(bus); + return ret; + } + +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,20 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) +- return -EBUSY; ++ if (kfifo_initialized(&adev->trace_fifo)) { ++ ret = -EBUSY; ++ goto err; ++ } + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- return ret; ++ goto err; + + file->private_data = adev; + return 0; ++err: ++ module_put(adev->dev->driver->owner); ++ return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -172,7 +172,7 @@ static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg + + /* Avoid deadlock as the exception may be the response to SET_D0IX. */ + if (current_work() != &ipc->d0ix_work.work) +- cancel_delayed_work_sync(&ipc->d0ix_work); ++ cancel_delayed_work(&ipc->d0ix_work); + ipc->in_d0ix = false; + /* Re-enabled on recovery completion. */ + pm_runtime_disable(adev->dev); +@@ -395,11 +395,11 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + struct avs_ipc *ipc = adev->ipc; + int ret; + ++ guard(mutex)(&ipc->msg_mutex); ++ + if (!ipc->ready) + return -EPERM; + +- guard(mutex)(&ipc->msg_mutex); +- + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); + avs_dsp_send_tx(adev, request, true); +diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig +--- a/sound/soc/intel/boards/Kconfig ++++ b/sound/soc/intel/boards/Kconfig +@@ -533,12 +533,14 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH + select SND_SOC_CS35L56_SPI + select SND_SOC_CS35L56_SDW + select SND_SOC_ES9356 ++ imply SND_SOC_TAC5XX2_SDW + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + imply SND_SOC_SDW_MOCKUP + help + Add support for Intel SoundWire-based platforms connected to +- MAX98373, RT700, RT711, RT1308 and RT715 ++ MAX98373, RT700, RT711, RT1308, RT715, TAC5XX2_SDW family (including ++ TAC5572, TAC5682). + If unsure select "N". + + endif +diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c +--- a/sound/soc/intel/boards/sof_rt5682.c ++++ b/sound/soc/intel/boards/sof_rt5682.c +@@ -908,6 +908,14 @@ static const struct platform_device_id board_ids[] = { + SOF_SSP_PORT_BT_OFFLOAD(2) | + SOF_BT_OFFLOAD_PRESENT), + }, ++ { ++ .name = "nvl_rt5682_def", ++ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | ++ SOF_SSP_PORT_CODEC(0) | ++ SOF_SSP_PORT_AMP(1) | ++ SOF_SSP_PORT_BT_OFFLOAD(2) | ++ SOF_BT_OFFLOAD_PRESENT), ++ }, + { + .name = "ptl_rt5682_c1_h02", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | +diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +@@ -51,10 +51,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { + }, + { + .comp_ids = &nvl_rt5682_rt5682s_hp, +- .drv_name = "sof_rt5682", +- .sof_tplg_filename = "sof-nvl-rt5682", /* the tplg suffix is added at run time */ +- .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | +- SND_SOC_ACPI_TPLG_INTEL_SSP_MSB, ++ .drv_name = "nvl_rt5682_def", ++ .sof_tplg_filename = "sof-nvl", /* the tplg suffix is added at run time */ ++ .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_AMP_NAME | ++ SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME, + }, + /* place amp/hdmi-in only boards in the end of table */ + { diff --git a/pkgbuilds/linux-omarchy-bore/0523-asoc-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-bore/0523-asoc-fixes-2.patch.sig new file mode 100644 index 0000000..906bd76 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0523-asoc-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0524-sound-fixes-4.patch b/pkgbuilds/linux-omarchy-bore/0524-sound-fixes-4.patch new file mode 100644 index 0000000..39c0ebf --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0524-sound-fixes-4.patch @@ -0,0 +1,135 @@ +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -364,7 +364,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, + sound_debugfs_root); + #endif + #ifdef CONFIG_SND_CTL_DEBUG +- card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL); ++ card->value_buf = kmalloc_obj(*card->value_buf); + if (!card->value_buf) + return -ENOMEM; + #endif +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4304,6 +4304,7 @@ enum { + ALC287_FIXUP_LEGION_16ACHG6, + ALC287_FIXUP_CS35L41_I2C_2, + ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, ++ ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, + ALC287_FIXUP_CS35L41_I2C_4, + ALC245_FIXUP_CS35L41_SPI_1, + ALC245_FIXUP_CS35L41_SPI_2, +@@ -6615,6 +6616,12 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_MUTE_LED, + }, ++ [ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = cs35l41_fixup_i2c_two, ++ .chained = true, ++ .chain_id = ALC245_FIXUP_HP_X360_MUTE_LEDS, ++ }, + [ALC287_FIXUP_CS35L41_I2C_4] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, +@@ -7372,6 +7379,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), + SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), ++ SND_PCI_QUIRK(0x103c, 0x8259, "HP OMEN 15-ax202nf", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), +@@ -7582,6 +7590,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), +@@ -7674,7 +7683,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8dcd, "HP Victus 15-fa2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8d9b, "HP 17 Turbine OmniBook 7 UMA", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -449,7 +449,9 @@ static void push_back_to_ready_list(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) + { + guard(spinlock_irqsave)(&ep->lock); +- list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); ++ /* ctx may still be linked: a stale completion racing a stop/restart. */ ++ if (list_empty(&ctx->ready_list)) ++ list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + } + + /* +@@ -1037,6 +1039,7 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) + */ + static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + { ++ struct snd_urb_ctx *ctx, *n; + unsigned int i; + + if (!force && atomic_read(&ep->running)) +@@ -1046,7 +1049,9 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + return 0; + + scoped_guard(spinlock_irqsave, &ep->lock) { +- INIT_LIST_HEAD(&ep->ready_playback_urbs); ++ /* Unlink each ctx; INIT_LIST_HEAD() alone would leave them looking linked. */ ++ list_for_each_entry_safe(ctx, n, &ep->ready_playback_urbs, ready_list) ++ list_del_init(&ctx->ready_list); + ep->next_packet_head = 0; + ep->next_packet_queued = 0; + } +diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c +--- a/sound/usb/implicit.c ++++ b/sound/usb/implicit.c +@@ -76,6 +76,7 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { + + /* Implicit feedback quirk table for capture: only FIXED type */ + static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { ++ IMPLICIT_FB_FIXED_DEV(0x1397, 0x0004, 0x01, 1), /* Behringer FCA1616 */ + {} /* terminator */ + }; + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -531,6 +531,15 @@ static const struct usbmix_name_map audient_id24_map[] = { + {} + }; + ++/* ++ * The GC553Pro returns no data for GET_CUR on its advertised mute control. ++ * SET_CUR succeeds but does not mute capture, so skip the control entirely. ++ */ ++static const struct usbmix_name_map avermedia_gc553pro_map[] = { ++ { 3, NULL, UAC_FU_MUTE }, ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -577,6 +586,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0763, 0x2031), + .selector_map = c400_selectors, + }, ++ { ++ .id = USB_ID(0x07ca, 0x1553), ++ .map = avermedia_gc553pro_map, ++ }, + { + .id = USB_ID(0x08bb, 0x2702), + .map = linex_map, diff --git a/pkgbuilds/linux-omarchy-bore/0524-sound-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-bore/0524-sound-fixes-4.patch.sig new file mode 100644 index 0000000..ef2122d Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0524-sound-fixes-4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0525-asoc-fixes-3.patch b/pkgbuilds/linux-omarchy-bore/0525-asoc-fixes-3.patch new file mode 100644 index 0000000..843a983 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0525-asoc-fixes-3.patch @@ -0,0 +1,549 @@ +diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h +--- a/include/sound/soc_sdw_utils.h ++++ b/include/sound/soc_sdw_utils.h +@@ -250,8 +250,6 @@ int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, +- struct snd_soc_dai *dai); + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix); + + /* MAXIM codec support */ +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -390,7 +390,7 @@ static int mc_probe(struct platform_device *pdev) + ctx->private = amd_ctx; + card = &ctx->card; + card->dev = &pdev->dev; +- card->name = "amd-soundwire"; ++ card->name = "amd-sdw"; + card->owner = THIS_MODULE; + card->late_probe = asoc_sdw_card_late_probe; + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -524,13 +524,13 @@ config SND_SOC_ADAU1977 + tristate + + config SND_SOC_ADAU1977_SPI +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - SPI" + depends on SPI_MASTER + select SND_SOC_ADAU1977 + select REGMAP_SPI + + config SND_SOC_ADAU1977_I2C +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - I2C" + depends on I2C + select SND_SOC_ADAU1977 + select REGMAP_I2C +diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c +--- a/sound/soc/codecs/adau1977-i2c.c ++++ b/sound/soc/codecs/adau1977-i2c.c +@@ -34,9 +34,18 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { + }; + MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); + ++static const struct of_device_id adau1977_i2c_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, adau1977_i2c_of_match); ++ + static struct i2c_driver adau1977_i2c_driver = { + .driver = { + .name = "adau1977", ++ .of_match_table = adau1977_i2c_of_match, + }, + .probe = adau1977_i2c_probe, + .id_table = adau1977_i2c_ids, +diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c +--- a/sound/soc/codecs/adau1977-spi.c ++++ b/sound/soc/codecs/adau1977-spi.c +@@ -53,18 +53,18 @@ static const struct spi_device_id adau1977_spi_ids[] = { + }; + MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); + +-static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = { +- { .compatible = "adi,adau1977" }, +- { .compatible = "adi,adau1978" }, +- { .compatible = "adi,adau1979" }, +- { }, ++static const struct of_device_id adau1977_spi_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, + }; + MODULE_DEVICE_TABLE(of, adau1977_spi_of_match); + + static struct spi_driver adau1977_spi_driver = { + .driver = { + .name = "adau1977", +- .of_match_table = of_match_ptr(adau1977_spi_of_match), ++ .of_match_table = adau1977_spi_of_match, + }, + .probe = adau1977_spi_probe, + .id_table = adau1977_spi_ids, +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -18,12 +18,16 @@ + static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201a ... 0x201f: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2230 ... 0x2232: + case 0x2f01 ... 0x2f0a: + case 0x2f35 ... 0x2f36: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x2f50: + case 0x2f54: + case 0x2f58 ... 0x2f5d: +@@ -48,6 +52,7 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201b: + case 0x201c: + case 0x201d: +@@ -56,6 +61,9 @@ static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + case 0x2230: + case 0x2f01: + case 0x2f35: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x320c: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT_GE49, RT712_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUNC_NUM_HID, RT712_SDCA_ENT_HID01, RT712_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -73,14 +73,57 @@ static int rt712_sdca_index_update_bits(struct rt712_sdca_priv *rt712, + return rt712_sdca_index_write(rt712, nid, reg, tmp); + } + ++static void rt712_sdca_clk_patch(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0017); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0001); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0081); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0080); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0010); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++} ++ ++static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0x0800, ++ 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0xf000, ++ 0x0000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x0c, 0xc000, ++ 0xc000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x00, 0xc000, ++ 0xc000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++} ++ + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + { + unsigned int val, loop_rc = 0, loop_dc = 0; + struct device *dev; + struct regmap *regmap = rt712->regmap; ++ unsigned int clk_base; + int chk_cnt = 100; + int ret = 0; + ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ + mutex_lock(&rt712->calibrate_mutex); + dev = regmap_get_device(regmap); + +@@ -109,8 +152,35 @@ static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + if (ret < 0) + goto _cali_fail_; + } +- if (loop_dc == chk_cnt) +- dev_err(dev, "%s, calibration time-out!\n", __func__); ++ ++ if (loop_dc == chk_cnt) { ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_FSM_CTL, 0x4100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0x7883); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0xf893); ++ rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ for (loop_dc = 0; loop_dc < chk_cnt && ++ (val & RT712_DAC_DC_CALI_TRIGGER); loop_dc++) { ++ usleep_range(10000, 11000); ++ ret = rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ if (ret < 0) ++ goto _cali_fail_; ++ } ++ ++ if (loop_dc == chk_cnt) ++ dev_err(dev, "%s, calibration time-out!\n", __func__); ++ else ++ dev_dbg(dev, "%s, calibration success!\n", __func__); ++ } + + if (loop_dc == chk_cnt || loop_rc == chk_cnt) + ret = -ETIMEDOUT; +@@ -1759,9 +1829,13 @@ static void rt712_sdca_va_io_init(struct rt712_sdca_priv *rt712) + + static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + { +- int ret = 0; + unsigned int jack_func_status, mic_func_status, amp_func_status; + struct device *dev = &rt712->slave->dev; ++ unsigned int clk_base; ++ int ret = 0; ++ ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ dev_dbg(dev, "%s clk_base=%x", __func__, clk_base); + + regmap_read(rt712->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT0, RT712_SDCA_CTL_FUNC_STATUS, 0), &jack_func_status); +@@ -1773,6 +1847,12 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + __func__, jack_func_status, mic_func_status, amp_func_status); + + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); ++ ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -162,6 +162,16 @@ struct rt712_dmic_kctrl_priv { + #define RT712_EAPD_HIGH 0x2 + #define RT712_EAPD_LOW 0x0 + ++/* SDW clock root frequency */ ++#define RT712_SDW_ROOT_CLK 0x004d ++#define RT712_SDW_SCALE_CLK0 0x0062 ++#define RT712_SDW_SCALE_CLK1 0x0072 ++ ++/* PLL2 config */ ++#define RT712_PLL2_CONF1 0x2f3a ++#define RT712_PLL2_CONF2 0x2f3d ++#define RT712_PLL2_CONF3 0x2f41 ++ + /* RC Calibration register */ + #define RT712_RC_CAL 0x3201 + +@@ -254,6 +264,14 @@ enum rt712_sdca_version { + RT712_VB, + }; + ++enum { ++ RT712_CLK_FREQ_19_2_MHZ = 1, ++ RT712_CLK_FREQ_24MHZ = 2, ++ RT712_CLK_FREQ_24_576MHZ = 3, ++ RT712_CLK_FREQ_22_5792MHZ = 4, ++}; ++ ++ + int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave); + int rt712_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave); +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -70,6 +70,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re + case 0x0310100: + case 0x2000000 ... 0x2000003: + case 0x2000013: ++ case 0x2000026: + case 0x200002c: + case 0x200003c: + case 0x2000046: +@@ -142,6 +143,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x200000d: + case 0x2000019: + case 0x2000020: ++ case 0x2000026: + case 0x200002c: + case 0x2000030: + case 0x2000046: +@@ -155,6 +157,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x5810039: + case 0x5b10018: + case 0x5b10019: ++ case 0x6100006: + return true; + default: + return false; +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -1497,6 +1497,15 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, + &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); + } + ++static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) ++{ ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_REG, ++ RT721_VD_HIDDEN_CTRL, RT721_HIDDEN_REG_SW_RESET, ++ RT721_HIDDEN_REG_SW_RESET); ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, ++ RT721_HDA_LEGACY_RESET_CTL, 0x1, 0x1); ++} ++ + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + { + struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); +@@ -1530,9 +1539,17 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + } + + pm_runtime_get_noresume(&slave->dev); ++ ++ if (!rt721->first_hw_init) ++ rt721_sdca_reset(rt721); ++ + rt721_sdca_dmic_preset(rt721); + rt721_sdca_amp_preset(rt721); + rt721_sdca_jack_preset(rt721); ++ ++ if (rt721->hs_jack && (!rt721->first_hw_init)) ++ rt721_sdca_jack_init(rt721); ++ + if (rt721->first_hw_init) { + regcache_cache_bypass(rt721->regmap, false); + regcache_mark_dirty(rt721->regmap); +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -775,9 +775,12 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, + s++; + } + ++ adsp_dbg(dsp, "Try '%s'\n", fw->filename); + ret = wm_adsp_firmware_request(&fw->firmware, fw->filename, cs_dsp->dev); + if (ret < 0) { +- adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ if (ret != -ENOENT) ++ adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ + kfree(fw->filename); + fw->filename = NULL; + if (ret != -ENOENT) +diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c +--- a/sound/soc/intel/boards/sof_es8336.c ++++ b/sound/soc/intel/boards/sof_es8336.c +@@ -360,6 +360,15 @@ static const struct dmi_system_id sof_es8336_quirk_table[] = { + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOC_ES8336_HEADSET_MIC1) + }, ++ { ++ .callback = sof_es8336_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "NDZ-WXX9"), ++ }, ++ .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | ++ SOC_ES8336_HEADSET_MIC1) ++ }, + {} + }; + +diff --git a/sound/soc/sdw_utils/soc_sdw_cs_amp.c b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_cs_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +@@ -14,7 +14,6 @@ + #include + #include + +-#define CS_AMP_CHANNELS_PER_AMP 4 + #define CS35L56_SPK_VOLUME_0DB 400 /* 0dB Max */ + + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix) +@@ -64,51 +63,6 @@ int asoc_sdw_cs_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai + } + EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_rtd_init, "SND_SOC_SDW_UTILS"); + +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +-{ +- const struct snd_soc_dai_link *dai_link = rtd->dai_link; +- const struct snd_soc_dai_link_ch_map *ch_map; +- const struct snd_soc_dai_link_component *codec_dlc; +- struct snd_soc_dai *codec_dai; +- u8 ch_slot[8] = {}; +- unsigned int amps_per_bus, ch_per_amp, mask; +- int i, ret; +- +- WARN_ON(dai_link->num_cpus > ARRAY_SIZE(ch_slot)); +- +- /* +- * CS35L56 has 4 TX channels. When the capture is aggregated the +- * same bus slots will be allocated to all the amps on a bus. Only +- * one amp on that bus can be transmitting in each slot so divide +- * the available 4 slots between all the amps on a bus. +- */ +- amps_per_bus = dai_link->num_codecs / dai_link->num_cpus; +- if ((amps_per_bus == 0) || (amps_per_bus > CS_AMP_CHANNELS_PER_AMP)) { +- dev_err(rtd->card->dev, "Illegal num_codecs:%u / num_cpus:%u\n", +- dai_link->num_codecs, dai_link->num_cpus); +- return -EINVAL; +- } +- +- ch_per_amp = CS_AMP_CHANNELS_PER_AMP / amps_per_bus; +- +- for_each_rtd_ch_maps(rtd, i, ch_map) { +- codec_dlc = snd_soc_link_to_codec(rtd->dai_link, i); +- codec_dai = snd_soc_find_dai(codec_dlc); +- mask = GENMASK(ch_per_amp - 1, 0) << ch_slot[ch_map->cpu]; +- +- ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, mask, 4, 32); +- if (ret < 0) { +- dev_err(rtd->card->dev, "Failed to set TDM slot:%d\n", ret); +- return ret; +- } +- +- ch_slot[ch_map->cpu] += ch_per_amp; +- } +- +- return 0; +-} +-EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_feedback_rtd_init, "SND_SOC_SDW_UTILS"); +- + int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -811,7 +811,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -840,7 +839,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -869,7 +867,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -898,7 +895,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -1565,7 +1561,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link_ch_map *ch_maps; + int ch = params_channels(params); +- unsigned int ch_mask; ++ unsigned int cpu_ch_mask, codec_ch_mask; + int num_codecs; + int step; + int i; +@@ -1575,8 +1571,9 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + + /* Identical data will be sent to all codecs in playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- ch_mask = GENMASK(ch - 1, 0); ++ cpu_ch_mask = GENMASK(ch - 1, 0); + step = 0; ++ codec_ch_mask = 0; + } else { + num_codecs = rtd->dai_link->num_codecs; + +@@ -1586,17 +1583,24 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- ch_mask = GENMASK(ch / num_codecs - 1, 0); +- step = hweight_long(ch_mask); ++ cpu_ch_mask = GENMASK(ch / num_codecs - 1, 0); ++ step = hweight_long(cpu_ch_mask); ++ codec_ch_mask = cpu_ch_mask; + } + + /* + * The captured data will be combined from each cpu DAI if the dai + * link has more than one codec DAIs. Set codec channel mask and + * ASoC will set the corresponding channel numbers for each cpu dai. ++ * ++ * sdw_stream_add_slave() assigns different payload offsets to each ++ * codec in a capture stream, so that the same channels on each ++ * codec map to different channels on the CPU. + */ +- for_each_link_ch_maps(rtd->dai_link, i, ch_maps) +- ch_maps->cpu_ch_mask = ch_mask << (i * step); ++ for_each_link_ch_maps(rtd->dai_link, i, ch_maps) { ++ ch_maps->cpu_ch_mask = cpu_ch_mask << (i * step); ++ ch_maps->codec_ch_mask = codec_ch_mask; ++ } + + return 0; + } diff --git a/pkgbuilds/linux-omarchy-bore/0525-asoc-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-bore/0525-asoc-fixes-3.patch.sig new file mode 100644 index 0000000..e53ea2a Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0525-asoc-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0526-sof-amd-acp7x-suspend-unknown-revision.patch b/pkgbuilds/linux-omarchy-bore/0526-sof-amd-acp7x-suspend-unknown-revision.patch new file mode 100644 index 0000000..3b71148 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0526-sof-amd-acp7x-suspend-unknown-revision.patch @@ -0,0 +1,13 @@ +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 +@@ -1197,7 +1197,8 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) + enable = true; + break; + default: +- break; ++ dev_err(sdev->dev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); ++ return -EINVAL; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); diff --git a/pkgbuilds/linux-omarchy-bore/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig b/pkgbuilds/linux-omarchy-bore/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig new file mode 100644 index 0000000..bb0d2f1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0527-sound-fixes-5.patch b/pkgbuilds/linux-omarchy-bore/0527-sound-fixes-5.patch new file mode 100644 index 0000000..f9378d4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0527-sound-fixes-5.patch @@ -0,0 +1,513 @@ +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -340,6 +340,15 @@ static inline void snd_card_unref(struct snd_card *card) + + DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T)) + ++/* ++ * For automatic error handling at probe time, assign the card like: ++ * struct snd_card *card __free(snd_card_free) = NULL; ++ * then create and process as usual. ++ * But, don't forget to clear to NULL at successful return for avoiding ++ * the unexpected cleanup! ++ */ ++DEFINE_FREE(snd_card_free, struct snd_card *, if (_T) snd_card_free(_T)) ++ + #define snd_card_set_dev(card, devptr) ((card)->dev = (devptr)) + + /* device.c */ +diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c +--- a/sound/core/seq/seq_compat.c ++++ b/sound/core/seq/seq_compat.c +@@ -44,7 +44,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned + return -EFAULT; + data->kernel = NULL; + +- err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ scoped_guard(mutex, &client->ioctl_mutex) { ++ err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ } + if (err < 0) + return err; + +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4025,6 +4025,7 @@ enum { + ALC269_FIXUP_SONY_VAIO, + ALC275_FIXUP_SONY_VAIO_GPIO2, + ALC269_FIXUP_DELL_M101Z, ++ ALC256_FIXUP_ASUS_PM3406CHA, + ALC269_FIXUP_SKU_IGNORE, + ALC269_FIXUP_ASUS_G73JW, + ALC269_FIXUP_ASUS_N7601ZM_PINS, +@@ -4124,6 +4125,7 @@ enum { + ALC285_FIXUP_HP_SPECTRE_X360_EB1, + ALC285_FIXUP_HP_SPECTRE_X360_DF1, + ALC285_FIXUP_HP_ENVY_X360, ++ ALC285_FIXUP_HP_ENVY_13AQ, + ALC288_FIXUP_DELL_HEADSET_MODE, + ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC288_FIXUP_DELL_XPS_13, +@@ -4199,6 +4201,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE, + ALC225_FIXUP_WYSE_AUTO_MUTE, + ALC225_FIXUP_WYSE_DISABLE_MIC_VREF, ++ ALC225_FIXUP_HP_AIO_SPK, + ALC286_FIXUP_ACER_AIO_HEADSET_MIC, + ALC256_FIXUP_ASUS_HEADSET_MIC, + ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4385,6 +4388,7 @@ enum { + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, + ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ++ ALC256_FIXUP_IPASON_SMARTBOOK_S1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4482,6 +4486,14 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC256_FIXUP_ASUS_PM3406CHA] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x7f20 }, ++ {} ++ } ++ }, + [ALC269_FIXUP_SKU_IGNORE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_sku_ignore, +@@ -5720,6 +5732,14 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, ++ [ALC225_FIXUP_HP_AIO_SPK] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x0220 }, ++ { } ++ }, ++ }, + [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { +@@ -6401,6 +6421,15 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, + }, ++ [ALC285_FIXUP_HP_ENVY_13AQ] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170150 }, /* front speakers */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, ++ }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, +@@ -7088,6 +7117,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC236_FIXUP_DELL_HP_POP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_invalidate_dacs, ++ .chained = true, ++ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC274_FIXUP_HP_89E9_GPIO] = { + .type = HDA_FIXUP_FUNC, +@@ -7150,6 +7181,13 @@ static const struct hda_fixup alc269_fixups[] = { + { } + } + }, ++ [ALC256_FIXUP_IPASON_SMARTBOOK_S1] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x1b, 0x90170110 }, /* the real internal speaker */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7197,6 +7235,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), ++ SND_PCI_QUIRK(0x1025, 0x151e, "Acer Nitro AN515-45", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC287_FIXUP_ACER_MICMUTE_LED), +@@ -7210,8 +7249,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x173b, "Acer Swift SFG16-72", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), +@@ -7317,6 +7358,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), + SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x2164, "HP Pavilion 15 Notebook PC", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), +@@ -7399,9 +7441,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x854a, "HP EliteBook 830 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x854d, "HP EliteBook 840 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), ++ HDA_CODEC_QUIRK(0x103c, 0x85ba, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f1, "HP Laptop 15-dw1xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), +@@ -7409,6 +7453,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x86ad, "HP ENVY 13-aq1xxx", ALC285_FIXUP_HP_ENVY_13AQ), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), + SND_PCI_QUIRK(0x103c, 0x86c8, "HP Laptop 14s-dr1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7437,6 +7482,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x87b2, "HP Pavilion Gaming Laptop 15-ec1xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x87b7, "HP Laptop 14-fq0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87cb, "HP Pavilion 15-eg0xxx", ALC287_FIXUP_HP_GPIO_LED), +@@ -7497,6 +7543,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8975, "HP EliteBook x360 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x897d, "HP mt440 Mobile Thin Client U74", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8981, "HP Elite Dragonfly G3", ALC245_FIXUP_CS35L41_SPI_4), ++ SND_PCI_QUIRK(0x103c, 0x8987, "HP Pavilion 14-dv1001TU", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898a, "HP Pavilion 15-eg100", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898e, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x898f, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7534,7 +7581,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8a31, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a36, "HP Pavilion Plus 14-eh0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a3d, "HP Victus 15-fb0xxx (MB 8A3D)", ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT), +@@ -7595,6 +7642,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bc7, "HP Pavilion 15-eh3174nw", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bd4, "HP Victus 16-s0xxx (MB 8BD4)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), +@@ -7617,6 +7665,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c2f, "HP Victus 15-fb2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7726,6 +7775,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7829,6 +7879,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1652, "ASUS ROG Zephyrus Do 15 SE", ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), + SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), +@@ -7931,7 +7982,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x3ef0, "ASUS Strix G635LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f00, "ASUS Strix G815LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f10, "ASUS Strix G835LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x3f30, "ASUS Strix G815LR_LW", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3fd0, "ASUS B3605CVA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3ff0, "ASUS B5405CVA", ALC245_FIXUP_CS35L41_SPI_2), +@@ -7954,6 +8005,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), + SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), + SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE), ++ SND_PCI_QUIRK(0x10ec, 0x111e, "Higole F9B", ALC290_FIXUP_MONO_SPEAKERS), + SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), + SND_PCI_QUIRK(0x10ec, 0x119e, "Positivo SU C1400", ALC269_FIXUP_ASPIRE_HEADSET_MIC), + SND_PCI_QUIRK(0x10ec, 0x11bc, "VAIO VJFE-IL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), +@@ -8179,7 +8231,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), +- SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8204,6 +8255,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8254,6 +8306,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38d5, "Yoga S990-16 Pro IMH YC Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d6, "Yoga S990-16 Pro IMH VECO Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d7, "Lenovo Yoga 9 14IMH9", ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN), ++ SND_PCI_QUIRK(0x17aa, 0x38dc, "Lenovo ThinkBook G8", ALC285_FIXUP_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +@@ -8273,7 +8326,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), + SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), +@@ -8369,6 +8422,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x2782, 0x0206, "IPASON SmartBook S1", ALC256_FIXUP_IPASON_SMARTBOOK_S1), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c +--- a/sound/hda/codecs/realtek/realtek.c ++++ b/sound/hda/codecs/realtek/realtek.c +@@ -1292,6 +1292,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1407,6 +1408,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_write_coef_idx(codec, 0x45, 0xc489); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); +@@ -1562,6 +1564,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) + alc_process_coef_fw(codec, coef0225); + alc_hp_enable_unmute(codec, 75); + break; ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1663,6 +1666,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1780,6 +1784,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1881,6 +1886,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + msleep(300); +diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c +--- a/sound/hda/controllers/tegra.c ++++ b/sound/hda/controllers/tegra.c +@@ -374,7 +374,8 @@ static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) + * in powers of 2, next available ratio is 16 which can be + * used as a limiting factor here. + */ +- if (of_device_is_compatible(np, "nvidia,tegra30-hda")) ++ if (of_device_is_compatible(np, "nvidia,tegra30-hda") || ++ of_device_is_compatible(np, "nvidia,tegra194-hda")) + chip->bus.core.sdo_limit = 16; + + /* codec detection */ +diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c +--- a/sound/pci/ctxfi/cthw20k2.c ++++ b/sound/pci/ctxfi/cthw20k2.c +@@ -1895,6 +1895,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + + if (hw->model == CTSB1270) { + /* Set up the PCM4220 ADC on Titanium HD */ ++ data = hw_read_20kx(hw, GPIO_DATA); + data &= ~0x0C; + if (1 == info->msr) + data |= 0x00; /* Single Speed Mode 32-50kHz */ +@@ -1903,6 +1904,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + else + data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */ + hw_write_20kx(hw, GPIO_DATA, data); ++ usleep_range(10000, 11000); + } + + hw_adc_start(hw); +diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c +--- a/sound/pci/ice1712/ice1712.c ++++ b/sound/pci/ice1712/ice1712.c +@@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) + { + static int dev; +- struct snd_card *card __free(snd_card_unref) = NULL; ++ struct snd_card *card __free(snd_card_free) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c +--- a/sound/usb/caiaq/device.c ++++ b/sound/usb/caiaq/device.c +@@ -562,6 +562,9 @@ static int snd_probe(struct usb_interface *intf, + ret = init_card(caiaqdev(card)); + if (ret < 0) { + dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret); ++#ifdef CONFIG_SND_USB_CAIAQ_INPUT ++ snd_usb_caiaq_input_disconnect(caiaqdev(card)); ++#endif + snd_card_free(card); + return ret; + } +diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c +--- a/sound/usb/line6/playback.c ++++ b/sound/usb/line6/playback.c +@@ -182,6 +182,13 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) + + fsize *= bytes_per_frame; + ++ if (fsize > line6pcm->max_packet_size_out) { ++ dev_err(line6pcm->line6->ifcdev, ++ "playback packet too large: %d > %d\n", ++ fsize, line6pcm->max_packet_size_out); ++ return -EMSGSIZE; ++ } ++ + fout->offset = urb_size; + fout->length = fsize; + urb_size += fsize; +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -1007,6 +1007,15 @@ static int detect_usb_format(struct ua101 *ua) + } + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->playback.max_packet_bytes = usb_endpoint_maxp(epd); ++ ++ if (ua->capture.max_packet_bytes / ua->capture.frame_bytes * ++ ua->playback.frame_bytes > ua->playback.max_packet_bytes) { ++ dev_err(&ua->dev->dev, ++ "playback packet size %u too small for %u capture frames\n", ++ ua->playback.max_packet_bytes, ++ ua->capture.max_packet_bytes / ua->capture.frame_bytes); ++ return -ENXIO; ++ } + return 0; + } + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -749,6 +749,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0db0, 0xa073), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, ++ { /* MSI MAG B850M MORTAR WIFI */ ++ .id = USB_ID(0x0db0, 0xcc78), ++ .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, ++ }, + { /* MSI TRX40 */ + .id = USB_ID(0x0db0, 0x543d), + .map = trx40_mobo_map, +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -1702,10 +1702,12 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); +- case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ +- return snd_usb_cm1a_boot_quirk(dev); + } + ++ /* Behringer devices may need explicit device descriptor read at boot */ ++ if (USB_ID_VENDOR(id) == 0x1397) ++ return snd_usb_cm1a_boot_quirk(dev); ++ + return 0; + } + +@@ -2366,6 +2368,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ + QUIRK_FLAG_ALWAYS_SET_RATE), ++ DEVICE_FLG(0x0b05, 0x1826, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), ++ DEVICE_FLG(0x0b05, 0x1827, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2510,8 +2516,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x262a, 0x9302, /* ddHiFi TC44C */ + QUIRK_FLAG_DSD_RAW), +- DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ +- QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x2772, 0x0502, /* Musical Fidelity M6s DAC */ + 0), /* for avoiding QUIRK_FLAG_DSD_RAW with vendor match */ + DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ +@@ -2542,6 +2546,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x3255, 0x0000, /* Luxman D-10X */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), ++ DEVICE_FLG(0x32bb, 0x0004, /* HiBy FC4 */ ++ QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x3302, 0x17c2, /* TTGK Technology USB-C Audio */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ +@@ -2608,6 +2614,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2622, /* IAG Limited devices */ + QUIRK_FLAG_DSD_RAW), ++ VENDOR_FLG(0x2708, /* Audient devices */ ++ QUIRK_FLAG_IGNORE_CTL_ERROR), + VENDOR_FLG(0x2772, /* Musical Fidelity devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x278b, /* Rotel? */ diff --git a/pkgbuilds/linux-omarchy-bore/0527-sound-fixes-5.patch.sig b/pkgbuilds/linux-omarchy-bore/0527-sound-fixes-5.patch.sig new file mode 100644 index 0000000..04aa2c3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0527-sound-fixes-5.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0528-asoc-fixes-4.patch b/pkgbuilds/linux-omarchy-bore/0528-asoc-fixes-4.patch new file mode 100644 index 0000000..2290ae4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0528-asoc-fixes-4.patch @@ -0,0 +1,523 @@ +diff --git a/drivers/spi/spi-cs42l43.c b/drivers/spi/spi-cs42l43.c +--- a/drivers/spi/spi-cs42l43.c ++++ b/drivers/spi/spi-cs42l43.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -215,12 +216,108 @@ static size_t cs42l43_spi_max_length(struct spi_device *spi) + return CS42L43_SPI_MAX_LENGTH; + } + ++/* ++ * Workaround needed for two speaker ID pins in one ACPI GpioIo() but ++ * the Linux-specific _DSD property only contains one pin. ++ * Create a temporary acpi_gpio_mapping pointing at both pins. ++ */ ++static const struct acpi_gpio_params cs42l43_2bit_speaker_id_from_one_gpioio_params[] = { ++ [0] = { ++ .crs_entry_index = 0, ++ .line_index = 0, ++ }, ++ [1] = { ++ .crs_entry_index = 0, ++ .line_index = 1, ++ }, ++}; ++ ++static const struct acpi_gpio_mapping cs42l43_2bit_speaker_id_from_one_gpioio_mapping[] = { ++ { ++ .name = "spk-id-quirk-gpios", ++ .data = cs42l43_2bit_speaker_id_from_one_gpioio_params, ++ .size = ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params), ++ }, ++ { } ++}; ++ ++static int cs42l43_get_2bit_speaker_id_from_one_gpioio(struct cs42l43_spi *priv, int *result) ++{ ++ struct fwnode_reference_args args; ++ struct acpi_device *adev; ++ struct gpio_desc *desc; ++ u32 spkid = 0; ++ int i, ret; ++ ++ /* Use the _DSD property to get the node containing the GpioIo() */ ++ ret = fwnode_property_get_reference_args(dev_fwnode(priv->dev), "spk-id-gpios", ++ NULL, 3, 0, &args); ++ if (ret) ++ return ret; ++ ++ struct fwnode_handle *fwnode __free(fwnode_handle) = args.fwnode; ++ ++ /* An acpi_gpio_mapping must be added to the node that contains the GpioIo() */ ++ adev = to_acpi_device_node(fwnode); ++ if (!adev) ++ return -EINVAL; ++ ++ ret = acpi_dev_add_driver_gpios(adev, cs42l43_2bit_speaker_id_from_one_gpioio_mapping); ++ if (ret) ++ return ret; ++ ++ /* gpiod_get_array() can't read from a mapping in a child node */ ++ for (i = 0; i < ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params); i++) { ++ desc = fwnode_gpiod_get_index(fwnode, "spk-id-quirk", i, GPIOD_IN, ++ dev_name(priv->dev)); ++ if (IS_ERR(desc)) { ++ ret = PTR_ERR(desc); ++ goto out; ++ } ++ ++ ret = gpiod_get_value_cansleep(desc); ++ gpiod_put(desc); ++ if (ret < 0) ++ goto out; ++ ++ spkid |= (u32)ret << i; ++ } ++ ++ dev_dbg(priv->dev, "spk-id-gpios = %u\n", spkid); ++ *result = spkid; ++ ret = 0; ++out: ++ acpi_dev_remove_driver_gpios(adev); ++ ++ return ret; ++} ++ ++static const struct dmi_system_id cs42l43_spk_id_quirks[] = { ++ { ++ .ident = "Dell XPS 13 DX13260", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53"), ++ }, ++ .driver_data = cs42l43_get_2bit_speaker_id_from_one_gpioio, ++ }, ++ { } ++}; ++ + static int cs42l43_get_speaker_id_gpios(struct cs42l43_spi *priv, int *result) + { ++ const struct dmi_system_id *dmi_id; + struct gpio_descs *descs; + u32 spkid; + int i, ret; + ++ dmi_id = dmi_first_match(cs42l43_spk_id_quirks); ++ if (dmi_id) { ++ int (*get_speaker_id)(struct cs42l43_spi *priv, int *result) = dmi_id->driver_data; ++ ++ return get_speaker_id(priv, result); ++ } ++ + descs = gpiod_get_array_optional(priv->dev, "spk-id", GPIOD_IN); + if (!descs) + return 0; +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -51,6 +51,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83V9"), + }, + }, ++ { ++ /* Lenovo Yoga Pro 7 14ASP9 (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83HN"), ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +@@ -63,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook S14 UM5406GA"), ++ }, ++ }, + { + /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ + .matches = { +diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig +--- a/sound/soc/amd/acp/Kconfig ++++ b/sound/soc/amd/acp/Kconfig +@@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH + select SND_SOC_RT715_SDW + select SND_SOC_RT715_SDCA_SDW + select SND_SOC_RT722_SDCA_SDW ++ select SND_SOC_TAS2783_SDW + help + This option enables Legacy(No DSP) sound card support for SoundWire + enabled AMD platforms along with ACP PDM controller. +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -748,7 +748,27 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_only[] = { + {} + }; + ++static const struct snd_soc_acpi_link_adr acp70_tas2783a_l0_rt712_vb_l1[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783_2_adr), ++ .adr_d = tas2783_2_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_1_group1_adr), ++ .adr_d = rt712_vb_1_group1_adr, ++ }, ++ {} ++}; ++ + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783a_l0_rt712_vb_l1, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0), + .links = acp70_tas2783_2, +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -248,6 +248,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "21J6"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "21JN"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -381,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83UU"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -1989,9 +1989,7 @@ config SND_SOC_RT715_SDCA_SDW + config SND_SOC_RT766_SDCA_SDW + tristate "Realtek RT766 SDCA Codec - SDW" + depends on SOUNDWIRE +- depends on SND_SOC_SDCA +- select SND_SOC_SDCA_HID +- select SND_SOC_SDCA_IRQ ++ depends on SND_SOC_SDCA_CLASS + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -1841,6 +1841,7 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ unsigned int gain; + u16 gain_reg; + u16 reg; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); +@@ -1882,6 +1883,8 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + } ++ gain = snd_soc_component_read(component, gain_reg); ++ snd_soc_component_write(component, gain_reg, gain); + wsa_macro_config_ear_spkr_gain(component, wsa, + event, gain_reg); + break; +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -200,7 +200,7 @@ static int max98363_sdw_dai_hw_params(struct snd_pcm_substream *substream, + struct max98363_priv *max98363 = + snd_soc_component_get_drvdata(component); + +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_runtime *stream; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -573,11 +574,10 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt1017_sdca_priv *rt1017 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -591,28 +591,17 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + dev_dbg(dai->dev, "frame_rate %d, ch_count %d, bps %d, direction %d, ch_mask %d, port: %d\n", +- params_rate(params), num_channels, snd_pcm_format_width(params_format(params)), +- direction, ch_mask, port); ++ stream_config.frame_rate, stream_config.ch_count, stream_config.bps, ++ stream_config.direction, port_config.ch_mask, port); + + retval = sdw_stream_add_slave(rt1017->sdw_slave, &stream_config, + &port_config, 1, sdw_stream); +diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c +--- a/sound/soc/codecs/rt274.c ++++ b/sound/soc/codecs/rt274.c +@@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, + unsigned int val = 0; + int d_len_code = 0, c_len_code = 0; + ++ /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ + switch (params_rate(params)) { +- /* bit 14 0:48K 1:44.1K */ + case 44100: ++ val |= BIT(14); ++ break; + case 48000: + break; + default: +diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c +--- a/sound/soc/codecs/rt286.c ++++ b/sound/soc/codecs/rt286.c +@@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) + + if (rt286->i2c->irq) + schedule_delayed_work(&rt286->jack_detect_work, +- msecs_to_jiffies(50)); ++ msecs_to_jiffies(1250)); + return 0; + } + +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -676,7 +676,7 @@ SOC_ENUM("DRC Decay Rate", drc_decay), + SOC_ENUM("DRC FF Delay", drc_ff_delay), + SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), + SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +-SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), ++SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_1, 6, 3, 0, drc_tlv_max), + SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), + SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), + SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c +--- a/sound/soc/intel/boards/bytcr_rt5651.c ++++ b/sound/soc/intel/boards/bytcr_rt5651.c +@@ -410,6 +410,23 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, ++ { ++ /* Chuwi Hi8 (CWI513) with a BIOS using generic DMI strings */ ++ .callback = byt_rt5651_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), ++ DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), ++ /* Above matches are too generic, add bios-date match */ ++ DMI_MATCH(DMI_BIOS_DATE, "05/21/2016"), ++ }, ++ .driver_data = (void *)(BYT_RT5651_MCLK_EN | ++ BYT_RT5651_JD1_1 | ++ BYT_RT5651_OVCD_TH_2000UA | ++ BYT_RT5651_OVCD_SF_1P0 | ++ BYT_RT5651_IN2_MAP | ++ BYT_RT5651_HP_LR_SWAPPED | ++ BYT_RT5651_MONO_SPEAKER), ++ }, + { + /* Chuwi Vi8 Plus (CWI519) */ + .callback = byt_rt5651_quirk_cb, +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -745,6 +745,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_cs42l43_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0.tplg", ++ .machine_check = snd_soc_acpi_intel_force_function_topology, + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,22 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++static const struct dmi_system_id force_function_topology_quirk_table[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83R0"), ++ }, ++ }, ++ {} ++}; ++ ++bool snd_soc_acpi_intel_force_function_topology(void *arg) ++{ ++ return !dmi_check_system(force_function_topology_quirk_table); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_force_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) + { + return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,6 +11,7 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_force_function_topology(void *arg); + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c +--- a/sound/soc/qcom/lpass-cpu.c ++++ b/sound/soc/qcom/lpass-cpu.c +@@ -1234,8 +1234,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) + } + if (drvdata->mi2s_playback_sd_mode[dai_id] == + LPAIF_I2SCTL_MODE_QUAD01) { +- variant->dai_driver[dai_id].playback.channels_min = 4; +- variant->dai_driver[dai_id].playback.channels_max = 4; ++ variant->dai_driver[i].playback.channels_min = 4; ++ variant->dai_driver[i].playback.channels_max = 4; + } + } + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -388,20 +388,27 @@ static int class_function_probe(struct auxiliary_device *auxdev, + + ret = devm_pm_runtime_enable(dev); + if (ret) +- return ret; ++ goto err_pm; + + ret = class_function_boot(drv); + if (ret) +- return ret; ++ goto err_pm; + + ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais); +- if (ret) +- return dev_err_probe(dev, ret, "failed to register component\n"); ++ if (ret) { ++ dev_err_probe(dev, ret, "failed to register component\n"); ++ goto err_pm; ++ } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; ++ ++err_pm: ++ pm_runtime_put_sync(dev); ++ ++ return ret; + } + + static void class_function_remove(struct auxiliary_device *auxdev) +@@ -490,8 +497,6 @@ static int class_function_suspend(struct device *dev) + struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); + int ret; + +- drv->suspended = true; +- + /* Ensure runtime resume runs on resume */ + ret = pm_runtime_resume_and_get(dev); + if (ret) { +@@ -499,6 +504,8 @@ static int class_function_suspend(struct device *dev) + return ret; + } + ++ drv->suspended = true; ++ + sdca_irq_disable(drv->function, drv->core->irq_info); + + ret = pm_runtime_force_suspend(dev); +diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c +--- a/sound/soc/tegra/tegra186_asrc.c ++++ b/sound/soc/tegra/tegra186_asrc.c +@@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = { + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream6 Input Threshold", +- ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, ++ ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + diff --git a/pkgbuilds/linux-omarchy-bore/0528-asoc-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-bore/0528-asoc-fixes-4.patch.sig new file mode 100644 index 0000000..e560a52 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0528-asoc-fixes-4.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 deleted file mode 100644 index 3762b2e..0000000 --- a/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index e516289..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig and /dev/null 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 index 39ccf4d..2e401f7 100644 --- a/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch +++ b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch @@ -1,7 +1,7 @@ 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[] = { +@@ -867,6 +867,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 */ 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 index 92c4d7b..818b577 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch.sig and b/pkgbuilds/linux-omarchy-bore/0541-cvs-nova-lake-acpi-id.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0543-media-laptop-cameras.patch b/pkgbuilds/linux-omarchy-bore/0543-media-laptop-cameras.patch new file mode 100644 index 0000000..9ffdd20 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0543-media-laptop-cameras.patch @@ -0,0 +1,3293 @@ +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -259,6 +259,7 @@ config VIDEO_IMX335 + + config VIDEO_IMX355 + tristate "Sony IMX355 sensor support" ++ select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX355 camera. +@@ -287,6 +288,16 @@ config VIDEO_IMX415 + To compile this driver as a module, choose M here: the + module will be called imx415. + ++config VIDEO_IMX471 ++ tristate "Sony IMX471 sensor support" ++ select V4L2_CCI_I2C ++ help ++ This is a Video4Linux2 sensor driver for the Sony ++ IMX471 camera. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called imx471. ++ + config VIDEO_MAX9271_LIB + tristate + +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -61,6 +61,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o + obj-$(CONFIG_VIDEO_IMX355) += imx355.o + obj-$(CONFIG_VIDEO_IMX412) += imx412.o + obj-$(CONFIG_VIDEO_IMX415) += imx415.o ++obj-$(CONFIG_VIDEO_IMX471) += imx471.o + obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o + obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o + obj-$(CONFIG_VIDEO_KS0127) += ks0127.o +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 +@@ -31,6 +31,7 @@ + + #define PCI_DEVICE_ID_INTEL_IPU7 0x645d /* MTL / LNL */ + #define PCI_DEVICE_ID_INTEL_IPU7P5 0xb05d /* ARL / PTL */ ++#define PCI_DEVICE_ID_INTEL_IPU8 0xd719 /* NVL */ + + /* + * IPU7 PCI device IDs not covered by ipu6_pci_tbl in ipu6-pci-table.h. +@@ -39,6 +40,7 @@ + static const struct pci_device_id icvs_ipu7_tbl[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7P5) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU8) }, + { } + }; + +diff --git a/drivers/media/i2c/cvs/icvs.h b/drivers/media/i2c/cvs/icvs.h +--- a/drivers/media/i2c/cvs/icvs.h ++++ b/drivers/media/i2c/cvs/icvs.h +@@ -432,7 +432,6 @@ enum icvs_state { + * @freq_ctrl: (future) frequency control pointer + * @pads: Local media pads (sink/source) + * @nr_of_lanes: Active CSI-2 lane count +- * @link_freq: Current link frequency (Hz) + * @ipu_link: PM runtime device link (IPU consumer, CVS supplier) + * @res: Resource capability (light/full) + * @caps: Reported device protocol capabilities +@@ -458,7 +457,6 @@ struct icvs { + struct v4l2_ctrl *freq_ctrl; + struct media_pad pads[ICVS_CSI_NUM_PADS]; + u32 nr_of_lanes; +- u64 link_freq; + struct device_link *ipu_link; + enum icvs_resources res; + struct icvs_dev_capabilities caps; +diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c +--- a/drivers/media/i2c/cvs/v4l2.c ++++ b/drivers/media/i2c/cvs/v4l2.c +@@ -46,6 +46,7 @@ static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { + /** + * csi_set_link_cfg - Program default CSI-2 link parameters + * @ctx: CVS device context ++ * @link_freq: Link frequency (Hz) + * + * Populates a HOST_SET_MIPI_CONFIG command using current lane count and + * link frequency, then submits it to the device. +@@ -53,12 +54,12 @@ static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { + * + * Return: 0 on success or negative errno. + */ +-static int csi_set_link_cfg(struct icvs *ctx) ++static int csi_set_link_cfg(struct icvs *ctx, u64 link_freq) + { + struct icvs_cmd cmd = { + .cmd_id = cpu_to_be16(ICVS_HOST_SET_MIPI_CONFIG), + .param.conf.nr_of_lanes = ctx->nr_of_lanes, +- .param.conf.link_freq = ctx->link_freq, ++ .param.conf.link_freq = link_freq, + }; + size_t cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf); + +@@ -91,7 +92,7 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(ctx->remote->entity); + struct device *dev = cvs_dev(ctx); +- s64 freq; ++ s64 link_freq; + int ret; + + /* cvs_set_link_owner(ICVS_CSI_LINK_HOST) */ +@@ -99,15 +100,14 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + if (ret < 0) + return ret; + +- freq = v4l2_get_link_freq(ctx->remote, 0, 0); +- if (freq < 0) { +- ret = freq; ++ link_freq = v4l2_get_link_freq(ctx->remote, 0, 0); ++ if (link_freq < 0) { ++ ret = link_freq; + goto err_rpm_put; + } +- ctx->link_freq = freq; + + if (ctx->i2c_client) { +- ret = csi_set_link_cfg(ctx); ++ ret = csi_set_link_cfg(ctx, link_freq); + if (ret < 0) + goto err_rpm_put_sync; + } +@@ -345,7 +345,6 @@ static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + if (freq < 0) + return -EINVAL; + +- ctx->link_freq = freq; + cfg->link_freq = freq; + + return 0; +diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c +--- a/drivers/media/i2c/imx355.c ++++ b/drivers/media/i2c/imx355.c +@@ -9,74 +9,97 @@ + #include + #include + #include ++#include + #include + #include + ++#include + #include + #include + #include + #include + +-#define IMX355_REG_MODE_SELECT 0x0100 ++#define IMX355_REG_MODE_SELECT CCI_REG8(0x0100) + #define IMX355_MODE_STANDBY 0x00 + #define IMX355_MODE_STREAMING 0x01 + + /* Chip ID */ +-#define IMX355_REG_CHIP_ID 0x0016 ++#define IMX355_REG_CHIP_ID CCI_REG16(0x0016) + #define IMX355_CHIP_ID 0x0355 + ++#define IMX355_REG_LANE_SEL CCI_REG8(0x0114) ++ ++/* PLL registers that depend on the external clock frequency */ ++#define IMX355_REG_EXTCLK_FREQ CCI_REG16(0x0136) ++#define IMX355_REG_PLL_OP_PREDIV CCI_REG8(0x030d) ++#define IMX355_REG_PLL_OP_MUL CCI_REG16(0x030e) ++#define IMX355_REG_PLL_IVT_PCK_DIV CCI_REG8(0x0301) ++#define IMX355_REG_PLL_IVT_SYSCK_DIV CCI_REG8(0x0303) ++#define IMX355_PLL_OP_PREDIV 2 ++#define IMX355_PLL_IVT_PCK_DIV 5 ++ + /* V_TIMING internal */ +-#define IMX355_REG_FLL 0x0340 ++#define IMX355_REG_FLL CCI_REG16(0x0340) + #define IMX355_FLL_MAX 0xffff ++#define IMX355_VBLANK_MIN 20 ++ ++#define IMX355_REG_LLP CCI_REG16(0x0342) ++#define IMX355_LLP_MAX 0xffff ++ ++#define IMX355_REG_X_ADD_START CCI_REG16(0x0344) ++#define IMX355_REG_Y_ADD_START CCI_REG16(0x0346) ++#define IMX355_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX355_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX355_REG_X_OUT_SIZE CCI_REG16(0x034c) ++#define IMX355_REG_Y_OUT_SIZE CCI_REG16(0x034e) + + /* Exposure control */ +-#define IMX355_REG_EXPOSURE 0x0202 ++#define IMX355_REG_EXPOSURE CCI_REG16(0x0202) + #define IMX355_EXPOSURE_MIN 1 + #define IMX355_EXPOSURE_STEP 1 + #define IMX355_EXPOSURE_DEFAULT 0x0282 ++#define IMX355_EXPOSURE_OFFSET 10 + + /* Analog gain control */ +-#define IMX355_REG_ANALOG_GAIN 0x0204 ++#define IMX355_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX355_ANA_GAIN_MIN 0 + #define IMX355_ANA_GAIN_MAX 960 + #define IMX355_ANA_GAIN_STEP 1 + #define IMX355_ANA_GAIN_DEFAULT 0 + + /* Digital gain control */ +-#define IMX355_REG_DPGA_USE_GLOBAL_GAIN 0x3070 +-#define IMX355_REG_DIG_GAIN_GLOBAL 0x020e ++#define IMX355_REG_DPGA_USE_GLOBAL_GAIN CCI_REG8(0x3070) ++#define IMX355_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) + #define IMX355_DGTL_GAIN_MIN 256 + #define IMX355_DGTL_GAIN_MAX 4095 + #define IMX355_DGTL_GAIN_STEP 1 + #define IMX355_DGTL_GAIN_DEFAULT 256 + + /* Test Pattern Control */ +-#define IMX355_REG_TEST_PATTERN 0x0600 ++#define IMX355_REG_TEST_PATTERN CCI_REG16(0x0600) + #define IMX355_TEST_PATTERN_DISABLED 0 + #define IMX355_TEST_PATTERN_SOLID_COLOR 1 + #define IMX355_TEST_PATTERN_COLOR_BARS 2 + #define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3 + #define IMX355_TEST_PATTERN_PN9 4 + ++#define IMX355_REG_REQ_LINK_BIT_RATE CCI_REG16(0x0820) ++ ++#define IMX355_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX355_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX355_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ + /* Flip Control */ +-#define IMX355_REG_ORIENTATION 0x0101 ++#define IMX355_REG_ORIENTATION CCI_REG8(0x0101) + +-/* default link frequency and external clock */ +-#define IMX355_LINK_FREQ_DEFAULT 360000000LL +-#define IMX355_EXT_CLK 19200000 +-#define IMX355_LINK_FREQ_INDEX 0 +- +-/* number of data lanes */ +-#define IMX355_DATA_LANES 4 +- +-struct imx355_reg { +- u16 address; +- u8 val; +-}; ++#define IMX355_PIXEL_ARRAY_TOP 0 ++#define IMX355_PIXEL_ARRAY_LEFT 0 ++#define IMX355_PIXEL_ARRAY_WIDTH 3280 ++#define IMX355_PIXEL_ARRAY_HEIGHT 2464 + + struct imx355_reg_list { + u32 num_of_regs; +- const struct imx355_reg *regs; ++ const struct cci_reg_sequence *regs; + }; + + /* Mode : resolution and related config&values */ +@@ -85,28 +108,58 @@ struct imx355_mode { + u32 width; + /* Frame height */ + u32 height; ++ struct v4l2_rect crop; + + /* V-timing */ + u32 fll_def; +- u32 fll_min; + + /* H-timing */ + u32 llp; + +- /* index of link frequency */ +- u32 link_freq_index; +- + /* Default register values */ + struct imx355_reg_list reg_list; + }; + ++struct imx355_clk_params { ++ u32 ext_clk; ++ u16 extclk_freq; /* External clock (MHz) in 8.8 fixed point) */ ++ u16 pll_op_mpy[2]; /* OP system PLL multiplier */ ++ u8 pll_op_prediv[2]; /* OP system pre PLL d */ ++}; ++ ++/* ++ * The clock tree is in single PLL mode, so PREDIV_VT and MPY_IVT do nothing. ++ * In 4 lane mode the MIPI rate is 360Mhz (720Mbit/s) and pixel rate is ++ * 288MPix/s. ++ * In 2 lane mode the MIPI rate is 444MHz (888Mbit/s) and pixel rate ++ * 177.6MPix/s with a 24MHz clock, and 441.6MHz (883.2Mbit/s) and 176.6MPix/s ++ * with a 19.2MHz clock. ++ */ ++static const struct imx355_clk_params imx355_clk_params[] = { ++ { ++ .ext_clk = 19200000, ++ .extclk_freq = 0x1333, ++ .pll_op_mpy = { 75, 92 }, ++ .pll_op_prediv = { 2, 2 } ++ }, ++ { ++ .ext_clk = 24000000, ++ .extclk_freq = 0x1800, ++ .pll_op_mpy = { 60, 111 }, ++ .pll_op_prediv = { 2, 3 } ++ }, ++}; ++ + struct imx355_hwcfg { ++ s64 link_freq_menu; + unsigned long link_freq_bitmap; ++ unsigned int num_lanes; + }; + + struct imx355 { + struct device *dev; + struct clk *clk; ++ struct regmap *regmap; + + struct v4l2_subdev sd; + struct media_pad pad; +@@ -114,24 +167,14 @@ struct imx355 { + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; +- struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + +- /* Current mode */ +- const struct imx355_mode *cur_mode; +- + struct imx355_hwcfg *hwcfg; +- +- /* +- * Mutex for serialized access: +- * Protect sensor set pad format and start/stop streaming safely. +- * Protect access to sensor v4l2 controls. +- */ +- struct mutex mutex; ++ const struct imx355_clk_params *clk_params; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +@@ -143,750 +186,147 @@ static const struct regulator_bulk_data imx355_supplies[] = { + { .supply = "dovdd" }, + }; + +-static const struct imx355_reg imx355_global_regs[] = { +- { 0x0136, 0x13 }, +- { 0x0137, 0x33 }, +- { 0x304e, 0x03 }, +- { 0x4348, 0x16 }, +- { 0x4350, 0x19 }, +- { 0x4408, 0x0a }, +- { 0x440c, 0x0b }, +- { 0x4411, 0x5f }, +- { 0x4412, 0x2c }, +- { 0x4623, 0x00 }, +- { 0x462c, 0x0f }, +- { 0x462d, 0x00 }, +- { 0x462e, 0x00 }, +- { 0x4684, 0x54 }, +- { 0x480a, 0x07 }, +- { 0x4908, 0x07 }, +- { 0x4909, 0x07 }, +- { 0x490d, 0x0a }, +- { 0x491e, 0x0f }, +- { 0x4921, 0x06 }, +- { 0x4923, 0x28 }, +- { 0x4924, 0x28 }, +- { 0x4925, 0x29 }, +- { 0x4926, 0x29 }, +- { 0x4927, 0x1f }, +- { 0x4928, 0x20 }, +- { 0x4929, 0x20 }, +- { 0x492a, 0x20 }, +- { 0x492c, 0x05 }, +- { 0x492d, 0x06 }, +- { 0x492e, 0x06 }, +- { 0x492f, 0x06 }, +- { 0x4930, 0x03 }, +- { 0x4931, 0x04 }, +- { 0x4932, 0x04 }, +- { 0x4933, 0x05 }, +- { 0x595e, 0x01 }, +- { 0x5963, 0x01 }, +- { 0x3030, 0x01 }, +- { 0x3031, 0x01 }, +- { 0x3045, 0x01 }, +- { 0x4010, 0x00 }, +- { 0x4011, 0x00 }, +- { 0x4012, 0x00 }, +- { 0x4013, 0x01 }, +- { 0x68a8, 0xfe }, +- { 0x68a9, 0xff }, +- { 0x6888, 0x00 }, +- { 0x6889, 0x00 }, +- { 0x68b0, 0x00 }, +- { 0x3058, 0x00 }, +- { 0x305a, 0x00 }, ++static const struct cci_reg_sequence imx355_global_regs[] = { ++ { CCI_REG8(0x304e), 0x03 }, ++ { CCI_REG8(0x4348), 0x16 }, ++ { CCI_REG8(0x4350), 0x19 }, ++ { CCI_REG8(0x4408), 0x0a }, ++ { CCI_REG8(0x440c), 0x0b }, ++ { CCI_REG8(0x4411), 0x5f }, ++ { CCI_REG8(0x4412), 0x2c }, ++ { CCI_REG8(0x4623), 0x00 }, ++ { CCI_REG8(0x462c), 0x0f }, ++ { CCI_REG8(0x462d), 0x00 }, ++ { CCI_REG8(0x462e), 0x00 }, ++ { CCI_REG8(0x4684), 0x54 }, ++ { CCI_REG8(0x480a), 0x07 }, ++ { CCI_REG8(0x4908), 0x07 }, ++ { CCI_REG8(0x4909), 0x07 }, ++ { CCI_REG8(0x490d), 0x0a }, ++ { CCI_REG8(0x491e), 0x0f }, ++ { CCI_REG8(0x4921), 0x06 }, ++ { CCI_REG8(0x4923), 0x28 }, ++ { CCI_REG8(0x4924), 0x28 }, ++ { CCI_REG8(0x4925), 0x29 }, ++ { CCI_REG8(0x4926), 0x29 }, ++ { CCI_REG8(0x4927), 0x1f }, ++ { CCI_REG8(0x4928), 0x20 }, ++ { CCI_REG8(0x4929), 0x20 }, ++ { CCI_REG8(0x492a), 0x20 }, ++ { CCI_REG8(0x492c), 0x05 }, ++ { CCI_REG8(0x492d), 0x06 }, ++ { CCI_REG8(0x492e), 0x06 }, ++ { CCI_REG8(0x492f), 0x06 }, ++ { CCI_REG8(0x4930), 0x03 }, ++ { CCI_REG8(0x4931), 0x04 }, ++ { CCI_REG8(0x4932), 0x04 }, ++ { CCI_REG8(0x4933), 0x05 }, ++ { CCI_REG8(0x595e), 0x01 }, ++ { CCI_REG8(0x5963), 0x01 }, ++ { CCI_REG8(0x3030), 0x01 }, ++ { CCI_REG8(0x3031), 0x01 }, ++ { CCI_REG8(0x3045), 0x01 }, ++ { CCI_REG8(0x4010), 0x00 }, ++ { CCI_REG8(0x4011), 0x00 }, ++ { CCI_REG8(0x4012), 0x00 }, ++ { CCI_REG8(0x4013), 0x01 }, ++ { CCI_REG8(0x68a8), 0xfe }, ++ { CCI_REG8(0x68a9), 0xff }, ++ { CCI_REG8(0x6888), 0x00 }, ++ { CCI_REG8(0x6889), 0x00 }, ++ { CCI_REG8(0x68b0), 0x00 }, ++ { CCI_REG8(0x3058), 0x00 }, ++ { CCI_REG8(0x305a), 0x00 }, ++ { CCI_REG8(0x0112), 0x0a }, ++ { CCI_REG8(0x0113), 0x0a }, ++ { IMX355_REG_PLL_IVT_PCK_DIV, IMX355_PLL_IVT_PCK_DIV }, ++ { CCI_REG8(0x0303), 0x01 }, ++ { CCI_REG8(0x0305), 0x02 }, ++ { CCI_REG8(0x0306), 0x00 }, ++ { CCI_REG8(0x0307), 0x78 }, ++ { CCI_REG8(0x030b), 0x01 }, ++ { IMX355_REG_PLL_OP_PREDIV, IMX355_PLL_OP_PREDIV }, ++ { CCI_REG8(0x0310), 0x00 }, ++ { CCI_REG8(0x0220), 0x00 }, ++ { CCI_REG8(0x0222), 0x01 }, ++ { CCI_REG8(0x3088), 0x04 }, ++ { CCI_REG8(0x6813), 0x02 }, ++ { CCI_REG8(0x6835), 0x07 }, ++ { CCI_REG8(0x6836), 0x01 }, ++ { CCI_REG8(0x6837), 0x04 }, ++ { CCI_REG8(0x684d), 0x07 }, ++ { CCI_REG8(0x684e), 0x01 }, ++ { CCI_REG8(0x684f), 0x04 }, + }; + +-static const struct imx355_reg_list imx355_global_setting = { +- .num_of_regs = ARRAY_SIZE(imx355_global_regs), +- .regs = imx355_global_regs, ++static const struct cci_reg_sequence mode_3268x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3268x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcb }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc4 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3264x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3264x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xc7 }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3280x2464_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3280x2464_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xd0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0xa0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1940x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1940x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x33 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x94 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1936x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1936x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2f }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x90 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1924x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1924x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2b }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x84 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1920x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1920x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x27 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x80 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x1232_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x1232_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x922_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x922_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0x30 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x08 }, +- { 0x034b, 0x63 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x03 }, +- { 0x034f, 0x9a }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1300x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1300x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x7f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x14 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1296x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1296x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x77 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x10 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1284x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1284x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x6f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x04 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1280x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1280x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x67 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x00 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, +-}; +- +-static const struct imx355_reg mode_820x616_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x02 }, +- { 0x0341, 0x8c }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x44 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x03 }, +- { 0x034d, 0x34 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0x68 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x02 }, +- { 0x0701, 0x78 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_820x616_regs[] = { ++ { CCI_REG8(0x0700), 0x02 }, ++ { CCI_REG8(0x0701), 0x78 }, + }; + + static const char * const imx355_test_pattern_menu[] = { +@@ -897,23 +337,19 @@ static const char * const imx355_test_pattern_menu[] = { + "Pseudorandom Sequence (PN9)", + }; + +-/* +- * When adding more than the one below, make sure the disallowed ones will +- * actually be disabled in the LINK_FREQ control. +- */ +-static const s64 link_freq_menu_items[] = { +- IMX355_LINK_FREQ_DEFAULT, +-}; +- + /* Mode configs */ + static const struct imx355_mode supported_modes[] = { + { + .width = 3280, + .height = 2464, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, +@@ -922,10 +358,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3268, + .height = 2448, ++ .crop = { ++ .width = 3268, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3268x2448_regs), + .regs = mode_3268x2448_regs, +@@ -934,10 +374,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3264, + .height = 2448, ++ .crop = { ++ .width = 3264, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, +@@ -946,10 +390,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1940, + .height = 1096, ++ .crop = { ++ .width = 1940, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1940x1096_regs), + .regs = mode_1940x1096_regs, +@@ -958,10 +406,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1936, + .height = 1096, ++ .crop = { ++ .width = 1936, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs), + .regs = mode_1936x1096_regs, +@@ -970,10 +422,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1924, + .height = 1080, ++ .crop = { ++ .width = 1924, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1924x1080_regs), + .regs = mode_1924x1080_regs, +@@ -982,10 +438,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, ++ .crop = { ++ .width = 1920, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, +@@ -994,10 +454,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 1232, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs), + .regs = mode_1640x1232_regs, +@@ -1006,10 +470,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 922, ++ .crop = { ++ .width = 3280, ++ .height = 1844, ++ .left = 0, ++ .top = 304, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x922_regs), + .regs = mode_1640x922_regs, +@@ -1018,10 +486,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1300, + .height = 736, ++ .crop = { ++ .width = 2600, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1300x736_regs), + .regs = mode_1300x736_regs, +@@ -1030,10 +502,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1296, + .height = 736, ++ .crop = { ++ .width = 2592, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x736_regs), + .regs = mode_1296x736_regs, +@@ -1042,10 +518,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1284, + .height = 720, ++ .crop = { ++ .width = 2568, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1284x720_regs), + .regs = mode_1284x720_regs, +@@ -1054,10 +534,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1280, + .height = 720, ++ .crop = { ++ .width = 2560, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, +@@ -1066,10 +550,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 820, + .height = 616, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 652, +- .fll_min = 652, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_820x616_regs), + .regs = mode_820x616_regs, +@@ -1095,116 +583,28 @@ static u32 imx355_get_format_code(struct imx355 *imx355) + { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, }, + }; + +- lockdep_assert_held(&imx355->mutex); + code = codes[imx355->vflip->val][imx355->hflip->val]; + + return code; + } + +-/* Read registers up to 4 at a time */ +-static int imx355_read_reg(struct imx355 *imx355, u16 reg, u32 len, u32 *val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- struct i2c_msg msgs[2]; +- u8 addr_buf[2]; +- u8 data_buf[4] = { 0 }; +- int ret; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, addr_buf); +- /* Write register address */ +- msgs[0].addr = client->addr; +- msgs[0].flags = 0; +- msgs[0].len = ARRAY_SIZE(addr_buf); +- msgs[0].buf = addr_buf; +- +- /* Read data from register */ +- msgs[1].addr = client->addr; +- msgs[1].flags = I2C_M_RD; +- msgs[1].len = len; +- msgs[1].buf = &data_buf[4 - len]; +- +- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); +- if (ret != ARRAY_SIZE(msgs)) +- return -EIO; +- +- *val = get_unaligned_be32(data_buf); +- +- return 0; +-} +- +-/* Write registers up to 4 at a time */ +-static int imx355_write_reg(struct imx355 *imx355, u16 reg, u32 len, u32 val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- u8 buf[6]; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, buf); +- put_unaligned_be32(val << (8 * (4 - len)), buf + 2); +- if (i2c_master_send(client, buf, len + 2) != len + 2) +- return -EIO; +- +- return 0; +-} +- +-/* Write a list of registers */ +-static int imx355_write_regs(struct imx355 *imx355, +- const struct imx355_reg *regs, u32 len) +-{ +- int ret; +- u32 i; +- +- for (i = 0; i < len; i++) { +- ret = imx355_write_reg(imx355, regs[i].address, 1, regs[i].val); +- if (ret) { +- dev_err_ratelimited(imx355->dev, +- "write reg 0x%4.4x return err %d", +- regs[i].address, ret); +- +- return ret; +- } +- } +- +- return 0; +-} +- +-/* Open sub-device */ +-static int imx355_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- struct v4l2_mbus_framefmt *try_fmt = +- v4l2_subdev_state_get_format(fh->state, 0); +- +- mutex_lock(&imx355->mutex); +- +- /* Initialize try_fmt */ +- try_fmt->width = imx355->cur_mode->width; +- try_fmt->height = imx355->cur_mode->height; +- try_fmt->code = imx355_get_format_code(imx355); +- try_fmt->field = V4L2_FIELD_NONE; +- +- mutex_unlock(&imx355->mutex); +- +- return 0; +-} +- + static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + { + struct imx355 *imx355 = container_of(ctrl->handler, + struct imx355, ctrl_handler); ++ const struct v4l2_mbus_framefmt *format = NULL; ++ struct v4l2_subdev_state *state; + s64 max; + int ret; + ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ format = v4l2_subdev_state_get_format(state, 0); ++ + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ +- max = imx355->cur_mode->height + ctrl->val - 10; ++ max = format->height + ctrl->val - IMX355_EXPOSURE_OFFSET; + __v4l2_ctrl_modify_range(imx355->exposure, + imx355->exposure->minimum, + max, imx355->exposure->step, max); +@@ -1221,31 +621,31 @@ static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + /* Analog gain = 1024/(1024 - ctrl->val) times */ +- ret = imx355_write_reg(imx355, IMX355_REG_ANALOG_GAIN, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_ANALOG_GAIN, ++ ctrl->val, NULL); + break; + case V4L2_CID_DIGITAL_GAIN: +- ret = imx355_write_reg(imx355, IMX355_REG_DIG_GAIN_GLOBAL, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); + break; + case V4L2_CID_EXPOSURE: +- ret = imx355_write_reg(imx355, IMX355_REG_EXPOSURE, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_EXPOSURE, ++ ctrl->val, NULL); + break; + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ +- ret = imx355_write_reg(imx355, IMX355_REG_FLL, 2, +- imx355->cur_mode->height + ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_FLL, ++ format->height + ctrl->val, NULL); + break; + case V4L2_CID_TEST_PATTERN: +- ret = imx355_write_reg(imx355, IMX355_REG_TEST_PATTERN, +- 2, ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_TEST_PATTERN, ++ ctrl->val, NULL); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: +- ret = imx355_write_reg(imx355, IMX355_REG_ORIENTATION, 1, +- imx355->hflip->val | +- imx355->vflip->val << 1); ++ ret = cci_write(imx355->regmap, IMX355_REG_ORIENTATION, ++ imx355->hflip->val | imx355->vflip->val << 1, ++ NULL); + break; + default: + ret = -EINVAL; +@@ -1272,9 +672,7 @@ static int imx355_enum_mbus_code(struct v4l2_subdev *sd, + if (code->index > 0) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + code->code = imx355_get_format_code(imx355); +- mutex_unlock(&imx355->mutex); + + return 0; + } +@@ -1288,12 +686,9 @@ static int imx355_enum_frame_size(struct v4l2_subdev *sd, + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + if (fse->code != imx355_get_format_code(imx355)) { +- mutex_unlock(&imx355->mutex); + return -EINVAL; + } +- mutex_unlock(&imx355->mutex); + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; +@@ -1311,36 +706,10 @@ static void imx355_update_pad_format(struct imx355 *imx355, + fmt->format.height = mode->height; + fmt->format.code = imx355_get_format_code(imx355); + fmt->format.field = V4L2_FIELD_NONE; +-} +- +-static int imx355_do_get_pad_format(struct imx355 *imx355, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct v4l2_mbus_framefmt *framefmt; +- +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- fmt->format = *framefmt; +- } else { +- imx355_update_pad_format(imx355, imx355->cur_mode, fmt); +- } +- +- return 0; +-} +- +-static int imx355_get_pad_format(struct v4l2_subdev *sd, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- int ret; +- +- mutex_lock(&imx355->mutex); +- ret = imx355_do_get_pad_format(imx355, sd_state, fmt); +- mutex_unlock(&imx355->mutex); +- +- return ret; ++ fmt->format.colorspace = V4L2_COLORSPACE_RAW; ++ fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_601; ++ fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; ++ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + } + + static int +@@ -1351,13 +720,8 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + struct imx355 *imx355 = to_imx355(sd); + const struct imx355_mode *mode; + struct v4l2_mbus_framefmt *framefmt; +- s32 vblank_def; +- s32 vblank_min; ++ struct v4l2_rect *crop; + s64 h_blank; +- u64 pixel_rate; +- u32 height; +- +- mutex_lock(&imx355->mutex); + + /* + * Only one bayer order is supported. +@@ -1370,23 +734,25 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + width, height, + fmt->format.width, fmt->format.height); + imx355_update_pad_format(imx355, mode, fmt); +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- *framefmt = fmt->format; +- } else { +- imx355->cur_mode = mode; +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; +- do_div(pixel_rate, 10); +- __v4l2_ctrl_s_ctrl_int64(imx355->pixel_rate, pixel_rate); ++ framefmt = v4l2_subdev_state_get_format(sd_state, 0); ++ ++ *framefmt = fmt->format; ++ ++ crop = v4l2_subdev_state_get_crop(sd_state, 0); ++ crop->width = mode->crop.width; ++ crop->height = mode->crop.height; ++ crop->left = mode->crop.left; ++ crop->top = mode->crop.top; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Update limits and set FPS to default */ +- height = imx355->cur_mode->height; +- vblank_def = imx355->cur_mode->fll_def - height; +- vblank_min = imx355->cur_mode->fll_min - height; +- height = IMX355_FLL_MAX - height; +- __v4l2_ctrl_modify_range(imx355->vblank, vblank_min, height, 1, +- vblank_def); +- __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def); +- h_blank = mode->llp - imx355->cur_mode->width; ++ __v4l2_ctrl_modify_range(imx355->vblank, IMX355_VBLANK_MIN, ++ IMX355_FLL_MAX - mode->height, 1, ++ mode->fll_def - mode->height); ++ __v4l2_ctrl_s_ctrl(imx355->vblank, mode->fll_def - mode->height); ++ ++ h_blank = mode->llp - mode->width; ++ + /* + * Currently hblank is not changeable. + * So FPS control is done only by vblank. +@@ -1395,7 +761,42 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + h_blank, 1, h_blank); + } + +- mutex_unlock(&imx355->mutex); ++ return 0; ++} ++ ++static int imx355_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, 0); ++ return 0; ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = IMX355_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX355_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX355_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX355_PIXEL_ARRAY_HEIGHT; ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx355_entity_init_state(struct v4l2_subdev *subdev, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { }; ++ ++ fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; ++ fmt.format.code = MEDIA_BUS_FMT_SRGGB10_1X10; ++ fmt.format.width = supported_modes[0].width; ++ fmt.format.height = supported_modes[0].height; ++ ++ imx355_set_pad_format(subdev, sd_state, &fmt); + + return 0; + } +@@ -1403,52 +804,98 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + /* Start streaming */ + static int imx355_start_streaming(struct imx355 *imx355) + { +- const struct imx355_reg_list *reg_list; +- int ret; ++ const struct v4l2_mbus_framefmt *fmt; ++ struct v4l2_subdev_state *state; ++ const struct imx355_mode *mode; ++ int lane_idx = imx355->hwcfg->num_lanes == 4 ? 0 : 1; ++ struct v4l2_rect *crop; ++ u64 link_bitrate; ++ u8 binning_mode; ++ int ret = 0; + + /* Global Setting */ +- reg_list = &imx355_global_setting; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set global settings"); +- return ret; +- } ++ cci_multi_reg_write(imx355->regmap, imx355_global_regs, ++ ARRAY_SIZE(imx355_global_regs), &ret); + +- /* Apply default values of current mode */ +- reg_list = &imx355->cur_mode->reg_list; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set mode"); +- return ret; +- } ++ /* Apply values of current mode */ ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ crop = v4l2_subdev_state_get_crop(state, 0); ++ mode = v4l2_find_nearest_size(supported_modes, ++ ARRAY_SIZE(supported_modes), ++ width, height, fmt->width, fmt->height); ++ cci_multi_reg_write(imx355->regmap, mode->reg_list.regs, ++ mode->reg_list.num_of_regs, &ret); ++ ++ /* Set readout crop and size registers */ ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_START, crop->left, ++ &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_START, crop->top, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_END, ++ crop->width + crop->left - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_END, ++ crop->height + crop->top - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_OUT_SIZE, fmt->width, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_OUT_SIZE, fmt->height, &ret); ++ ++ binning_mode = ((crop->width / fmt->width) << 4) | ++ (crop->height / fmt->height); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_MODE, ++ binning_mode == 0x11 ? 0x00 : 0x01, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_TYPE, binning_mode, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_WEIGHTING, 0x00, &ret); ++ ++ /* Set PLL registers for the external clock frequency */ ++ cci_write(imx355->regmap, IMX355_REG_EXTCLK_FREQ, ++ imx355->clk_params->extclk_freq, &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_MUL, ++ imx355->clk_params->pll_op_mpy[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_PREDIV, ++ imx355->clk_params->pll_op_prediv[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_IVT_SYSCK_DIV, ++ lane_idx ? 2 : 1, &ret); ++ ++ /* Set MIPI configuration */ ++ cci_write(imx355->regmap, IMX355_REG_LANE_SEL, ++ imx355->hwcfg->num_lanes - 1, &ret); ++ ++ link_bitrate = imx355->link_freq->qmenu_int[imx355->link_freq->val] * ++ imx355->hwcfg->num_lanes * 2; ++ do_div(link_bitrate, 1000000); ++ cci_write(imx355->regmap, IMX355_REG_REQ_LINK_BIT_RATE, link_bitrate, ++ &ret); + + /* set digital gain control to all color mode */ +- ret = imx355_write_reg(imx355, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, 1); +- if (ret) +- return ret; ++ cci_write(imx355->regmap, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, &ret); ++ ++ /* set line length */ ++ cci_write(imx355->regmap, IMX355_REG_LLP, ++ imx355->hblank->val + fmt->width, &ret); + + /* Apply customized values from user */ +- ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); +- if (ret) +- return ret; ++ if (!ret) ++ ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); + +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STREAMING); ++ cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, IMX355_MODE_STREAMING, ++ &ret); ++ ++ return ret; + } + + /* Stop streaming */ + static int imx355_stop_streaming(struct imx355 *imx355) + { +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STANDBY); ++ return cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, ++ IMX355_MODE_STANDBY, NULL); + } + + static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + { + struct imx355 *imx355 = to_imx355(sd); ++ struct v4l2_subdev_state *state; + int ret = 0; + +- mutex_lock(&imx355->mutex); ++ state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) { + ret = pm_runtime_resume_and_get(imx355->dev); +@@ -1464,21 +911,21 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + goto err_rpm_put; + } else { + imx355_stop_streaming(imx355); +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + } + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx355->vflip, enable); + __v4l2_ctrl_grab(imx355->hflip, enable); + +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + + err_rpm_put: +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + err_unlock: +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + } +@@ -1487,14 +934,14 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + static int imx355_identify_module(struct imx355 *imx355) + { + int ret; +- u32 val; ++ u64 val; + +- ret = imx355_read_reg(imx355, IMX355_REG_CHIP_ID, 2, &val); ++ ret = cci_read(imx355->regmap, IMX355_REG_CHIP_ID, &val, NULL); + if (ret) + return ret; + + if (val != IMX355_CHIP_ID) { +- dev_err(imx355->dev, "chip id mismatch: %x!=%x", ++ dev_err(imx355->dev, "chip id mismatch: %x!=%llx", + IMX355_CHIP_ID, val); + return -EIO; + } +@@ -1512,9 +959,10 @@ static const struct v4l2_subdev_video_ops imx355_video_ops = { + + static const struct v4l2_subdev_pad_ops imx355_pad_ops = { + .enum_mbus_code = imx355_enum_mbus_code, +- .get_fmt = imx355_get_pad_format, ++ .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx355_set_pad_format, + .enum_frame_size = imx355_enum_frame_size, ++ .get_selection = imx355_get_selection, + }; + + static const struct v4l2_subdev_ops imx355_subdev_ops = { +@@ -1528,7 +976,7 @@ static const struct media_entity_operations imx355_subdev_entity_ops = { + }; + + static const struct v4l2_subdev_internal_ops imx355_internal_ops = { +- .open = imx355_open, ++ .init_state = imx355_entity_init_state, + }; + + static int imx355_power_off(struct device *dev) +@@ -1582,13 +1030,11 @@ static int imx355_init_controls(struct imx355 *imx355) + { + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; ++ const struct imx355_mode *mode = &supported_modes[0]; + s64 exposure_max; + s64 vblank_def; +- s64 vblank_min; + s64 hblank; + u64 pixel_rate; +- const struct imx355_mode *mode; +- u32 max; + int ret; + + ctrl_hdlr = &imx355->ctrl_handler; +@@ -1596,40 +1042,34 @@ static int imx355_init_controls(struct imx355 *imx355) + if (ret) + return ret; + +- ctrl_hdlr->lock = &imx355->mutex; +- max = ARRAY_SIZE(link_freq_menu_items) - 1; + imx355->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_LINK_FREQ, max, 0, +- link_freq_menu_items); ++ V4L2_CID_LINK_FREQ, 0, 0, ++ &imx355->hwcfg->link_freq_menu); + if (imx355->link_freq) + imx355->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; ++ pixel_rate = imx355->hwcfg->link_freq_menu * 2 * imx355->hwcfg->num_lanes; + do_div(pixel_rate, 10); +- /* By default, PIXEL_RATE is read only */ +- imx355->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_PIXEL_RATE, pixel_rate, +- pixel_rate, 1, pixel_rate); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_PIXEL_RATE, ++ pixel_rate, pixel_rate, 1, pixel_rate); + + /* Initialize vblank/hblank/exposure parameters based on current mode */ +- mode = imx355->cur_mode; + vblank_def = mode->fll_def - mode->height; +- vblank_min = mode->fll_min - mode->height; + imx355->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_VBLANK, vblank_min, ++ V4L2_CID_VBLANK, IMX355_VBLANK_MIN, + IMX355_FLL_MAX - mode->height, + 1, vblank_def); + + hblank = mode->llp - mode->width; +- imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_HBLANK, hblank, hblank, +- 1, hblank); ++ imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, ++ hblank, hblank, 1, hblank); + if (imx355->hblank) + imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* fll >= exposure time + adjust parameter (default value is 10) */ +- exposure_max = mode->fll_def - 10; ++ exposure_max = mode->fll_def - IMX355_EXPOSURE_OFFSET; + imx355->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX355_EXPOSURE_MIN, exposure_max, +@@ -1683,14 +1123,17 @@ static int imx355_init_controls(struct imx355 *imx355) + return ret; + } + +-static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) ++static struct imx355_hwcfg *imx355_get_hwcfg(struct imx355 *imx355) + { ++ struct device *dev = imx355->dev; + struct imx355_hwcfg *cfg; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; ++ const struct imx355_clk_params *clk = imx355->clk_params; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); ++ int lane_idx; + int ret; + + if (!fwnode) +@@ -1708,13 +1151,18 @@ static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) + if (!cfg) + goto out_err; + +- if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX355_DATA_LANES) ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 && ++ bus_cfg.bus.mipi_csi2.num_data_lanes != 4) + goto out_err; + ++ cfg->num_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; ++ ++ lane_idx = cfg->num_lanes == 4 ? 0 : 1; ++ cfg->link_freq_menu = (clk->ext_clk * clk->pll_op_mpy[lane_idx]) / ++ (clk->pll_op_prediv[lane_idx] * 2); + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, +- link_freq_menu_items, +- ARRAY_SIZE(link_freq_menu_items), ++ &cfg->link_freq_menu, 1, + &cfg->link_freq_bitmap); + if (ret) + goto out_err; +@@ -1741,7 +1189,10 @@ static int imx355_probe(struct i2c_client *client) + + imx355->dev = &client->dev; + +- mutex_init(&imx355->mutex); ++ imx355->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(imx355->regmap)) ++ return dev_err_probe(imx355->dev, PTR_ERR(imx355->regmap), ++ "Unable to initialize I2C\n"); + + imx355->clk = devm_v4l2_sensor_clk_get(imx355->dev, NULL); + if (IS_ERR(imx355->clk)) +@@ -1749,7 +1200,13 @@ static int imx355_probe(struct i2c_client *client) + "failed to get clock\n"); + + freq = clk_get_rate(imx355->clk); +- if (freq != IMX355_EXT_CLK) ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx355_clk_params); i++) { ++ if (freq == imx355_clk_params[i].ext_clk) { ++ imx355->clk_params = &imx355_clk_params[i]; ++ break; ++ } ++ } ++ if (!imx355->clk_params) + return dev_err_probe(imx355->dev, -EINVAL, + "external clock %lu is not supported\n", + freq); +@@ -1760,7 +1217,7 @@ static int imx355_probe(struct i2c_client *client) + &imx355->supplies); + if (ret) { + dev_err_probe(imx355->dev, ret, "could not get regulators"); +- goto error_probe; ++ return ret; + } + + imx355->reset_gpio = devm_gpiod_get_optional(imx355->dev, "reset", +@@ -1768,22 +1225,21 @@ static int imx355_probe(struct i2c_client *client) + if (IS_ERR(imx355->reset_gpio)) { + ret = dev_err_probe(imx355->dev, PTR_ERR(imx355->reset_gpio), + "failed to get gpios"); +- goto error_probe; ++ return ret; + } + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops); + +- imx355->hwcfg = imx355_get_hwcfg(imx355->dev); ++ imx355->hwcfg = imx355_get_hwcfg(imx355); + if (!imx355->hwcfg) { + dev_err(imx355->dev, "failed to get hwcfg"); +- ret = -ENODEV; +- goto error_probe; ++ return -ENODEV; + } + + ret = imx355_power_on(imx355->dev); + if (ret) +- goto error_probe; ++ return ret; + + /* Check module identity */ + ret = imx355_identify_module(imx355); +@@ -1792,9 +1248,6 @@ static int imx355_probe(struct i2c_client *client) + goto error_power_off; + } + +- /* Set default mode to max resolution */ +- imx355->cur_mode = &supported_modes[0]; +- + ret = imx355_init_controls(imx355); + if (ret) { + dev_err(imx355->dev, "failed to init controls: %d", ret); +@@ -1816,24 +1269,36 @@ static int imx355_probe(struct i2c_client *client) + goto error_handler_free; + } + ++ imx355->sd.state_lock = imx355->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&imx355->sd); ++ if (ret < 0) { ++ dev_err_probe(imx355->dev, ret, "subdev init error\n"); ++ goto error_media_entity_free; ++ } ++ + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(imx355->dev); + pm_runtime_enable(imx355->dev); ++ pm_runtime_set_autosuspend_delay(imx355->dev, 1000); ++ pm_runtime_use_autosuspend(imx355->dev); + + ret = v4l2_async_register_subdev_sensor(&imx355->sd); + if (ret < 0) +- goto error_media_entity_runtime_pm; ++ goto error_subdev_cleanup_runtime_pm; + + pm_runtime_idle(imx355->dev); + + return 0; + +-error_media_entity_runtime_pm: ++error_subdev_cleanup_runtime_pm: + pm_runtime_disable(imx355->dev); + pm_runtime_set_suspended(imx355->dev); ++ pm_runtime_dont_use_autosuspend(imx355->dev); ++ v4l2_subdev_cleanup(&imx355->sd); ++error_media_entity_free: + media_entity_cleanup(&imx355->sd.entity); + + error_handler_free: +@@ -1842,9 +1307,6 @@ static int imx355_probe(struct i2c_client *client) + error_power_off: + imx355_power_off(imx355->dev); + +-error_probe: +- mutex_destroy(&imx355->mutex); +- + return ret; + } + +@@ -1854,6 +1316,7 @@ static void imx355_remove(struct i2c_client *client) + struct imx355 *imx355 = to_imx355(sd); + + v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + +@@ -1864,7 +1327,7 @@ static void imx355_remove(struct i2c_client *client) + pm_runtime_set_suspended(imx355->dev); + } + +- mutex_destroy(&imx355->mutex); ++ pm_runtime_dont_use_autosuspend(imx355->dev); + } + + static const struct acpi_device_id imx355_acpi_ids[] __maybe_unused = { +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +new file mode 100644 +--- /dev/null ++++ b/drivers/media/i2c/imx471.c +@@ -0,0 +1,957 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * imx471.c - imx471 sensor driver ++ * ++ * Copyright (C) 2025 Intel Corporation ++ * Copyright (C) 2026 Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define IMX471_REG_MODE_SELECT CCI_REG8(0x0100) ++#define IMX471_MODE_STANDBY 0x00 ++#define IMX471_MODE_STREAMING 0x01 ++ ++/* Chip ID */ ++#define IMX471_REG_CHIP_ID CCI_REG16(0x0016) ++#define IMX471_CHIP_ID 0x0471 ++ ++/* V_TIMING internal */ ++#define IMX471_REG_FLL CCI_REG16(0x0340) ++#define IMX471_FLL_MAX 0xffff ++ ++/* Exposure control */ ++#define IMX471_REG_EXPOSURE CCI_REG16(0x0202) ++#define IMX471_EXPOSURE_MIN 1 ++#define IMX471_EXPOSURE_STEP 1 ++#define IMX471_EXPOSURE_DEFAULT 1270 ++ ++/* Default exposure margin */ ++#define IMX471_EXPOSURE_MARGIN 18 ++ ++/* Analog gain control */ ++#define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) ++#define IMX471_ANA_GAIN_MIN 0 ++#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_STEP 1 ++#define IMX471_ANA_GAIN_DEFAULT 0 ++ ++/* Digital gain control */ ++#define IMX471_REG_DPGA_USE_GLOBAL_GAIN CCI_REG16(0x3ff9) ++#define IMX471_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) ++#define IMX471_DGTL_GAIN_MIN 256 ++#define IMX471_DGTL_GAIN_MAX 4095 ++#define IMX471_DGTL_GAIN_STEP 1 ++#define IMX471_DGTL_GAIN_DEFAULT 256 ++ ++/* HFLIP and VFLIP control */ ++#define IMX471_REG_ORIENTATION CCI_REG8(0x0101) ++ ++/* Test Pattern Control */ ++#define IMX471_REG_TEST_PATTERN CCI_REG8(0x0600) ++ ++/* default link frequency and external clock */ ++#define IMX471_LINK_FREQ_DEFAULT 200000000LL ++#define IMX471_EXT_CLK 19200000 ++ ++/* PLL */ ++#define IMX471_REG_VTPXCK_DIV CCI_REG8(0x0301) ++#define IMX471_REG_VTSYCK_DIV CCI_REG8(0x0303) ++#define IMX471_REG_PREPLLCK_VT_DIV CCI_REG8(0x0305) ++#define IMX471_REG_PLL_VT_MPY CCI_REG16(0x0306) ++#define IMX471_REG_OPPXCK_DIV CCI_REG8(0x0309) ++#define IMX471_REG_OPSYCK_DIV CCI_REG8(0x030b) ++#define IMX471_REG_PLL_MULT_DRIV CCI_REG8(0x0310) ++#define IMX471_PLL_SINGLE 0 ++#define IMX471_PLL_DUAL 1 ++ ++/* IMX471 native and active pixel array size */ ++#define IMX471_NATIVE_WIDTH 4672 ++#define IMX471_NATIVE_HEIGHT 3512 ++#define IMX471_PIXEL_ARRAY_LEFT 8 ++#define IMX471_PIXEL_ARRAY_TOP 8 ++#define IMX471_PIXEL_ARRAY_WIDTH 4656 ++#define IMX471_PIXEL_ARRAY_HEIGHT 3496 ++ ++#define IMX471_REG_EXCK_FREQ CCI_REG16(0x0136) ++ ++#define IMX471_REG_CSI_DATA_FORMAT CCI_REG16(0x0112) ++#define IMX471_CSI_DATA_FORMAT_RAW10 0x0a0a ++ ++#define IMX471_REG_CSI_LANE_MODE CCI_REG8(0x0114) ++#define IMX471_CSI_2_LANE_MODE 1 ++#define IMX471_CSI_4_LANE_MODE 3 ++ ++#define IMX471_REG_X_ADD_STA CCI_REG16(0x0344) ++#define IMX471_REG_Y_ADD_STA CCI_REG16(0x0346) ++#define IMX471_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX471_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX471_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) ++#define IMX471_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) ++#define IMX471_REG_X_EVEN_INC CCI_REG8(0x0381) ++#define IMX471_REG_X_ODD_INC CCI_REG8(0x0383) ++#define IMX471_REG_Y_EVEN_INC CCI_REG8(0x0385) ++#define IMX471_REG_Y_ODD_INC CCI_REG8(0x0387) ++ ++#define IMX471_REG_DIG_CROP_X_OFFSET CCI_REG16(0x0408) ++#define IMX471_REG_DIG_CROP_Y_OFFSET CCI_REG16(0x040a) ++#define IMX471_REG_DIG_CROP_WIDTH CCI_REG16(0x040c) ++#define IMX471_REG_DIG_CROP_HEIGHT CCI_REG16(0x040e) ++ ++/* Binning mode */ ++#define IMX471_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX471_BINNING_NONE 0 ++#define IMX471_BINNING_ENABLE 1 ++#define IMX471_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX471_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ ++#define to_imx471(_sd) container_of_const(_sd, struct imx471, sd) ++ ++static const char * const imx471_supply_name[] = { ++ "vana", ++}; ++ ++struct imx471_mode { ++ u32 width; ++ u32 height; ++ ++ /* V-timing */ ++ u32 fll_def; ++ u32 fll_min; ++ ++ /* H-timing */ ++ u32 llp; ++ ++ const struct cci_reg_sequence *default_mode_regs; ++ unsigned int default_mode_regs_length; ++}; ++ ++struct imx471 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ ++ struct v4l2_ctrl_handler ctrl_handler; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *exposure; ++ ++ struct gpio_desc *reset_gpio; ++ struct regulator_bulk_data supplies[ARRAY_SIZE(imx471_supply_name)]; ++ struct clk *img_clk; ++ ++ struct device *dev; ++ struct regmap *regmap; ++}; ++ ++static const struct cci_reg_sequence imx471_global_regs[] = { ++ { IMX471_REG_EXCK_FREQ, 0x1333 }, ++ { CCI_REG8(0x3c7e), 0x08 }, ++ { CCI_REG8(0x3c7f), 0x05 }, ++ { CCI_REG8(0x3e35), 0x00 }, ++ { CCI_REG8(0x3e36), 0x00 }, ++ { CCI_REG8(0x3e37), 0x00 }, ++ { CCI_REG8(0x3f7f), 0x01 }, ++ { CCI_REG8(0x4431), 0x04 }, ++ { CCI_REG8(0x531c), 0x01 }, ++ { CCI_REG8(0x531d), 0x02 }, ++ { CCI_REG8(0x531e), 0x04 }, ++ { CCI_REG8(0x5928), 0x00 }, ++ { CCI_REG8(0x5929), 0x2f }, ++ { CCI_REG8(0x592a), 0x00 }, ++ { CCI_REG8(0x592b), 0x85 }, ++ { CCI_REG8(0x592c), 0x00 }, ++ { CCI_REG8(0x592d), 0x32 }, ++ { CCI_REG8(0x592e), 0x00 }, ++ { CCI_REG8(0x592f), 0x88 }, ++ { CCI_REG8(0x5930), 0x00 }, ++ { CCI_REG8(0x5931), 0x3d }, ++ { CCI_REG8(0x5932), 0x00 }, ++ { CCI_REG8(0x5933), 0x93 }, ++ { CCI_REG8(0x5938), 0x00 }, ++ { CCI_REG8(0x5939), 0x24 }, ++ { CCI_REG8(0x593a), 0x00 }, ++ { CCI_REG8(0x593b), 0x7a }, ++ { CCI_REG8(0x593c), 0x00 }, ++ { CCI_REG8(0x593d), 0x24 }, ++ { CCI_REG8(0x593e), 0x00 }, ++ { CCI_REG8(0x593f), 0x7a }, ++ { CCI_REG8(0x5940), 0x00 }, ++ { CCI_REG8(0x5941), 0x2f }, ++ { CCI_REG8(0x5942), 0x00 }, ++ { CCI_REG8(0x5943), 0x85 }, ++ { CCI_REG8(0x5f0e), 0x6e }, ++ { CCI_REG8(0x5f11), 0xc6 }, ++ { CCI_REG8(0x5f17), 0x5e }, ++ { CCI_REG8(0x7990), 0x01 }, ++ { CCI_REG8(0x7993), 0x5d }, ++ { CCI_REG8(0x7994), 0x5d }, ++ { CCI_REG8(0x7995), 0xa1 }, ++ { CCI_REG8(0x799a), 0x01 }, ++ { CCI_REG8(0x799d), 0x00 }, ++ { CCI_REG8(0x8169), 0x01 }, ++ { CCI_REG8(0x8359), 0x01 }, ++ { CCI_REG8(0x9302), 0x1e }, ++ { CCI_REG8(0x9306), 0x1f }, ++ { CCI_REG8(0x930a), 0x26 }, ++ { CCI_REG8(0x930e), 0x23 }, ++ { CCI_REG8(0x9312), 0x23 }, ++ { CCI_REG8(0x9316), 0x2c }, ++ { CCI_REG8(0x9317), 0x19 }, ++ { CCI_REG8(0xb046), 0x01 }, ++ { CCI_REG8(0xb048), 0x01 }, ++}; ++ ++static const struct cci_reg_sequence mode_1928x1088_regs[] = { ++ { IMX471_REG_CSI_DATA_FORMAT, IMX471_CSI_DATA_FORMAT_RAW10 }, ++ { IMX471_REG_CSI_LANE_MODE, IMX471_CSI_4_LANE_MODE }, ++ { IMX471_REG_X_ADD_STA, 8 }, ++ { IMX471_REG_Y_ADD_STA, 408 }, ++ { IMX471_REG_X_ADD_END, 4647 }, ++ { IMX471_REG_Y_ADD_END, 3051 }, ++ { IMX471_REG_X_EVEN_INC, 1 }, ++ { IMX471_REG_X_ODD_INC, 1 }, ++ { IMX471_REG_Y_EVEN_INC, 1 }, ++ { IMX471_REG_Y_ODD_INC, 1 }, ++ { IMX471_REG_BINNING_MODE, IMX471_BINNING_ENABLE }, ++ { IMX471_REG_BINNING_TYPE, 0x22 }, ++ { IMX471_REG_BINNING_WEIGHTING, 0x08 }, ++ { IMX471_REG_DIG_CROP_X_OFFSET, 208 }, ++ { IMX471_REG_DIG_CROP_Y_OFFSET, 108 }, ++ { IMX471_REG_DIG_CROP_WIDTH, 1928 }, ++ { IMX471_REG_DIG_CROP_HEIGHT, 1088 }, ++ { IMX471_REG_X_OUTPUT_SIZE, 1928 }, ++ { IMX471_REG_Y_OUTPUT_SIZE, 1088 }, ++ { IMX471_REG_VTPXCK_DIV, 0x06 }, ++ { IMX471_REG_VTSYCK_DIV, 0x02 }, ++ { IMX471_REG_PREPLLCK_VT_DIV, 0x02 }, ++ { IMX471_REG_PLL_VT_MPY, 0x0079 }, ++ { IMX471_REG_OPSYCK_DIV, 0x01 }, ++ { CCI_REG8(0x030d), 0x02 }, ++ { CCI_REG8(0x030e), 0x00 }, ++ { CCI_REG8(0x030f), 0x53 }, ++ { IMX471_REG_PLL_MULT_DRIV, IMX471_PLL_DUAL }, ++ { CCI_REG8(0x3f4c), 0x81 }, ++ { CCI_REG8(0x3f4d), 0x81 }, ++ { CCI_REG8(0x3f78), 0x01 }, ++ { CCI_REG8(0x3f79), 0x31 }, ++ { CCI_REG8(0x3ffe), 0x00 }, ++ { CCI_REG8(0x3fff), 0x8a }, ++ { CCI_REG8(0x5f0a), 0xb6 }, ++}; ++ ++static const char * const imx471_test_pattern_menu[] = { ++ "Disabled", ++ "Solid Colour", ++ "Eight Vertical Colour Bars", ++ "Colour Bars With Fade to Grey", ++ "Pseudorandom Sequence (PN9)", ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ IMX471_LINK_FREQ_DEFAULT, ++}; ++ ++/* ++ * The Bayer formats for the flipping. ++ * - no flip ++ * - h flip ++ * - v flip ++ * - h and v flips ++ */ ++static const u32 imx471_hv_flips_bayer_order[] = { ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++}; ++ ++static const struct imx471_mode imx471_modes[] = { ++ { ++ .width = 1928, ++ .height = 1088, ++ .fll_def = 1308, ++ .fll_min = 1308, ++ .llp = 2328, ++ .default_mode_regs = mode_1928x1088_regs, ++ .default_mode_regs_length = ARRAY_SIZE(mode_1928x1088_regs), ++ }, ++}; ++ ++static int imx471_get_regulators(struct device *dev, struct imx471 *sensor) ++{ ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx471_supply_name); i++) ++ sensor->supplies[i].supply = imx471_supply_name[i]; ++ ++ return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++} ++ ++static int imx471_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct imx471 *sensor = container_of_const(ctrl->handler, ++ struct imx471, ++ ctrl_handler); ++ struct v4l2_subdev_state *state = ++ v4l2_subdev_get_locked_active_state(&sensor->sd); ++ const struct v4l2_mbus_framefmt *format = ++ v4l2_subdev_state_get_format(state, 0); ++ int ret; ++ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ s64 exposure_max = format->height + ctrl->val - ++ IMX471_EXPOSURE_MARGIN; ++ ret = __v4l2_ctrl_modify_range(sensor->exposure, ++ sensor->exposure->minimum, ++ exposure_max, ++ sensor->exposure->step, ++ exposure_max); ++ if (ret) ++ return ret; ++ } ++ ++ if (!pm_runtime_get_if_in_use(sensor->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_ANALOG_GAIN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = cci_write(sensor->regmap, IMX471_REG_EXPOSURE, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_VBLANK: ++ /* Update FLL that meets expected vertical blanking */ ++ ret = cci_write(sensor->regmap, IMX471_REG_FLL, ++ format->height + ctrl->val, NULL); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = cci_write(sensor->regmap, IMX471_REG_TEST_PATTERN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_HFLIP: ++ case V4L2_CID_VFLIP: ++ ret = cci_write(sensor->regmap, IMX471_REG_ORIENTATION, ++ sensor->hflip->val | sensor->vflip->val << 1, ++ NULL); ++ break; ++ default: ++ ret = -EINVAL; ++ dev_err(sensor->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n", ++ ctrl->id, ctrl->val); ++ break; ++ } ++ ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops imx471_ctrl_ops = { ++ .s_ctrl = imx471_set_ctrl, ++}; ++ ++static u32 imx471_get_format_code(struct imx471 *sensor) ++{ ++ unsigned int i; ++ ++ i = (sensor->vflip->val ? 2 : 0) | (sensor->hflip->val ? 1 : 0); ++ ++ return imx471_hv_flips_bayer_order[i]; ++} ++ ++static int imx471_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ ++ if (code->index >= (ARRAY_SIZE(imx471_hv_flips_bayer_order) / 4)) ++ return -EINVAL; ++ ++ code->code = imx471_get_format_code(sensor); ++ ++ return 0; ++} ++ ++static int imx471_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->index >= ARRAY_SIZE(imx471_modes)) ++ return -EINVAL; ++ ++ fse->min_width = imx471_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = imx471_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static void imx471_update_pad_format(struct imx471 *sensor, ++ const struct imx471_mode *mode, ++ struct v4l2_subdev_format *fmt) ++{ ++ fmt->format.code = imx471_get_format_code(sensor); ++ fmt->format.width = mode->width; ++ fmt->format.height = mode->height; ++ fmt->format.field = V4L2_FIELD_NONE; ++} ++ ++static int imx471_set_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ int h_blank, ret; ++ ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->format.width, ++ fmt->format.height); ++ ++ imx471_update_pad_format(sensor, mode, fmt); ++ ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ return 0; ++ ++ if (media_entity_is_streaming(&sensor->sd.entity)) ++ return -EBUSY; ++ ++ ret = __v4l2_ctrl_modify_range(sensor->vblank, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ if (ret) ++ return ret; ++ ++ h_blank = mode->llp - mode->width; ++ /* ++ * Currently hblank is not changeable. ++ * So FPS control is done only by vblank. ++ */ ++ return __v4l2_ctrl_modify_range(sensor->hblank, h_blank, ++ h_blank, 1, h_blank); ++} ++ ++static int imx471_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); ++ break; ++ ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = IMX471_NATIVE_WIDTH; ++ sel->r.height = IMX471_NATIVE_HEIGHT; ++ return 0; ++ ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.top = IMX471_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX471_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX471_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX471_PIXEL_ARRAY_HEIGHT; ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx471_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_TRY, ++ .format = { ++ .code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .width = imx471_modes[0].width, ++ .height = imx471_modes[0].height, ++ }, ++ }; ++ ++ return imx471_set_pad_format(sd, sd_state, &fmt); ++} ++ ++static int imx471_identify_module(struct imx471 *sensor) ++{ ++ int ret; ++ u64 val; ++ ++ ret = cci_read(sensor->regmap, IMX471_REG_CHIP_ID, &val, NULL); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to read chip id\n"); ++ ++ if (val != IMX471_CHIP_ID) ++ return dev_err_probe(sensor->dev, -EIO, ++ "chip id mismatch: %x!=%llx\n", ++ IMX471_CHIP_ID, val); ++ ++ return 0; ++} ++ ++static int imx471_power_off(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ ++ clk_disable_unprepare(sensor->img_clk); ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ ++ return 0; ++} ++ ++static int imx471_power_on(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ if (ret < 0) { ++ dev_err(dev, "failed to enable regulators: %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(sensor->img_clk); ++ if (ret < 0) { ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ dev_err(dev, "failed to enable imaging clock: %d\n", ret); ++ return ret; ++ } ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ ++ usleep_range(10000, 15000); ++ ++ return 0; ++} ++ ++static int imx471_enable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(sensor->dev); ++ if (ret) ++ return ret; ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_multi_reg_write(sensor->regmap, imx471_global_regs, ++ ARRAY_SIZE(imx471_global_regs), NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set global settings: %d\n", ++ ret); ++ goto error_powerdown; ++ } ++ ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->width, fmt->height); ++ ++ ret = cci_multi_reg_write(sensor->regmap, mode->default_mode_regs, ++ mode->default_mode_regs_length, NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set mode: %d\n", ret); ++ goto error_powerdown; ++ } ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_DPGA_USE_GLOBAL_GAIN, 1, ++ NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = __v4l2_ctrl_handler_setup(&sensor->ctrl_handler); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STREAMING, NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ __v4l2_ctrl_grab(sensor->vflip, true); ++ __v4l2_ctrl_grab(sensor->hflip, true); ++ ++ return ret; ++ ++error_powerdown: ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static int imx471_disable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STANDBY, NULL); ++ pm_runtime_put(sensor->dev); ++ ++ if (ret) ++ dev_err(sensor->dev, ++ "failed to disable stream with return value: %d\n", ++ ret); ++ ++ __v4l2_ctrl_grab(sensor->vflip, false); ++ __v4l2_ctrl_grab(sensor->hflip, false); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops imx471_video_ops = { ++ .s_stream = v4l2_subdev_s_stream_helper, ++}; ++ ++static const struct v4l2_subdev_pad_ops imx471_pad_ops = { ++ .enum_mbus_code = imx471_enum_mbus_code, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .set_fmt = imx471_set_pad_format, ++ .get_selection = imx471_get_selection, ++ .enum_frame_size = imx471_enum_frame_size, ++ .enable_streams = imx471_enable_stream, ++ .disable_streams = imx471_disable_stream, ++}; ++ ++static const struct v4l2_subdev_ops imx471_subdev_ops = { ++ .video = &imx471_video_ops, ++ .pad = &imx471_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops imx471_internal_ops = { ++ .init_state = imx471_init_state, ++}; ++ ++static int imx471_init_controls(struct imx471 *sensor) ++{ ++ const struct imx471_mode *mode = &imx471_modes[0]; ++ struct v4l2_fwnode_device_properties props; ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ struct v4l2_ctrl *link_freq; ++ s64 exposure_max, hblank; ++ u64 pixel_rate; ++ int ret; ++ ++ ret = v4l2_fwnode_device_parse(sensor->dev, &props); ++ if (ret) { ++ dev_err(sensor->dev, "failed to parse fwnode: %d\n", ret); ++ return ret; ++ } ++ ++ ctrl_hdlr = &sensor->ctrl_handler; ++ v4l2_ctrl_handler_init(ctrl_hdlr, 12); ++ ++ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx471_ctrl_ops, &props); ++ ++ link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_LINK_FREQ, ++ ARRAY_SIZE(link_freq_menu_items) - 1, ++ 0, ++ link_freq_menu_items); ++ ++ /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ ++ pixel_rate = div_u64(IMX471_LINK_FREQ_DEFAULT * 2 * 4, 10); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_PIXEL_RATE, pixel_rate, ++ pixel_rate, 1, pixel_rate); ++ ++ sensor->vblank = v4l2_ctrl_new_std(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_VBLANK, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ ++ hblank = mode->llp - mode->width; ++ sensor->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HBLANK, hblank, hblank, ++ 1, hblank); ++ ++ /* fll >= exposure time + adjust parameter (default value is 18) */ ++ exposure_max = mode->fll_def - IMX471_EXPOSURE_MARGIN; ++ sensor->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_EXPOSURE, ++ IMX471_EXPOSURE_MIN, exposure_max, ++ IMX471_EXPOSURE_STEP, ++ IMX471_EXPOSURE_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ IMX471_ANA_GAIN_MIN, IMX471_ANA_GAIN_MAX, ++ IMX471_ANA_GAIN_STEP, IMX471_ANA_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ IMX471_DGTL_GAIN_MIN, IMX471_DGTL_GAIN_MAX, ++ IMX471_DGTL_GAIN_STEP, IMX471_DGTL_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(imx471_test_pattern_menu) - 1, ++ 0, 0, imx471_test_pattern_menu); ++ ++ sensor->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ++ sensor->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ if (ctrl_hdlr->error) { ++ dev_err(sensor->dev, "%s control init failed: %d\n", ++ __func__, ctrl_hdlr->error); ++ goto error; ++ } ++ ++ link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ ++ sensor->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++ ++error: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ ++ return ctrl_hdlr->error; ++} ++ ++static int imx471_check_hwcfg(struct imx471 *sensor) ++{ ++ struct v4l2_fwnode_endpoint bus_cfg = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY, ++ }; ++ struct fwnode_handle *ep, *fwnode = dev_fwnode(sensor->dev); ++ unsigned long link_freq_bitmap; ++ struct clk *clk; ++ int ret; ++ ++ clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(clk), ++ "can't get clock frequency\n"); ++ ++ if (clk_get_rate(clk) != IMX471_EXT_CLK) ++ return dev_err_probe(sensor->dev, -EINVAL, ++ "external clock %lu is not supported\n", ++ clk_get_rate(clk)); ++ ++ ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0); ++ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); ++ fwnode_handle_put(ep); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "parsing endpoint failed\n"); ++ ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { ++ ret = dev_err_probe(sensor->dev, -EINVAL, ++ "number of CSI2 data lanes %u is not supported\n", ++ bus_cfg.bus.mipi_csi2.num_data_lanes); ++ goto done_endpoint_free; ++ } ++ ++ ret = v4l2_link_freq_to_bitmap(sensor->dev, bus_cfg.link_frequencies, ++ bus_cfg.nr_of_link_frequencies, ++ link_freq_menu_items, ++ ARRAY_SIZE(link_freq_menu_items), ++ &link_freq_bitmap); ++ ++done_endpoint_free: ++ v4l2_fwnode_endpoint_free(&bus_cfg); ++ ++ return ret; ++} ++ ++static int imx471_probe(struct i2c_client *client) ++{ ++ struct imx471 *sensor; ++ int ret; ++ ++ sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return dev_err_probe(&client->dev, -ENOMEM, ++ "failed to allocate memory\n"); ++ ++ sensor->dev = &client->dev; ++ ++ ret = imx471_check_hwcfg(sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to check hwcfg: %d\n", ret); ++ ++ ret = imx471_get_regulators(sensor->dev, sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to get regulators\n"); ++ ++ sensor->reset_gpio = devm_gpiod_get_optional(sensor->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset_gpio), ++ "failed to get reset gpio\n"); ++ ++ sensor->img_clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(sensor->img_clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->img_clk), ++ "failed to get imaging clock\n"); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &imx471_subdev_ops); ++ ++ sensor->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(sensor->regmap)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->regmap), ++ "failed to initialize CCI\n"); ++ ++ ret = imx471_power_on(sensor->dev); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to power on\n"); ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to find sensor: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ ret = imx471_init_controls(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to init controls: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ sensor->sd.internal_ops = &imx471_internal_ops; ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, ++ "failed to init entity pads: %d\n", ret); ++ goto error_v4l2_ctrl_handler_free; ++ } ++ ++ sensor->sd.state_lock = sensor->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&sensor->sd); ++ if (ret < 0) { ++ dev_err_probe(sensor->dev, ret, "failed to init subdev: %d\n", ++ ret); ++ goto error_media_entity_pm; ++ } ++ ++ pm_runtime_set_active(sensor->dev); ++ pm_runtime_enable(sensor->dev); ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret < 0) ++ goto error_v4l2_subdev_cleanup; ++ ++ pm_runtime_idle(sensor->dev); ++ ++ return 0; ++ ++error_v4l2_subdev_cleanup: ++ pm_runtime_disable(sensor->dev); ++ pm_runtime_set_suspended(sensor->dev); ++ v4l2_subdev_cleanup(&sensor->sd); ++ ++error_media_entity_pm: ++ media_entity_cleanup(&sensor->sd.entity); ++ ++error_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); ++ ++error_power_off: ++ imx471_power_off(sensor->dev); ++ ++ return ret; ++} ++ ++static void imx471_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ ++ v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_ctrl_handler_free(sd->ctrl_handler); ++ ++ pm_runtime_disable(&client->dev); ++ ++ if (!pm_runtime_status_suspended(&client->dev)) { ++ imx471_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++ } ++} ++ ++static DEFINE_RUNTIME_DEV_PM_OPS(imx471_pm_ops, imx471_power_off, ++ imx471_power_on, NULL); ++ ++static const struct acpi_device_id imx471_acpi_ids[] __maybe_unused = { ++ { "SONY471A" }, ++ { "TBE20A0" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(acpi, imx471_acpi_ids); ++ ++static struct i2c_driver imx471_i2c_driver = { ++ .driver = { ++ .name = "imx471", ++ .acpi_match_table = ACPI_PTR(imx471_acpi_ids), ++ .pm = pm_sleep_ptr(&imx471_pm_ops), ++ }, ++ .probe = imx471_probe, ++ .remove = imx471_remove, ++}; ++module_i2c_driver(imx471_i2c_driver); ++ ++MODULE_AUTHOR("Jimmy Su "); ++MODULE_AUTHOR("Serin Yeh "); ++MODULE_AUTHOR("Kate Hsuan "); ++MODULE_DESCRIPTION("Sony imx471 sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c +--- a/drivers/media/i2c/ov2740.c ++++ b/drivers/media/i2c/ov2740.c +@@ -1241,41 +1241,42 @@ static int ov2740_nvmem_read(void *priv, unsigned int off, void *val, + return ret; + } + ++static const struct regmap_config regmap_config = { ++ .val_bits = 8, ++ .reg_bits = 16, ++ .disable_locking = true, ++}; ++ + static int ov2740_register_nvmem(struct i2c_client *client, + struct ov2740 *ov2740) + { + struct nvm_data *nvm; +- struct regmap_config regmap_config = { }; +- struct nvmem_config nvmem_config = { }; + struct regmap *regmap; + struct device *dev = ov2740->dev; ++ struct nvmem_config nvmem_config = { ++ .name = dev_name(dev), ++ .dev = dev, ++ .read_only = true, ++ .root_only = true, ++ .owner = THIS_MODULE, ++ .compat = true, ++ .base_dev = dev, ++ .reg_read = ov2740_nvmem_read, ++ .stride = 1, ++ .word_size = 1, ++ .size = CUSTOMER_USE_OTP_SIZE, ++ }; + + nvm = devm_kzalloc(dev, sizeof(*nvm), GFP_KERNEL); + if (!nvm) + return -ENOMEM; + +- regmap_config.val_bits = 8; +- regmap_config.reg_bits = 16; +- regmap_config.disable_locking = true; + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvm->regmap = regmap; +- +- nvmem_config.name = dev_name(dev); +- nvmem_config.dev = dev; +- nvmem_config.read_only = true; +- nvmem_config.root_only = true; +- nvmem_config.owner = THIS_MODULE; +- nvmem_config.compat = true; +- nvmem_config.base_dev = dev; +- nvmem_config.reg_read = ov2740_nvmem_read; +- nvmem_config.reg_write = NULL; + nvmem_config.priv = nvm; +- nvmem_config.stride = 1; +- nvmem_config.word_size = 1; +- nvmem_config.size = CUSTOMER_USE_OTP_SIZE; + + nvm->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvm->nvmem)) +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -49,6 +49,8 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { ++ /* Himax HM1092 */ ++ IPU_SENSOR_CONFIG("HIMX1092", 2, 180000000, 180480000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +@@ -97,6 +99,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* Sony IMX471 */ + IPU_SENSOR_CONFIG("SONY471A", 1, 200000000), ++ /* Sony IMX471 (found on Lenovo X1 Carbon G14) */ ++ IPU_SENSOR_CONFIG("TBE20A0", 1, 200000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), + }; +@@ -194,8 +198,8 @@ static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev + for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) { + const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i]; + struct acpi_device *consumer, *ivsc_adev; +- + acpi_handle handle = acpi_device_handle(ACPI_PTR(adev)); ++ + for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1) + /* camera sensor depends on IVSC in DSDT if exist */ + for_each_acpi_consumer_dev(ivsc_adev, consumer) diff --git a/pkgbuilds/linux-omarchy-bore/0543-media-laptop-cameras.patch.sig b/pkgbuilds/linux-omarchy-bore/0543-media-laptop-cameras.patch.sig new file mode 100644 index 0000000..afd9deb Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0543-media-laptop-cameras.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0544-ipu-bridge-lenovo-x9-upside-down.patch b/pkgbuilds/linux-omarchy-bore/0544-ipu-bridge-lenovo-x9-upside-down.patch new file mode 100644 index 0000000..abda19b --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0544-ipu-bridge-lenovo-x9-upside-down.patch @@ -0,0 +1,49 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -138,6 +138,45 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "OVTI02C1", + }, ++ /* ++ * The first four characters of DMI_BOARD_NAME identify the Lenovo ++ * machine type/model. For example, a DMI_BOARD_NAME starting with ++ * "21Q6" indicates a ThinkPad X9-15. ++ * ++ * Reference: https://psref.lenovo.com/ ++ */ ++ { ++ /* Lenovo X9-14 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21QA"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-14 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21QB"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-15 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21Q6"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-15 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21Q7"), ++ }, ++ .driver_data = "SONY471A", ++ }, + {} /* Terminating entry */ + }; + diff --git a/pkgbuilds/linux-omarchy-bore/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig b/pkgbuilds/linux-omarchy-bore/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig new file mode 100644 index 0000000..844b9fe Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch b/pkgbuilds/linux-omarchy-bore/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch new file mode 100644 index 0000000..7c15eb7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch @@ -0,0 +1,17 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -138,6 +138,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "OVTI02C1", + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 16 (Dell 16 Premium) DA16250"), ++ }, ++ .driver_data = "OVTI02C1", ++ }, + /* + * The first four characters of DMI_BOARD_NAME identify the Lenovo + * machine type/model. For example, a DMI_BOARD_NAME starting with diff --git a/pkgbuilds/linux-omarchy-bore/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig b/pkgbuilds/linux-omarchy-bore/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig new file mode 100644 index 0000000..4e6cc4f Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch b/pkgbuilds/linux-omarchy-bore/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch new file mode 100644 index 0000000..a42d186 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -184,6 +184,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "SONY471A", + }, ++ { ++ /* Samsung Galaxy Book5 Pro 360 */ ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "960QHA"), ++ }, ++ .driver_data = "OVTI02E1", ++ }, + {} /* Terminating entry */ + }; + diff --git a/pkgbuilds/linux-omarchy-bore/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig b/pkgbuilds/linux-omarchy-bore/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig new file mode 100644 index 0000000..18998fb Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0547-int3472-imx471-vana-power-enable.patch b/pkgbuilds/linux-omarchy-bore/0547-int3472-imx471-vana-power-enable.patch new file mode 100644 index 0000000..db38adc --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0547-int3472-imx471-vana-power-enable.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -164,6 +164,24 @@ static const struct int3472_gpio_map int3472_gpio_map[] = { + .con_id = "dvdd", + .enable_time_us = 45 * USEC_PER_MSEC, + }, ++ { /* imx471 expects "vana" as con_id for power enable */ ++ .hid = "SONY471A", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, ++ { ++ /* ++ * imx471 (on Lenovo ThinkPads X1 G14) expects "vana" as con_id ++ * for power enable ++ */ ++ .hid = "TBE20A0", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, + }; + + static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type, diff --git a/pkgbuilds/linux-omarchy-bore/0547-int3472-imx471-vana-power-enable.patch.sig b/pkgbuilds/linux-omarchy-bore/0547-int3472-imx471-vana-power-enable.patch.sig new file mode 100644 index 0000000..fbd699c Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0547-int3472-imx471-vana-power-enable.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0548-media-cvs-sensor-node-in-bound-callback.patch b/pkgbuilds/linux-omarchy-bore/0548-media-cvs-sensor-node-in-bound-callback.patch new file mode 100644 index 0000000..b041d90 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0548-media-cvs-sensor-node-in-bound-callback.patch @@ -0,0 +1,37 @@ +diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c +--- a/drivers/media/i2c/cvs/v4l2.c ++++ b/drivers/media/i2c/cvs/v4l2.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -400,7 +401,7 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_async_connection *asc) + { + struct icvs *ctx = notifier_to_csi(notifier); +- int pad; ++ int pad, ret; + + pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, + MEDIA_PAD_FL_SOURCE); +@@ -409,9 +410,13 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + + ctx->remote = &sd->entity.pads[pad]; + +- return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, +- ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | +- MEDIA_LNK_FL_IMMUTABLE); ++ ret = media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, ++ ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | ++ MEDIA_LNK_FL_IMMUTABLE); ++ if (ret) ++ return ret; ++ ++ return v4l2_device_register_subdev_nodes(sd->v4l2_dev); + } + + /** diff --git a/pkgbuilds/linux-omarchy-bore/0548-media-cvs-sensor-node-in-bound-callback.patch.sig b/pkgbuilds/linux-omarchy-bore/0548-media-cvs-sensor-node-in-bound-callback.patch.sig new file mode 100644 index 0000000..5355a32 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0548-media-cvs-sensor-node-in-bound-callback.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0549-media-imx471-analog-gain-max-960.patch b/pkgbuilds/linux-omarchy-bore/0549-media-imx471-analog-gain-max-960.patch new file mode 100644 index 0000000..a11c4f2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0549-media-imx471-analog-gain-max-960.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +--- a/drivers/media/i2c/imx471.c ++++ b/drivers/media/i2c/imx471.c +@@ -41,7 +41,7 @@ + /* Analog gain control */ + #define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX471_ANA_GAIN_MIN 0 +-#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_MAX 960 + #define IMX471_ANA_GAIN_STEP 1 + #define IMX471_ANA_GAIN_DEFAULT 0 + diff --git a/pkgbuilds/linux-omarchy-bore/0549-media-imx471-analog-gain-max-960.patch.sig b/pkgbuilds/linux-omarchy-bore/0549-media-imx471-analog-gain-max-960.patch.sig new file mode 100644 index 0000000..bd5334b Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0549-media-imx471-analog-gain-max-960.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0560-input-updates.patch b/pkgbuilds/linux-omarchy-bore/0560-input-updates.patch new file mode 100644 index 0000000..d8548c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0560-input-updates.patch @@ -0,0 +1,3869 @@ +diff --git a/drivers/input/input.c b/drivers/input/input.c +--- a/drivers/input/input.c ++++ b/drivers/input/input.c +@@ -318,7 +318,7 @@ static int input_get_disposition(struct input_dev *dev, + static void input_event_dispose(struct input_dev *dev, int disposition, + unsigned int type, unsigned int code, int value) + { +- if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) ++ if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event && dev->ready) + dev->event(dev, type, code, value); + + if (disposition & INPUT_PASS_TO_HANDLERS) { +@@ -568,6 +568,75 @@ void input_release_device(struct input_handle *handle) + } + EXPORT_SYMBOL(input_release_device); + ++#define INPUT_DO_TOGGLE(dev, type, bits, on) \ ++ do { \ ++ int i; \ ++ bool active; \ ++ \ ++ if (!test_bit(EV_##type, dev->evbit)) \ ++ break; \ ++ \ ++ for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ ++ active = test_bit(i, dev->bits); \ ++ if (!active && !on) \ ++ continue; \ ++ \ ++ dev->event(dev, EV_##type, i, on ? active : 0); \ ++ } \ ++ } while (0) ++ ++/* ++ * Iterate through the logical state of the input device (LEDs, sounds, ++ * auto-repeat) and explicitly push that state down to the hardware ++ * via dev->event() to match the current logical state (if activate is true), ++ * or forcibly turn off all feedback like LEDs and sounds during teardown ++ * or suspend (if activate is false). ++ * ++ * Primarily used as a state-replay mechanism after a device is opened ++ * or uninhibited, as events might have been dropped by the core while the ++ * hardware was not marked as ready. ++ */ ++static void input_dev_toggle(struct input_dev *dev, bool activate) ++{ ++ if (!dev->event || !dev->ready) ++ return; ++ ++ INPUT_DO_TOGGLE(dev, LED, led, activate); ++ INPUT_DO_TOGGLE(dev, SND, snd, activate); ++ ++ if (activate && test_bit(EV_REP, dev->evbit)) { ++ dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); ++ dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); ++ } ++} ++ ++static int input_start_device(struct input_dev *dev) ++{ ++ int error; ++ ++ lockdep_assert_held(&dev->mutex); ++ ++ if (dev->users++ == 0 && !dev->inhibited) { ++ if (dev->open) { ++ error = dev->open(dev); ++ if (error) { ++ dev->users--; ++ return error; ++ } ++ } ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ dev->ready = true; ++ input_dev_toggle(dev, true); ++ } ++ ++ if (dev->poller) ++ input_dev_poller_start(dev->poller); ++ } ++ ++ return 0; ++} ++ + /** + * input_open_device - open input device + * @handle: handle through which device is being accessed +@@ -586,21 +655,9 @@ int input_open_device(struct input_handle *handle) + + handle->open++; + +- if (handle->handler->passive_observer) +- return 0; +- +- if (dev->users++ || dev->inhibited) { +- /* +- * Device is already opened and/or inhibited, +- * so we can exit immediately and report success. +- */ +- return 0; +- } +- +- if (dev->open) { +- error = dev->open(dev); ++ if (!handle->handler->passive_observer) { ++ error = input_start_device(dev); + if (error) { +- dev->users--; + handle->open--; + /* + * Make sure we are not delivering any more +@@ -611,8 +668,8 @@ int input_open_device(struct input_handle *handle) + } + } + +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ if (handle->open == 1 && handle->handler->start) ++ handle->handler->start(handle); + } + + return 0; +@@ -651,6 +708,12 @@ void input_close_device(struct input_handle *handle) + if (!--dev->users && !dev->inhibited) { + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ + if (dev->close) + dev->close(dev); + } +@@ -1702,37 +1765,6 @@ static int input_dev_uevent(const struct device *device, struct kobj_uevent_env + return 0; + } + +-#define INPUT_DO_TOGGLE(dev, type, bits, on) \ +- do { \ +- int i; \ +- bool active; \ +- \ +- if (!test_bit(EV_##type, dev->evbit)) \ +- break; \ +- \ +- for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ +- active = test_bit(i, dev->bits); \ +- if (!active && !on) \ +- continue; \ +- \ +- dev->event(dev, EV_##type, i, on ? active : 0); \ +- } \ +- } while (0) +- +-static void input_dev_toggle(struct input_dev *dev, bool activate) +-{ +- if (!dev->event) +- return; +- +- INPUT_DO_TOGGLE(dev, LED, led, activate); +- INPUT_DO_TOGGLE(dev, SND, snd, activate); +- +- if (activate && test_bit(EV_REP, dev->evbit)) { +- dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); +- dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); +- } +-} +- + /** + * input_reset_device() - reset/restore the state of input device + * @dev: input device whose state needs to be reset +@@ -1756,52 +1788,73 @@ static int input_inhibit_device(struct input_dev *dev) + { + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (dev->inhibited) + return 0; + + if (dev->users) { +- if (dev->close) +- dev->close(dev); + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ ++ if (dev->close) ++ dev->close(dev); + } + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_mt_release_slots(dev); + input_dev_release_keys(dev); + input_handle_event(dev, EV_SYN, SYN_REPORT, 1); +- input_dev_toggle(dev, false); ++ dev->inhibited = true; + } + +- dev->inhibited = true; +- + return 0; + } + + static int input_uninhibit_device(struct input_dev *dev) + { ++ struct input_handle *handle; + int error; + + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (!dev->inhibited) + return 0; + ++ dev->inhibited = false; ++ + if (dev->users) { + if (dev->open) { + error = dev->open(dev); +- if (error) ++ if (error) { ++ dev->inhibited = true; + return error; ++ } + } +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ scoped_guard(spinlock_irq, &dev->event_lock) ++ dev->ready = true; + } + +- dev->inhibited = false; +- + scoped_guard(spinlock_irq, &dev->event_lock) + input_dev_toggle(dev, true); + ++ if (dev->users && dev->poller) ++ input_dev_poller_start(dev->poller); ++ ++ list_for_each_entry(handle, &dev->h_list, d_node) { ++ if (handle->open && handle->handler->start) ++ handle->handler->start(handle); ++ } ++ + return 0; + } + +@@ -2655,9 +2708,6 @@ int input_register_handle(struct input_handle *handle) + */ + list_add_tail_rcu(&handle->h_node, &handler->h_list); + +- if (handler->start) +- handler->start(handle); +- + return 0; + } + EXPORT_SYMBOL(input_register_handle); +diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig +--- a/drivers/input/keyboard/Kconfig ++++ b/drivers/input/keyboard/Kconfig +@@ -119,44 +119,6 @@ config KEYBOARD_ATKBD + To compile this driver as a module, choose M here: the + module will be called atkbd. + +-config KEYBOARD_ATKBD_HP_KEYCODES +- bool "Use HP keyboard scancodes" +- depends on PARISC && KEYBOARD_ATKBD +- default y +- help +- Say Y here if you have a PA-RISC machine and want to use an AT or +- PS/2 keyboard, and your keyboard uses keycodes that are specific to +- PA-RISC keyboards. +- +- Say N if you use a standard keyboard. +- +-config KEYBOARD_ATKBD_RDI_KEYCODES +- bool "Use PrecisionBook keyboard scancodes" +- depends on KEYBOARD_ATKBD_HP_KEYCODES +- default n +- help +- If you have an RDI PrecisionBook, say Y here if you want to use its +- built-in keyboard (as opposed to an external keyboard). +- +- The PrecisionBook has five keys that conflict with those used by most +- AT and PS/2 keyboards. These are as follows: +- +- PrecisionBook Standard AT or PS/2 +- +- F1 F12 +- Left Ctrl Left Alt +- Caps Lock Left Ctrl +- Right Ctrl Caps Lock +- Left 102nd key (the key to the right of Left Shift) +- +- If you say N here, and use the PrecisionBook keyboard, then each key +- in the left-hand column will be interpreted as the corresponding key +- in the right-hand column. +- +- If you say Y here, and use an external keyboard, then each key in the +- right-hand column will be interpreted as the key shown in the +- left-hand column. +- + config KEYBOARD_QT1050 + tristate "Microchip AT42QT1050 Touch Sensor Chip" + depends on I2C +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -73,13 +73,6 @@ MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard conne + #define ATKBD_KEYMAP_SIZE 512 + + static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { +-#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES +- +-/* XXX: need a more general approach */ +- +-#include "hpps2atkbd.h" /* include the keyboard scancodes */ +- +-#else + 0, 67, 65, 63, 61, 59, 60, 88,183, 68, 66, 64, 62, 15, 41,117, + 184, 56, 42, 93, 29, 16, 2, 0,185, 0, 44, 31, 30, 17, 3, 0, + 186, 46, 45, 32, 18, 5, 4, 95,187, 57, 47, 33, 20, 19, 6,183, +@@ -99,7 +92,6 @@ static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + + 0, 0, 0, 65, 99, +-#endif + }; + + static const unsigned short atkbd_set3_keycode[ATKBD_KEYMAP_SIZE] = { +diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c +--- a/drivers/input/keyboard/cap11xx.c ++++ b/drivers/input/keyboard/cap11xx.c +@@ -5,6 +5,8 @@ + * (c) 2014 Daniel Mack + */ + ++#include ++#include + #include + #include + #include +@@ -16,63 +18,43 @@ + #include + #include + ++#define CAP1114_REG_BUTTON_STATUS1 0x03 ++#define CAP1114_REG_BUTTON_STATUS2 0x04 ++#define CAP1114_REG_CONFIG2 0x40 ++#define CAP1114_REG_CONFIG2_VOL_UP_DOWN BIT(1) ++#define CAP1114_REG_LED_OUTPUT_CONTROL1 0x73 ++ + #define CAP11XX_REG_MAIN_CONTROL 0x00 + #define CAP11XX_REG_MAIN_CONTROL_GAIN_SHIFT (6) + #define CAP11XX_REG_MAIN_CONTROL_GAIN_MASK (0xc0) + #define CAP11XX_REG_MAIN_CONTROL_DLSEEP BIT(4) +-#define CAP11XX_REG_GENERAL_STATUS 0x02 + #define CAP11XX_REG_SENSOR_INPUT 0x03 +-#define CAP11XX_REG_NOISE_FLAG_STATUS 0x0a ++#define CAP1114_REG_BUTTON_STATUS2 0x04 + #define CAP11XX_REG_SENOR_DELTA(X) (0x10 + (X)) + #define CAP11XX_REG_SENSITIVITY_CONTROL 0x1f + #define CAP11XX_REG_SENSITIVITY_CONTROL_DELTA_SENSE_MASK 0x70 +-#define CAP11XX_REG_CONFIG 0x20 +-#define CAP11XX_REG_SENSOR_ENABLE 0x21 +-#define CAP11XX_REG_SENSOR_CONFIG 0x22 +-#define CAP11XX_REG_SENSOR_CONFIG2 0x23 +-#define CAP11XX_REG_SAMPLING_CONFIG 0x24 +-#define CAP11XX_REG_CALIBRATION 0x26 +-#define CAP11XX_REG_INT_ENABLE 0x27 + #define CAP11XX_REG_REPEAT_RATE 0x28 + #define CAP11XX_REG_SIGNAL_GUARD_ENABLE 0x29 +-#define CAP11XX_REG_MT_CONFIG 0x2a +-#define CAP11XX_REG_MT_PATTERN_CONFIG 0x2b +-#define CAP11XX_REG_MT_PATTERN 0x2d +-#define CAP11XX_REG_RECALIB_CONFIG 0x2f + #define CAP11XX_REG_SENSOR_THRESH(X) (0x30 + (X)) +-#define CAP11XX_REG_SENSOR_NOISE_THRESH 0x38 +-#define CAP11XX_REG_STANDBY_CHANNEL 0x40 +-#define CAP11XX_REG_STANDBY_CONFIG 0x41 +-#define CAP11XX_REG_STANDBY_SENSITIVITY 0x42 +-#define CAP11XX_REG_STANDBY_THRESH 0x43 + #define CAP11XX_REG_CONFIG2 0x44 + #define CAP11XX_REG_CONFIG2_ALT_POL BIT(6) +-#define CAP11XX_REG_SENSOR_BASE_CNT(X) (0x50 + (X)) +-#define CAP11XX_REG_LED_POLARITY 0x73 + #define CAP11XX_REG_LED_OUTPUT_CONTROL 0x74 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG 0x80 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG2 0x81 +- +-#define CAP11XX_REG_LED_DUTY_CYCLE_1 0x90 +-#define CAP11XX_REG_LED_DUTY_CYCLE_2 0x91 +-#define CAP11XX_REG_LED_DUTY_CYCLE_3 0x92 + #define CAP11XX_REG_LED_DUTY_CYCLE_4 0x93 + +-#define CAP11XX_REG_LED_DUTY_MIN_MASK (0x0f) +-#define CAP11XX_REG_LED_DUTY_MIN_MASK_SHIFT (0) + #define CAP11XX_REG_LED_DUTY_MAX_MASK (0xf0) +-#define CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT (4) + #define CAP11XX_REG_LED_DUTY_MAX_VALUE (15) + +-#define CAP11XX_REG_SENSOR_CALIB (0xb1 + (X)) +-#define CAP11XX_REG_SENSOR_CALIB_LSB1 0xb9 +-#define CAP11XX_REG_SENSOR_CALIB_LSB2 0xba + #define CAP11XX_REG_PRODUCT_ID 0xfd + #define CAP11XX_REG_MANUFACTURER_ID 0xfe + #define CAP11XX_REG_REVISION 0xff + + #define CAP11XX_MANUFACTURER_ID 0x5d + ++#define CAP11XX_T_RST_FILT_MIN_US 10000 ++#define CAP11XX_T_RST_ON_MIN_MS 400 ++ + #ifdef CONFIG_LEDS_CLASS + struct cap11xx_led { + struct cap11xx_priv *priv; +@@ -85,6 +67,7 @@ struct cap11xx_priv { + struct regmap *regmap; + struct device *dev; + struct input_dev *idev; ++ struct gpio_desc *reset_gpio; + const struct cap11xx_hw_model *model; + + struct cap11xx_led *leds; +@@ -101,47 +84,32 @@ struct cap11xx_priv { + + struct cap11xx_hw_model { + u8 product_id; ++ u8 led_output_control_reg_base; ++ u8 sensor_input_reg_base; + unsigned int num_channels; + unsigned int num_leds; ++ unsigned int num_sensor_thresholds; + bool has_gain; ++ bool has_grouped_sensors; + bool has_irq_config; ++ bool has_repeat_en; + bool has_sensitivity_control; + bool has_signal_guard; + }; + + static const struct reg_default cap11xx_reg_defaults[] = { + { CAP11XX_REG_MAIN_CONTROL, 0x00 }, +- { CAP11XX_REG_GENERAL_STATUS, 0x00 }, +- { CAP11XX_REG_SENSOR_INPUT, 0x00 }, +- { CAP11XX_REG_NOISE_FLAG_STATUS, 0x00 }, + { CAP11XX_REG_SENSITIVITY_CONTROL, 0x2f }, +- { CAP11XX_REG_CONFIG, 0x20 }, +- { CAP11XX_REG_SENSOR_ENABLE, 0x3f }, +- { CAP11XX_REG_SENSOR_CONFIG, 0xa4 }, +- { CAP11XX_REG_SENSOR_CONFIG2, 0x07 }, +- { CAP11XX_REG_SAMPLING_CONFIG, 0x39 }, +- { CAP11XX_REG_CALIBRATION, 0x00 }, +- { CAP11XX_REG_INT_ENABLE, 0x3f }, + { CAP11XX_REG_REPEAT_RATE, 0x3f }, +- { CAP11XX_REG_MT_CONFIG, 0x80 }, +- { CAP11XX_REG_MT_PATTERN_CONFIG, 0x00 }, +- { CAP11XX_REG_MT_PATTERN, 0x3f }, +- { CAP11XX_REG_RECALIB_CONFIG, 0x8a }, + { CAP11XX_REG_SENSOR_THRESH(0), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(1), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(2), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(3), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(4), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(5), 0x40 }, +- { CAP11XX_REG_SENSOR_NOISE_THRESH, 0x01 }, +- { CAP11XX_REG_STANDBY_CHANNEL, 0x00 }, +- { CAP11XX_REG_STANDBY_CONFIG, 0x39 }, +- { CAP11XX_REG_STANDBY_SENSITIVITY, 0x02 }, +- { CAP11XX_REG_STANDBY_THRESH, 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(6), 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(7), 0x40 }, + { CAP11XX_REG_CONFIG2, 0x40 }, +- { CAP11XX_REG_LED_POLARITY, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB1, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB2, 0x00 }, + }; + + static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) +@@ -149,6 +117,12 @@ static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) + switch (reg) { + case CAP11XX_REG_MAIN_CONTROL: + case CAP11XX_REG_SENSOR_INPUT: ++ /* ++ * CAP1114_REG_BUTTON_STATUS1 (CAP11XX_REG_SENSOR_INPUT) and ++ * CAP1114_REG_BUTTON_STATUS2 is volatile for the CAP1114, ++ * which supports more than 8 touch channels. ++ */ ++ case CAP1114_REG_BUTTON_STATUS2: + case CAP11XX_REG_SENOR_DELTA(0): + case CAP11XX_REG_SENOR_DELTA(1): + case CAP11XX_REG_SENOR_DELTA(2): +@@ -257,8 +231,8 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + + if (!of_property_read_u32_array(node, "microchip,input-threshold", +- priv->thresholds, priv->model->num_channels)) { +- for (i = 0; i < priv->model->num_channels; i++) { ++ priv->thresholds, priv->model->num_sensor_thresholds)) { ++ for (i = 0; i < priv->model->num_sensor_thresholds; i++) { + if (priv->thresholds[i] > 127) { + dev_err(dev, "Invalid input-threshold value %u\n", + priv->thresholds[i]); +@@ -273,10 +247,13 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + } + +- if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", +- priv->calib_sensitivities, +- priv->model->num_channels)) { +- if (priv->model->has_sensitivity_control) { ++ if (of_property_present(node, "microchip,calib-sensitivity")) { ++ if (!priv->model->has_sensitivity_control) { ++ dev_warn(dev, ++ "This model doesn't support 'calib-sensitivity'\n"); ++ } else if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", ++ priv->calib_sensitivities, ++ priv->model->num_channels)) { + for (i = 0; i < priv->model->num_channels; i++) { + if (!is_power_of_2(priv->calib_sensitivities[i]) || + priv->calib_sensitivities[i] > 4) { +@@ -296,32 +273,31 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + if (error) + return error; + } +- } else { +- dev_warn(dev, +- "This model doesn't support 'calib-sensitivity'\n"); + } + } + +- for (i = 0; i < priv->model->num_channels; i++) { +- if (!of_property_read_u32_index(node, "microchip,signal-guard", +- i, &u32_val)) { +- if (u32_val > 1) +- return -EINVAL; +- if (u32_val) +- priv->signal_guard_inputs_mask |= 0x01 << i; +- } +- } +- +- if (priv->signal_guard_inputs_mask) { +- if (priv->model->has_signal_guard) { +- error = regmap_write(priv->regmap, +- CAP11XX_REG_SIGNAL_GUARD_ENABLE, +- priv->signal_guard_inputs_mask); +- if (error) +- return error; +- } else { ++ if (of_property_present(node, "microchip,signal-guard")) { ++ if (!priv->model->has_signal_guard) { + dev_warn(dev, + "This model doesn't support 'signal-guard'\n"); ++ } else { ++ for (i = 0; i < priv->model->num_channels; i++) { ++ if (!of_property_read_u32_index(node, "microchip,signal-guard", ++ i, &u32_val)) { ++ if (u32_val > 1) ++ return -EINVAL; ++ if (u32_val) ++ priv->signal_guard_inputs_mask |= 0x01 << i; ++ } ++ } ++ ++ if (priv->signal_guard_inputs_mask) { ++ error = regmap_write(priv->regmap, ++ CAP11XX_REG_SIGNAL_GUARD_ENABLE, ++ priv->signal_guard_inputs_mask); ++ if (error) ++ return error; ++ } + } + } + +@@ -332,10 +308,23 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + of_property_read_u32_array(node, "linux,keycodes", + priv->keycodes, priv->model->num_channels); + +- /* Disable autorepeat. The Linux input system has its own handling. */ +- error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); +- if (error) +- return error; ++ /* ++ * CAP1114 needs dedicated configuration to split ++ * grouped sensors into independent inputs. ++ */ ++ if (priv->model->has_grouped_sensors) { ++ error = regmap_set_bits(priv->regmap, CAP1114_REG_CONFIG2, ++ CAP1114_REG_CONFIG2_VOL_UP_DOWN); ++ if (error) ++ return error; ++ } ++ ++ if (priv->model->has_repeat_en) { ++ /* Disable autorepeat. The Linux input system has its own handling. */ ++ error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); ++ if (error) ++ return error; ++ } + + return 0; + } +@@ -354,10 +343,25 @@ static irqreturn_t cap11xx_thread_func(int irq_num, void *data) + if (ret < 0) + goto out; + +- ret = regmap_read(priv->regmap, CAP11XX_REG_SENSOR_INPUT, &status); ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base, &status); + if (ret < 0) + goto out; + ++ if (priv->model->num_channels > 8) { ++ unsigned int status2; ++ ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base + 1, &status2); ++ if (ret < 0) ++ goto out; ++ ++ /* ++ * CAP1114 STATUS1 register only contains data for the first 6 channels. ++ * the remaining channels is stored in STATUS2. ++ */ ++ status &= GENMASK(5, 0); ++ status |= FIELD_PREP(GENMASK(13, 6), status2); ++ } ++ + for (i = 0; i < priv->idev->keycodemax; i++) + input_report_key(priv->idev, priv->keycodes[i], + status & (1 << i)); +@@ -407,10 +411,16 @@ static int cap11xx_led_set(struct led_classdev *cdev, + * limitation. Brightness levels per LED are either + * 0 (OFF) and 1 (ON). + */ +- return regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, +- BIT(led->reg), +- value ? BIT(led->reg) : 0); ++ if (led->reg >= 8) ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ BIT(led->reg - 8), ++ value ? BIT(led->reg - 8) : 0); ++ else ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base, ++ BIT(led->reg), ++ value ? BIT(led->reg) : 0); + } + + static int cap11xx_init_leds(struct device *dev, +@@ -420,6 +430,7 @@ static int cap11xx_init_leds(struct device *dev, + struct cap11xx_led *led; + int cnt = of_get_child_count(node); + int error; ++ u32 duty_val; + + if (!num_leds || !cnt) + return 0; +@@ -433,15 +444,26 @@ static int cap11xx_init_leds(struct device *dev, + + priv->leds = led; + ++ /* Set all LEDs to off */ + error = regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, 0xff, 0); ++ priv->model->led_output_control_reg_base, ++ GENMASK(min(num_leds, 8) - 1, 0), 0); + if (error) + return error; + ++ if (num_leds > 8) { ++ error = regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ GENMASK(num_leds - 8 - 1, 0), 0); ++ if (error) ++ return error; ++ } ++ ++ duty_val = FIELD_PREP(CAP11XX_REG_LED_DUTY_MAX_MASK, ++ CAP11XX_REG_LED_DUTY_MAX_VALUE); ++ + error = regmap_update_bits(priv->regmap, CAP11XX_REG_LED_DUTY_CYCLE_4, +- CAP11XX_REG_LED_DUTY_MAX_MASK, +- CAP11XX_REG_LED_DUTY_MAX_VALUE << +- CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT); ++ CAP11XX_REG_LED_DUTY_MAX_MASK, duty_val); + if (error) + return error; + +@@ -510,9 +532,20 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + ++ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(priv->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), ++ "Failed to get 'reset' GPIO\n"); ++ ++ if (priv->reset_gpio) { ++ usleep_range(CAP11XX_T_RST_FILT_MIN_US, CAP11XX_T_RST_FILT_MIN_US * 2); ++ gpiod_set_value_cansleep(priv->reset_gpio, 0); ++ msleep(CAP11XX_T_RST_ON_MIN_MS); ++ } ++ + error = regmap_read(priv->regmap, CAP11XX_REG_PRODUCT_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read product ID\n"); + + if (val != cap->product_id) { + dev_err(dev, "Product ID: Got 0x%02x, expected 0x%02x\n", +@@ -522,7 +555,7 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + + error = regmap_read(priv->regmap, CAP11XX_REG_MANUFACTURER_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read manufacturer ID\n"); + + if (val != CAP11XX_MANUFACTURER_ID) { + dev_err(dev, "Manufacturer ID: Got 0x%02x, expected 0x%02x\n", +@@ -531,11 +564,8 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + error = regmap_read(priv->regmap, CAP11XX_REG_REVISION, &rev); +- if (error < 0) +- return error; +- +- dev_info(dev, "CAP11XX detected, model %s, revision 0x%02x\n", +- id->name, rev); ++ if (error) ++ return dev_err_probe(dev, error, "Failed to read revision\n"); + + priv->model = cap; + +@@ -599,47 +629,79 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + static const struct cap11xx_hw_model cap1106_model = { +- .product_id = 0x55, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x55, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, ++}; ++ ++static const struct cap11xx_hw_model cap1114_model = { ++ .product_id = 0x3a, ++ .num_channels = 14, .num_leds = 11, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP1114_REG_LED_OUTPUT_CONTROL1, ++ .sensor_input_reg_base = CAP1114_REG_BUTTON_STATUS1, ++ .has_grouped_sensors = true, + }; + + static const struct cap11xx_hw_model cap1126_model = { +- .product_id = 0x53, .num_channels = 6, .num_leds = 2, ++ .product_id = 0x53, ++ .num_channels = 6, .num_leds = 2, .num_sensor_thresholds = 6, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1188_model = { +- .product_id = 0x50, .num_channels = 8, .num_leds = 8, ++ .product_id = 0x50, ++ .num_channels = 8, .num_leds = 8, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1203_model = { +- .product_id = 0x6d, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6d, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1206_model = { +- .product_id = 0x67, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x67, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1293_model = { +- .product_id = 0x6f, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6f, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct cap11xx_hw_model cap1298_model = { +- .product_id = 0x71, .num_channels = 8, .num_leds = 0, ++ .product_id = 0x71, ++ .num_channels = 8, .num_leds = 0, .num_sensor_thresholds = 8, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct of_device_id cap11xx_dt_ids[] = { + { .compatible = "microchip,cap1106", .data = &cap1106_model }, ++ { .compatible = "microchip,cap1114", .data = &cap1114_model }, + { .compatible = "microchip,cap1126", .data = &cap1126_model }, + { .compatible = "microchip,cap1188", .data = &cap1188_model }, + { .compatible = "microchip,cap1203", .data = &cap1203_model }, +@@ -652,6 +714,7 @@ MODULE_DEVICE_TABLE(of, cap11xx_dt_ids); + + static const struct i2c_device_id cap11xx_i2c_ids[] = { + { .name = "cap1106", .driver_data = (kernel_ulong_t)&cap1106_model }, ++ { .name = "cap1114", .driver_data = (kernel_ulong_t)&cap1114_model }, + { .name = "cap1126", .driver_data = (kernel_ulong_t)&cap1126_model }, + { .name = "cap1188", .driver_data = (kernel_ulong_t)&cap1188_model }, + { .name = "cap1203", .driver_data = (kernel_ulong_t)&cap1203_model }, +diff --git a/drivers/input/keyboard/charlieplex_keypad.c b/drivers/input/keyboard/charlieplex_keypad.c +--- a/drivers/input/keyboard/charlieplex_keypad.c ++++ b/drivers/input/keyboard/charlieplex_keypad.c +@@ -77,7 +77,9 @@ static int charlieplex_keypad_scan_line(struct charlieplex_keypad *keypad, + int err; + + /* Activate only one line as output at a time. */ +- gpiod_direction_output(line_gpios->desc[oline], 1); ++ err = gpiod_direction_output(line_gpios->desc[oline], 1); ++ if (err) ++ return err; + + if (keypad->settling_time_us) + fsleep(keypad->settling_time_us); +diff --git a/drivers/input/keyboard/hpps2atkbd.h b/drivers/input/keyboard/hpps2atkbd.h +deleted file mode 100644 +--- a/drivers/input/keyboard/hpps2atkbd.h ++++ /dev/null +@@ -1,110 +0,0 @@ +-/* +- * drivers/input/keyboard/hpps2atkbd.h +- * +- * Copyright (c) 2004 Helge Deller +- * Copyright (c) 2002 Laurent Canet +- * Copyright (c) 2002 Thibaut Varene +- * Copyright (c) 2000 Xavier Debacker +- * +- * HP PS/2 AT-compatible Keyboard, found in PA/RISC Workstations & Laptops +- * +- * This file is subject to the terms and conditions of the GNU General Public +- * License. See the file "COPYING" in the main directory of this archive +- * for more details. +- */ +- +- +-/* Is the keyboard an RDI PrecisionBook? */ +-#ifndef CONFIG_KEYBOARD_ATKBD_RDI_KEYCODES +-# define CONFLICT(x,y) x +-#else +-# define CONFLICT(x,y) y +-#endif +- +-/* sadly RDI (Tadpole) decided to ship a different keyboard layout +- than HP for their PS/2 laptop keyboard which leads to conflicting +- keycodes between a normal HP PS/2 keyboard and a RDI Precisionbook. +- HP: RDI: */ +-#define C_07 CONFLICT( KEY_F12, KEY_F1 ) +-#define C_11 CONFLICT( KEY_LEFTALT, KEY_LEFTCTRL ) +-#define C_14 CONFLICT( KEY_LEFTCTRL, KEY_CAPSLOCK ) +-#define C_58 CONFLICT( KEY_CAPSLOCK, KEY_RIGHTCTRL ) +-#define C_61 CONFLICT( KEY_102ND, KEY_LEFT ) +- +-/* Raw SET 2 scancode table */ +- +-/* 00 */ KEY_RESERVED, KEY_F9, KEY_RESERVED, KEY_F5, KEY_F3, KEY_F1, KEY_F2, C_07, +-/* 08 */ KEY_ESC, KEY_F10, KEY_F8, KEY_F6, KEY_F4, KEY_TAB, KEY_GRAVE, KEY_F2, +-/* 10 */ KEY_RESERVED, C_11, KEY_LEFTSHIFT, KEY_RESERVED, C_14, KEY_Q, KEY_1, KEY_F3, +-/* 18 */ KEY_RESERVED, KEY_LEFTALT, KEY_Z, KEY_S, KEY_A, KEY_W, KEY_2, KEY_F4, +-/* 20 */ KEY_RESERVED, KEY_C, KEY_X, KEY_D, KEY_E, KEY_4, KEY_3, KEY_F5, +-/* 28 */ KEY_RESERVED, KEY_SPACE, KEY_V, KEY_F, KEY_T, KEY_R, KEY_5, KEY_F6, +-/* 30 */ KEY_RESERVED, KEY_N, KEY_B, KEY_H, KEY_G, KEY_Y, KEY_6, KEY_F7, +-/* 38 */ KEY_RESERVED, KEY_RIGHTALT, KEY_M, KEY_J, KEY_U, KEY_7, KEY_8, KEY_F8, +-/* 40 */ KEY_RESERVED, KEY_COMMA, KEY_K, KEY_I, KEY_O, KEY_0, KEY_9, KEY_F9, +-/* 48 */ KEY_RESERVED, KEY_DOT, KEY_SLASH, KEY_L, KEY_SEMICOLON, KEY_P, KEY_MINUS, KEY_F10, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_APOSTROPHE,KEY_RESERVED, KEY_LEFTBRACE, KEY_EQUAL, KEY_F11, KEY_SYSRQ, +-/* 58 */ C_58, KEY_RIGHTSHIFT,KEY_ENTER, KEY_RIGHTBRACE,KEY_BACKSLASH, KEY_BACKSLASH,KEY_F12, KEY_SCROLLLOCK, +-/* 60 */ KEY_DOWN, C_61, KEY_PAUSE, KEY_UP, KEY_DELETE, KEY_END, KEY_BACKSPACE, KEY_INSERT, +-/* 68 */ KEY_RESERVED, KEY_KP1, KEY_RIGHT, KEY_KP4, KEY_KP7, KEY_PAGEDOWN, KEY_HOME, KEY_PAGEUP, +-/* 70 */ KEY_KP0, KEY_KPDOT, KEY_KP2, KEY_KP5, KEY_KP6, KEY_KP8, KEY_ESC, KEY_NUMLOCK, +-/* 78 */ KEY_F11, KEY_KPPLUS, KEY_KP3, KEY_KPMINUS, KEY_KPASTERISK,KEY_KP9, KEY_SCROLLLOCK,KEY_102ND, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RIGHTALT, 255, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_CAPSLOCK, KEY_RESERVED, KEY_LEFTMETA, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTMETA, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_COMPOSE, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPSLASH, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPENTER, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_END, KEY_RESERVED, KEY_LEFT, KEY_HOME, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_INSERT, KEY_DELETE, KEY_DOWN, KEY_RESERVED, KEY_RIGHT, KEY_UP, KEY_RESERVED, KEY_PAUSE, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED, KEY_SYSRQ, KEY_PAGEUP, KEY_RESERVED, KEY_RESERVED, +- +-/* These are offset for escaped keycodes: */ +- +-/* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_F7, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 08 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 10 */ KEY_RESERVED, KEY_RIGHTALT, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 18 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 20 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 28 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 30 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 38 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 40 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 48 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 58 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 60 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +- +-#undef CONFLICT +-#undef C_07 +-#undef C_11 +-#undef C_14 +-#undef C_58 +-#undef C_61 +- +diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c +--- a/drivers/input/keyboard/matrix_keypad.c ++++ b/drivers/input/keyboard/matrix_keypad.c +@@ -16,7 +16,6 @@ + #include + #include + #include +-#include + #include + #include + #include +diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c +--- a/drivers/input/keyboard/pmic8xxx-keypad.c ++++ b/drivers/input/keyboard/pmic8xxx-keypad.c +@@ -462,15 +462,9 @@ static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp) + + static int pmic8xxx_kp_disable(struct pmic8xxx_kp *kp) + { +- int rc; +- + kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN; + +- rc = regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); +- if (rc < 0) +- return rc; +- +- return rc; ++ return regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); + } + + static int pmic8xxx_kp_open(struct input_dev *dev) +diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c +--- a/drivers/input/keyboard/samsung-keypad.c ++++ b/drivers/input/keyboard/samsung-keypad.c +@@ -142,7 +142,7 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + unsigned int row_state[SAMSUNG_MAX_COLS]; + bool key_down; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + do { + readl(keypad->base + SAMSUNG_KEYIFSTSCLR); +@@ -158,8 +158,6 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + + } while (key_down && !keypad->stopped); + +- pm_runtime_put(&keypad->pdev->dev); +- + return IRQ_HANDLED; + } + +@@ -167,7 +165,7 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Tell IRQ thread that it may poll the device. */ + keypad->stopped = false; +@@ -182,14 +180,14 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + /* KEYIFCOL reg clear. */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); + +- pm_runtime_put(&keypad->pdev->dev); ++ enable_irq(keypad->irq); + } + + static void samsung_keypad_stop(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Signal IRQ thread to stop polling and disable the handler. */ + keypad->stopped = true; +@@ -205,14 +203,6 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); +- +- /* +- * Now that chip should not generate interrupts we can safely +- * re-enable the handler. +- */ +- enable_irq(keypad->irq); +- +- pm_runtime_put(&keypad->pdev->dev); + } + + static int samsung_keypad_open(struct input_dev *input_dev) +@@ -412,7 +402,8 @@ static int samsung_keypad_probe(struct platform_device *pdev) + } + + error = devm_request_threaded_irq(&pdev->dev, keypad->irq, NULL, +- samsung_keypad_irq, IRQF_ONESHOT, ++ samsung_keypad_irq, ++ IRQF_ONESHOT | IRQF_NO_AUTOEN, + dev_name(&pdev->dev), keypad); + if (error) { + dev_err(&pdev->dev, "failed to register keypad interrupt\n"); +@@ -492,15 +483,17 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, + + val = readl(keypad->base + SAMSUNG_KEYIFCON); + if (enable) { ++ enable_irq_wake(keypad->irq); + val |= SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- enable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); + } else { + val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- disable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); ++ disable_irq_wake(keypad->irq); ++ ++ if (!input_device_enabled(keypad->input_dev)) ++ writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); + } +- writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); + } +@@ -516,7 +509,8 @@ static int samsung_keypad_suspend(struct device *dev) + if (input_device_enabled(input_dev)) + samsung_keypad_stop(keypad); + +- samsung_keypad_toggle_wakeup(keypad, true); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, true); + + return 0; + } +@@ -529,7 +523,8 @@ static int samsung_keypad_resume(struct device *dev) + + guard(mutex)(&input_dev->mutex); + +- samsung_keypad_toggle_wakeup(keypad, false); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, false); + + if (input_device_enabled(input_dev)) + samsung_keypad_start(keypad); +diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c +--- a/drivers/input/keyboard/snvs_pwrkey.c ++++ b/drivers/input/keyboard/snvs_pwrkey.c +@@ -39,6 +39,9 @@ struct pwrkey_drv_data { + int keycode; + int keystate; /* 1:pressed */ + int wakeup; ++ bool suspended; /* Track suspend state */ ++ bool pending_press; /* Key pressed during suspend, report from timer callback */ ++ spinlock_t lock; /* Protects keystate, suspended and pending_press */ + struct timer_list check_timer; + struct input_dev *input; + u8 minor_rev; +@@ -49,14 +52,38 @@ static void imx_imx_snvs_check_for_events(struct timer_list *t) + struct pwrkey_drv_data *pdata = timer_container_of(pdata, t, + check_timer); + struct input_dev *input = pdata->input; ++ bool state_changed = false; ++ bool pending_press; + u32 state; + + regmap_read(pdata->snvs, SNVS_HPSR_REG, &state); + state = state & SNVS_HPSR_BTN ? 1 : 0; + +- /* only report new event if status changed */ +- if (state ^ pdata->keystate) { +- pdata->keystate = state; ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ pending_press = pdata->pending_press; ++ if (pending_press) { ++ pdata->pending_press = false; ++ pdata->keystate = 1; ++ } ++ /* only report new event if status changed */ ++ if (state ^ pdata->keystate) { ++ pdata->keystate = state; ++ state_changed = true; ++ } ++ } ++ ++ /* ++ * Report a press event latched during suspend. If the key is still ++ * held, state_changed will be 0 (keystate already set to 1 above), ++ * so no duplicate press is reported. If already released, ++ * state_changed will fire next to report the release. ++ */ ++ if (pending_press) { ++ input_report_key(input, pdata->keycode, 1); ++ input_sync(input); ++ } ++ ++ if (state_changed) { + input_event(input, EV_KEY, pdata->keycode, state); + input_sync(input); + pm_relax(pdata->input->dev.parent); +@@ -92,8 +119,17 @@ static irqreturn_t imx_snvs_pwrkey_interrupt(int irq, void *dev_id) + input_sync(input); + pm_relax(input->dev.parent); + } else { ++ /* ++ * If the key is pressed during suspend, latch it so ++ * the timer callback can report the press event in ++ * softirq context, avoiding out-of-order events. ++ */ ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ if (pdata->suspended) ++ pdata->pending_press = true; ++ } + mod_timer(&pdata->check_timer, +- jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); ++ jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); + } + } + +@@ -112,6 +148,7 @@ static void imx_snvs_pwrkey_act(void *pdata) + + static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; + struct pwrkey_drv_data *pdata; + struct input_dev *input; + struct device_node *np; +@@ -122,37 +159,35 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + u32 vid; + + /* Get SNVS register Page */ +- np = pdev->dev.of_node; ++ np = dev->of_node; + if (!np) +- return -ENODEV; ++ return dev_err_probe(dev, -ENODEV, "Device tree node not found\n"); + +- pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->snvs = syscon_regmap_lookup_by_phandle(np, "regmap"); +- if (IS_ERR(pdata->snvs)) { +- dev_err(&pdev->dev, "Can't get snvs syscon\n"); +- return PTR_ERR(pdata->snvs); +- } ++ if (IS_ERR(pdata->snvs)) ++ return dev_err_probe(dev, PTR_ERR(pdata->snvs), "Can't get snvs syscon\n"); + + if (of_property_read_u32(np, "linux,keycode", &pdata->keycode)) { + pdata->keycode = KEY_POWER; +- dev_warn(&pdev->dev, "KEY_POWER without setting in dts\n"); ++ dev_warn(dev, "KEY_POWER without setting in dts\n"); + } + +- clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); +- if (IS_ERR(clk)) { +- dev_err(&pdev->dev, "Failed to get snvs clock (%pe)\n", clk); +- return PTR_ERR(clk); +- } ++ clk = devm_clk_get_optional_enabled(dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(dev, PTR_ERR(clk), ++ "Failed to get snvs clock (%pe)\n", clk); + + pdata->wakeup = of_property_read_bool(np, "wakeup-source"); + + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq < 0) +- return -EINVAL; ++ return pdata->irq; + ++ spin_lock_init(&pdata->lock); + error = of_property_read_u32(np, "power-off-time-sec", &val); + if (!error) { + switch (val) { +@@ -165,9 +200,8 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + bpt = (val / 5) - 1; + break; + default: +- dev_err(&pdev->dev, +- "power-off-time-sec %d out of range\n", val); +- return -EINVAL; ++ return dev_err_probe(dev, -EINVAL, ++ "power-off-time-sec %d out of range\n", val); + } + + regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, SNVS_LPCR_BPT_MASK, +@@ -184,11 +218,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + + timer_setup(&pdata->check_timer, imx_imx_snvs_check_for_events, 0); + +- input = devm_input_allocate_device(&pdev->dev); +- if (!input) { +- dev_err(&pdev->dev, "failed to allocate the input device\n"); +- return -ENOMEM; +- } ++ input = devm_input_allocate_device(dev); ++ if (!input) ++ return dev_err_probe(dev, -ENOMEM, "failed to allocate the input device\n"); + + input->name = pdev->name; + input->phys = "snvs-pwrkey/input0"; +@@ -197,37 +229,57 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + input_set_capability(input, EV_KEY, pdata->keycode); + + /* input customer action to cancel release timer */ +- error = devm_add_action(&pdev->dev, imx_snvs_pwrkey_act, pdata); +- if (error) { +- dev_err(&pdev->dev, "failed to register remove action\n"); +- return error; +- } ++ error = devm_add_action(dev, imx_snvs_pwrkey_act, pdata); ++ if (error) ++ return dev_err_probe(dev, error, "failed to register remove action\n"); + + pdata->input = input; + platform_set_drvdata(pdev, pdata); + +- error = devm_request_irq(&pdev->dev, pdata->irq, +- imx_snvs_pwrkey_interrupt, +- 0, pdev->name, pdev); +- if (error) { +- dev_err(&pdev->dev, "interrupt not available.\n"); +- return error; +- } ++ error = devm_request_irq(dev, pdata->irq, ++ imx_snvs_pwrkey_interrupt, ++ 0, pdev->name, pdev); ++ if (error) ++ return dev_err_probe(dev, error, "interrupt not available.\n"); + + error = input_register_device(input); +- if (error < 0) { +- dev_err(&pdev->dev, "failed to register input device\n"); +- return error; +- } ++ if (error < 0) ++ return dev_err_probe(dev, error, "failed to register input device\n"); + +- device_init_wakeup(&pdev->dev, pdata->wakeup); +- error = dev_pm_set_wake_irq(&pdev->dev, pdata->irq); ++ device_init_wakeup(dev, pdata->wakeup); ++ error = dev_pm_set_wake_irq(dev, pdata->irq); + if (error) +- dev_err(&pdev->dev, "irq wake enable failed.\n"); ++ dev_err(dev, "irq wake enable failed.\n"); + + return 0; + } + ++static int imx_snvs_pwrkey_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = true; ++ ++ return 0; ++} ++ ++static int imx_snvs_pwrkey_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = false; ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(imx_snvs_pwrkey_pm_ops, ++ imx_snvs_pwrkey_suspend, ++ imx_snvs_pwrkey_resume); ++ + static const struct of_device_id imx_snvs_pwrkey_ids[] = { + { .compatible = "fsl,sec-v4.0-pwrkey" }, + { /* sentinel */ } +@@ -238,6 +290,7 @@ static struct platform_driver imx_snvs_pwrkey_driver = { + .driver = { + .name = "snvs_pwrkey", + .of_match_table = imx_snvs_pwrkey_ids, ++ .pm = pm_ptr(&imx_snvs_pwrkey_pm_ops), + }, + .probe = imx_snvs_pwrkey_probe, + }; +diff --git a/drivers/input/keyboard/tca8418_keypad.c b/drivers/input/keyboard/tca8418_keypad.c +--- a/drivers/input/keyboard/tca8418_keypad.c ++++ b/drivers/input/keyboard/tca8418_keypad.c +@@ -254,7 +254,8 @@ static int tca8418_configure(struct tca8418_keypad *keypad_data, + return error; + + error = tca8418_write_byte(keypad_data, REG_CFG, +- CFG_INT_CFG | CFG_OVR_FLOW_IEN | CFG_KE_IEN); ++ CFG_INT_CFG | CFG_OVR_FLOW_IEN | ++ CFG_OVR_FLOW_M | CFG_KE_IEN); + + return error; + } +diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig +--- a/drivers/input/misc/Kconfig ++++ b/drivers/input/misc/Kconfig +@@ -842,6 +842,18 @@ config INPUT_IQS7222 + To compile this driver as a module, choose M here: the + module will be called iqs7222. + ++config INPUT_ISA1200_HAPTIC ++ tristate "Imagis ISA1200 haptic feedback unit" ++ depends on I2C ++ select INPUT_FF_MEMLESS ++ select REGMAP_I2C ++ help ++ Say Y to enable support for the Imagis ISA1200 haptic ++ feedback unit. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called isa1200. ++ + config INPUT_CMA3000 + tristate "VTI CMA3000 Tri-axis accelerometer" + help +diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile +--- a/drivers/input/misc/Makefile ++++ b/drivers/input/misc/Makefile +@@ -49,6 +49,7 @@ obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o + obj-$(CONFIG_INPUT_IQS269A) += iqs269a.o + obj-$(CONFIG_INPUT_IQS626A) += iqs626a.o + obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o ++obj-$(CONFIG_INPUT_ISA1200_HAPTIC) += isa1200.o + obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o + obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o + obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -2234,6 +2234,7 @@ static const struct usb_device_id ims_pcu_id_table[] = { + }, + { } + }; ++MODULE_DEVICE_TABLE(usb, ims_pcu_id_table); + + static const struct attribute_group *ims_pcu_sysfs_groups[] = { + &ims_pcu_attr_group, +diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c +new file mode 100644 +--- /dev/null ++++ b/drivers/input/misc/isa1200.c +@@ -0,0 +1,533 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * System control (LDO regulator) ++ * ++ * LDO voltage to register mapping is linear, but it is split in two parts: ++ * 2.3V - 3.0V map to 0x08 - 0x0f; 3.1V - 3.8V map to 0x00 - 0x7 ++ */ ++ ++#define ISA1200_SCTRL 0x00 ++#define ISA1200_LDO_VOLTAGE_BASE 0x08 ++#define ISA1200_LDO_VOLTAGE_STEP 100000 ++#define ISA1200_LDO_VOLTAGE_2V3 23 ++#define ISA1200_LDO_VOLTAGE_3V1 31 ++#define ISA1200_LDO_VOLTAGE_MIN 2300000 ++#define ISA1200_LDO_VOLTAGE_MAX 3800000 ++ ++/* ++ * The output frequency is calculated with this formula: ++ * ++ * base clock frequency ++ * fout = ----------------------------------------- ++ * (128 - PWM_FREQ) * 2 * PLLDIV * PWM_PERIOD ++ * ++ * The base clock frequency is the clock frequency provided on the ++ * clock input to the chip, divided by the value in HCTRL0 ++ * ++ * PWM_FREQ is configured in register HCTRL4, it is common to set this ++ * to 0 to get only two variables to calculate. ++ * ++ * PLLDIV is configured in register HCTRL3 (bits 7..4, so 0..15) ++ * PWM_PERIOD is configured in register HCTRL6 ++ * Further the duty cycle can be configured in HCTRL5 ++ */ ++ ++/* ++ * HCTRL0 configures clock or PWM input and selects the divider for ++ * the clock input. ++ */ ++#define ISA1200_HCTRL0 0x30 ++#define ISA1200_HCTRL0_HAP_ENABLE BIT(7) ++#define ISA1200_HCTRL0_PWM_GEN_MODE BIT(4) ++#define ISA1200_HCTRL0_PWM_INPUT_MODE BIT(3) ++#define ISA1200_HCTRL0_CLKDIV_128 128 ++ ++/* ++ * HCTRL1 configures the motor type and clock sourse ++ */ ++#define ISA1200_HCTRL1 0x31 ++#define ISA1200_HCTRL1_EXT_CLOCK BIT(7) ++#define ISA1200_HCTRL1_DAC_INVERT BIT(6) ++#define ISA1200_HCTRL1_MODE(n) (((n) & 1) << 5) ++ ++/* HCTRL2 controls software reset of the chip */ ++#define ISA1200_HCTRL2 0x32 ++#define ISA1200_HCTRL2_SW_RESET BIT(0) ++ ++/* ++ * HCTRL3 controls the PLL divisor ++ * ++ * Bits [0,1] are always set to 1 (we don't know what they are ++ * used for) and bit 4 and upward control the PLL divisor. ++ */ ++#define ISA1200_HCTRL3 0x33 ++#define ISA1200_HCTRL3_DEFAULT 0x03 ++#define ISA1200_HCTRL3_PLLDIV(n) (((n) & 0xf) << 4) ++ ++/* HCTRL4 controls the PWM frequency of external channel */ ++#define ISA1200_HCTRL4 0x34 ++ ++/* HCTRL5 controls the PWM high duty cycle of internal channel */ ++#define ISA1200_HCTRL5 0x35 ++ ++/* HCTRL6 controls the PWM period of internal channel */ ++#define ISA1200_HCTRL6 0x36 ++#define ISA1200_HCTRL6_PERIOD_SCALE 100 ++ ++/* The use for these registers is unknown but they exist */ ++#define ISA1200_HCTRL7 0x37 ++#define ISA1200_HCTRL8 0x38 ++#define ISA1200_HCTRL9 0x39 ++#define ISA1200_HCTRLA 0x3a ++#define ISA1200_HCTRLB 0x3b ++#define ISA1200_HCTRLC 0x3c ++#define ISA1200_HCTRLD 0x3d ++ ++#define ISA1200_EN_PINS_MAX 2 ++ ++static const struct regulator_bulk_data isa1200_supplies[] = { ++ { .supply = "vdd" }, { .supply = "vddp" }, ++}; ++ ++struct isa1200_config { ++ u32 ldo_voltage; ++ u32 mode; ++ u32 clkdiv; ++ u32 plldiv; ++ u32 freq; ++ u32 period; ++ u32 duty; ++}; ++ ++struct isa1200 { ++ struct input_dev *input; ++ struct regmap *map; ++ ++ struct clk *clk; ++ struct pwm_device *pwm; ++ struct gpio_descs *enable_gpios; ++ struct regulator_bulk_data *supplies; ++ ++ struct work_struct play_work; ++ struct isa1200_config config; ++ ++ int level; ++ bool suspended; ++ bool active; ++}; ++ ++static const struct regmap_config isa1200_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = ISA1200_HCTRLD, ++}; ++ ++static void isa1200_start(struct isa1200 *isa) ++{ ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &isa->input->dev; ++ struct pwm_state state; ++ u8 hctrl0 = 0, hctrl1 = 0; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ int err; ++ ++ if (!isa->active) { ++ err = regulator_bulk_enable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ if (err) { ++ dev_err(dev, "failed to enable supplies (%d)\n", err); ++ return; ++ } ++ ++ err = clk_prepare_enable(isa->clk); ++ if (err) { ++ dev_err(dev, "failed to enable clock (%d)\n", err); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ return; ++ } ++ ++ bitmap_fill(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ usleep_range(200, 300); ++ } ++ ++ regmap_write(isa->map, ISA1200_SCTRL, config->ldo_voltage); ++ ++ if (isa->clk) { ++ hctrl0 = ISA1200_HCTRL0_PWM_GEN_MODE; ++ hctrl1 = ISA1200_HCTRL1_EXT_CLOCK; ++ } ++ ++ if (isa->pwm) { ++ hctrl0 = ISA1200_HCTRL0_PWM_INPUT_MODE; ++ hctrl1 = 0; ++ } ++ ++ hctrl0 |= __ffs(config->clkdiv / ISA1200_HCTRL0_CLKDIV_128); ++ hctrl1 |= ISA1200_HCTRL1_DAC_INVERT; ++ hctrl1 |= ISA1200_HCTRL1_MODE(config->mode); ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ regmap_write(isa->map, ISA1200_HCTRL1, hctrl1); ++ ++ /* Make sure to de-assert software reset */ ++ regmap_write(isa->map, ISA1200_HCTRL2, 0x00); ++ ++ /* PLL divisor */ ++ regmap_write(isa->map, ISA1200_HCTRL3, ++ ISA1200_HCTRL3_PLLDIV(config->plldiv) | ++ ISA1200_HCTRL3_DEFAULT); ++ ++ /* Frequency */ ++ regmap_write(isa->map, ISA1200_HCTRL4, config->freq); ++ /* Duty cycle */ ++ regmap_write(isa->map, ISA1200_HCTRL5, config->period >> 1); ++ /* Period */ ++ regmap_write(isa->map, ISA1200_HCTRL6, config->period); ++ ++ hctrl0 |= ISA1200_HCTRL0_HAP_ENABLE; ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ ++ if (isa->clk) ++ regmap_write(isa->map, ISA1200_HCTRL5, config->duty); ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = config->duty; ++ state.enabled = true; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ isa->active = true; ++} ++ ++static void isa1200_stop(struct isa1200 *isa) ++{ ++ struct pwm_state state; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ ++ if (!isa->active) ++ return; ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = 0; ++ state.enabled = false; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, 0x00); ++ ++ bitmap_zero(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ clk_disable_unprepare(isa->clk); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ ++ isa->active = false; ++} ++ ++static void isa1200_play_work(struct work_struct *work) ++{ ++ struct isa1200 *isa = container_of(work, struct isa1200, play_work); ++ ++ if (!READ_ONCE(isa->suspended)) { ++ if (isa->level) ++ isa1200_start(isa); ++ else ++ isa1200_stop(isa); ++ } ++} ++ ++static int isa1200_vibrator_play_effect(struct input_dev *input, void *data, ++ struct ff_effect *effect) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ int level; ++ ++ /* ++ * TODO: we currently only support rumble. ++ * The ISA1200 can control two motors and some devices ++ * also have two motors mounted. ++ */ ++ level = effect->u.rumble.strong_magnitude; ++ if (!level) ++ level = effect->u.rumble.weak_magnitude; ++ ++ dev_dbg(&input->dev, "FF effect type %d level %d\n", ++ effect->type, level); ++ ++ if (isa->level != level) { ++ isa->level = level; ++ if (!READ_ONCE(isa->suspended)) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static void isa1200_vibrator_close(struct input_dev *input) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ isa->level = 0; ++} ++ ++static int isa1200_of_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa = i2c_get_clientdata(client); ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &client->dev; ++ struct fwnode_handle *ldo_node; ++ int err; ++ ++ isa->clk = devm_clk_get_optional(dev, NULL); ++ if (IS_ERR(isa->clk)) ++ return dev_err_probe(dev, PTR_ERR(isa->clk), ++ "failed to get clock\n"); ++ ++ isa->pwm = devm_pwm_get(dev, NULL); ++ if (IS_ERR(isa->pwm)) { ++ err = PTR_ERR(isa->pwm); ++ if (err == -ENODEV || err == -EINVAL) ++ isa->pwm = NULL; ++ else ++ return dev_err_probe(dev, err, "getting PWM\n"); ++ } ++ ++ if (!isa->clk && !isa->pwm) ++ return dev_err_probe(dev, -EINVAL, ++ "clock or PWM are required, none were provided\n"); ++ ++ err = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(isa1200_supplies), ++ isa1200_supplies, &isa->supplies); ++ if (err) ++ return dev_err_probe(dev, err, "failed to get supplies\n"); ++ ++ isa->enable_gpios = devm_gpiod_get_array_optional(dev, "control", ++ GPIOD_OUT_LOW); ++ if (IS_ERR(isa->enable_gpios)) ++ return dev_err_probe(dev, PTR_ERR(isa->enable_gpios), ++ "failed to get enable gpios\n"); ++ ++ if (isa->enable_gpios && isa->enable_gpios->ndescs > ISA1200_EN_PINS_MAX) ++ return dev_err_probe(dev, -EINVAL, "too many enable gpios\n"); ++ ++ ldo_node = device_get_named_child_node(dev, "ldo"); ++ if (!ldo_node) ++ return dev_err_probe(dev, -ENODEV, ++ "failed to get embedded LDO node\n"); ++ ++ err = fwnode_property_read_u32(ldo_node, "regulator-min-microvolt", ++ &config->ldo_voltage); ++ fwnode_handle_put(ldo_node); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get ldo voltage\n"); ++ ++ config->ldo_voltage = clamp(config->ldo_voltage, ++ ISA1200_LDO_VOLTAGE_MIN, ++ ISA1200_LDO_VOLTAGE_MAX); ++ ++ config->ldo_voltage /= ISA1200_LDO_VOLTAGE_STEP; ++ if (config->ldo_voltage < ISA1200_LDO_VOLTAGE_3V1) ++ config->ldo_voltage = config->ldo_voltage - ++ ISA1200_LDO_VOLTAGE_2V3 + ++ ISA1200_LDO_VOLTAGE_BASE; ++ else ++ config->ldo_voltage -= ISA1200_LDO_VOLTAGE_3V1; ++ ++ config->mode = 0; /* LRA_MODE */ ++ device_property_read_u32(dev, "imagis,mode", &config->mode); ++ ++ config->clkdiv = ISA1200_HCTRL0_CLKDIV_128; ++ device_property_read_u32(dev, "imagis,clk-div", &config->clkdiv); ++ if (!config->clkdiv) ++ return dev_err_probe(dev, -EINVAL, "clk-div cannot be zero\n"); ++ ++ config->clkdiv = clamp(config->clkdiv, ISA1200_HCTRL0_CLKDIV_128, ++ ISA1200_HCTRL0_CLKDIV_128 << 3); ++ ++ err = device_property_read_u32(dev, "imagis,pll-div", &config->plldiv); ++ if (err || !config->plldiv) ++ config->plldiv = 1; ++ ++ config->period = 0; ++ config->freq = 0; ++ config->duty = 0; ++ ++ if (isa->clk) { ++ err = device_property_read_u32(dev, "imagis,period-ns", ++ &config->period); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get period\n"); ++ ++ /* ++ * TODO: The scale value is arbitrary, but it fits observations ++ * quite well, and the exact conversion method is unknown. ++ * The period property value returned above is the HCTRL6 ++ * register value set by the vendor code, multiplied by 100. ++ */ ++ config->period /= ISA1200_HCTRL6_PERIOD_SCALE; ++ config->duty = config->period >> 1; ++ } ++ ++ if (isa->pwm) { ++ struct pwm_state state; ++ ++ pwm_init_state(isa->pwm, &state); ++ ++ if (!state.period) ++ return dev_err_probe(dev, -EINVAL, ++ "PWM period cannot be zero\n"); ++ ++ config->freq = div64_u64(NANO, state.period * config->clkdiv); ++ config->duty = state.period >> 1; ++ ++ err = pwm_apply_might_sleep(isa->pwm, &state); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to apply initial PWM state\n"); ++ } ++ ++ /* ++ * TODO: If device is using a clock, this property should return the ++ * value written to the HCTRL5 register by downstrem code. It likely ++ * needs to be converted into a meaningful duty cycle value, though ++ * unfortunately the exact conversion mechanism is unknown. If the ++ * device uses PWM, this property will return the correct duty cycle ++ * in nanoseconds. ++ */ ++ device_property_read_u32(dev, "imagis,duty-cycle-ns", &config->duty); ++ ++ return 0; ++} ++ ++static int isa1200_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa; ++ struct device *dev = &client->dev; ++ int err; ++ ++ isa = devm_kzalloc(dev, sizeof(*isa), GFP_KERNEL); ++ if (!isa) ++ return -ENOMEM; ++ ++ isa->input = devm_input_allocate_device(dev); ++ if (!isa->input) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, isa); ++ ++ err = isa1200_of_probe(client); ++ if (err) ++ return err; ++ ++ isa->map = devm_regmap_init_i2c(client, &isa1200_regmap_config); ++ if (IS_ERR(isa->map)) ++ return dev_err_probe(dev, PTR_ERR(isa->map), ++ "failed to initialize register map\n"); ++ ++ INIT_WORK(&isa->play_work, isa1200_play_work); ++ ++ isa->input->name = "isa1200-haptic"; ++ isa->input->id.bustype = BUS_I2C; ++ isa->input->close = isa1200_vibrator_close; ++ ++ isa->active = false; ++ ++ input_set_drvdata(isa->input, isa); ++ ++ /* TODO: this hardware can likely support more than rumble */ ++ input_set_capability(isa->input, EV_FF, FF_RUMBLE); ++ ++ err = input_ff_create_memless(isa->input, NULL, ++ isa1200_vibrator_play_effect); ++ if (err) ++ return dev_err_probe(dev, err, "failed to create FF dev\n"); ++ ++ err = input_register_device(isa->input); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register input dev\n"); ++ ++ return 0; ++} ++ ++static int isa1200_suspend(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, true); ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ } ++ ++ return 0; ++} ++ ++static int isa1200_resume(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, false); ++ if (isa->level) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); ++ ++static const struct of_device_id isa1200_of_match[] = { ++ { .compatible = "imagis,isa1200" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, isa1200_of_match); ++ ++static struct i2c_driver isa1200_i2c_driver = { ++ .driver = { ++ .name = "isa1200", ++ .of_match_table = isa1200_of_match, ++ .pm = pm_sleep_ptr(&isa1200_pm_ops), ++ }, ++ .probe = isa1200_probe, ++}; ++module_i2c_driver(isa1200_i2c_driver); ++ ++MODULE_AUTHOR("Linus Walleij "); ++MODULE_AUTHOR("Svyatoslav Ryhel "); ++MODULE_DESCRIPTION("Imagis ISA1200 haptic feedback unit"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/mouse/elan_i2c.h b/drivers/input/mouse/elan_i2c.h +--- a/drivers/input/mouse/elan_i2c.h ++++ b/drivers/input/mouse/elan_i2c.h +@@ -102,7 +102,8 @@ struct elan_transport_ops { + int (*prepare_fw_update)(struct i2c_client *client, u16 ic_type, + u8 iap_version, u16 fw_page_size); + int (*write_fw_block)(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx); ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx); + int (*finish_fw_update)(struct i2c_client *client, + struct completion *reset_done); + +diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c +--- a/drivers/input/mouse/elan_i2c_core.c ++++ b/drivers/input/mouse/elan_i2c_core.c +@@ -16,27 +16,27 @@ + */ + + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +-#include +-#include +-#include +-#include +-#include +-#include +-#include + #include +-#include ++#include ++#include + #include + #include + #include + #include ++#include ++#include ++#include ++#include + #include + + #include "elan_i2c.h" +@@ -86,6 +86,7 @@ struct elan_tp_data { + u16 ic_type; + u16 fw_validpage_count; + u16 fw_page_size; ++ u16 fw_page_delay; + u32 fw_signature_address; + + u8 min_baseline; +@@ -127,8 +128,11 @@ static u32 elan_i2c_lookup_quirks(u16 ic_type, u16 product_id) + } + + static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, +- u32 *signature_address, u16 *page_size) ++ u32 *signature_address, u16 *page_size, ++ u16 *page_delay) + { ++ *page_delay = 30; ++ + switch (ic_type) { + case 0x00: + case 0x06: +@@ -164,6 +168,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + break; + case 0x19: + *validpage_count = 2032; ++ *page_delay = 10; + break; + default: + /* unknown ic type clear value */ +@@ -179,6 +184,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + if ((ic_type == 0x14 || ic_type == 0x15) && iap_version >= 2) { + *validpage_count /= 8; + *page_size = ETP_FW_PAGE_SIZE_512; ++ *page_delay = 50; + } else if (ic_type >= 0x0D && iap_version >= 1) { + *validpage_count /= 2; + *page_size = ETP_FW_PAGE_SIZE_128; +@@ -368,7 +374,8 @@ static int elan_query_device_info(struct elan_tp_data *data) + error = elan_get_fwinfo(data->ic_type, data->iap_version, + &data->fw_validpage_count, + &data->fw_signature_address, +- &data->fw_page_size); ++ &data->fw_page_size, ++ &data->fw_page_delay); + if (error) + dev_warn(&data->client->dev, + "unexpected iap version %#04x (ic type: %#04x), firmware update will not work\n", +@@ -479,14 +486,16 @@ static int elan_query_device_parameters(struct elan_tp_data *data) + ********************************************************** + */ + static int elan_write_fw_block(struct elan_tp_data *data, u16 page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 page_delay, const u8 *page, u16 checksum, ++ int idx) + { + int retry = ETP_RETRY_COUNT; + int error; + + do { + error = data->ops->write_fw_block(data->client, page_size, +- page, checksum, idx); ++ page_delay, page, checksum, ++ idx); + if (!error) + return 0; + +@@ -525,7 +534,8 @@ static int __elan_update_firmware(struct elan_tp_data *data, + checksum += ((page[j + 1] << 8) | page[j]); + + error = elan_write_fw_block(data, data->fw_page_size, +- page, checksum, i); ++ data->fw_page_delay, page, checksum, ++ i); + if (error) { + dev_err(dev, "write page %d fail: %d\n", i, error); + return error; +diff --git a/drivers/input/mouse/elan_i2c_i2c.c b/drivers/input/mouse/elan_i2c_i2c.c +--- a/drivers/input/mouse/elan_i2c_i2c.c ++++ b/drivers/input/mouse/elan_i2c_i2c.c +@@ -625,7 +625,8 @@ static int elan_i2c_prepare_fw_update(struct i2c_client *client, u16 ic_type, + } + + static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + u8 val[3]; +@@ -650,7 +651,7 @@ static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, + } + + /* Wait for F/W to update one page ROM data. */ +- msleep(fw_page_size == ETP_FW_PAGE_SIZE_512 ? 50 : 35); ++ msleep(fw_page_delay); + + error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val); + if (error) { +diff --git a/drivers/input/mouse/elan_i2c_smbus.c b/drivers/input/mouse/elan_i2c_smbus.c +--- a/drivers/input/mouse/elan_i2c_smbus.c ++++ b/drivers/input/mouse/elan_i2c_smbus.c +@@ -416,7 +416,8 @@ static int elan_smbus_prepare_fw_update(struct i2c_client *client, u16 ic_type, + + + static int elan_smbus_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + int error; +diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c +--- a/drivers/input/mouse/focaltech.c ++++ b/drivers/input/mouse/focaltech.c +@@ -78,8 +78,8 @@ struct focaltech_finger_state { + * Absolute position (from the bottom left corner) of the + * finger. + */ +- unsigned int x; +- unsigned int y; ++ int x; ++ int y; + }; + + /* +@@ -108,7 +108,7 @@ struct focaltech_hw_state { + }; + + struct focaltech_data { +- unsigned int x_max, y_max; ++ int x_max, y_max; + struct focaltech_hw_state state; + }; + +@@ -126,17 +126,16 @@ static void focaltech_report_state(struct psmouse *psmouse) + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); + if (active) { +- unsigned int clamped_x, clamped_y; + /* + * The touchpad might report invalid data, so we clamp + * the resulting values so that we do not confuse +- * userspace. ++ * userspace or accumulate coordinate wind-up. + */ +- clamped_x = clamp(finger->x, 0U, priv->x_max); +- clamped_y = clamp(finger->y, 0U, priv->y_max); +- input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); ++ finger->x = clamp(finger->x, 0, priv->x_max); ++ finger->y = clamp(finger->y, 0, priv->y_max); ++ input_report_abs(dev, ABS_MT_POSITION_X, finger->x); + input_report_abs(dev, ABS_MT_POSITION_Y, +- priv->y_max - clamped_y); ++ priv->y_max - finger->y); + input_report_abs(dev, ABS_TOOL_WIDTH, state->width); + } + } +diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c +--- a/drivers/input/mouse/psmouse-base.c ++++ b/drivers/input/mouse/psmouse-base.c +@@ -256,9 +256,8 @@ static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_sta + */ + void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + { +- serio_pause_rx(psmouse->ps2dev.serio); ++ guard(serio_pause_rx)(psmouse->ps2dev.serio); + __psmouse_set_state(psmouse, new_state); +- serio_continue_rx(psmouse->ps2dev.serio); + } + + /* +@@ -267,7 +266,15 @@ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + */ + static int psmouse_handle_byte(struct psmouse *psmouse) + { +- psmouse_ret_t rc = psmouse->protocol_handler(psmouse); ++ psmouse_ret_t rc; ++ ++ /* protocol_handler is NULL when device is being disconnected */ ++ if (unlikely(!psmouse->protocol_handler)) { ++ psmouse->pktcnt = 0; ++ return 0; ++ } ++ ++ rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: +@@ -484,13 +491,16 @@ static int psmouse_poll(struct psmouse *psmouse) + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)); + } + +-static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) ++static bool psmouse_check_pnp_id(const char *p, const char * const ids[]) + { +- int i; ++ const char * const *id; ++ size_t len; + +- for (i = 0; ids[i]; i++) +- if (!strcasecmp(id, ids[i])) ++ for (id = ids; *id; id++) { ++ len = strlen(*id); ++ if (!strncasecmp(p, *id, len) && (p[len] == ' ' || p[len] == '\0')) + return true; ++ } + + return false; + } +@@ -501,28 +511,26 @@ static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) + bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]) + { + struct serio *serio = psmouse->ps2dev.serio; +- char *p, *fw_id_copy, *save_ptr; +- bool found = false; ++ const char *p = serio->firmware_id; + +- if (strncmp(serio->firmware_id, "PNP: ", 5)) ++ if (!strstarts(p, "PNP: ")) + return false; + +- fw_id_copy = kstrndup(&serio->firmware_id[5], +- sizeof(serio->firmware_id) - 5, +- GFP_KERNEL); +- if (!fw_id_copy) +- return false; ++ p += 5; ++ while (*p) { ++ p = skip_spaces(p); ++ if (!*p) ++ break; + +- save_ptr = fw_id_copy; +- while ((p = strsep(&fw_id_copy, " ")) != NULL) { +- if (psmouse_check_pnp_id(p, ids)) { +- found = true; ++ if (psmouse_check_pnp_id(p, ids)) ++ return true; ++ ++ p = strchr(p, ' '); ++ if (!p) + break; +- } + } + +- kfree(save_ptr); +- return found; ++ return false; + } + + /* +@@ -1311,10 +1319,10 @@ static void psmouse_resync(struct work_struct *work) + bool failed = false, enabled = false; + int i; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (psmouse->state != PSMOUSE_RESYNCING) +- goto out; ++ return; + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1392,8 +1400,6 @@ static void psmouse_resync(struct work_struct *work) + + if (parent) + psmouse_activate(parent); +- out: +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1404,7 +1410,7 @@ static void psmouse_cleanup(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1440,8 +1446,6 @@ static void psmouse_cleanup(struct serio *serio) + + psmouse_activate(parent); + } +- +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1452,20 +1456,20 @@ static void psmouse_disconnect(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ disable_work_sync(&psmouse->resync_work); ++ ++ guard(mutex)(&psmouse_mutex); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + +- /* make sure we don't have a resync in progress */ +- mutex_unlock(&psmouse_mutex); +- disable_work_sync(&psmouse->resync_work); +- mutex_lock(&psmouse_mutex); +- + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + +@@ -1484,8 +1488,6 @@ static void psmouse_disconnect(struct serio *serio) + + if (parent) + psmouse_activate(parent); +- +- mutex_unlock(&psmouse_mutex); + } + + static int psmouse_switch_protocol(struct psmouse *psmouse, +@@ -1654,14 +1656,12 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + enum psmouse_type type; + int rc = -1; + +- mutex_lock(&psmouse_mutex); ++ lockdep_assert_held(&psmouse_mutex); + + if (fast_reconnect) { + reconnect_handler = psmouse->fast_reconnect; +- if (!reconnect_handler) { +- rc = -ENOENT; +- goto out_unlock; +- } ++ if (!reconnect_handler) ++ return -ENOENT; + } else { + reconnect_handler = psmouse->reconnect; + } +@@ -1713,18 +1713,20 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + if (parent) + psmouse_activate(parent); + +-out_unlock: +- mutex_unlock(&psmouse_mutex); + return rc; + } + + static int psmouse_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, false); + } + + static int psmouse_fast_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, true); + } + +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -164,6 +164,7 @@ static const char * const topbuttonpad_pnp_ids[] = { + #ifdef CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS + static const char * const smbus_pnp_ids[] = { + /* all of the topbuttonpad_pnp_ids are valid, we just add some extras */ ++ "DLL0597", /* Dell Inspiron 3521 */ + "DLL060d", /* Dell Precision M3800 */ + "LEN0048", /* X1 Carbon 3 */ + "LEN0046", /* X250 */ +diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c +--- a/drivers/input/mouse/synaptics_i2c.c ++++ b/drivers/input/mouse/synaptics_i2c.c +@@ -261,7 +261,7 @@ static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8 val) + if (error) + return error; + +- return error; ++ return 0; + } + + static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg) +diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c +--- a/drivers/input/rmi4/rmi_f12.c ++++ b/drivers/input/rmi4/rmi_f12.c +@@ -155,6 +155,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + offset += 4; + } + ++ /* When platform data are provided, we're done */ ++ if (sensor->x_mm && sensor->y_mm) ++ return 0; ++ + /* + * Use the Query DPM feature when the resolution query register + * exists. +@@ -171,8 +175,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + } + dpm_resolution = buf[0]; + +- sensor->x_mm = sensor->max_x / dpm_resolution; +- sensor->y_mm = sensor->max_y / dpm_resolution; ++ if (!sensor->x_mm) ++ sensor->x_mm = sensor->max_x / dpm_resolution; ++ if (!sensor->y_mm) ++ sensor->y_mm = sensor->max_y / dpm_resolution; + } else { + if (rmi_register_desc_has_subpacket(item, 3)) { + rx_receivers = buf[offset]; +@@ -184,8 +190,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + if (rmi_register_desc_has_subpacket(item, 4)) + offset += 1; + +- sensor->x_mm = (pitch_x * rx_receivers) >> 12; +- sensor->y_mm = (pitch_y * tx_receivers) >> 12; ++ if (!sensor->x_mm) ++ sensor->x_mm = (pitch_x * rx_receivers) >> 12; ++ if (!sensor->y_mm) ++ sensor->y_mm = (pitch_y * tx_receivers) >> 12; + } + + rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: x_mm: %d y_mm: %d\n", __func__, +diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c +--- a/drivers/input/rmi4/rmi_smbus.c ++++ b/drivers/input/rmi4/rmi_smbus.c +@@ -177,13 +177,8 @@ static int smb_block_read(struct rmi_transport_dev *xport, + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + struct i2c_client *client = rmi_smb->client; +- int retval; + +- retval = i2c_smbus_read_block_data(client, commandcode, buf); +- if (retval < 0) +- return retval; +- +- return retval; ++ return i2c_smbus_read_block_data(client, commandcode, buf); + } + + static int rmi_smb_read_block(struct rmi_transport_dev *xport, u16 rmiaddr, +diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig +--- a/drivers/input/serio/Kconfig ++++ b/drivers/input/serio/Kconfig +@@ -106,6 +106,33 @@ config SERIO_GSCPS2 + To compile this driver as a module, choose M here: the + module will be called gscps2. + ++config SERIO_GSCPS2_RDI_KEYCODES ++ bool "Use PrecisionBook keyboard scancodes" ++ depends on SERIO_GSCPS2 ++ default n ++ help ++ If you have an RDI PrecisionBook, say Y here if you want to use its ++ built-in keyboard (as opposed to an external keyboard). ++ ++ The PrecisionBook has five keys that conflict with those used by most ++ AT and PS/2 keyboards. These are as follows: ++ ++ PrecisionBook Standard AT or PS/2 ++ ++ F1 F12 ++ Left Ctrl Left Alt ++ Caps Lock Left Ctrl ++ Right Ctrl Caps Lock ++ Left 102nd key (the key to the right of Left Shift) ++ ++ If you say N here, and use the PrecisionBook keyboard, then each key ++ in the left-hand column will be interpreted as the corresponding key ++ in the right-hand column. ++ ++ If you say Y here, and use an external keyboard, then each key in the ++ right-hand column will be interpreted as the key shown in the ++ left-hand column. ++ + config HP_SDC + tristate "HP System Device Controller i8042 Support" + depends on (GSC || HP300) && SERIO +diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c +--- a/drivers/input/serio/gscps2.c ++++ b/drivers/input/serio/gscps2.c +@@ -22,18 +22,15 @@ + * was usable/enabled ?) + */ + +-#include +-#include +-#include +-#include ++#include + #include + #include +-#include +-#include ++#include + #include ++#include ++#include + + #include +-#include + #include + + MODULE_AUTHOR("Laurent Canet , Thibaut Varene , Helge Deller "); +@@ -80,6 +77,116 @@ MODULE_LICENSE("GPL"); + #define GSC_ID_KEYBOARD 0 /* device ID values */ + #define GSC_ID_MOUSE 1 + ++#ifndef CONFIG_SERIO_GSCPS2_RDI_KEYCODES ++# define CONFLICT(x, y) x ++#else ++# define CONFLICT(x, y) y ++#endif ++ ++/* ++ * Sadly RDI (Tadpole) decided to ship a different keyboard layout ++ * than HP for their PS/2 laptop keyboard which leads to conflicting ++ * keycodes between a normal HP PS/2 keyboard and a RDI PrecisionBook. ++ * HP: RDI: ++ */ ++#define C_07 CONFLICT(KEY_F12, KEY_F1) ++#define C_11 CONFLICT(KEY_LEFTALT, KEY_LEFTCTRL) ++#define C_14 CONFLICT(KEY_LEFTCTRL, KEY_CAPSLOCK) ++#define C_58 CONFLICT(KEY_CAPSLOCK, KEY_RIGHTCTRL) ++#define C_61 CONFLICT(KEY_102ND, KEY_LEFT) ++ ++/* ++ * Special keycode value recognized by atkbd (ATKBD_KEY_NULL) to silently ++ * discard scancodes without generating input events or "unknown key" warnings. ++ */ ++#define KEY_NULL 255 ++ ++#define KEYMAP_ENTRY(scancode, keycode) (((scancode) << 16) | (keycode)) ++ ++static const u32 gscps2_keymap[] = { ++ KEYMAP_ENTRY(0x01, KEY_F9), KEYMAP_ENTRY(0x03, KEY_F5), ++ KEYMAP_ENTRY(0x04, KEY_F3), KEYMAP_ENTRY(0x05, KEY_F1), ++ KEYMAP_ENTRY(0x06, KEY_F2), KEYMAP_ENTRY(0x07, C_07), ++ KEYMAP_ENTRY(0x08, KEY_ESC), KEYMAP_ENTRY(0x09, KEY_F10), ++ KEYMAP_ENTRY(0x0a, KEY_F8), KEYMAP_ENTRY(0x0b, KEY_F6), ++ KEYMAP_ENTRY(0x0c, KEY_F4), KEYMAP_ENTRY(0x0d, KEY_TAB), ++ KEYMAP_ENTRY(0x0e, KEY_GRAVE), KEYMAP_ENTRY(0x0f, KEY_F2), ++ KEYMAP_ENTRY(0x11, C_11), KEYMAP_ENTRY(0x12, KEY_LEFTSHIFT), ++ KEYMAP_ENTRY(0x14, C_14), KEYMAP_ENTRY(0x15, KEY_Q), ++ KEYMAP_ENTRY(0x16, KEY_1), KEYMAP_ENTRY(0x17, KEY_F3), ++ KEYMAP_ENTRY(0x19, KEY_LEFTALT), KEYMAP_ENTRY(0x1a, KEY_Z), ++ KEYMAP_ENTRY(0x1b, KEY_S), KEYMAP_ENTRY(0x1c, KEY_A), ++ KEYMAP_ENTRY(0x1d, KEY_W), KEYMAP_ENTRY(0x1e, KEY_2), ++ KEYMAP_ENTRY(0x1f, KEY_F4), KEYMAP_ENTRY(0x21, KEY_C), ++ KEYMAP_ENTRY(0x22, KEY_X), KEYMAP_ENTRY(0x23, KEY_D), ++ KEYMAP_ENTRY(0x24, KEY_E), KEYMAP_ENTRY(0x25, KEY_4), ++ KEYMAP_ENTRY(0x26, KEY_3), KEYMAP_ENTRY(0x27, KEY_F5), ++ KEYMAP_ENTRY(0x29, KEY_SPACE), KEYMAP_ENTRY(0x2a, KEY_V), ++ KEYMAP_ENTRY(0x2b, KEY_F), KEYMAP_ENTRY(0x2c, KEY_T), ++ KEYMAP_ENTRY(0x2d, KEY_R), KEYMAP_ENTRY(0x2e, KEY_5), ++ KEYMAP_ENTRY(0x2f, KEY_F6), KEYMAP_ENTRY(0x31, KEY_N), ++ KEYMAP_ENTRY(0x32, KEY_B), KEYMAP_ENTRY(0x33, KEY_H), ++ KEYMAP_ENTRY(0x34, KEY_G), KEYMAP_ENTRY(0x35, KEY_Y), ++ KEYMAP_ENTRY(0x36, KEY_6), KEYMAP_ENTRY(0x37, KEY_F7), ++ KEYMAP_ENTRY(0x39, KEY_RIGHTALT), KEYMAP_ENTRY(0x3a, KEY_M), ++ KEYMAP_ENTRY(0x3b, KEY_J), KEYMAP_ENTRY(0x3c, KEY_U), ++ KEYMAP_ENTRY(0x3d, KEY_7), KEYMAP_ENTRY(0x3e, KEY_8), ++ KEYMAP_ENTRY(0x3f, KEY_F8), KEYMAP_ENTRY(0x41, KEY_COMMA), ++ KEYMAP_ENTRY(0x42, KEY_K), KEYMAP_ENTRY(0x43, KEY_I), ++ KEYMAP_ENTRY(0x44, KEY_O), KEYMAP_ENTRY(0x45, KEY_0), ++ KEYMAP_ENTRY(0x46, KEY_9), KEYMAP_ENTRY(0x47, KEY_F9), ++ KEYMAP_ENTRY(0x49, KEY_DOT), KEYMAP_ENTRY(0x4a, KEY_SLASH), ++ KEYMAP_ENTRY(0x4b, KEY_L), KEYMAP_ENTRY(0x4c, KEY_SEMICOLON), ++ KEYMAP_ENTRY(0x4d, KEY_P), KEYMAP_ENTRY(0x4e, KEY_MINUS), ++ KEYMAP_ENTRY(0x4f, KEY_F10), KEYMAP_ENTRY(0x52, KEY_APOSTROPHE), ++ KEYMAP_ENTRY(0x54, KEY_LEFTBRACE), KEYMAP_ENTRY(0x55, KEY_EQUAL), ++ KEYMAP_ENTRY(0x56, KEY_F11), KEYMAP_ENTRY(0x57, KEY_SYSRQ), ++ KEYMAP_ENTRY(0x58, C_58), KEYMAP_ENTRY(0x59, KEY_RIGHTSHIFT), ++ KEYMAP_ENTRY(0x5a, KEY_ENTER), KEYMAP_ENTRY(0x5b, KEY_RIGHTBRACE), ++ KEYMAP_ENTRY(0x5c, KEY_BACKSLASH), KEYMAP_ENTRY(0x5d, KEY_BACKSLASH), ++ KEYMAP_ENTRY(0x5e, KEY_F12), KEYMAP_ENTRY(0x5f, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x60, KEY_DOWN), KEYMAP_ENTRY(0x61, C_61), ++ KEYMAP_ENTRY(0x62, KEY_PAUSE), KEYMAP_ENTRY(0x63, KEY_UP), ++ KEYMAP_ENTRY(0x64, KEY_DELETE), KEYMAP_ENTRY(0x65, KEY_END), ++ KEYMAP_ENTRY(0x66, KEY_BACKSPACE), KEYMAP_ENTRY(0x67, KEY_INSERT), ++ KEYMAP_ENTRY(0x69, KEY_KP1), KEYMAP_ENTRY(0x6a, KEY_RIGHT), ++ KEYMAP_ENTRY(0x6b, KEY_KP4), KEYMAP_ENTRY(0x6c, KEY_KP7), ++ KEYMAP_ENTRY(0x6d, KEY_PAGEDOWN), KEYMAP_ENTRY(0x6e, KEY_HOME), ++ KEYMAP_ENTRY(0x6f, KEY_PAGEUP), KEYMAP_ENTRY(0x70, KEY_KP0), ++ KEYMAP_ENTRY(0x71, KEY_KPDOT), KEYMAP_ENTRY(0x72, KEY_KP2), ++ KEYMAP_ENTRY(0x73, KEY_KP5), KEYMAP_ENTRY(0x74, KEY_KP6), ++ KEYMAP_ENTRY(0x75, KEY_KP8), KEYMAP_ENTRY(0x76, KEY_ESC), ++ KEYMAP_ENTRY(0x77, KEY_NUMLOCK), KEYMAP_ENTRY(0x78, KEY_F11), ++ KEYMAP_ENTRY(0x79, KEY_KPPLUS), KEYMAP_ENTRY(0x7a, KEY_KP3), ++ KEYMAP_ENTRY(0x7b, KEY_KPMINUS), KEYMAP_ENTRY(0x7c, KEY_KPASTERISK), ++ KEYMAP_ENTRY(0x7d, KEY_KP9), KEYMAP_ENTRY(0x7e, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x7f, KEY_102ND), KEYMAP_ENTRY(0x91, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x92, KEY_NULL), KEYMAP_ENTRY(0x94, KEY_RIGHTCTRL), ++ KEYMAP_ENTRY(0x9d, KEY_CAPSLOCK), KEYMAP_ENTRY(0x9f, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0xa7, KEY_RIGHTMETA), KEYMAP_ENTRY(0xaf, KEY_COMPOSE), ++ KEYMAP_ENTRY(0xca, KEY_KPSLASH), KEYMAP_ENTRY(0xda, KEY_KPENTER), ++ KEYMAP_ENTRY(0xe9, KEY_END), KEYMAP_ENTRY(0xeb, KEY_LEFT), ++ KEYMAP_ENTRY(0xec, KEY_HOME), KEYMAP_ENTRY(0xf0, KEY_INSERT), ++ KEYMAP_ENTRY(0xf1, KEY_DELETE), KEYMAP_ENTRY(0xf2, KEY_DOWN), ++ KEYMAP_ENTRY(0xf4, KEY_RIGHT), KEYMAP_ENTRY(0xf5, KEY_UP), ++ KEYMAP_ENTRY(0xf7, KEY_PAUSE), KEYMAP_ENTRY(0xfa, KEY_PAGEDOWN), ++ KEYMAP_ENTRY(0xfc, KEY_SYSRQ), KEYMAP_ENTRY(0xfd, KEY_PAGEUP), ++ ++ /* Escaped keycodes */ ++ KEYMAP_ENTRY(0x103, KEY_F7), KEYMAP_ENTRY(0x10b, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0x10c, KEY_RIGHTMETA), KEYMAP_ENTRY(0x111, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x114, KEY_RIGHTCTRL), ++}; ++ ++static const struct property_entry gscps2_props[] = { ++ PROPERTY_ENTRY_U32_ARRAY("linux,keymap", gscps2_keymap), ++ { } ++}; ++ ++static const struct software_node gscps2_keyboard_node = { ++ .name = "gscps2-keyboard", ++ .properties = gscps2_props, ++}; + + static irqreturn_t gscps2_interrupt(int irq, void *dev); + +@@ -398,6 +505,17 @@ static int __init gscps2_probe(struct parisc_device *dev) + goto fail; + #endif + ++ if (ps2port->id == GSC_ID_KEYBOARD) { ++ ret = device_add_software_node(&serio->dev, ++ &gscps2_keyboard_node); ++ if (ret) { ++ dev_err(&dev->dev, ++ "failed to add software node for keyboard: %d\n", ++ ret); ++ goto fail; ++ } ++ } ++ + pr_info("serio: %s port at 0x%08lx irq %d @ %s\n", + ps2port->port->name, + hpa, +@@ -411,11 +529,16 @@ static int __init gscps2_probe(struct parisc_device *dev) + return 0; + + fail: ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&serio->dev); ++ + free_irq(dev->irq, ps2port); + + fail_miserably: + iounmap(ps2port->addr); ++#if 0 + release_mem_region(dev->hpa.start, GSC_STATUS + 4); ++#endif + + fail_nomem: + kfree(ps2port); +@@ -434,6 +557,9 @@ static void __exit gscps2_remove(struct parisc_device *dev) + { + struct gscps2port *ps2port = dev_get_drvdata(&dev->dev); + ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&ps2port->port->dev); ++ + serio_unregister_port(ps2port->port); + free_irq(dev->irq, ps2port); + gscps2_flush(ps2port); +@@ -465,16 +591,25 @@ static struct parisc_driver parisc_ps2_driver __refdata = { + + static int __init gscps2_init(void) + { +- register_parisc_driver(&parisc_ps2_driver); +- return 0; ++ int error; ++ ++ error = software_node_register(&gscps2_keyboard_node); ++ if (error) ++ return error; ++ ++ error = register_parisc_driver(&parisc_ps2_driver); ++ if (error) ++ software_node_unregister(&gscps2_keyboard_node); ++ ++ return error; + } + + static void __exit gscps2_exit(void) + { + unregister_parisc_driver(&parisc_ps2_driver); ++ software_node_unregister(&gscps2_keyboard_node); + } + + + module_init(gscps2_init); + module_exit(gscps2_exit); +- +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -3,6 +3,7 @@ + #define _I8042_ACPIPNPIO_H + + #include ++#include + + #ifdef CONFIG_X86 + #include +@@ -1486,17 +1487,21 @@ static char i8042_pnp_aux_name[32]; + + static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size) + { +- strscpy(dst, "PNP:", dst_size); ++ struct seq_buf sb; ++ ++ seq_buf_init(&sb, dst, dst_size); ++ seq_buf_printf(&sb, "PNP:"); + + while (id) { +- strlcat(dst, " ", dst_size); +- strlcat(dst, id->id, dst_size); ++ seq_buf_printf(&sb, " %s", id->id); + id = id->next; + } + } + + static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1506,11 +1511,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev,0)) + i8042_pnp_kbd_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); +- strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); +- } ++ scnprintf(i8042_pnp_kbd_name, sizeof(i8042_pnp_kbd_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id, + sizeof(i8042_kbd_firmware_id)); + i8042_kbd_fwnode = dev_fwnode(&dev->dev); +@@ -1524,6 +1526,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + + static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1533,11 +1537,8 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev, 0)) + i8042_pnp_aux_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); +- strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); +- } ++ scnprintf(i8042_pnp_aux_name, sizeof(i8042_pnp_aux_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id, + sizeof(i8042_aux_firmware_id)); + +diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c +--- a/drivers/input/touchscreen/elo.c ++++ b/drivers/input/touchscreen/elo.c +@@ -257,7 +257,7 @@ static int elo_command_10(struct elo *elo, unsigned char *packet) + + static int elo_setup_10(struct elo *elo) + { +- static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; ++ static const char * const elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; + struct input_dev *dev = elo->dev; + unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD }; + +@@ -273,7 +273,7 @@ static int elo_setup_10(struct elo *elo) + + dev_info(&elo->serio->dev, + "%sTouch touchscreen, fw: %02x.%02x, features: 0x%02x, controller: 0x%02x\n", +- elo_types[(packet[1] -'0') & 0x03], ++ elo_types[(packet[1] - '0') & 0x03], + packet[5], packet[4], packet[3], packet[7]); + + return 0; +diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c +--- a/drivers/input/touchscreen/inexio.c ++++ b/drivers/input/touchscreen/inexio.c +@@ -81,7 +81,9 @@ static irqreturn_t inexio_interrupt(struct serio *serio, + if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0]) + inexio_process_data(pinexio); + else +- printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]); ++ dev_dbg(&serio->dev, ++ "unknown/unsynchronized data from device, byte %x\n", ++ pinexio->data[0]); + + return IRQ_HANDLED; + } +diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c +--- a/drivers/input/touchscreen/iqs5xx.c ++++ b/drivers/input/touchscreen/iqs5xx.c +@@ -785,7 +785,8 @@ static int iqs5xx_fw_file_parse(struct i2c_client *client, + switch (rec_type) { + case IQS5XX_REC_TYPE_DATA: + if (rec_addr < IQS5XX_CHKSM || +- rec_addr > IQS5XX_PMAP_END) { ++ rec_addr > IQS5XX_PMAP_END || ++ rec_len > IQS5XX_PMAP_END + 1 - rec_addr) { + dev_err(&client->dev, + "Invalid address at record %u\n", + rec_num); +diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c +--- a/drivers/input/touchscreen/mms114.c ++++ b/drivers/input/touchscreen/mms114.c +@@ -4,6 +4,8 @@ + // Copyright (c) 2012 Samsung Electronics Co., Ltd. + // Author: Joonyoung Shim + ++#include ++#include + #include + #include + #include +@@ -52,21 +54,13 @@ + #define MMS114_TYPE_TOUCHSCREEN 1 + #define MMS114_TYPE_TOUCHKEY 2 + +-enum mms_type { +- TYPE_MMS114 = 114, +- TYPE_MMS134S = 134, +- TYPE_MMS136 = 136, +- TYPE_MMS152 = 152, +- TYPE_MMS345L = 345, +-}; +- + struct mms114_data { ++ const struct mms_chip *chip; + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *core_reg; + struct regulator *io_reg; + struct touchscreen_properties props; +- enum mms_type type; + unsigned int contact_threshold; + unsigned int moving_threshold; + +@@ -77,9 +71,23 @@ struct mms114_data { + u8 cache_mode_control; + }; + ++struct mms_chip { ++ const char *name; ++ int event_size; ++ bool has_config_regs; ++ int (*get_version)(struct mms114_data *data); ++}; ++ ++#define MMS114_FLAGS_ID_MASK GENMASK(3, 0) ++#define MMS114_FLAGS_TYPE_MASK GENMASK(6, 5) ++#define MMS114_FLAGS_PRESSED_MASK BIT(7) ++ ++#define MMS114_XY_HI_X_MASK GENMASK(3, 0) ++#define MMS114_XY_HI_Y_MASK GENMASK(7, 4) ++ + struct mms114_touch { +- u8 id:4, reserved_bit4:1, type:2, pressed:1; +- u8 x_hi:4, y_hi:4; ++ u8 flags; ++ u8 xy_hi; + u8 x_lo; + u8 y_lo; + u8 width; +@@ -87,16 +95,16 @@ struct mms114_touch { + u8 reserved[2]; + } __packed; + +-static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, +- unsigned int len, u8 *val) ++static int __mms114_read_reg(struct mms114_data *data, u8 reg, ++ unsigned int len, void *val) + { + struct i2c_client *client = data->client; + struct i2c_msg xfer[2]; +- u8 buf = reg & 0xff; ++ u8 buf = reg; + int error; + +- if (reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL) +- BUG(); ++ if (WARN_ON(reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL)) ++ return -EINVAL; + + /* Write register */ + xfer[0].addr = client->addr; +@@ -116,12 +124,12 @@ static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c transfer failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + return 0; + } + +-static int mms114_read_reg(struct mms114_data *data, unsigned int reg) ++static int mms114_read_reg(struct mms114_data *data, u8 reg) + { + u8 val; + int error; +@@ -133,15 +141,14 @@ static int mms114_read_reg(struct mms114_data *data, unsigned int reg) + return error < 0 ? error : val; + } + +-static int mms114_write_reg(struct mms114_data *data, unsigned int reg, +- unsigned int val) ++static int mms114_write_reg(struct mms114_data *data, u8 reg, u8 val) + { + struct i2c_client *client = data->client; + u8 buf[2]; + int error; + +- buf[0] = reg & 0xff; +- buf[1] = val & 0xff; ++ buf[0] = reg; ++ buf[1] = val; + + error = i2c_master_send(client, buf, 2); + if (error != 2) { +@@ -149,7 +156,7 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c send failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + if (reg == MMS114_MODE_CONTROL) + data->cache_mode_control = val; +@@ -157,32 +164,119 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + return 0; + } + ++static int mms114_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[6]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", ++ buf[0], buf[1], buf[3]); ++ return 0; ++} ++ ++static int mms152_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int group; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ group = i2c_smbus_read_byte_data(data->client, MMS152_COMPAT_GROUP); ++ if (group < 0) ++ return group; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", ++ buf[0], buf[1], buf[2], group); ++ return 0; ++} ++ ++static int mms345l_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", ++ buf[0], buf[1], buf[2]); ++ return 0; ++} ++ ++static const struct mms_chip mms114_descriptor = { ++ .name = "MMS114", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms134s_descriptor = { ++ .name = "MMS134S", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms136_descriptor = { ++ .name = "MMS136", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms152_descriptor = { ++ .name = "MMS152", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms152_get_version, ++}; ++ ++static const struct mms_chip mms345l_descriptor = { ++ .name = "MMS345L", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms345l_get_version, ++}; ++ + static void mms114_process_mt(struct mms114_data *data, struct mms114_touch *touch) + { + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; +- unsigned int id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + unsigned int x; + unsigned int y; + +- if (touch->id == 0 || touch->id > MMS114_MAX_TOUCH) { +- dev_err(&client->dev, "Wrong touch id (%d)\n", touch->id); ++ if (id == 0 || id > MMS114_MAX_TOUCH) { ++ dev_err(&client->dev, "Wrong touch id (%d)\n", id); + return; + } + +- id = touch->id - 1; +- x = touch->x_lo | touch->x_hi << 8; +- y = touch->y_lo | touch->y_hi << 8; ++ id--; ++ x = touch->x_lo | FIELD_GET(MMS114_XY_HI_X_MASK, touch->xy_hi) << 8; ++ y = touch->y_lo | FIELD_GET(MMS114_XY_HI_Y_MASK, touch->xy_hi) << 8; + + dev_dbg(&client->dev, + "id: %d, type: %d, pressed: %d, x: %d, y: %d, width: %d, strength: %d\n", +- id, touch->type, touch->pressed, ++ id, type, pressed, + x, y, touch->width, touch->strength); + + input_mt_slot(input_dev, id); +- input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, touch->pressed); ++ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, pressed); + +- if (touch->pressed) { ++ if (pressed) { + touchscreen_report_pos(input_dev, &data->props, x, y, true); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch->width); + input_report_abs(input_dev, ABS_MT_PRESSURE, touch->strength); +@@ -195,21 +289,23 @@ static void mms114_process_touchkey(struct mms114_data *data, + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; + unsigned int keycode_id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + +- if (touch->id == 0) ++ if (id == 0) + return; + +- if (touch->id > data->num_keycodes) { ++ if (id > data->num_keycodes) { + dev_err(&client->dev, "Wrong touch id for touchkey (%d)\n", +- touch->id); ++ id); + return; + } + +- keycode_id = touch->id - 1; ++ keycode_id = id - 1; + dev_dbg(&client->dev, "keycode id: %d, pressed: %d\n", keycode_id, +- touch->pressed); ++ pressed); + +- input_report_key(input_dev, data->keycodes[keycode_id], touch->pressed); ++ input_report_key(input_dev, data->keycodes[keycode_id], pressed); + } + + static irqreturn_t mms114_interrupt(int irq, void *dev_id) +@@ -218,8 +314,8 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + struct i2c_client *client = data->client; + struct mms114_touch touch[MMS114_MAX_TOUCH]; + struct mms114_touch *t; ++ int event_size = data->chip->event_size; + int packet_size; +- int event_size; + int touch_size; + int index; + int error; +@@ -234,23 +330,17 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + goto out; + } + +- /* MMS136 has slightly different event size */ +- if (data->type == TYPE_MMS134S || data->type == TYPE_MMS136) +- event_size = MMS136_EVENT_SIZE; +- else +- event_size = MMS114_EVENT_SIZE; +- + touch_size = packet_size / event_size; + +- error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, +- (u8 *)touch); +- if (error < 0) ++ error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, touch); ++ if (error) + goto out; + + for (index = 0; index < touch_size; index++) { + t = (struct mms114_touch *)((u8 *)touch + index * event_size); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, t->flags); + +- switch (t->type) { ++ switch (type) { + case MMS114_TYPE_TOUCHSCREEN: + mms114_process_mt(data, t); + break; +@@ -261,7 +351,7 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + + default: + dev_err(&client->dev, "Wrong touch type (%d)\n", +- t->type); ++ type); + break; + } + } +@@ -290,65 +380,17 @@ static int mms114_set_active(struct mms114_data *data, bool active) + return mms114_write_reg(data, MMS114_MODE_CONTROL, val); + } + +-static int mms114_get_version(struct mms114_data *data) +-{ +- struct device *dev = &data->client->dev; +- u8 buf[6]; +- int group; +- int error; +- +- switch (data->type) { +- case TYPE_MMS345L: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", +- buf[0], buf[1], buf[2]); +- break; +- +- case TYPE_MMS152: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- group = i2c_smbus_read_byte_data(data->client, +- MMS152_COMPAT_GROUP); +- if (group < 0) +- return group; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", +- buf[0], buf[1], buf[2], group); +- break; +- +- case TYPE_MMS114: +- case TYPE_MMS134S: +- case TYPE_MMS136: +- error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", +- buf[0], buf[1], buf[3]); +- break; +- } +- +- return 0; +-} +- + static int mms114_setup_regs(struct mms114_data *data) + { + const struct touchscreen_properties *props = &data->props; + int val; + int error; + +- error = mms114_get_version(data); +- if (error < 0) ++ error = data->chip->get_version(data); ++ if (error) + return error; + +- /* MMS114, MMS134S and MMS136 have configuration and power on registers */ +- if (data->type != TYPE_MMS114 && data->type != TYPE_MMS134S && +- data->type != TYPE_MMS136) ++ if (!data->chip->has_config_regs) + return 0; + + error = mms114_set_active(data, true); +@@ -366,21 +408,21 @@ static int mms114_setup_regs(struct mms114_data *data) + if (error < 0) + return error; + +- val = props->max_x & 0xff; ++ val = props->max_y & 0xff; + error = mms114_write_reg(data, MMS114_Y_RESOLUTION, val); + if (error < 0) + return error; + + if (data->contact_threshold) { + error = mms114_write_reg(data, MMS114_CONTACT_THRESHOLD, +- data->contact_threshold); ++ data->contact_threshold); + if (error < 0) + return error; + } + + if (data->moving_threshold) { + error = mms114_write_reg(data, MMS114_MOVING_THRESHOLD, +- data->moving_threshold); ++ data->moving_threshold); + if (error < 0) + return error; + } +@@ -466,9 +508,9 @@ static int mms114_parse_legacy_bindings(struct mms114_data *data) + } + + device_property_read_u32(dev, "contact-threshold", +- &data->contact_threshold); ++ &data->contact_threshold); + device_property_read_u32(dev, "moving-threshold", +- &data->moving_threshold); ++ &data->moving_threshold); + + if (device_property_read_bool(dev, "x-invert")) + props->invert_x = true; +@@ -484,7 +526,6 @@ static int mms114_probe(struct i2c_client *client) + { + struct mms114_data *data; + struct input_dev *input_dev; +- const void *match_data; + int error; + int i; + +@@ -504,12 +545,10 @@ static int mms114_probe(struct i2c_client *client) + data->client = client; + data->input_dev = input_dev; + +- match_data = device_get_match_data(&client->dev); +- if (!match_data) ++ data->chip = i2c_get_match_data(client); ++ if (!data->chip) + return -EINVAL; + +- data->type = (enum mms_type)match_data; +- + data->num_keycodes = device_property_count_u32(&client->dev, + "linux,keycodes"); + if (data->num_keycodes == -EINVAL) { +@@ -521,7 +560,7 @@ static int mms114_probe(struct i2c_client *client) + return data->num_keycodes; + } else if (data->num_keycodes > MMS114_MAX_TOUCHKEYS) { + dev_warn(&client->dev, +- "Found %d linux,keycodes but max is %d, ignoring the rest\n", ++ "Found %d linux,keycodes but max is %d, ignoring the rest\n", + data->num_keycodes, MMS114_MAX_TOUCHKEYS); + data->num_keycodes = MMS114_MAX_TOUCHKEYS; + } +@@ -566,8 +605,7 @@ static int mms114_probe(struct i2c_client *client) + 0, data->props.max_y, 0, 0); + } + +- if (data->type == TYPE_MMS114 || data->type == TYPE_MMS134S || +- data->type == TYPE_MMS136) { ++ if (data->chip->has_config_regs) { + /* + * The firmware handles movement and pressure fuzz, so + * don't duplicate that in software. +@@ -582,8 +620,8 @@ static int mms114_probe(struct i2c_client *client) + } + + input_dev->name = devm_kasprintf(&client->dev, GFP_KERNEL, +- "MELFAS MMS%d Touchscreen", +- data->type); ++ "MELFAS %s Touchscreen", ++ data->chip->name); + if (!input_dev->name) + return -ENOMEM; + +@@ -679,29 +717,22 @@ static int mms114_resume(struct device *dev) + static DEFINE_SIMPLE_DEV_PM_OPS(mms114_pm_ops, mms114_suspend, mms114_resume); + + static const struct i2c_device_id mms114_id[] = { +- { .name = "mms114" }, ++ { .name = "mms114", .driver_data = (kernel_ulong_t)&mms114_descriptor }, ++ { .name = "mms134s", .driver_data = (kernel_ulong_t)&mms134s_descriptor }, ++ { .name = "mms136", .driver_data = (kernel_ulong_t)&mms136_descriptor }, ++ { .name = "mms152", .driver_data = (kernel_ulong_t)&mms152_descriptor }, ++ { .name = "mms345l", .driver_data = (kernel_ulong_t)&mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(i2c, mms114_id); + + #ifdef CONFIG_OF + static const struct of_device_id mms114_dt_match[] = { +- { +- .compatible = "melfas,mms114", +- .data = (void *)TYPE_MMS114, +- }, { +- .compatible = "melfas,mms134s", +- .data = (void *)TYPE_MMS134S, +- }, { +- .compatible = "melfas,mms136", +- .data = (void *)TYPE_MMS136, +- }, { +- .compatible = "melfas,mms152", +- .data = (void *)TYPE_MMS152, +- }, { +- .compatible = "melfas,mms345l", +- .data = (void *)TYPE_MMS345L, +- }, ++ { .compatible = "melfas,mms114", .data = &mms114_descriptor }, ++ { .compatible = "melfas,mms134s", .data = &mms134s_descriptor }, ++ { .compatible = "melfas,mms136", .data = &mms136_descriptor }, ++ { .compatible = "melfas,mms152", .data = &mms152_descriptor }, ++ { .compatible = "melfas,mms345l", .data = &mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(of, mms114_dt_match); +@@ -722,4 +753,4 @@ module_i2c_driver(mms114_driver); + /* Module information */ + MODULE_AUTHOR("Joonyoung Shim "); + MODULE_DESCRIPTION("MELFAS mms114 Touchscreen driver"); +-MODULE_LICENSE("GPL v2"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/touchscreen/sur40.c b/drivers/input/touchscreen/sur40.c +--- a/drivers/input/touchscreen/sur40.c ++++ b/drivers/input/touchscreen/sur40.c +@@ -128,8 +128,8 @@ struct sur40_image_header { + /* polling interval (ms) */ + #define POLL_INTERVAL 1 + +-/* maximum number of contacts FIXME: this is a guess? */ +-#define MAX_CONTACTS 64 ++/* maximum number of contacts */ ++#define MAX_CONTACTS 52 + + /* control commands */ + #define SUR40_GET_VERSION 0xb0 /* 12 bytes string */ +diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c +--- a/drivers/input/touchscreen/wacom_w8001.c ++++ b/drivers/input/touchscreen/wacom_w8001.c +@@ -417,6 +417,13 @@ static int w8001_detect(struct w8001 *w8001) + return 0; + } + ++static void w8001_append_suffix(char *dest, const char *suffix, size_t dest_sz) ++{ ++ size_t used = strnlen(dest, dest_sz); ++ ++ strscpy(dest + used, suffix, dest_sz - used); ++} ++ + static int w8001_setup_pen(struct w8001 *w8001, char *basename, + size_t basename_sz) + { +@@ -453,7 +460,7 @@ static int w8001_setup_pen(struct w8001 *w8001, char *basename, + } + + w8001->id = 0x90; +- strlcat(basename, " Penabled", basename_sz); ++ w8001_append_suffix(basename, " Penabled", basename_sz); + + return 0; + } +@@ -503,14 +510,14 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + case 2: + w8001->pktlen = W8001_PKTLEN_TOUCH93; + w8001->id = 0x93; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + break; + + case 1: + case 3: + case 4: + w8001->pktlen = W8001_PKTLEN_TOUCH9A; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + w8001->id = 0x9a; + break; + +@@ -534,7 +541,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + input_abs_set_res(dev, ABS_MT_POSITION_X, touch.panel_res); + input_abs_set_res(dev, ABS_MT_POSITION_Y, touch.panel_res); + +- strlcat(basename, " 2FG", basename_sz); ++ w8001_append_suffix(basename, " 2FG", basename_sz); + if (w8001->max_pen_x && w8001->max_pen_y) + w8001->id = 0xE3; + else +@@ -542,7 +549,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + break; + } + +- strlcat(basename, " Touchscreen", basename_sz); ++ w8001_append_suffix(basename, " Touchscreen", basename_sz); + + return 0; + } +diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c +--- a/drivers/mfd/rohm-bd71828.c ++++ b/drivers/mfd/rohm-bd71828.c +@@ -5,7 +5,8 @@ + * ROHM BD718[15/28/79] and BD72720 PMIC driver + */ + +-#include ++#include ++#include + #include + #include + #include +@@ -18,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -37,19 +39,6 @@ + }, \ + } + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +- .wakeup = 1, +-}; +- +-static const struct gpio_keys_platform_data bd71828_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd71828-pwrkey", +-}; +- + static const struct resource bd71815_rtc_irqs[] = { + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC0, "bd70528-rtc-alm-0"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC1, "bd70528-rtc-alm-1"), +@@ -174,10 +163,6 @@ static struct mfd_cell bd71828_mfd_cells[] = { + .name = "bd71828-rtc", + .resources = bd71828_rtc_irqs, + .num_resources = ARRAY_SIZE(bd71828_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, + }; + +@@ -242,11 +227,8 @@ static const struct mfd_cell bd72720_mfd_cells[] = { + .name = "bd72720-rtc", + .resources = bd72720_rtc_irqs, + .num_resources = ARRAY_SIZE(bd72720_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, ++ /* Power button is registered separately */ + }; + + static const struct regmap_range bd71815_volatile_ranges[] = { +@@ -877,6 +859,84 @@ static int set_clk_mode(struct device *dev, struct regmap *regmap, + OUT32K_MODE_CMOS); + } + ++static const struct property_entry bd71828_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd71828-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd71828_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ PROPERTY_ENTRY_BOOL("wakeup-source"), ++ { } ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd71828_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd71828_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd71828_i2c_register_pwrbutton(struct device *dev, int button_irq, ++ struct irq_domain *irq_domain) ++{ ++ const struct resource res[] = { ++ DEFINE_RES_IRQ_NAMED(button_irq, "bd71828-pwrkey"), ++ }; ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = res, ++ .num_resources = ARRAY_SIZE(res), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd71828_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd71828_powerkey_props; ++ ++ ret = bd71828_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd71828_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static struct i2c_client *bd71828_dev; + static void bd71828_power_off(void) + { +@@ -929,6 +989,7 @@ static struct regmap *bd72720_do_regmaps(struct i2c_client *i2c) + static int bd71828_i2c_probe(struct i2c_client *i2c) + { + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + struct regmap *regmap = NULL; + const struct regmap_config *regmap_config; +@@ -1022,23 +1083,23 @@ static int bd71828_i2c_probe(struct i2c_client *i2c) + "Failed to enable main level IRQs\n"); + } + } +- if (button_irq) { +- ret = regmap_irq_get_virq(irq_data, button_irq); +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, +- "Failed to get the power-key IRQ\n"); +- +- button.irq = ret; +- } + + ret = set_clk_mode(&i2c->dev, regmap, clkmode_reg); + if (ret) + return ret; + ++ irq_domain = regmap_irq_get_domain(irq_data); ++ + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, mfd, cells, +- NULL, 0, regmap_irq_get_domain(irq_data)); ++ NULL, 0, irq_domain); + if (ret) +- return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ ++ if (button_irq) { ++ ret = bd71828_i2c_register_pwrbutton(&i2c->dev, button_irq, irq_domain); ++ if (ret) ++ return ret; ++ } + + if (of_device_is_system_power_controller(i2c->dev.of_node) && + chip_type == ROHM_CHIP_TYPE_BD71828) { +diff --git a/drivers/mfd/rohm-bd718x7.c b/drivers/mfd/rohm-bd718x7.c +--- a/drivers/mfd/rohm-bd718x7.c ++++ b/drivers/mfd/rohm-bd718x7.c +@@ -7,7 +7,8 @@ + // Datasheet for BD71837MWV available from + // https://www.rohm.com/datasheet/BD71837MWV/bd71837mwv-e + +-#include ++#include ++#include + #include + #include + #include +@@ -15,37 +16,16 @@ + #include + #include + #include ++#include + #include + #include + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +-}; +- +-static struct gpio_keys_platform_data bd718xx_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd718xx-pwrkey", +-}; +- + static struct mfd_cell bd71837_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71837-clk", }, + { .name = "bd71837-pmic", }, + }; + + static struct mfd_cell bd71847_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71847-clk", }, + { .name = "bd71847-pmic", }, + }; +@@ -125,10 +105,89 @@ static int bd718xx_init_press_duration(struct regmap *regmap, + return 0; + } + ++static const struct property_entry bd718xx_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd718xx-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd718xx_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ { } ++}; ++ ++static const struct resource bd718xx_powerkey_resources[] = { ++ DEFINE_RES_IRQ_NAMED(BD718XX_INT_PWRBTN_S, "bd718xx-pwrkey"), ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd718xx_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd718xx_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd718xx_i2c_register_pwrbutton(struct device *dev, ++ struct irq_domain *irq_domain) ++{ ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = bd718xx_powerkey_resources, ++ .num_resources = ARRAY_SIZE(bd718xx_powerkey_resources), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd718xx_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd718xx_powerkey_props; ++ ++ ret = bd718xx_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd718xx_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static int bd718xx_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + unsigned int chip_type; + struct mfd_cell *mfd; +@@ -169,20 +228,18 @@ static int bd718xx_i2c_probe(struct i2c_client *i2c) + if (ret) + return ret; + +- ret = regmap_irq_get_virq(irq_data, BD718XX_INT_PWRBTN_S); +- +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, "Failed to get the IRQ\n"); +- +- button.irq = ret; ++ irq_domain = regmap_irq_get_domain(irq_data); + + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, +- mfd, cells, NULL, 0, +- regmap_irq_get_domain(irq_data)); ++ mfd, cells, NULL, 0, irq_domain); + if (ret) +- dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); + +- return ret; ++ ret = bd718xx_i2c_register_pwrbutton(&i2c->dev, irq_domain); ++ if (ret) ++ return ret; ++ ++ return 0; + } + + static const struct of_device_id bd718xx_of_match[] = { +diff --git a/include/linux/input.h b/include/linux/input.h +--- a/include/linux/input.h ++++ b/include/linux/input.h +@@ -117,7 +117,8 @@ enum input_clock_type { + * user opens device and dev->close() is called when the very + * last user closes the device + * @going_away: marks devices that are in a middle of unregistering and +- * causes input_open_device*() fail with -ENODEV. ++ * causes input_open_device() and input_inhibit/uninhibit_device() ++ * to fail with -ENODEV. + * @dev: driver model's view of this device + * @h_list: list of input handles associated with the device. When + * accessing the list dev->mutex must be held +@@ -128,11 +129,14 @@ enum input_clock_type { + * @devres_managed: indicates that devices is managed with devres framework + * and needs not be explicitly unregistered or freed. + * @timestamp: storage for a timestamp set by input_set_timestamp called +- * by a driver ++ * by a driver + * @inhibited: indicates that the input device is inhibited. If that is +- * the case then input core ignores any events generated by the device. +- * Device's close() is called when it is being inhibited and its open() +- * is called when it is being uninhibited. ++ * the case then input core ignores any events generated by the device. ++ * Device's close() is called when it is being inhibited and its open() ++ * is called when it is being uninhibited. ++ * @ready: indicates that the device has been successfully opened and is ++ * prepared to process events (like LEDs or sounds) sent from the ++ * input core. + */ + struct input_dev { + const char *name; +@@ -209,6 +213,7 @@ struct input_dev { + ktime_t timestamp[INPUT_CLK_MAX]; + + bool inhibited; ++ bool ready; + }; + #define to_input_dev(d) container_of(d, struct input_dev, dev) + +@@ -284,8 +289,9 @@ struct input_handle; + * @connect: called when attaching a handler to an input device + * @disconnect: disconnects a handler from input device + * @start: starts handler for given handle. This function is called by +- * input core right after connect() method and also when a process +- * that "grabbed" a device releases it ++ * input core when device is open and ready to process events, ++ * and also when device is uninhibited or when a process that "grabbed" ++ * a device releases it + * @passive_observer: set to %true by drivers only interested in observing + * data stream from devices if there are other users present. Such + * drivers will not result in starting underlying hardware device +diff --git a/include/linux/input/elan-i2c-ids.h b/include/linux/input/elan-i2c-ids.h +--- a/include/linux/input/elan-i2c-ids.h ++++ b/include/linux/input/elan-i2c-ids.h +@@ -18,7 +18,7 @@ + #ifndef __ELAN_I2C_IDS_H + #define __ELAN_I2C_IDS_H + +-#include ++#include + + static const struct acpi_device_id elan_acpi_id[] = { + { "ELAN0000", 0 }, diff --git a/pkgbuilds/linux-omarchy-bore/0560-input-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0560-input-updates.patch.sig new file mode 100644 index 0000000..594f27d Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0560-input-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0560-input.patch b/pkgbuilds/linux-omarchy-bore/0560-input.patch deleted file mode 100644 index 0d58569..0000000 --- a/pkgbuilds/linux-omarchy-bore/0560-input.patch +++ /dev/null @@ -1,76 +0,0 @@ -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 deleted file mode 100644 index 409e402..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0560-input.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0561-input-fixes.patch b/pkgbuilds/linux-omarchy-bore/0561-input-fixes.patch new file mode 100644 index 0000000..b067aaa --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0561-input-fixes.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c +--- a/drivers/input/touchscreen/tsc2007_core.c ++++ b/drivers/input/touchscreen/tsc2007_core.c +@@ -221,7 +221,6 @@ static int tsc2007_get_pendown_state_gpio(struct device *dev) + static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + { + u32 val32; +- u64 val64; + + if (!device_property_read_u32(dev, "ti,max-rt", &val32)) + ts->max_rt = val32; +@@ -237,8 +236,8 @@ static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + if (!device_property_read_u32(dev, "ti,fuzzz", &val32)) + ts->fuzzz = val32; + +- if (!device_property_read_u64(dev, "ti,poll-period", &val64)) +- ts->poll_period = msecs_to_jiffies(val64); ++ if (!device_property_read_u32(dev, "ti,poll-period", &val32)) ++ ts->poll_period = msecs_to_jiffies(val32); + else + ts->poll_period = msecs_to_jiffies(1); + diff --git a/pkgbuilds/linux-omarchy-bore/0561-input-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0561-input-fixes.patch.sig new file mode 100644 index 0000000..609b474 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0561-input-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0562-hid-fixes.patch b/pkgbuilds/linux-omarchy-bore/0562-hid-fixes.patch new file mode 100644 index 0000000..47e03fa --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0562-hid-fixes.patch @@ -0,0 +1,483 @@ +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h +--- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h +@@ -12,11 +12,15 @@ + + #include + #include ++#include + #include "amd_sfh_hid.h" + + #define PCI_DEVICE_ID_AMD_MP2 0x15E4 + #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A + ++/* The BAR 2 size must cover the highest register offset (0x10958) */ ++#define AMD_SFH_MIN_BAR_SIZE SZ_128K ++ + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) + #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) + +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +@@ -451,6 +451,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i + if (rc) + return rc; + ++ if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { ++ dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); ++ return -ENODEV; ++ } ++ ++ if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { ++ dev_err(&pdev->dev, "BAR 2 is too small\n"); ++ return -EINVAL; ++ } ++ + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); + if (rc) + return rc; +diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c +--- a/drivers/hid/bpf/hid_bpf_dispatch.c ++++ b/drivers/hid/bpf/hid_bpf_dispatch.c +@@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) + + static int + __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, +- enum hid_report_type rtype) ++ enum hid_report_type rtype, bool hw_request) + { + struct hid_report_enum *report_enum; + struct hid_report *report; +@@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, + + report_len = hid_report_len(report); + ++ /* unnumbered reports need to have a report ID reserved in the first byte */ ++ if (hw_request && report_enum->numbered == 0) ++ report_len += 1; ++ + if (*buf__sz > report_len) + *buf__sz = report_len; + +@@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); + if (ret) + return ret; + +@@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); + if (ret) + return ret; + +@@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); + if (ret) + return ret; + +diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c +--- a/drivers/hid/hid-alps.c ++++ b/drivers/hid/hid-alps.c +@@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) + return 1; + + case U1_SP_ABSOLUTE_REPORT_ID: ++ if (!hdata->input2) ++ return 0; + sp_x = get_unaligned_le16(data+2); + sp_y = get_unaligned_le16(data+4); + +@@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + goto exit; + } + +- data->input2 = input2; + input2->phys = input->phys; + input2->name = "DualPoint Stick"; + input2->id.bustype = BUS_I2C; +@@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + __set_bit(INPUT_PROP_POINTER, input2->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); + +- if (input_register_device(data->input2)) { ++ if (input_register_device(input2)) { + input_free_device(input2); + ret = -ENOENT; + goto exit; + } ++ data->input2 = input2; + } + + exit: +@@ -823,6 +825,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) + return 0; + } + ++static void alps_remove(struct hid_device *hdev) ++{ ++ struct alps_dev *data = hid_get_drvdata(hdev); ++ ++ /* ++ * input2 ("DualPoint Stick") is allocated separately and is not ++ * tracked in hdev->inputs, so the default remove path ++ * (hid_hw_stop -> hidinput_disconnect) does not unregister it. ++ * ++ * Stop the device first so that no URB callback can touch input2 ++ * while it is being unregistered, then drop it explicitly. ++ */ ++ hid_hw_stop(hdev); ++ ++ if (data->input2) ++ input_unregister_device(data->input2); ++} ++ + static const struct hid_device_id alps_id[] = { + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, +@@ -845,6 +865,7 @@ static struct hid_driver alps_driver = { + .input_configured = alps_input_configured, + .resume = pm_ptr(alps_post_resume), + .reset_resume = pm_ptr(alps_post_reset), ++ .remove = alps_remove, + }; + + module_hid_driver(alps_driver); +diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c +--- a/drivers/hid/hid-corsair-void.c ++++ b/drivers/hid/hid-corsair-void.c +@@ -51,20 +51,23 @@ + /* Receiver report information: (ID 102) */ + /* -------------------------------------------------------------------------- */ + /* +- * When queried, the recevier responds with 4 bytes to describe the firmware +- * The first 2 bytes are for the receiver, the second 2 are the headset ++ * When queried, the receiver responds with 5 bytes to describe the firmware ++ * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset + * The headset firmware version will be 0 if no headset is connected + * +- * 0: Recevier firmware major version ++ * 0: Report ID ++ * 102 for the firmware packet ++ * ++ * 1: Receiver firmware major version + * Major version of the receiver's firmware + * +- * 1: Recevier firmware minor version ++ * 2: Receiver firmware minor version + * Minor version of the receiver's firmware + * +- * 2: Headset firmware major version ++ * 3: Headset firmware major version + * Major version of the headset's firmware + * +- * 3: Headset firmware minor version ++ * 4: Headset firmware minor version + * Minor version of the headset's firmware + */ + /* -------------------------------------------------------------------------- */ +diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c +--- a/drivers/hid/hid-elecom.c ++++ b/drivers/hid/hid-elecom.c +@@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: + case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: +- case USB_DEVICE_ID_ELECOM_M_XT4DRBK: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: + /* + * Report descriptor format: + * 12: button bit count +@@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); + break; + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: + /* + * Report descriptor format: + * 22: button bit count +@@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -472,7 +472,8 @@ + #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c +-#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e + #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe + #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff + #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d +@@ -759,6 +760,7 @@ + #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a + #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 + #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 ++#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db + #define USB_DEVICE_ID_ITE8595 0x8595 + #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 + +@@ -1283,6 +1285,9 @@ + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 + ++#define USB_VENDOR_ID_SDINNOVATION 0x36ae ++#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab ++ + #define USB_VENDOR_ID_SEMICO 0x1a2c + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 +diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c +--- a/drivers/hid/hid-logitech-hidpp.c ++++ b/drivers/hid/hid-logitech-hidpp.c +@@ -4656,6 +4656,8 @@ static const struct hid_device_id hidpp_devices[] = { + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, + { /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, ++ { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ ++ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, + { /* Logitech G703 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, + { /* Logitech G703 Hero Gaming Mouse over USB */ +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_YOGABOOK9I BIT(24) + #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) ++#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_APPLE_TOUCHBAR 0x0114 + #define MT_CLS_YOGABOOK9I 0x0115 + #define MT_CLS_EGALAX_P80H84 0x0116 ++#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_ASUS_CUSTOM_UP }, ++ { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ .quirks = MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS | ++ MT_QUIRK_CONFIDENCE | ++ MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, ++ .export_all_inputs = true }, + { .name = MT_CLS_VTL, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | +@@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { + + static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + { ++ struct mt_device *td = hid_get_drvdata(hdev); + int ret; + u32 size = hid_report_len(report); + u8 *buf; +@@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + dev_warn(&hdev->dev, "failed to fetch feature %d\n", + report->id); + } else { +- /* The report ID in the request and the response should match */ +- if (report->id != buf[0]) { ++ /* ++ * The report ID in the request and the response should match. ++ * Some firmware (e.g. the ASUS ROG Z13 Folio ++ * touchpad) returns a mismatched ID on this specific fetch; ++ * tolerate it only for devices explicitly flagged as such. ++ */ ++ if (report->id != buf[0] && ++ !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { + hid_err(hdev, "Returned feature report did not match the request\n"); + goto free; + } +@@ -2726,6 +2745,12 @@ static const struct hid_device_id mt_devices[] = { + HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, + I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + ++ /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ ++ { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, ++ USB_VENDOR_ID_ASUSTEK, ++ USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, ++ + /* Generic MT device */ + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, + +diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c +--- a/drivers/hid/hid-oxp.c ++++ b/drivers/hid/hid-oxp.c +@@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, + + static void oxp_hid_remove(struct hid_device *hdev) + { +- cancel_delayed_work(&drvdata.oxp_rgb_queue); +- cancel_delayed_work(&drvdata.oxp_btn_queue); +- cancel_delayed_work(&drvdata.oxp_mcu_init); ++ cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_btn_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_mcu_init); + hid_hw_close(hdev); + hid_hw_stop(hdev); + } +diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c +--- a/drivers/hid/hid-quirks.c ++++ b/drivers/hid/hid-quirks.c +@@ -183,6 +183,7 @@ static const struct hid_device_id hid_quirks[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, +@@ -422,13 +423,14 @@ static const struct hid_device_id hid_have_special_driver[] = { + #endif + #if IS_ENABLED(CONFIG_HID_ELECOM) + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, +- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c +--- a/drivers/hid/hid-roccat.c ++++ b/drivers/hid/hid-roccat.c +@@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + return temp; + } + +- mutex_unlock(&devices_lock); +- + init_waitqueue_head(&device->wait); + INIT_LIST_HEAD(&device->readers); + mutex_init(&device->readers_lock); +@@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + device->cbuf_end = 0; + device->report_size = report_size; + ++ mutex_unlock(&devices_lock); + return minor; + } + EXPORT_SYMBOL_GPL(roccat_connect); +@@ -369,15 +368,12 @@ void roccat_disconnect(int minor) + + mutex_lock(&devices_lock); + device = devices[minor]; +- mutex_unlock(&devices_lock); + + device->exist = 0; /* TODO exist maybe not needed */ + + device_destroy(device->dev->class, MKDEV(roccat_major, minor)); + +- mutex_lock(&devices_lock); + devices[minor] = NULL; +- mutex_unlock(&devices_lock); + + if (device->open) { + hid_hw_close(device->hid); +@@ -385,6 +381,8 @@ void roccat_disconnect(int minor) + } else { + roccat_free_device(device); + } ++ ++ mutex_unlock(&devices_lock); + } + EXPORT_SYMBOL_GPL(roccat_disconnect); + +diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c +--- a/drivers/hid/hid-winwing.c ++++ b/drivers/hid/hid-winwing.c +@@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) + static int winwing_play_effect(struct input_dev *dev, void *context, + struct ff_effect *effect) + { +- struct winwing_drv_data *data = (struct winwing_drv_data *) context; ++ struct hid_device *hdev = input_get_drvdata(dev); ++ struct winwing_drv_data *data = hid_get_drvdata(hdev); + + if (effect->type != FF_RUMBLE) + return 0; +@@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) + + input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); + +- return input_ff_create_memless(hidinput->input, data, ++ /* ++ * input_ff_create_memless() takes ownership of the context pointer ++ * and frees it on teardown; do not hand it the devm-managed drvdata. ++ * winwing_play_effect() fetches it from the input device instead. ++ */ ++ return input_ff_create_memless(hidinput->input, NULL, + winwing_play_effect); + } + +diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c +--- a/drivers/hid/i2c-hid/i2c-hid-core.c ++++ b/drivers/hid/i2c-hid/i2c-hid-core.c +@@ -123,9 +123,11 @@ static const struct i2c_hid_quirks { + __u32 quirks; + } i2c_hid_quirks[] = { + { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, +- I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, + { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, +diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c +--- a/drivers/hid/wacom_sys.c ++++ b/drivers/hid/wacom_sys.c +@@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, + + /* Queue events which have invalid tool type or serial number */ + for (i = 0; i < report->maxfield; i++) { +- for (j = 0; j < report->field[i]->maxusage; j++) { +- struct hid_field *field = report->field[i]; ++ struct hid_field *field = report->field[i]; ++ ++ for (j = 0; j < field->report_count; j++) { + struct hid_usage *usage = &field->usage[j]; + unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); + unsigned int offset; diff --git a/pkgbuilds/linux-omarchy-bore/0562-hid-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0562-hid-fixes.patch.sig new file mode 100644 index 0000000..b890b57 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0562-hid-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0563-input-fixes-2.patch b/pkgbuilds/linux-omarchy-bore/0563-input-fixes-2.patch new file mode 100644 index 0000000..1682da2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0563-input-fixes-2.patch @@ -0,0 +1,88 @@ +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -1938,6 +1938,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { + }, + .callback = atkbd_deactivate_fixup, + }, ++ { ++ /* Lenovo IdeaPad Slim 3 15IWC11 (83RR) */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"), ++ }, ++ .callback = atkbd_deactivate_fixup, ++ }, + { + /* Xiaomi Book Pro 14 (TM2424) */ + .matches = { +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -192,6 +192,7 @@ static const char * const smbus_pnp_ids[] = { + "LEN2054", /* E480 */ + "LEN2055", /* E580 */ + "LEN2058", /* E490 */ ++ "LEN205b", /* T490 */ + "LEN2068", /* T14 Gen 1 */ + "SYN1221", /* TUXEDO InfinityBook Pro 14 v5 */ + "SYN3003", /* HP EliteBook 850 G1 */ +@@ -1839,8 +1840,10 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, + return -ENXIO; + } + +- /* Disable intertouch on known-broken board revisions */ +- if (info->board_id == 2722) { ++ /* Disable intertouch on known-broken T440p board revisions */ ++ if (info->board_id == 2722 && ++ psmouse_matches_pnp_id(psmouse, ++ (const char * const []){"LEN0036", NULL})) { + psmouse_info(psmouse, + "Disabling intertouch for board id %u\n", + info->board_id); +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -671,6 +671,15 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { + }, + .driver_data = (void *)(SERIO_QUIRK_NOAUX) + }, ++ { ++ /* Fujitsu LIFEBOOK U7410 */ ++ /* Mux mode also disturbs the EC keyboard, causing spurious ACKs */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU CLIENT COMPUTING LIMITED"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U7410"), ++ }, ++ .driver_data = (void *)(SERIO_QUIRK_NOMUX) ++ }, + { + /* Gigabyte M912 */ + .matches = { +diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c +--- a/drivers/input/touchscreen/s6sy761.c ++++ b/drivers/input/touchscreen/s6sy761.c +@@ -500,10 +500,22 @@ static int s6sy761_suspend(struct device *dev) + static int s6sy761_resume(struct device *dev) + { + struct s6sy761_data *sdata = dev_get_drvdata(dev); ++ int err; ++ ++ err = s6sy761_power_on(sdata); ++ if (err) ++ return err; + + enable_irq(sdata->client->irq); + +- return s6sy761_power_on(sdata); ++ guard(mutex)(&sdata->input->mutex); ++ ++ if (!input_device_enabled(sdata->input)) ++ return 0; ++ ++ err = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON); ++ ++ return err; + } + + static const struct dev_pm_ops s6sy761_pm_ops = { diff --git a/pkgbuilds/linux-omarchy-bore/0563-input-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-bore/0563-input-fixes-2.patch.sig new file mode 100644 index 0000000..36b52ad Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0563-input-fixes-2.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 index e569f4c..d7870a2 100644 --- 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 @@ -1,7 +1,7 @@ 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) +@@ -1480,12 +1480,14 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) return 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 index f1805db..42cf5b7 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig 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 deleted file mode 100644 index 5fe836e..0000000 --- a/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 33208c2..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0600-usb4stream-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0601-thunderbolt-updates.patch b/pkgbuilds/linux-omarchy-bore/0601-thunderbolt-updates.patch new file mode 100644 index 0000000..7a0d9ca --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0601-thunderbolt-updates.patch @@ -0,0 +1,1131 @@ +diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c +--- a/drivers/net/thunderbolt/main.c ++++ b/drivers/net/thunderbolt/main.c +@@ -1362,7 +1362,7 @@ static void tbnet_generate_mac(struct net_device *dev) + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + } + +-static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbnet_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct net_device *dev; +@@ -1482,7 +1482,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume); + + static const struct tb_service_id tbnet_ids[] = { + { TB_SERVICE("network", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbnet_ids); + +diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c +--- a/drivers/thunderbolt/debugfs.c ++++ b/drivers/thunderbolt/debugfs.c +@@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf, + if (!access_ok(user_buf, *count)) + return ERR_PTR(-EFAULT); + +- buf = (void *)get_zeroed_page(GFP_KERNEL); ++ buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + nbytes = min_t(size_t, *count, PAGE_SIZE); + if (copy_from_user(buf, user_buf, nbytes)) { +- free_page((unsigned long)buf); ++ kfree(buf); + return ERR_PTR(-EFAULT); + } + +@@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file, + out: + pm_runtime_mark_last_busy(&rt->dev); + pm_runtime_put_autosuspend(&rt->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf, + margining->ber_level = val; + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + out_unlock: + mutex_unlock(&tb->lock); + +@@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf, + } + } + +- free_page((unsigned long)buf); ++ kfree(buf); + + if (lane == -1) + return -EINVAL; +@@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + else + goto err_free; + +- free_page((unsigned long)buf); ++ kfree(buf); + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) { + if (!margining->software) +@@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + return count; + + err_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return -EINVAL; + } + +@@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file, + ret = -EINVAL; + } + +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c +--- a/drivers/thunderbolt/dma_test.c ++++ b/drivers/thunderbolt/dma_test.c +@@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc) + debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops); + } + +-static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int dma_test_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct dma_test *dt; +@@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = { + + static const struct tb_service_id dma_test_ids[] = { + { TB_SERVICE("dma_test", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, dma_test_ids); + +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev) + { + struct tb_service *svc = tb_to_service(dev); + struct tb_service_driver *driver; +- const struct tb_service_id *id; + + driver = container_of(dev->driver, struct tb_service_driver, driver); +- id = __tb_service_match(dev, &driver->driver); + +- return driver->probe(svc, id); ++ return driver->probe(svc); + } + + static void tb_service_remove(struct device *dev) +@@ -790,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + transmit_ring, receive_path, receive_ring); + } + ++static void tb_domain_reset_interface(struct tb *tb) ++{ ++ struct tb_nhi *nhi = tb->nhi; ++ ++ if (!nhi->ops->reset_interface) ++ return; ++ ++ guard(mutex)(&tb->lock); ++ ++ /* The reset clears the ring state so stop the control channel */ ++ tb_ctl_stop(tb->ctl); ++ nhi->ops->reset_interface(nhi); ++ tb_ctl_start(tb->ctl); ++} ++ + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -812,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { ++ int ret; ++ + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); ++ if (ret) ++ return ret; ++ ++ if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) ++ tb_domain_reset_interface(tb); ++ ++ return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) + { + struct ring_frame *frame, *n; + struct ring_desc *descriptor; ++ u32 flags; ++ ++ flags = RING_DESC_POSTED; ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ flags |= RING_DESC_INTERRUPT; ++ + list_for_each_entry_safe(frame, n, &ring->queue, list) { + if (ring_full(ring)) + break; +@@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) + descriptor = &ring->descriptors[ring->head]; + descriptor->phys = frame->buffer_phy; + descriptor->time = 0; +- descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; ++ descriptor->flags = flags; + if (ring->is_tx) { + descriptor->length = frame->size; + descriptor->eof = frame->eof; +@@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); + * @ring: Ring to poll + * + * This function can be called when @start_poll callback of the @ring +- * has been called. It will read one completed frame from the ring and +- * return it to the caller. ++ * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. ++ * It will read one completed frame from the ring and return it to the ++ * caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. +@@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", + transmit ? "TX" : "RX", hop, size); + ++ if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { ++ dev_WARN(nhi->dev, ++ "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); ++ return NULL; ++ } ++ + ring = kzalloc_obj(*ring); + if (!ring) + return NULL; +@@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + if (!ring->descriptors) + goto err_free_ring; + +- if (nhi->ops->request_ring_irq) { ++ if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } +@@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) + ring_iowrite32options(ring, flags, 0); + } + +- ring_interrupt_active(ring, true); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, true); + ring->running = true; + err: + spin_unlock(&ring->lock); +@@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) + RING_TYPE(ring), ring->hop); + goto err; + } +- ring_interrupt_active(ring, false); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, false); + + ring_iowrite32options(ring, 0, 0); + ring_iowrite64desc(ring, 0, 0); +@@ -1160,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + ++/** ++ * nhi_reset_interface() - Reset the host interface ++ * @nhi: Host interface to reset ++ * ++ * Brings the registers in the memory BAR back to their default state and ++ * clears the End-to-End Flow Control state. The caller is responsible for ++ * stopping the control channel over the reset because it clears the ring ++ * state as well. ++ */ ++void nhi_reset_interface(struct tb_nhi *nhi) ++{ ++ u32 val; ++ ++ val = ioread32(nhi->iobase + REG_CAPS); ++ /* Only v1 host interfaces implement the reset */ ++ if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) ++ return; ++ ++ dev_dbg(nhi->dev, "issuing host interface reset\n"); ++ ++ iowrite32(REG_HOST_INTERFACE_RESET_RST, ++ nhi->iobase + REG_HOST_INTERFACE_RESET); ++ /* Wait for tHIReset (10 ms) to complete */ ++ usleep_range(10000, 20000); ++} ++ + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +@@ -1235,6 +1276,8 @@ int nhi_probe(struct tb_nhi *nhi) + + dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); + ++ nhi->host_reset = host_reset; ++ + res = tb_domain_add(tb, host_reset); + if (res) { + /* +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); ++void nhi_reset_interface(struct tb_nhi *nhi); ++ + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook ++ * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -66,6 +69,7 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); ++ void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -116,11 +120,24 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e ++ + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) ++#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,6 +115,10 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + ++/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ ++#define REG_HOST_INTERFACE_RESET 0x39858 ++#define REG_HOST_INTERFACE_RESET_RST BIT(0) ++ + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } ++ } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { ++ switch (pdev->device) { ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: ++ /* ++ * These AMD hosts may hang the Tx ring when the ++ * DMA paths are torn down so they need the host ++ * interface reset after each teardown. ++ */ ++ nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; ++ break; ++ } + } + } + +@@ -112,7 +133,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +- struct device *dev = &pdev->dev; + int res, irq, nvec; + + ida_init(&nhi_pci->msix_ida); +@@ -139,7 +159,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + res = devm_request_irq(&pdev->dev, irq, nhi_msi, + IRQF_NO_SUSPEND, "thunderbolt", nhi); + if (res) +- return dev_err_probe(dev, res, "request_irq failed, aborting\n"); ++ return res; + } + + return 0; +@@ -230,7 +250,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring) + ring->irq = 0; + } + +-static void nhi_pci_shutdown(struct tb_nhi *nhi) ++static void nhi_pci_release_irq(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +@@ -256,9 +276,10 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, +- .shutdown = nhi_pci_shutdown, ++ .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -424,7 +445,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi) + + static void icl_nhi_shutdown(struct tb_nhi *nhi) + { +- nhi_pci_shutdown(nhi); ++ nhi_pci_release_irq(nhi); + + icl_nhi_force_power(nhi, false); + } +@@ -442,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +@@ -479,11 +501,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) + return nhi_probe(&nhi_pci->nhi); + } + +-static void nhi_pci_remove(struct pci_dev *pdev) ++static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset) + { + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + ++ /* ++ * On system shutdown/reboot force a host router reset so the ++ * connection manager asserts DPR on connected Thunderbolt 3 devices ++ * before the router tree is removed (see tb_stop()). ++ */ ++ if (reset) ++ nhi->host_reset = true; ++ + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); +@@ -493,6 +523,16 @@ static void nhi_pci_remove(struct pci_dev *pdev) + nhi_shutdown(nhi); + } + ++static void nhi_pci_remove(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, false); ++} ++ ++static void nhi_pci_shutdown(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, true); ++} ++ + static struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and +@@ -593,7 +633,7 @@ static struct pci_driver nhi_driver = { + .id_table = nhi_ids, + .probe = nhi_pci_probe, + .remove = nhi_pci_remove, +- .shutdown = nhi_pci_remove, ++ .shutdown = nhi_pci_shutdown, + .driver.pm = &nhi_pm_ops, + }; + +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -9,10 +9,12 @@ + + #define pr_fmt(fmt) "tbstream: " fmt + ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -128,6 +130,7 @@ struct tbstream_ring { + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns ++ * @busy_poll: Instead of interrupts, busy poll the rings + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. +@@ -147,6 +150,7 @@ struct tbstream_dev { + int out_hopid; + unsigned int ring_size; + unsigned int throttling; ++ bool busy_poll; + int users; + bool closed; + bool removed; +@@ -536,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); + } + ++static void ++tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) ++{ ++ struct ring_frame *frame; ++ ++ if (!sdev->busy_poll) ++ return; ++ ++ while ((frame = tb_ring_poll(ring->ring))) ++ frame->callback(ring->ring, frame, false); ++} ++ + static int tbstream_dev_send_close(struct tbstream_dev *sdev) + { + struct tbstream_frame *sf; + ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling it's the write(2) path that ++ * advances the completions so it is possible that the ++ * ring is full at this point. Advance the ring here so ++ * that there is room for the CLOSE packet to be sent. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ } ++ + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); +@@ -549,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) + static int tbstream_dev_start(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); ++ unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; + u16 sof_mask, eof_mask; + struct tb_ring *ring; + int ret, e2e_tx_hop; + +- ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E); ++ if (sdev->busy_poll) ++ flags |= RING_FLAG_NO_INTERRUPT; ++ ++ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); + if (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; +@@ -567,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + sof_mask = BIT(TBSTREAM_FRAME_START); + eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); + +- ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, +- sof_mask, eof_mask, NULL, NULL); ++ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, ++ e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; +@@ -607,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + return ret; + } + ++static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) ++{ ++ const struct tbstream_ring *ring = &sdev->tx_ring; ++ ++ /* ++ * Everything is completed when number of free TX slots is back ++ * to the maximum. ++ */ ++ return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; ++} ++ + static void tbstream_dev_stop(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd; + +- /* Wait for the ring to complete any outstanding frames */ +- tb_ring_flush(sdev->tx_ring.ring, 500); +- tb_ring_stop(sdev->tx_ring.ring); +- tb_ring_flush(sdev->rx_ring.ring, 500); +- tb_ring_stop(sdev->rx_ring.ring); ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling we must advance the ring ourselves ++ * to push all outstanding frames on the wire. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_dev_tx_drained(sdev)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } else { ++ tb_ring_flush(sdev->tx_ring.ring, 500); ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_flush(sdev->rx_ring.ring, 500); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } + + xd = tbstream_dev_xdomain(sdev); + if (xd) { +@@ -633,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) + sdev->tx_ring.ring = NULL; + } + ++/* Use only with read_iter/write_iter() to handle nowait */ ++static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) ++{ ++ if (nowait) { ++ if (!mutex_trylock(&sdev->lock)) ++ return -EAGAIN; ++ } else { ++ if (mutex_lock_interruptible(&sdev->lock)) ++ return -ERESTARTSYS; ++ } ++ return 0; ++} ++ + static ssize_t + tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -645,31 +722,50 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->rx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->rx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ /* When busy polling, advance any completions manually */ ++ tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return 0; ++ } ++ ++ if (tbstream_ring_available(&sdev->rx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->rx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return 0; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -729,6 +825,7 @@ static ssize_t + tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -737,31 +834,49 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->tx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->tx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return -ENXIO; ++ } ++ ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->tx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return -ENXIO; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -795,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + __poll_t mask = 0; + ++ /* ++ * Without interrupts there is nothing that can wake us up so ++ * return failure instead. ++ */ ++ if (sdev->busy_poll) ++ return EPOLLERR; ++ + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { +@@ -904,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) + return container_of(group, struct tbstream_dev, group); + } + ++static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ ++ return sysfs_emit(buf, "%u\n", sdev->busy_poll); ++} ++ ++static ssize_t ++tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, ++ size_t count) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ bool busy_poll; ++ int ret; ++ ++ ret = kstrtobool(buf, &busy_poll); ++ if (ret) ++ return ret; ++ ++ guard(mutex)(&sdev->lock); ++ if (sdev->users) ++ return -EBUSY; ++ sdev->busy_poll = busy_poll; ++ return count; ++} ++CONFIGFS_ATTR(tbstream_dev_, busy_poll); ++ + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) + { + struct config_group *group = to_config_group(item); +@@ -1210,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, + CONFIGFS_ATTR(tbstream_dev_, throttling); + + static struct configfs_attribute *tbstream_dev_attrs[] = { ++ &tbstream_dev_attr_busy_poll, + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, +@@ -1542,7 +1694,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream) + config_group_put(&sg->group); + } + +-static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbstream_probe(struct tb_service *svc) + { + struct tbstream *stream; + +@@ -1632,7 +1784,7 @@ static const struct dev_pm_ops tbstream_pm_ops = { + + static const struct tb_service_id tbstream_ids[] = { + { TB_SERVICE("stream", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); + +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port) + return __tb_port_enable(port, false); + } + +-static int tb_port_reset(struct tb_port *port) ++/** ++ * tb_port_reset() - Reset the port ++ * @port: Port to reset ++ * ++ * Resets @port. For USB4 ports this issues a USB4 port reset and for ++ * legacy ports the link controller port is reset. ++ * ++ * Return: %0 on success, negative errno otherwise. ++ */ ++int tb_port_reset(struct tb_port *port) + { + if (tb_switch_is_usb4(port->sw)) + return port->cap_usb4 ? usb4_port_reset(port) : 0; +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, + static void tb_stop(struct tb *tb) + { + struct tb_cm *tcm = tb_priv(tb); ++ struct tb_nhi *nhi = tb->nhi; + struct tb_tunnel *tunnel; ++ struct tb_port *port; + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); +@@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } ++ /* ++ * Signal disconnect to connected devices before the router tree is ++ * removed below. A Thunderbolt 3 device directly connected to a USB4 ++ * host otherwise never receives a disconnect indication, leaving ++ * firmware to poll the dead link for up to ~60 s which on some ++ * platforms turns the shutdown into a warm reset. Asserting ++ * PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device ++ * detects SBRX low and goes to Uninitialized Unplugged immediately. ++ */ ++ if (nhi->host_reset) { ++ tb_switch_for_each_port(tb->root_switch, port) { ++ if (!tb_port_is_null(port) || !tb_port_has_remote(port)) ++ continue; ++ if (tb_switch_is_usb4(port->remote->sw)) ++ continue; ++ if (tb_port_reset(port)) ++ tb_port_dbg(port, "downstream port reset failed, continuing\n"); ++ } ++ } + tb_switch_remove(tb->root_switch); + tb->root_switch = NULL; + tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter); + int tb_port_unlock(struct tb_port *port); + int tb_port_enable(struct tb_port *port); + int tb_port_disable(struct tb_port *port); ++int tb_port_reset(struct tb_port *port); + int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid); + void tb_port_release_in_hopid(struct tb_port *port, int hopid); + int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid); +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -48,6 +48,9 @@ + #define TB_DP_AUX_PRIORITY 2 + #define TB_DP_AUX_WEIGHT 1 + ++/* struct tb_regs_hop::initial_credits is 7 bits wide */ ++#define TB_MAX_CREDITS 127 ++ + /* Minimum number of credits needed for PCIe path */ + #define TB_MIN_PCIE_CREDITS 6U + /* +@@ -1778,8 +1781,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits) + if (available < TB_MIN_DMA_CREDITS) + return -ENOSPC; + +- while (credits > available) +- credits--; ++ credits = min(credits, available); + + tb_port_dbg(port, "reserving %u credits for DMA path\n", + credits); +@@ -1908,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + struct tb_tunnel *tunnel; + size_t npaths = 0, i = 0; + struct tb_path *path; +- int credits; ++ unsigned int credits; + + /* Ring 0 is reserved for control channel */ + if (WARN_ON(!receive_ring || !transmit_ring)) +@@ -1931,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + tunnel->destroy = tb_dma_destroy; + + credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits); ++ if (credits > TB_MAX_CREDITS) { ++ tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n", ++ credits, TB_MAX_CREDITS); ++ credits = TB_MAX_CREDITS; ++ } + + if (receive_ring > 0) { + path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, + */ + int tb_register_service_driver(struct tb_service_driver *drv) + { ++ if (!drv->probe) ++ return -EINVAL; ++ + drv->driver.bus = &tb_bus_type; + return driver_register(&drv->driver); + } +@@ -1811,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; ++ tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -465,7 +465,7 @@ static inline struct tb_service *tb_to_service(struct device *dev) + */ + struct tb_service_driver { + struct device_driver driver; +- int (*probe)(struct tb_service *svc, const struct tb_service_id *id); ++ int (*probe)(struct tb_service *svc); + void (*remove)(struct tb_service *svc); + void (*shutdown)(struct tb_service *svc); + const struct tb_service_id *id_table; +@@ -514,6 +514,11 @@ void tb_service_properties_changed(struct tb_service *svc); + * @hop_count: Number of rings (end point hops) supported by NHI. + * @quirks: NHI specific quirks if any + * @domain_released: Completed when domain has been fully released ++ * @host_reset: Host router was reset on driver load, or forced on system ++ * shutdown/reboot. When set, tb_stop() asserts DPR on connected ++ * downstream ports to signal disconnect before tearing down the ++ * router tree. Only Thunderbolt 3 devices are reset; USB4 ++ * routers are skipped. + */ + struct tb_nhi { + spinlock_t lock; +@@ -528,6 +533,7 @@ struct tb_nhi { + u32 hop_count; + unsigned long quirks; + struct completion domain_released; ++ bool host_reset; + }; + + /** +@@ -592,6 +598,8 @@ struct tb_ring { + #define RING_FLAG_FRAME BIT(1) + /* Enable end-to-end flow control */ + #define RING_FLAG_E2E BIT(2) ++/* Do not enable interrupt for the ring */ ++#define RING_FLAG_NO_INTERRUPT BIT(3) + + struct ring_frame; + typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); diff --git a/pkgbuilds/linux-omarchy-bore/0601-thunderbolt-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0601-thunderbolt-updates.patch.sig new file mode 100644 index 0000000..89bd5ac Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0601-thunderbolt-updates.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 deleted file mode 100644 index 0cb7d2a..0000000 --- a/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch +++ /dev/null @@ -1,477 +0,0 @@ -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 deleted file mode 100644 index 8f8f07d..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0601-usb4stream-busy-poll.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0602-thunderbolt-fixes.patch b/pkgbuilds/linux-omarchy-bore/0602-thunderbolt-fixes.patch new file mode 100644 index 0000000..f2e51c0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0602-thunderbolt-fixes.patch @@ -0,0 +1,557 @@ +diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c +--- a/drivers/thunderbolt/ctl.c ++++ b/drivers/thunderbolt/ctl.c +@@ -73,7 +73,6 @@ struct tb_ctl { + #define tb_ctl_dbg_once(ctl, format, arg...) \ + dev_dbg_once((ctl)->nhi->dev, format, ## arg) + +-static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); + /* Serializes access to request kref_get/put */ + static DEFINE_MUTEX(tb_cfg_request_lock); + +@@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req) + static int tb_cfg_request_enqueue(struct tb_ctl *ctl, + struct tb_cfg_request *req) + { ++ tb_cfg_request_get(req); ++ + WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)); + WARN_ON(req->ctl); + +- mutex_lock(&ctl->request_queue_lock); ++ guard(mutex)(&ctl->request_queue_lock); + if (!ctl->running) { +- mutex_unlock(&ctl->request_queue_lock); ++ tb_cfg_request_put(req); + return -ENOTCONN; + } + req->ctl = ctl; + list_add_tail(&req->list, &ctl->request_queue); + set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- mutex_unlock(&ctl->request_queue_lock); + return 0; + } + ++static bool tb_cfg_request_is_active(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++} ++ ++static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++} ++ + static void tb_cfg_request_dequeue(struct tb_cfg_request *req) + { + struct tb_ctl *ctl = req->ctl; + +- mutex_lock(&ctl->request_queue_lock); +- if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { +- mutex_unlock(&ctl->request_queue_lock); +- return; ++ guard(mutex)(&ctl->request_queue_lock); ++ if (tb_cfg_request_is_active(req)) { ++ list_del(&req->list); ++ clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++ tb_cfg_request_put(req); + } +- +- list_del(&req->list); +- clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) +- wake_up(&tb_cfg_request_cancel_queue); +- mutex_unlock(&ctl->request_queue_lock); +-} +- +-static bool tb_cfg_request_is_active(struct tb_cfg_request *req) +-{ +- return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + } + + static struct tb_cfg_request * +@@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) + mutex_lock(&pkg->ctl->request_queue_lock); + list_for_each_entry(iter, &pkg->ctl->request_queue, list) { + tb_cfg_request_get(iter); +- if (iter->match(iter, pkg)) { ++ if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) { + req = iter; + break; + } +@@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req); + + if (req) { +- if (req->copy(req, pkg)) +- schedule_work(&req->work); ++ scoped_guard(mutex, &pkg->ctl->request_queue_lock) { ++ if (!tb_cfg_request_is_canceled(req) && ++ req->copy(req, pkg)) ++ schedule_work(&req->work); ++ } + tb_cfg_request_put(req); + } + +@@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work) + { + struct tb_cfg_request *req = container_of(work, typeof(*req), work); + +- if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) ++ if (!tb_cfg_request_is_canceled(req)) + req->callback(req->callback_data); + + tb_cfg_request_dequeue(req); +- tb_cfg_request_put(req); + } + + /** +@@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + INIT_WORK(&req->work, tb_cfg_request_work); + INIT_LIST_HEAD(&req->list); + +- tb_cfg_request_get(req); + ret = tb_cfg_request_enqueue(ctl, req); + if (ret) +- goto err_put; ++ return ret; + + ret = tb_ctl_tx(ctl, req->request, req->request_size, + req->request_type); +@@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + + err_dequeue: + tb_cfg_request_dequeue(req); +-err_put: +- tb_cfg_request_put(req); +- + return ret; + } + +@@ -588,9 +586,10 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + */ + void tb_cfg_request_cancel(struct tb_cfg_request *req, int err) + { +- set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); +- schedule_work(&req->work); +- wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req)); ++ scoped_guard(mutex, &req->ctl->request_queue_lock) ++ set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++ cancel_work_sync(&req->work); ++ tb_cfg_request_dequeue(req); + req->result.err = err; + } + +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + INIT_LIST_HEAD(&ring->in_flight); + INIT_WORK(&ring->work, ring_work); + init_waitqueue_head(&ring->wait); ++ lockdep_register_key(&ring->lock_key); ++ lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0); + + ring->nhi = nhi; + ring->hop = hop; +@@ -599,6 +602,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + ring->size * sizeof(*ring->descriptors), + ring->descriptors, ring->descriptors_dma); + err_free_ring: ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + + return NULL; +@@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring) + * to finish before freeing the ring. + */ + flush_work(&ring->work); ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + } + EXPORT_SYMBOL_GPL(tb_ring_free); +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + { + int port_max_hopid; + struct ida *ida; ++ int ret; + + if (in) { + port_max_hopid = port->config.max_in_hop_id; +@@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + if (max_hopid < 0 || max_hopid > port_max_hopid) + max_hopid = port_max_hopid; + +- return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ if (ret >= 0) ++ tb_switch_get(port->sw); ++ ++ return ret; + } + + /** +@@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid) + void tb_port_release_in_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->in_hopids, hopid); ++ tb_switch_put(port->sw); + } + + /** +@@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid) + void tb_port_release_out_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->out_hopids, hopid); ++ tb_switch_put(port->sw); + } + + static inline bool tb_switch_is_reachable(const struct tb_switch *parent, +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, + const char *reason); + static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port, + int retry, unsigned long delay); ++static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data); + + static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) + { +@@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, + + switch (port->config.type) { + case TB_TYPE_DP_HDMI_IN: +- tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); ++ tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids, ++ tb_dp_tunnel_active, tb); + tb_increase_tmu_accuracy(tunnel); + break; + +@@ -1910,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + struct tb *tb = data; + + mutex_lock(&tb->lock); ++ ++ /* ++ * If the DPRX read was canceled the tunnel is already being torn ++ * down by whoever canceled it. Do not touch the adapters here ++ * because the routers may be gone by now. ++ */ ++ if (tunnel->dprx_canceled) { ++ tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n"); ++ mutex_unlock(&tb->lock); ++ return; ++ } ++ + if (tb_tunnel_is_active(tunnel)) { + int consumed_up, consumed_down, ret; + +@@ -1964,8 +1978,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); + } + mutex_unlock(&tb->lock); +- +- tb_domain_put(tb); + } + + static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, +@@ -2026,8 +2038,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + available_up, available_down); + + tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, +- available_down, tb_dp_tunnel_active, +- tb_domain_get(tb)); ++ available_down, tb_dp_tunnel_active, tb); + if (!tunnel) { + tb_port_dbg(out, "could not allocate DP tunnel\n"); + goto err_reclaim_usb; +@@ -2048,7 +2059,6 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + tb_tunnel_put(tunnel); + err_reclaim_usb: + tb_reclaim_usb3_bandwidth(tb, in, out); +- tb_domain_put(tb); + err_detach_group: + tb_detach_bandwidth_group(in); + err_dealloc_dp: +@@ -2950,11 +2960,12 @@ static void tb_stop(struct tb *tb) + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +- * DMA tunnels require the driver to be functional so we +- * tear them down. Other protocol tunnels can be left +- * intact. ++ * DMA tunnels and DP tunnels which are not yet active require ++ * the driver to be functional so we tear them down. ++ * Other protocol tunnels can be left intact. + */ +- if (tb_tunnel_is_dma(tunnel)) ++ if (tb_tunnel_is_dma(tunnel) || ++ (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel))) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } +@@ -3274,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work) + struct tb *tb = tcm_to_tb(tcm); + + mutex_lock(&tb->lock); +- if (tb->root_switch) ++ if (tb->root_switch) { + tb_free_unplugged_children(tb->root_switch); ++ tb_free_unplugged_xdomains(tb->root_switch); ++ } + mutex_unlock(&tb->lock); +- +- tb_free_unplugged_xdomains(tb->root_switch); + } + + static int tb_runtime_resume(struct tb *tb) +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, + goto err_deactivate; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1093,8 +1094,14 @@ static void tb_dp_dprx_work(struct work_struct *work) + struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work); + struct tb *tb = tunnel->tb; + ++ /* ++ * The DPRX read can be canceled while this work is waiting for ++ * tb->lock. Check the flag only once it is held: while the lock is ++ * held the tunnel cannot be torn down under us and the adapters are ++ * safe to access. ++ */ ++ mutex_lock(&tb->lock); + if (!tunnel->dprx_canceled) { +- mutex_lock(&tb->lock); + if (tb_dp_is_usb4(tunnel->src_port->sw) && + tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { + if (ktime_before(ktime_get(), tunnel->dprx_timeout)) { +@@ -1106,41 +1113,42 @@ static void tb_dp_dprx_work(struct work_struct *work) + } else { + tb_tunnel_set_active(tunnel, true); + } +- mutex_unlock(&tb->lock); + } ++ mutex_unlock(&tb->lock); + +- if (tunnel->callback) +- tunnel->callback(tunnel, tunnel->callback_data); ++ tunnel->callback(tunnel, tunnel->callback_data); + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); + } + + static int tb_dp_dprx_start(struct tb_tunnel *tunnel) + { + /* +- * Bump up the reference to keep the tunnel around. It will be +- * dropped in tb_dp_dprx_stop() once the tunnel is deactivated. ++ * Bump up the references to keep the tunnel and the domain around ++ * until the work has run or has been canceled. + */ + tb_tunnel_get(tunnel); ++ tb_domain_get(tunnel->tb); + + tunnel->dprx_started = true; ++ tunnel->dprx_canceled = false; ++ tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); ++ queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); + +- if (tunnel->callback) { +- tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); +- queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); +- return -EINPROGRESS; +- } +- +- return tb_dp_is_usb4(tunnel->src_port->sw) ? +- tb_dp_wait_dprx(tunnel, dprx_timeout) : 0; ++ return -EINPROGRESS; + } + + static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) + { ++ struct tb *tb = tunnel->tb; ++ + if (tunnel->dprx_started) { + tunnel->dprx_started = false; + tunnel->dprx_canceled = true; +- if (cancel_delayed_work(&tunnel->dprx_work)) ++ if (cancel_delayed_work(&tunnel->dprx_work)) { + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); ++ } + } + } + +@@ -1582,20 +1590,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) + * @tb: Pointer to the domain structure + * @in: DP in adapter + * @alloc_hopid: Allocate HopIDs from visited ports ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * If @in adapter is active, follows the tunnel to the DP out adapter + * and back. Returns the discovered tunnel or %NULL if there was no +- * tunnel. ++ * tunnel. See tb_tunnel_alloc_dp() for @callback. + * + * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. + */ + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid) ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data) + { + struct tb_tunnel *tunnel; + struct tb_port *port; + struct tb_path *path; + ++ if (WARN_ON(!callback)) ++ return NULL; ++ + if (!tb_dp_port_is_enabled(in)) + return NULL; + +@@ -1611,6 +1627,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; + tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; + tunnel->src_port = in; ++ tunnel->callback = callback; ++ tunnel->callback_data = callback_data; ++ INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work); + + path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1, + &tunnel->dst_port, "Video", alloc_hopid); +@@ -1656,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + + tb_dp_dump(tunnel); + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1677,16 +1697,16 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + * %0 if no available bandwidth. + * @max_down: Maximum available downstream bandwidth for the DP tunnel. + * %0 if no available bandwidth. +- * @callback: Optional callback that is called when the DP tunnel is +- * fully activated (or there is an error) +- * @callback_data: Optional data for @callback ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * Allocates a tunnel between @in and @out that is capable of tunneling +- * Display Port traffic. If @callback is not %NULL it will be called +- * after tb_tunnel_activate() once the tunnel has been fully activated. +- * It can call tb_tunnel_is_active() to check if activation was +- * successful (or if it returns %false there was some sort of issue). +- * The @callback is called without @tb->lock held. ++ * Display Port traffic. The @callback is called after tb_tunnel_activate() ++ * once the tunnel has been fully activated. It can call ++ * tb_tunnel_is_active() to check if activation was successful (or if it ++ * returns %false there was some sort of issue). The @callback is called ++ * without @tb->lock held. + * + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. + */ +@@ -1701,7 +1721,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_path *path; + bool pm_support; + +- if (WARN_ON(!in->cap_adap || !out->cap_adap)) ++ if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback)) + return NULL; + + tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); +@@ -2288,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, + tb_usb3_reclaim_available_bandwidth; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h +--- a/drivers/thunderbolt/tunnel.h ++++ b/drivers/thunderbolt/tunnel.h +@@ -66,8 +66,8 @@ enum tb_tunnel_state { + * @dprx_canceled: Was DPRX capabilities read poll canceled + * @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes + * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read +- * @callback: Optional callback called when DP tunnel is fully activated +- * @callback_data: Optional data for @callback ++ * @callback: Callback called when DP tunnel is fully activated ++ * @callback_data: Data for @callback + * @paths: All paths required by the tunnel + */ + struct tb_tunnel { +@@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, + bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, + int *reserved_down); + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid); ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data); + struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_port *out, int link_nr, + int max_up, int max_down, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1814,7 +1814,6 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; +- tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -22,6 +22,7 @@ struct device; + #include + #include + #include ++#include + #include + #include + #include +@@ -565,6 +566,7 @@ struct tb_nhi { + * @interval_nsec: Interval counter if interrupt throttling is to be + * used with this ring (in ns) + * @wait: Used to signal that the ring may be empty now ++ * @lock_key: Lock validator class key per-ring + */ + struct tb_ring { + spinlock_t lock; +@@ -590,6 +592,7 @@ struct tb_ring { + void *poll_data; + unsigned int interval_nsec; + wait_queue_head_t wait; ++ struct lock_class_key lock_key; + }; + + /* Leave ring interrupt enabled on suspend */ diff --git a/pkgbuilds/linux-omarchy-bore/0602-thunderbolt-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0602-thunderbolt-fixes.patch.sig new file mode 100644 index 0000000..bc9e5e4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0602-thunderbolt-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0603-usb-updates.patch b/pkgbuilds/linux-omarchy-bore/0603-usb-updates.patch new file mode 100644 index 0000000..171a4b4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0603-usb-updates.patch @@ -0,0 +1,5629 @@ +diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c +--- a/drivers/power/supply/power_supply_core.c ++++ b/drivers/power/supply/power_supply_core.c +@@ -477,6 +477,159 @@ struct power_supply *power_supply_get_by_name(const char *name) + } + EXPORT_SYMBOL_GPL(power_supply_get_by_name); + ++static bool power_supply_is_system_battery(struct power_supply *psy) ++{ ++ union power_supply_propval val; ++ ++ if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) ++ return false; ++ ++ if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE, ++ &val)) ++ if (val.intval == POWER_SUPPLY_SCOPE_DEVICE) ++ return false; ++ ++ return true; ++} ++ ++static int __power_supply_get_num_system_batteries(struct power_supply *epsy, ++ void *data) ++{ ++ int *count = data; ++ ++ if (power_supply_is_system_battery(epsy)) ++ (*count)++; ++ ++ return 0; ++} ++ ++static int power_supply_get_num_system_batteries(struct device *dev) ++{ ++ int ret, count = 0; ++ ++ ret = power_supply_for_each_psy(&count, ++ __power_supply_get_num_system_batteries); ++ ++ dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret); ++ ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++struct psy_get_supplies_data { ++ int cnt; ++ int size; ++ struct power_supply **psys; ++}; ++ ++static int ++__power_supply_populate_system_batteries_array(struct power_supply *epsy, ++ void *_data) ++{ ++ struct psy_get_supplies_data *data = _data; ++ ++ if (power_supply_is_system_battery(epsy)) { ++ if (data->size <= data->cnt) ++ return -EOVERFLOW; ++ ++ get_device(&epsy->dev); ++ data->psys[data->cnt] = epsy; ++ atomic_inc(&epsy->use_cnt); ++ data->cnt++; ++ } ++ ++ return 0; ++} ++ ++static int ++power_supply_populate_system_batteries_array(struct device *dev, int size, ++ struct power_supply **batteries) ++{ ++ int ret; ++ ++ struct psy_get_supplies_data data = { ++ .cnt = 0, ++ .size = size, ++ .psys = batteries, ++ }; ++ ++ ret = power_supply_for_each_psy(&data, ++ __power_supply_populate_system_batteries_array); ++ ++ dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n", ++ __func__, data.cnt, data.size, ret); ++ ++ if (ret < 0 || !data.cnt) { ++ power_supply_put_system_batteries(batteries, data.cnt); ++ return ret; ++ } ++ ++ return data.cnt; ++} ++ ++/** ++ * power_supply_get_system_batteries() - Fetches references to battery type ++ * power supplies in the system. ++ * @dev: Pointer to device requesting the power supply refs. ++ * @psys: Pointer to an array of power supply refs. ++ * ++ * Helper function to get handles to battery type power supplies in the system. ++ * If acquiring a ref to a power supply fails, then the search for battery type ++ * power supplies will abort and the acquired power supply references will be ++ * released. ++ * ++ * Return: Indicates the number of battery type power supplies returned on ++ * success or a negative error code on failure. ++ * ++ * Call power_supply_put_system_batteries() after use to cleanup resources. ++ */ ++int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ int ret; ++ ++ if (!psys) ++ return -EINVAL; ++ ++ ret = power_supply_get_num_system_batteries(dev); ++ if (ret <= 0) { ++ *psys = NULL; ++ return ret; ++ } ++ ++ *psys = kzalloc_objs(**psys, ret); ++ if (!*psys) ++ return -ENOMEM; ++ ++ ret = power_supply_populate_system_batteries_array(dev, ret, *psys); ++ if (ret <= 0) ++ *psys = NULL; ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(power_supply_get_system_batteries); ++ ++/** ++ * power_supply_put_system_batteries() - Cleanup resources allocated by ++ * power_supply_get_system_batteries() ++ * @psys: Array of power supply references to release and free. ++ * @count: Number of elements in the array. ++ */ ++void power_supply_put_system_batteries(struct power_supply **psys, int count) ++{ ++ int i; ++ ++ for (i = 0; i < count; i++) { ++ if (psys[i]) ++ power_supply_put(psys[i]); ++ } ++ ++ kfree(psys); ++} ++EXPORT_SYMBOL_GPL(power_supply_put_system_batteries); ++ + /** + * power_supply_put() - Drop reference obtained with power_supply_get_by_name + * @psy: Reference to put +diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c +--- a/drivers/usb/cdns3/cdns3-plat.c ++++ b/drivers/usb/cdns3/cdns3-plat.c +@@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev) + cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); + if (IS_ERR(cdns->usb2_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy), +- "Failed to get cdn3,usb2-phy\n"); ++ "Failed to get cdns3,usb2-phy\n"); + + cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); + if (IS_ERR(cdns->usb3_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy), +- "Failed to get cdn3,usb3-phy\n"); ++ "Failed to get cdns3,usb3-phy\n"); + + ret = phy_init(cdns->usb2_phy); + if (ret) +diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c +--- a/drivers/usb/core/config.c ++++ b/drivers/usb/core/config.c +@@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, + usb_endpoint_xfer_int(&ep->desc)) && + desc->bmAttributes != 0) { + dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", +- usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", ++ usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt", + desc->bmAttributes, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 0; +diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c +--- a/drivers/usb/core/devices.c ++++ b/drivers/usb/core/devices.c +@@ -37,6 +37,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + return 0; + /* allocate 2^1 pages = 8K (on i386); + * should be more than enough for one device */ +- pages_start = (char *)__get_free_pages(GFP_NOIO, 1); ++ pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO); + if (!pages_start) + return -ENOMEM; + +@@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + if (length > *nbytes) + length = *nbytes; + if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + return -EFAULT; + } + *nbytes -= length; +@@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + } else + *skip_bytes -= length; + +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + + /* Now look at all of this device's children. */ + usb_hub_for_each_child(usbdev, chix, childdev) { +diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c +--- a/drivers/usb/core/devio.c ++++ b/drivers/usb/core/devio.c +@@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + if (!connected(ps)) + return -ENODEV; + ++ if (ps->dev->state != USB_STATE_CONFIGURED) ++ return -EHOSTUNREACH; ++ ++ intf = usb_ifnum_to_if(ps->dev, ctl->ifno); ++ if (!intf) ++ return -EINVAL; ++ + /* alloc buffer */ + size = _IOC_SIZE(ctl->ioctl_code); + if (size > 0) { +@@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + } + } + +- if (ps->dev->state != USB_STATE_CONFIGURED) +- retval = -EHOSTUNREACH; +- else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno))) +- retval = -EINVAL; +- else switch (ctl->ioctl_code) { ++ switch (ctl->ioctl_code) { + + /* disconnect kernel driver from interface */ + case USBDEVFS_DISCONNECT: +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, + mutex_lock(&hub->status_mutex); + ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len); + if (ret < len) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_port_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->port.wPortStatus); + *change = le16_to_cpu(hub->status->port.wPortChange); +@@ -995,11 +995,11 @@ static int hub_hub_status(struct usb_hub *hub, + mutex_lock(&hub->status_mutex); + ret = get_hub_status(hub->hdev, &hub->status->hub); + if (ret < (int)sizeof(hub->status->hub)) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_hub_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->hub.wHubStatus); + *change = le16_to_cpu(hub->status->hub.wHubChange); +diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c +--- a/drivers/usb/dwc2/core_intr.c ++++ b/drivers/usb/dwc2/core_intr.c +@@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) + + /** + * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state +- * + * @hsotg: Programming view of DWC_otg controller +- * ++ * @remotewakeup: Whether this is a remote wakeup + */ + void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup) + { +diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c +--- a/drivers/usb/dwc3/core.c ++++ b/drivers/usb/dwc3/core.c +@@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work) + ret, dwc->current_limit); + } + +-static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) ++static int dwc3_psy_notifier(struct notifier_block *nb, ++ unsigned long event, void *data) + { +- struct power_supply *usb_psy; +- const char *usb_psy_name; ++ struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); ++ struct power_supply *psy = data; ++ unsigned long flags; ++ ++ if (dwc->usb_psy) ++ return NOTIFY_DONE; ++ ++ if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) ++ return NOTIFY_DONE; ++ ++ /* Explicitly get the reference for this psy */ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return NOTIFY_DONE; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * The USB power_supply may already be set. This can happen if notifier ++ * callbacks for the USB power_supply race, or if a previous notifier ++ * callback has already successfully fetched and associated the instance. ++ * In such cases, release the newly acquired reference and ignore ++ * subsequent notifications until the notifier is unregistered. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return NOTIFY_DONE; ++ } ++ ++ dwc->usb_psy = psy; ++ if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) ++ schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ return NOTIFY_OK; ++} ++ ++static void dwc3_get_usb_power_supply(struct dwc3 *dwc) ++{ ++ struct power_supply *psy; ++ unsigned long flags; + int ret; + +- ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name); ++ ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); + if (ret < 0) +- return NULL; +- +- usb_psy = power_supply_get_by_name(usb_psy_name); +- if (!usb_psy) +- return ERR_PTR(-EPROBE_DEFER); ++ return; + + INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); +- return usb_psy; ++ ++ dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; ++ dwc->psy_nb.notifier_call = dwc3_psy_notifier; ++ ret = power_supply_reg_notifier(&dwc->psy_nb); ++ if (ret) { ++ dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); ++ dwc->psy_nb.notifier_call = NULL; ++ return; ++ } ++ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return; ++ ++ /* Unregister the notifier now that we have the power supply */ ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ dwc->psy_nb.notifier_call = NULL; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * It is possible that the notifier callback ran before we reached here ++ * and successfully fetched the power supply. In that case we need to ++ * release the above reference. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return; ++ } ++ ++ dwc->usb_psy = psy; ++ spin_unlock_irqrestore(&dwc->lock, flags); + } + + int dwc3_core_probe(const struct dwc3_probe_data *data) +@@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + + dwc3_get_software_properties(dwc, &data->properties); + +- dwc->usb_psy = dwc3_get_usb_power_supply(dwc); +- if (IS_ERR(dwc->usb_psy)) +- return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); ++ spin_lock_init(&dwc->lock); ++ ++ dwc3_get_usb_power_supply(dwc); + + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); +@@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + dwc->num_usb3_ports = 1; + } + +- spin_lock_init(&dwc->lock); + mutex_init(&dwc->mutex); + + pm_runtime_get_noresume(dev); +@@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + err_assert_reset: + reset_control_assert(dwc->reset); + err_put_psy: ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) + power_supply_put(dwc->usb_psy); + +@@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc) + + dwc3_free_event_buffers(dwc); + ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ + if (dwc->usb_psy) { + cancel_work_sync(&dwc->vbus_draw_work); + power_supply_put(dwc->usb_psy); +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -722,7 +722,6 @@ struct dwc3_event_buffer { + * @cancelled_list: list of cancelled requests for this endpoint + * @pending_list: list of pending requests for this endpoint + * @started_list: list of started requests on this endpoint +- * @regs: pointer to first endpoint register + * @trb_pool: array of transaction buffers + * @trb_pool_dma: dma address of @trb_pool + * @trb_enqueue: enqueue 'pointer' into TRB array +@@ -1059,6 +1058,8 @@ struct dwc3_glue_ops { + * @role_switch_default_mode: default operation mode of controller while + * usb role is USB_ROLE_NONE. + * @usb_psy: pointer to power supply interface. ++ * @usb_psy_name: name of the USB power supply ++ * @psy_nb: power supply notifier block + * @vbus_draw_work: Work to set the vbus drawing limit + * @current_limit: How much current to draw from vbus, in milliAmperes. + * @usb2_phy: pointer to USB2 PHY +@@ -1251,9 +1252,13 @@ struct dwc3 { + enum usb_dr_mode role_switch_default_mode; + + struct power_supply *usb_psy; ++ const char *usb_psy_name; ++ struct notifier_block psy_nb; + struct work_struct vbus_draw_work; + unsigned int current_limit; + ++#define DWC3_CURRENT_UNSPECIFIED UINT_MAX ++ + u32 fladj; + u32 ref_clk_per; + u32 irq_gadget; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62) + + dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); + +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + + /* Set mode valid bit to indicate role is valid */ + reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); +@@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev) + { + struct dwc3_am62 *am62 = dev_get_drvdata(dev); + u32 reg; ++ int ret; + + reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); + if (reg != USBSS_DEBUG_CFG_DISABLED) { + /* lost power/context */ +- dwc3_ti_init(am62); ++ ret = dwc3_ti_init(am62); ++ if (ret) ++ return ret; + } else { + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + } + + if (device_may_wakeup(dev)) { +diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c +--- a/drivers/usb/dwc3/dwc3-qcom.c ++++ b/drivers/usb/dwc3/dwc3-qcom.c +@@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) + pm_runtime_mark_last_busy(qcom->dev); + } + +-struct dwc3_glue_ops dwc3_qcom_glue_ops = { ++static struct dwc3_glue_ops dwc3_qcom_glue_ops = { + .pre_set_role = dwc3_qcom_set_role_notifier, + .pre_run_stop = dwc3_qcom_run_stop_notifier, + }; +diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c +--- a/drivers/usb/dwc3/dwc3-xilinx.c ++++ b/drivers/usb/dwc3/dwc3-xilinx.c +@@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) + + dwc3_xlnx_mask_phy_rst(priv_data, false); + +- /* Assert and De-assert reset */ +- ret = reset_control_assert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to assert Reset\n"); +- return ret; +- } +- +- ret = reset_control_deassert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to De-assert Reset\n"); +- return ret; +- } ++ /* assert and deassert reset */ ++ ret = reset_control_reset(crst); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to assert and deassert reset\n"); + + dwc3_xlnx_mask_phy_rst(priv_data, true); + dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG); +@@ -194,7 +186,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) + } + + if (priv_data->usb3_phy) { +- /* Set PIPE Power Present signal in FPD Power Present Register*/ ++ /* Set PIPE Power Present signal in FPD Power Present Register */ + writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT); + /* Set the PIPE Clock Select bit in FPD PIPE Clock register */ + writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -3129,15 +3129,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, + static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) + { + struct dwc3 *dwc = gadget_to_dwc(g); ++ unsigned long flags; + + if (dwc->usb2_phy) + return usb_phy_set_power(dwc->usb2_phy, mA); + +- if (!dwc->usb_psy) +- return -EOPNOTSUPP; ++ spin_lock_irqsave(&dwc->lock, flags); ++ if (!dwc->usb_psy) { ++ if (!dwc->psy_nb.notifier_call) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -EOPNOTSUPP; ++ } ++ dwc->current_limit = mA; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); ++ return 0; ++ } + + dwc->current_limit = mA; + schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; + } +diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c +--- a/drivers/usb/dwc3/host.c ++++ b/drivers/usb/dwc3/host.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + #include "../host/xhci-port.h" + #include "../host/xhci-ext-caps.h" +@@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) + return; + } + +- op_regs_base = HC_LENGTH(readl(xhci_regs)); ++ op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs)); + reg = readl(xhci_regs + XHCI_HCSPARAMS1); +- port_num = HCS_MAX_PORTS(reg); ++ port_num = FIELD_GET(HCS_MAX_PORTS, reg); + + for (i = 1; i <= port_num; i++) { + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); +diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c +--- a/drivers/usb/gadget/configfs.c ++++ b/drivers/usb/gadget/configfs.c +@@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy) + if (copy) { + str = copy; + } else { +- str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); ++ str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); + if (!str) + return -ENOMEM; + } +- strcpy(str, s); ++ memcpy(str, s, ret + 1); + if (str[ret - 1] == '\n') + str[ret - 1] = '\0'; + *s_copy = str; +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -159,7 +159,9 @@ struct ffs_epfile { + struct mutex mutex; + + struct ffs_data *ffs; +- struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ + + /* + * Buffer for holding data from partial reads which may happen since +@@ -219,12 +221,13 @@ struct ffs_epfile { + struct ffs_buffer *read_buffer; + #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) + +- char name[5]; ++ char name[8]; + + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + +- unsigned char _pad; ++ u8 zlp_enabled; /* P: ffs->eps_lock */ ++ bool is_rw_proxy; + + /* Protects dmabufs */ + struct mutex dmabufs_mutex; +@@ -991,9 +994,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, + return ret; + } + +-static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) ++static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) + { +- struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; + +@@ -1020,17 +1022,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + char *data = NULL; + ssize_t ret, data_len = -EINVAL; + int halt; ++ bool is_rw_proxy = epfile->is_rw_proxy; + + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + +- ep = ffs_epfile_wait_ep(file); ++ /* Proxy to base endpoint if rw_proxy */ ++ if (is_rw_proxy) ++ epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; ++ ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + /* Do we halt? */ +- halt = (!io_data->read == !epfile->in); ++ halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); + if (halt && epfile->isoc) + return -EINVAL; + +@@ -1127,6 +1134,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1178,6 +1187,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1666,7 +1677,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + priv = attach->importer_priv; + +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + goto err_attachment_put; +@@ -1732,6 +1743,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + /* Now that the dma_fence is in place, queue the transfer. */ + ++ usb_req->zero = epfile->zlp_enabled; + usb_req->length = req->length; + usb_req->buf = NULL; + usb_req->sg = priv->sgt->sgl; +@@ -1779,10 +1791,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; ++ __u32 enable_zlp = 0; + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + ++ if (epfile->is_rw_proxy) ++ return -ENOTTY; ++ + switch (code) { + case FUNCTIONFS_DMABUF_ATTACH: + { +@@ -1811,12 +1827,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + + return ffs_dmabuf_transfer(file, &req); + } ++ /* ++ * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace ++ * to configure ZLP behavior immediately without blocking indefinitely ++ * while waiting for the USB host to connect and enable the endpoint. ++ */ ++ case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: ++ if (!epfile->in) ++ return -EINVAL; ++ ++ if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) ++ return -EFAULT; ++ ++ spin_lock_irq(&epfile->ffs->eps_lock); ++ epfile->zlp_enabled = !!enable_zlp; ++ spin_unlock_irq(&epfile->ffs->eps_lock); ++ ++ return 0; + default: + break; + } + + /* Wait for endpoint to be enabled */ +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + +@@ -2214,7 +2247,7 @@ static void ffs_data_closed(struct ffs_data *ffs) + + if (epfiles) + ffs_epfiles_destroy(ffs->sb, epfiles, +- ffs->eps_count); ++ ffs->epfiles_count); + + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); +@@ -2273,7 +2306,7 @@ static void ffs_data_clear(struct ffs_data *ffs) + * copy of epfile will save us from use-after-free. + */ + if (epfiles) { +- ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); ++ ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); + ffs->epfiles = NULL; + } + +@@ -2371,11 +2404,16 @@ static void functionfs_unbind(struct ffs_data *ffs) + static int ffs_epfiles_create(struct ffs_data *ffs) + { + struct ffs_epfile *epfile, *epfiles; +- unsigned i, count; ++ unsigned int i, count, epfiles_count; + int err; + + count = ffs->eps_count; +- epfiles = kzalloc_objs(*epfiles, count); ++ epfiles_count = count; ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) ++ epfiles_count += count / 2; ++ ffs->epfiles_count = epfiles_count; ++ ++ epfiles = kzalloc_objs(*epfiles, epfiles_count); + if (!epfiles) + return -ENOMEM; + +@@ -2398,6 +2436,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs) + } + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ struct ffs_epfile *comp = epfiles + count; ++ ++ for (i = 0; i < count; i += 2, ++comp) { ++ struct ffs_epfile *ep1 = &epfiles[i]; ++ struct ffs_epfile *ep2 = &epfiles[i + 1]; ++ bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; ++ ++ comp->ffs = ffs; ++ comp->is_rw_proxy = true; ++ comp->epfile_in = ep1_in ? ep1 : ep2; ++ comp->epfile_out = ep1_in ? ep2 : ep1; ++ mutex_init(&comp->mutex); ++ mutex_init(&comp->dmabufs_mutex); ++ INIT_LIST_HEAD(&comp->dmabufs); ++ snprintf(comp->name, sizeof(comp->name), "%s_rw", ++ epfiles[i].name); ++ err = ffs_sb_create_file(ffs->sb, comp->name, ++ comp, &ffs_epfile_operations); ++ if (err) { ++ ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); ++ return err; ++ } ++ } ++ } ++ + ffs->epfiles = epfiles; + return 0; + } +@@ -2975,7 +3039,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + FUNCTIONFS_VIRTUAL_ADDR | + FUNCTIONFS_EVENTFD | + FUNCTIONFS_ALL_CTRL_RECIP | +- FUNCTIONFS_CONFIG0_SETUP)) { ++ FUNCTIONFS_CONFIG0_SETUP | ++ FUNCTIONFS_RW_PROXY_EPS)) { + ret = -ENOSYS; + goto error; + } +@@ -3063,6 +3128,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + goto error; + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ if (ffs->eps_count % 2) { ++ ret = -EINVAL; ++ goto error; ++ } ++ ++ for (i = 1; i < ffs->eps_count; i += 2) { ++ if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == ++ (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { ++ ret = -EINVAL; ++ goto error; ++ } ++ } ++ } ++ + ffs->raw_descs_data = _data; + ffs->raw_descs = raw_descs; + ffs->raw_descs_length = data - raw_descs; +@@ -3358,7 +3438,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, + struct usb_request *req; + struct usb_ep *ep; + u8 bEndpointAddress; +- u16 wMaxPacketSize; ++ __le16 wMaxPacketSize; + + /* + * We back up bEndpointAddress because autoconfig overwrites +diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c +--- a/drivers/usb/gadget/function/f_mass_storage.c ++++ b/drivers/usb/gadget/function/f_mass_storage.c +@@ -203,7 +203,6 @@ + /*------------------------------------------------------------------------*/ + + #define FSG_DRIVER_DESC "Mass Storage Function" +-#define FSG_DRIVER_VERSION "2009/09/11" + + static const char fsg_string_interface[] = "Mass Storage"; + +@@ -3516,8 +3515,6 @@ static struct usb_function_instance *fsg_alloc_inst(void) + if (rc) + goto release_common; + +- pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); +- + memset(&config, 0, sizeof(config)); + config.removable = true; + rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0", +diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c +--- a/drivers/usb/gadget/function/u_audio.c ++++ b/drivers/usb/gadget/function/u_audio.c +@@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev) + ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } + +- prm->fb_ep_enabled = true; + ret = usb_ep_enable(ep_fback); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } ++ prm->fb_ep_enabled = true; + req_len = ep_fback->maxpacket; + + req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); +@@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + + return 0; ++ ++err_out_ep: ++ set_active(prm, false); ++ free_ep(prm, ep); ++ ++ return ret; + } + EXPORT_SYMBOL_GPL(u_audio_start_capture); + +diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h +--- a/drivers/usb/gadget/function/u_fs.h ++++ b/drivers/usb/gadget/function/u_fs.h +@@ -252,8 +252,14 @@ struct ffs_data { + + unsigned short strings_count; + unsigned short interfaces_count; ++ ++ /* ++ * eps_count tracks the number of underlying hardware endpoints. ++ * epfiles_count tracks the total number of VFS endpoint files. ++ * When companion endpoints are active, epfiles_count > eps_count. ++ */ + unsigned short eps_count; +- unsigned short _pad1; ++ unsigned short epfiles_count; + + /* filled by __ffs_data_got_strings() */ + /* ids in stringtabs are set in functionfs_bind() */ +diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c +--- a/drivers/usb/gadget/legacy/inode.c ++++ b/drivers/usb/gadget/legacy/inode.c +@@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) + struct kiocb *iocb = priv->iocb; + size_t ret; + +- kthread_use_mm(mm); +- ret = copy_to_iter(priv->buf, priv->actual, &priv->to); +- kthread_unuse_mm(mm); +- if (!ret) ++ if (mmget_not_zero(mm)) { ++ kthread_use_mm(mm); ++ ret = copy_to_iter(priv->buf, priv->actual, &priv->to); ++ kthread_unuse_mm(mm); ++ mmput(mm); ++ if (!ret) ++ ret = -EFAULT; ++ } else { + ret = -EFAULT; ++ } ++ mmdrop(mm); + + /* completing the iocb can drop the ctx and mm, don't touch mm after */ + iocb->ki_complete(iocb, ret); +@@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) + * complete the aio request immediately. + */ + if (priv->to_free == NULL || unlikely(req->actual == 0)) { ++ mmdrop(priv->mm); + kfree(req->buf); + kfree(priv->to_free); + kfree(priv); +@@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + priv->epdata = epdata; + priv->actual = 0; + priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ ++ mmgrab(priv->mm); + + /* each kiocb is coupled to one usb_request, but we can't + * allocate or submit those if the host disconnected. +@@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + + fail: + spin_unlock_irq(&epdata->dev->lock); ++ mmdrop(priv->mm); + kfree(priv->to_free); + kfree(priv); + put_ep(epdata); +diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c +--- a/drivers/usb/gadget/legacy/nokia.c ++++ b/drivers/usb/gadget/legacy/nokia.c +@@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; + */ + #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS + +-#endif /* CONFIG_USB_DEBUG */ ++#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + + FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig +--- a/drivers/usb/gadget/udc/Kconfig ++++ b/drivers/usb/gadget/udc/Kconfig +@@ -90,7 +90,7 @@ config USB_BCM63XX_UDC + + config USB_FSL_USB2 + tristate "Freescale Highspeed USB DR Peripheral Controller" +- depends on FSL_SOC ++ depends on FSL_SOC || COMPILE_TEST + help + Some of Freescale PowerPC and i.MX processors have a High Speed + Dual-Role(DR) USB controller, which supports device mode. +diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c +--- a/drivers/usb/gadget/udc/fsl_qe_udc.c ++++ b/drivers/usb/gadget/udc/fsl_qe_udc.c +@@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) + { + u16 usb_status = 0; ++ struct usb_request *usb_req; + struct qe_req *req; + struct qe_ep *ep; + int status = 0; +@@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + } + } + +- req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL), +- struct qe_req, req); ++ usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL); ++ if (!usb_req) ++ goto stall; ++ ++ req = container_of(usb_req, struct qe_req, req); + req->req.length = 2; + req->req.buf = udc->statusbuf; + *(u16 *)req->req.buf = cpu_to_le16(usb_status); +diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c +--- a/drivers/usb/gadget/udc/pch_udc.c ++++ b/drivers/usb/gadget/udc/pch_udc.c +@@ -320,7 +320,6 @@ struct pch_vbus_gpio_data { + * @lock: protects all state + * @stall: stall requested + * @prot_stall: protcol stall requested +- * @registered: driver registered with system + * @suspended: driver in suspended state + * @connected: gadget driver associated + * @vbus_session: required vbus_session state +diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c +--- a/drivers/usb/host/fsl-mph-dr-of.c ++++ b/drivers/usb/host/fsl-mph-dr-of.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + struct fsl_usb2_dev_data { + char *dr_mode; /* controller mode */ +@@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + const struct of_device_id *match; + const unsigned char *prop; + static unsigned int idx; +- int i; ++ int i, err; + + if (!of_device_is_available(np)) + return -ENODEV; +@@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + } + } + ++ err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus"); ++ if (err) ++ return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n"); ++ + for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { + if (!dev_data->drivers[i]) + continue; +diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c +--- a/drivers/usb/host/ohci-dbg.c ++++ b/drivers/usb/host/ohci-dbg.c +@@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf) + int ret; + + if (!buf->page) +- buf->page = (char *)get_zeroed_page(GFP_KERNEL); ++ buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL); + + if (!buf->page) { + ret = -ENOMEM; +@@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file) + { + struct debug_buffer *buf = file->private_data; + +- if (buf) { +- if (buf->page) +- free_page((unsigned long)buf->page); +- kfree(buf); +- } ++ kfree(buf->page); ++ kfree(buf); + + return 0; + } +diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h +--- a/drivers/usb/host/xhci-caps.h ++++ b/drivers/usb/host/xhci-caps.h +@@ -5,22 +5,23 @@ + */ + + #include ++#include + + /* hc_capbase - bitmasks */ + /* bits 7:0 - Capability Registers Length */ +-#define HC_LENGTH(p) ((p) & 0xff) ++#define HC_LENGTH GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - Host Controller Interface Version Number */ +-#define HC_VERSION(p) (((p) >> 16) & 0xffff) ++#define HC_VERSION GENMASK(31, 16) + + /* HCSPARAMS1 - hcs_params1 - bitmasks */ + /* bits 7:0 - Number of Device Slots */ +-#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +-#define HCS_SLOTS_MASK 0xff +-/* bits 18:8 - Number of Interrupters, max values is 1024 */ +-#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +-/* bits 31:24, Max Ports - max value is 255 */ +-#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff) ++#define HCS_SLOTS_MASK GENMASK(7, 0) ++/* bits 18:8 - Number of Interrupters, max values is 1024 */ ++#define HCS_MAX_INTRS GENMASK(18, 8) ++/* bits 23:19 - Rsvd */ ++/* bits 31:24 - Max Ports, max values is 255 */ ++#define HCS_MAX_PORTS GENMASK(31, 24) + + /* HCSPARAMS2 - hcs_params2 - bitmasks */ + /* +@@ -33,24 +34,25 @@ + * Note: 1 Frame = 8 Microframes + * xHCI specification section 5.3.4. + */ +-#define HCS_IST_VALUE(p) ((p) & 0x7) ++#define HCS_IST_VALUE GENMASK(2, 0) + #define HCS_IST_UNIT BIT(3) + /* bits 7:4 - Event Ring Segment Table Max, 2^(n) */ +-#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) ++#define HCS_ERST_MAX GENMASK(7, 4) + /* bits 20:8 - Rsvd */ + /* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */ +-#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f) ++#define HCS_MAX_SP_HI GENMASK(25, 21) + /* bit 26 - Scratchpad restore, for save/restore HW state */ + /* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */ +-#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f) +-#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p)) ++#define HCS_MAX_SP_LO GENMASK(31, 27) ++#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ ++ FIELD_GET(HCS_MAX_SP_LO, (p))) + + /* HCSPARAMS3 - hcs_params3 - bitmasks */ + /* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */ +-#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) ++#define HCS_U1_LATENCY GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */ +-#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) ++#define HCS_U2_LATENCY GENMASK(31, 16) + + /* HCCPARAMS1 - hcc_params - bitmasks */ + /* bit 0 - 64-bit Addressing Capability */ +@@ -77,19 +79,20 @@ + /* bit 11 - Contiguous Frame ID Capability */ + #define HCC_CFC BIT(11) + /* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */ +-#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) ++#define HCC_MAX_PSA GENMASK(15, 12) ++#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1)) + /* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */ +-#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff) ++#define HCC_EXT_CAPS GENMASK(31, 16) + + /* DBOFF - db_off - bitmasks */ + /* bits 1:0 - Rsvd */ + /* bits 31:2 - Doorbell Array Offset */ +-#define DBOFF_MASK (0xfffffffc) ++#define DBOFF_MASK GENMASK(31, 2) + + /* RTSOFF - run_regs_off - bitmasks */ + /* bits 4:0 - Rsvd */ + /* bits 31:5 - Runtime Register Space Offse */ +-#define RTSOFF_MASK (~0x1f) ++#define RTSOFF_MASK GENMASK(31, 5) + + /* HCCPARAMS2 - hcc_params2 - bitmasks */ + /* bit 0 - U3 Entry Capability */ +diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c +--- a/drivers/usb/host/xhci-dbgtty.c ++++ b/drivers/usb/host/xhci-dbgtty.c +@@ -665,11 +665,11 @@ int dbc_tty_init(void) + + void dbc_tty_exit(void) + { +- if (dbc_tty_driver) { +- tty_unregister_driver(dbc_tty_driver); +- tty_driver_kref_put(dbc_tty_driver); +- dbc_tty_driver = NULL; +- } ++ if (IS_ERR_OR_NULL(dbc_tty_driver)) ++ return; + ++ tty_unregister_driver(dbc_tty_driver); ++ tty_driver_kref_put(dbc_tty_driver); + idr_destroy(&dbc_tty_minors); ++ dbc_tty_driver = NULL; + } +diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c +--- a/drivers/usb/host/xhci-debugfs.c ++++ b/drivers/usb/host/xhci-debugfs.c +@@ -9,6 +9,7 @@ + + #include + #include ++#include + + #include "xhci.h" + #include "xhci-debugfs.h" +@@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) + xhci->debugfs_root, "reg-cap"); + + xhci_debugfs_regset(xhci, +- HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)), ++ FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), + xhci_op_regs, ARRAY_SIZE(xhci_op_regs), + xhci->debugfs_root, "reg-op"); + +diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c +--- a/drivers/usb/host/xhci-histb.c ++++ b/drivers/usb/host/xhci-histb.c +@@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev) + if (ret) + goto put_usb3_hcd; + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c +--- a/drivers/usb/host/xhci-hub.c ++++ b/drivers/usb/host/xhci-hub.c +@@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, + + if ((xhci->quirks & XHCI_LPM_SUPPORT)) { + reg = readl(&xhci->cap_regs->hcs_params3); +- ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg); +- ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg)); ++ ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg); ++ ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg)); + } + + if (wLength < le16_to_cpu(bos->wTotalLength)) +@@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + } + /* In spec software should not attempt to suspend + * a port unless the port reports that it is in the +- * enabled (PED = ‘1’,PLS < ‘3’) state. ++ * enabled (PED = '1',PLS < '3') state. + */ + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || +diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1001,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, + for (i = 0; i < 31; i++) { + dev->eps[i].ep_index = i; + dev->eps[i].vdev = dev; +- dev->eps[i].xhci = xhci; + INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); + INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); + } +@@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, + return -ENOMEM; + + virt_dev->eps[ep_index].skip = false; ++ virt_dev->eps[ep_index].next_uframe = -1; + ep_ring = virt_dev->eps[ep_index].new_ring; + xhci_ring_init(xhci, ep_ring); + +@@ -2083,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, + addr, port_offset, port_count, major_revision); + /* Port count includes the current port offset */ + if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) +- /* WTF? "Valid values are ‘1’ to MaxPorts" */ ++ /* WTF? "Valid values are '1' to MaxPorts" */ + return; + + port_cap = &xhci->port_caps[xhci->num_port_caps++]; +@@ -2300,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) + if (!segs) + segs = ERST_DEFAULT_SEGS; + +- max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2)); ++ max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c +--- a/drivers/usb/host/xhci-mtk.c ++++ b/drivers/usb/host/xhci-mtk.c +@@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) + * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, + * and it's 3 when support it. + */ +- if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4) ++ if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4) + xhci->quirks |= XHCI_BROKEN_STREAMS; + } + +@@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c +--- a/drivers/usb/host/xhci-pci.c ++++ b/drivers/usb/host/xhci-pci.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && ++ GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; + + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ +diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c +--- a/drivers/usb/host/xhci-plat.c ++++ b/drivers/usb/host/xhci-plat.c +@@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -57,6 +57,8 @@ + #include + #include + #include ++#include ++ + #include "xhci.h" + #include "xhci-trace.h" + +@@ -561,8 +563,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + * pointer command pending because the device can choose to start any + * stream once the endpoint is on the HW schedule. + */ +- if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) || +- (ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT)) ++ if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED | ++ EP_CLEARING_TT | EP_DROP_PENDING)) + return; + + trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); +@@ -573,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + } + + /* Ring the doorbell for any rings with pending URBs */ +-static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) ++void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id, ++ unsigned int ep_index) + { + unsigned int stream_id; + struct xhci_virt_ep *ep; +@@ -598,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, + } + } + +-void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) +-{ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); +-} +- + static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +@@ -686,112 +680,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, + return le64_to_cpu(ep_ctx->deq); + } + +-static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, +- unsigned int slot_id, unsigned int ep_index, +- unsigned int stream_id, struct xhci_td *td) ++/* ++ * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or ++ * to enqueue if no TDs are queued (empty ring) ++ * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list ++ * before calling this function ++ */ ++static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci, ++ struct xhci_virt_ep *ep, ++ unsigned int stream_id) + { +- struct xhci_virt_device *dev = xhci->devs[slot_id]; +- struct xhci_virt_ep *ep = &dev->eps[ep_index]; +- struct xhci_ring *ep_ring; + struct xhci_command *cmd; +- struct xhci_segment *new_seg; +- union xhci_trb *new_deq; +- int new_cycle; ++ struct xhci_ring *ring; ++ struct xhci_td *td; + dma_addr_t addr; +- u64 hw_dequeue; +- bool hw_dequeue_found = false; +- bool td_last_trb_found = false; ++ int new_cycle; + u32 trb_sct = 0; +- int ret; ++ int ret = 0; + +- ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, +- ep_index, stream_id); +- if (!ep_ring) { ++ ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); ++ if (!ring) { + xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", + stream_id); + return -ENODEV; + } + +- hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; +- new_seg = ep_ring->deq_seg; +- new_deq = ep_ring->dequeue; +- new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; +- +- /* +- * Walk the ring until both the next TRB and hw_dequeue are found (don't +- * move hw_dequeue back if it went forward due to a HW bug). Cycle state +- * is loaded from a known good TRB, track later toggles to maintain it. +- */ +- do { +- if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) +- == (dma_addr_t)hw_dequeue) { +- hw_dequeue_found = true; +- if (td_last_trb_found) +- break; +- } +- if (new_deq == td->end_trb) +- td_last_trb_found = true; +- +- if (td_last_trb_found && trb_is_link(new_deq) && +- link_trb_toggles_cycle(new_deq)) +- new_cycle ^= 0x1; +- +- next_trb(&new_seg, &new_deq); +- +- /* Search wrapped around, bail out */ +- if (new_deq == ep->ring->dequeue) { +- xhci_err(xhci, "Error: Failed finding new dequeue state\n"); +- return -EINVAL; +- } +- +- } while (!hw_dequeue_found || !td_last_trb_found); +- +- /* Don't update the ring cycle state for the producer (us). */ +- addr = xhci_trb_virt_to_dma(new_seg, new_deq); +- if (addr == 0) { +- xhci_warn(xhci, "Can't find dma of new dequeue ptr\n"); +- xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq); +- return -EINVAL; +- } +- + if ((ep->ep_state & SET_DEQ_PENDING)) { +- xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n", +- &addr); ++ xhci_warn(xhci, "Set TR Deq already pending\n"); + return -EBUSY; + } + + /* This function gets called from contexts where it cannot sleep */ + cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!cmd) { +- xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr); ++ xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n"); + return -ENOMEM; + } + ++ /* ++ * Move dequeue to the beginning of next td, or to enqueue if ring is ++ * empty. Avoid moving dequeue to a link trb (empty ring) as it causes ++ * issues on some hosts. In that case advance the enqueue to next segment ++ * before moving dequeue to it ++ */ ++ ++ if (list_empty(&ring->td_list)) { ++ if (trb_is_link(ring->enqueue)) ++ inc_enq_past_link(xhci, ring, 0); ++ ep->queued_deq_seg = ring->enq_seg; ++ ep->queued_deq_ptr = ring->enqueue; ++ new_cycle = ring->cycle_state; ++ } else { ++ td = list_first_entry(&ring->td_list, struct xhci_td, td_list); ++ ep->queued_deq_seg = td->start_seg; ++ ep->queued_deq_ptr = td->start_trb; ++ new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE; ++ } ++ ++ addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr); ++ if (addr == 0) { ++ xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n", ++ ep->queued_deq_seg, ep->queued_deq_ptr); ++ ret = -EINVAL; ++ goto err_out; ++ } ++ + if (stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + ret = queue_command(xhci, cmd, + lower_32_bits(addr) | trb_sct | new_cycle, + upper_32_bits(addr), +- STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) | +- EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false); +- if (ret < 0) { +- xhci_free_command(xhci, cmd); +- return ret; +- } +- ep->queued_deq_seg = new_seg; +- ep->queued_deq_ptr = new_deq; ++ STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) | ++ EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false); ++ if (ret < 0) ++ goto err_out; + + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); + +- /* Stop the TD queueing code from ringing the doorbell until +- * this command completes. The HC won't set the dequeue pointer +- * if the ring is running, and ringing the doorbell starts the +- * ring running. ++ /* ++ * Stop the TD queueing code from ringing the doorbell until this ++ * command completes. The HC won't set the dequeue pointer if the ring ++ * is running, and ringing the doorbell starts the ring. + */ + ep->ep_state |= SET_DEQ_PENDING; + xhci_ring_cmd_db(xhci); ++ + return 0; ++ ++err_out: ++ xhci_free_command(xhci, cmd); ++ ep->queued_deq_seg = NULL; ++ ep->queued_deq_ptr = NULL; ++ ++ return ret; + } + + /* flip_cycle means flip the cycle bit of all but the first and last TRB. +@@ -950,7 +932,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh + } + + /* Complete the cancelled URBs we unlinked from td_list. */ +-static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) ++static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_ring *ring; + struct xhci_td *td, *tmp_td; +@@ -958,17 +940,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + +- ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ring = xhci_urb_to_transfer_ring(xhci, td->urb); + + if (td->cancel_status == TD_CLEARED) { +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ring, td->status); ++ xhci_td_cleanup(xhci, td, ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } +- if (ep->xhci->xhc_state & XHCI_STATE_DYING) ++ if (xhci->xhc_state & XHCI_STATE_DYING) + return; + } + } +@@ -1002,15 +984,19 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + struct xhci_td *td, + enum xhci_ep_reset_type reset_type) + { ++ struct xhci_port *rhub_port = ep->vdev->rhub_port; + unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +- * Avoid resetting endpoint if link is inactive. Can cause host hang. +- * Device will be reset soon to recover the link so don't do anything ++ * Avoid resetting endpoint if link is inactive or device disonnected. ++ * Can cause host hang. ++ * Device will be reset to recover an inactive link, so don't do anything + */ +- if (ep->vdev->flags & VDEV_PORT_ERROR) ++ if (rhub_port->link_inactive || !rhub_port->connected) { ++ ep->ep_state |= EP_DROP_PENDING; + return -ENODEV; ++ } + + /* add td to cancelled list and let reset ep handler take care of it */ + if (reset_type == EP_HARD_RESET) { +@@ -1047,15 +1033,13 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + * only call this when ring is not in a running state + */ + +-static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) ++static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- struct xhci_hcd *xhci; + struct xhci_td *td = NULL; + struct xhci_td *tmp_td = NULL; + struct xhci_td *cached_td = NULL; + struct xhci_ring *ring; + u64 hw_deq; +- unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +@@ -1065,8 +1049,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (ep->ep_state & SET_DEQ_PENDING) + return 0; + +- xhci = ep->xhci; +- + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", +@@ -1080,6 +1062,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + td->urb, td->urb->stream_id); + continue; + } ++ ++ /* device disconnected or link error, ep will be dropped */ ++ if (ep->ep_state & EP_DROP_PENDING) { ++ td->cancel_status = TD_CLEARED; ++ continue; ++ } ++ + /* + * If a ring stopped on the TD we need to cancel then we have to + * move the xHC endpoint ring dequeue pointer past this TD. +@@ -1131,9 +1120,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (!cached_td) + return 0; + +- err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, +- cached_td->urb->stream_id, +- cached_td); ++ err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id); ++ + if (err) { + /* Failed to move past cached td, just set cached TDs to no-op */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { +@@ -1161,23 +1149,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + * + * Call under xhci->lock on a stopped endpoint. + */ +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep) ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- xhci_invalidate_cancelled_tds(ep); +- xhci_giveback_invalidated_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } + + /* + * Returns the TD the endpoint ring halted on. + * Only call for non-running rings without streams. + */ +-static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) ++static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_td *td; + u64 hw_deq; + + if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ +- hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); ++ hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0); + hw_deq &= TR_DEQ_PTR_MASK; + td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); + if (trb_in_td(td, hw_deq)) +@@ -1252,7 +1240,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + reset_type = EP_SOFT_RESET; + } else { + reset_type = EP_HARD_RESET; +- td = find_halted_td(ep); ++ td = find_halted_td(xhci, ep); + if (td) + td->status = -EPROTO; + } +@@ -1310,13 +1298,17 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + } + } + ++ /* link is inactive or disconnected, ep is not running and shouldn't be restarted */ ++ if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected) ++ ep->ep_state |= EP_DROP_PENDING; ++ + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + ep->ep_state &= ~EP_STOP_CMD_PENDING; + + /* Otherwise ring the doorbell(s) to restart queued transfers */ +- xhci_giveback_invalidated_tds(ep); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_giveback_invalidated_tds(xhci, ep); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) +@@ -1537,14 +1529,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + /* HW cached TDs cleared from cache, give them back */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { +- ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb); + if (td->cancel_status == TD_CLEARING_CACHE) { + td->cancel_status = TD_CLEARED; +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); ++ xhci_td_cleanup(xhci, td, ep_ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } + } +@@ -1555,17 +1547,17 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + + /* Check for deferred or newly cancelled TDs */ + if (!list_empty(&ep->cancelled_td_list)) { +- xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n", ++ xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n", + __func__); +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + /* Try to restart the endpoint if all is done */ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + /* Start giving back any TDs invalidated above */ +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } else { + /* Restart any rings with pending URBs */ +- xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + } + +@@ -1591,16 +1583,16 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, + "Ignoring reset ep completion code of %u", cmd_comp_code); + + /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + + /* Clear our internal halted state */ + ep->ep_state &= ~EP_HALTED; + +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + + /* if this was a soft reset, then restart */ + if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command, +@@ -2008,13 +2000,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) + static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + { + struct xhci_virt_device *vdev = NULL; +- struct usb_hcd *hcd; +- u32 port_id; +- u32 portsc, cmd_reg; +- unsigned int hcd_portnum; + struct xhci_bus_state *bus_state; +- bool bogus_port_status = false; + struct xhci_port *port; ++ struct usb_hcd *hcd; ++ bool bogus_port_status = false; ++ unsigned int hcd_portnum; ++ u32 cmd_reg; ++ u32 port_id; ++ u32 portsc; ++ u32 pls; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) +@@ -2051,6 +2045,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + bus_state = &port->rhub->bus_state; + hcd_portnum = port->hcd_portnum; + portsc = xhci_portsc_readl(port); ++ pls = portsc & PORT_PLS_MASK; + + xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n", + hcd->self.busnum, hcd_portnum + 1, port_id, portsc); +@@ -2062,11 +2057,13 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + usb_hcd_resume_root_hub(hcd); + } + +- if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) { +- if (!(portsc & PORT_RESET)) +- vdev->flags |= VDEV_PORT_ERROR; +- } else if (vdev && portsc & PORT_RC) { +- vdev->flags &= ~VDEV_PORT_ERROR; ++ /* ++ * Tag broken links to avoid retries while hub driver sorts it out. ++ * Link status is not relible while port is in reset. ++ */ ++ if (!(portsc & PORT_RESET)) { ++ port->link_inactive = (pls == XDEV_INACTIVE); ++ port->connected = !!(portsc & PORT_CONNECT); + } + + if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { +@@ -3978,88 +3975,102 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci, + /* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */ + static int xhci_ist_microframes(struct xhci_hcd *xhci) + { +- int ist = HCS_IST_VALUE(xhci->hcs_params2); ++ int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2); + + if (xhci->hcs_params2 & HCS_IST_UNIT) + ist *= 8; + return ist; + } + +-/* +- * Calculates Frame ID field of the isochronous TRB identifies the +- * target frame that the Interval associated with this Isochronous +- * Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec. +- * +- * Returns actual frame id on success, negative value on error. +- */ +-static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, +- struct urb *urb, int index) ++ ++static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep, int i) + { +- int start_frame, ist, ret = 0; +- int start_frame_id, end_frame_id, current_frame_id; ++ if (urb->transfer_flags & URB_ISO_ASAP) ++ return false; + +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- start_frame = urb->start_frame + index * urb->interval; +- else +- start_frame = (urb->start_frame + index * urb->interval) >> 3; ++ if (xhci->hcc_params & HCC_CFC) ++ return true; + ++ /* set frame id for first TD of first URB in stream */ ++ if (ep->next_uframe == -1 && i == 0) ++ return true; ++ ++ return false; ++} ++ ++/* ++ * Check if frame is in the valid frame window, including start and end. ++ * If start > end then assume window wrapped around at a limit the frame ++ * value won't exceed. ++ */ ++static bool xhci_frame_in_range(u32 frame, u32 start, u32 end) ++{ ++ /* frame window end wrapped around */ ++ if (start > end) ++ return frame >= start || frame <= end; ++ ++ return frame >= start && frame <= end; ++} ++ ++/* ++ * Set the urb->start_frame of the URB. ++ * ++ * Returns microframe index of first TD ++ */ ++static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep) ++{ ++ u32 curr_frame, start_uframe; ++ u32 urb_start, urb_end; ++ u32 win_start, win_end; ++ bool frame_unit; ++ int uinterval; ++ u32 mfindex; ++ int ist; ++ ++ /* check if urb uses frame units instead of microframes */ ++ frame_unit = (urb->dev->speed == USB_SPEED_FULL || ++ urb->dev->speed == USB_SPEED_LOW); ++ ++ uinterval = urb->interval; ++ if (frame_unit) ++ uinterval *= 8; ++ ++ /* get current microframe index and isoc scheduling threshold */ ++ mfindex = readl(&xhci->run_regs->microframe_index); + ist = xhci_ist_microframes(xhci); + +- /* Software shall not schedule an Isoch TD with a Frame ID value that +- * is less than the Start Frame ID or greater than the End Frame ID, +- * where: +- * +- * End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048 +- * Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048 +- * +- * Both the End Frame ID and Start Frame ID values are calculated +- * in microframes. When software determines the valid Frame ID value; +- * The End Frame ID value should be rounded down to the nearest Frame +- * boundary, and the Start Frame ID value should be rounded up to the +- * nearest Frame boundary. +- */ +- current_frame_id = readl(&xhci->run_regs->microframe_index); +- start_frame_id = roundup(current_frame_id + ist + 1, 8); +- end_frame_id = rounddown(current_frame_id + 895 * 8, 8); ++ /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */ ++ curr_frame = MFINDEX_TO_FRAME(mfindex); ++ win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES; ++ win_end = (curr_frame + 895) % MAX_FRAMES; + +- start_frame &= 0x7ff; +- start_frame_id = (start_frame_id >> 3) & 0x7ff; +- end_frame_id = (end_frame_id >> 3) & 0x7ff; +- +- if (start_frame_id < end_frame_id) { +- if (start_frame > end_frame_id || +- start_frame < start_frame_id) +- ret = -EINVAL; +- } else if (start_frame_id > end_frame_id) { +- if ((start_frame > end_frame_id && +- start_frame < start_frame_id)) +- ret = -EINVAL; ++ /* Is this the first URB starting the whole isoc data flow? */ ++ if (ep->next_uframe < 0) { ++ /* align first URB to next interval boundary, or at last to full frame */ ++ start_uframe = mfindex + ist + XHCI_CFC_DELAY; ++ start_uframe = roundup(start_uframe, 8); ++ start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES; + } else { +- ret = -EINVAL; +- } ++ /* URB is mid stream and expected to handle the next frame */ ++ start_uframe = ep->next_uframe; ++ urb_start = start_uframe / 8; ++ urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8; ++ urb_end %= MAX_FRAMES; + +- if (index == 0) { +- if (ret == -EINVAL || start_frame == start_frame_id) { +- start_frame = start_frame_id + 1; +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- urb->start_frame = start_frame; +- else +- urb->start_frame = start_frame << 3; +- ret = 0; +- } +- } ++ if (!xhci_frame_in_range(urb_start, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + +- if (ret) { +- xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n", +- start_frame, current_frame_id, index, +- start_frame_id, end_frame_id); +- xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n"); +- return ret; ++ if (!xhci_frame_in_range(urb_end, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + } ++ /* set urb->start_frame */ ++ urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe; + +- return start_frame; ++ return start_uframe; + } + + /* Check if we should generate event interrupt for a TD in an isoc URB */ +@@ -4099,7 +4110,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + int i, j; + bool more_trbs_coming; + struct xhci_virt_ep *xep; +- int frame_id; ++ int uinterval = urb->interval; ++ int start_uframe; + + xep = &xhci->devs[slot_id]->eps[ep_index]; + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; +@@ -4115,6 +4127,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + start_cycle = ep_ring->cycle_state; + + urb_priv = urb->hcpriv; ++ ++ if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW) ++ uinterval = urb->interval * 8; ++ ++ start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep); ++ + /* Queue the TRBs for each TD, even if they are zero-length */ + for (i = 0; i < num_tds; i++) { + unsigned int total_pkt_count, max_pkt; +@@ -4146,14 +4164,16 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + goto cleanup; + } + td = &urb_priv->td[i]; +- /* use SIA as default, if frame id is used overwrite it */ +- sia_frame_id = TRB_SIA; +- if (!(urb->transfer_flags & URB_ISO_ASAP) && +- (xhci->hcc_params & HCC_CFC)) { +- frame_id = xhci_get_isoc_frame_id(xhci, urb, i); +- if (frame_id >= 0) +- sia_frame_id = TRB_FRAME_ID(frame_id); ++ ++ ++ /* Choose SIA or frame ID based scheduling for this TD */ ++ if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) { ++ sia_frame_id = (start_uframe + i * uinterval) / 8; ++ sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES); ++ } else { ++ sia_frame_id = TRB_SIA; + } ++ + /* + * Set isoc specific data for the first TRB in a TD. + * Prevent HW from getting the TRBs by keeping the cycle state +@@ -4232,9 +4252,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + } + } + +- /* store the next frame id */ +- if (xhci->hcc_params & HCC_CFC) +- xep->next_frame_id = urb->start_frame + num_tds * urb->interval; ++ xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES; + + if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { + if (xhci->quirks & XHCI_AMD_PLL_FIX) +@@ -4281,11 +4299,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + struct xhci_ep_ctx *ep_ctx; +- int start_frame; ++ struct xhci_virt_ep *xep; + int num_tds, num_trbs, i; + int ret; +- struct xhci_virt_ep *xep; +- int ist; + + xdev = xhci->devs[slot_id]; + xep = &xhci->devs[slot_id]->eps[ep_index]; +@@ -4311,38 +4327,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + */ + check_interval(urb, ep_ctx); + +- /* Calculate the start frame and put it in urb->start_frame. */ +- if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) { +- if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) { +- urb->start_frame = xep->next_frame_id; +- goto skip_start_over; +- } +- } +- +- start_frame = readl(&xhci->run_regs->microframe_index); +- start_frame &= 0x3fff; + /* +- * Round up to the next frame and consider the time before trb really +- * gets scheduled by hardare. ++ * Check if this starts the isoc data flow. Relies on hw setting ep ctx ++ * state after doorbell ring. Consider adding list_empty(td_list) check + */ +- ist = xhci_ist_microframes(xhci); +- start_frame += ist + XHCI_CFC_DELAY; +- start_frame = roundup(start_frame, 8); +- +- /* +- * Round up to the next ESIT (Endpoint Service Interval Time) if ESIT +- * is greate than 8 microframes. +- */ +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) { +- start_frame = roundup(start_frame, urb->interval << 3); +- urb->start_frame = start_frame >> 3; +- } else { +- start_frame = roundup(start_frame, urb->interval); +- urb->start_frame = start_frame; +- } +- +-skip_start_over: ++ if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ xep->next_uframe = -1; + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } +diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c +--- a/drivers/usb/host/xhci-tegra.c ++++ b/drivers/usb/host/xhci-tegra.c +@@ -6,6 +6,7 @@ + * Copyright (C) 2014 Google, Inc. + */ + ++#include + #include + #include + #include +@@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) + u32 value; + + cap_regs = tegra->regs; +- op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); ++ op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)), + + ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); + +@@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) + goto remove_usb2; + } + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1664,7 +1665,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag + goto free_priv; + } + +- if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) { ++ if (xhci->devs[slot_id]->rhub_port->link_inactive) { + xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); + ret = -ENODEV; + goto free_priv; +@@ -1852,11 +1853,11 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) + } + + /* In this case no commands are pending but the endpoint is stopped */ +- if (ep->ep_state & EP_CLEARING_TT) { ++ if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) { + /* and cancelled TDs can be given back right away */ + xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", + urb->dev->slot_id, ep_index, ep->ep_state); +- xhci_process_cancelled_tds(ep); ++ xhci_process_cancelled_tds(xhci, ep); + } else { + /* Otherwise, queue a new Stop Endpoint command */ + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); +@@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci, + * level page entries), but that's an optional feature for xHCI host + * controllers. xHCs must support at least 4 stream IDs. + */ +- max_streams = HCC_MAX_PSA(xhci->hcc_params); ++ max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params); + if (*num_stream_ctxs > max_streams) { + xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n", + max_streams); +@@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + + /* MaxPSASize value 0 (2 streams) means streams are not supported */ + if ((xhci->quirks & XHCI_BROKEN_STREAMS) || +- HCC_MAX_PSA(xhci->hcc_params) < 4) { ++ GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) { + xhci_dbg(xhci, "xHCI controller does not support streams.\n"); + return -ENOSYS; + } +@@ -4035,7 +4036,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + xhci_get_slot_state(xhci, virt_dev->out_ctx)); + xhci_dbg(xhci, "Not freeing device rings.\n"); + /* Don't treat this as an error. May change my mind later. */ +- virt_dev->flags = 0; + ret = 0; + goto command_cleanup; + case COMP_SUCCESS: +@@ -4087,10 +4087,12 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + } + /* If necessary, update the number of active TTs on this root port */ + xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); +- virt_dev->flags = 0; + ret = 0; + + command_cleanup: ++ for (i = 0; i < EP_CTX_PER_DEV; i++) ++ virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING; ++ + xhci_free_command(xhci, reset_device_cmd); + return ret; + } +@@ -4607,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, + int besl_device = 0; + u32 field; + +- u2del = HCS_U2_LATENCY(xhci->hcs_params3); ++ u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3); + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + if (field & USB_BESL_SUPPORT) { +@@ -5459,26 +5461,28 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + xhci_warn(xhci, "Host controller not accessible, removed?\n"); + return -ENODEV; + } +- xhci->op_regs = hcd->regs + HC_LENGTH(hc_capbase); ++ xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase); ++ + xhci->run_regs = hcd->regs + + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + hcs_params1 = readl(&xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); +- xhci->hci_version = HC_VERSION(hc_capbase); ++ xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase); + xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); + if (xhci->hci_version > 0x100) + xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + + xhci->dma_mask_bits = 64; +- xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); +- xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); ++ xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS); ++ xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS); ++ + /* xhci-plat or xhci-pci might have set max_interrupters already */ + if (!xhci->max_interrupters) +- xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS); +- else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1)) +- xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1); ++ xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS); ++ else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1)) ++ xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1); + + xhci->quirks |= quirks; + +@@ -5519,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + * DMA_BIT_MASK(32)) in this xhci_gen_setup(). + */ + if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) +- xhci->hcc_params &= ~BIT(0); ++ xhci->hcc_params &= ~HCC_64BIT_ADDR; + + /* + * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports +diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h +--- a/drivers/usb/host/xhci.h ++++ b/drivers/usb/host/xhci.h +@@ -290,6 +290,11 @@ struct xhci_run_regs { + struct xhci_intr_reg ir_set[1024]; + }; + ++/* Bits [13:3] of the microframe index equals the 1ms frame index */ ++#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff) ++#define MAX_FRAMES 2048 ++#define MAX_UFRAMES (MAX_FRAMES * 8) ++ + /** + * struct doorbell_array + * +@@ -677,9 +682,9 @@ struct xhci_virt_ep { + #define EP_SOFT_CLEAR_TOGGLE BIT(7) + /* usb_hub_clear_tt_buffer is in progress */ + #define EP_CLEARING_TT BIT(8) ++#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ + /* ---- Related to URB cancellation ---- */ + struct list_head cancelled_td_list; +- struct xhci_hcd *xhci; + /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue + * command. We'll need to update the ring's dequeue segment and dequeue + * pointer after the command completes. +@@ -699,7 +704,7 @@ struct xhci_virt_ep { + struct list_head bw_endpoint_list; + unsigned long stop_time; + /* Isoch Frame ID checking storage */ +- int next_frame_id; ++ int next_uframe; + /* Use new Isoch TRB layout needed for extended TBC support */ + bool use_extended_tbc; + /* set if this endpoint is controlled via sideband access*/ +@@ -753,14 +758,6 @@ struct xhci_virt_device { + struct xhci_port *rhub_port; + struct xhci_interval_bw_table *bw_table; + struct xhci_tt_bw_info *tt_info; +- /* +- * flags for state tracking based on events and issued commands. +- * Software can not rely on states from output contexts because of +- * latency between events and xHC updating output context values. +- * See xhci 1.1 section 4.8.3 for more details +- */ +- unsigned long flags; +-#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */ + + /* The current max exit latency for the enabled USB3 link states. */ + u16 current_mel; +@@ -1480,6 +1477,8 @@ struct xhci_port { + int hcd_portnum; + struct xhci_hub *rhub; + struct xhci_port_cap *port_cap; ++ unsigned int link_inactive:1; ++ unsigned int connected:1; + unsigned int lpm_incapable:1; + unsigned long resume_timestamp; + bool rexit_active; +@@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len); + unsigned int xhci_num_trbs_free(struct xhci_ring *ring); + int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + int suspend, gfp_t gfp_flags); +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep); + void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb); +diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c +--- a/drivers/usb/misc/cypress_cy7c63.c ++++ b/drivers/usb/misc/cypress_cy7c63.c +@@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request, + address, data, iobuf, CYPRESS_MAX_REQSIZE, + USB_CTRL_GET_TIMEOUT); + /* we must not process garbage */ +- if (retval < 2) ++ if (retval < 2) { ++ if (retval >= 0) ++ retval = -EIO; + goto err_buf; ++ } + + /* store returned data (more READs to be added) */ + switch (request) { +@@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr, + dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); + + result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); +- + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); ++ if (result < 0) ++ return result; + + return sprintf(buf, "%d", cyp->port[port_num]); + } +diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c +--- a/drivers/usb/misc/uss720.c ++++ b/drivers/usb/misc/uss720.c +@@ -223,6 +223,7 @@ static int get_1284_register(struct parport *pp, unsigned char reg, unsigned cha + } + printk(KERN_WARNING "get_1284_register timeout\n"); + kill_all_async_requests_priv(priv); ++ kref_put(&rq->ref_count, destroy_async); + return -EIO; + } + +diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c +--- a/drivers/usb/mtu3/mtu3_host.c ++++ b/drivers/usb/mtu3/mtu3_host.c +@@ -46,6 +46,14 @@ + #define WC1_IS_P_95 BIT(12) + #define WC1_IS_EN_P0_95 BIT(6) + ++/* mt8196 */ ++#define PERI_WK_CTRL0_8196 0x08 ++#define WC0_IS_EN_P0_96 BIT(0) ++#define WC0_IS_EN_P1_96 BIT(7) ++ ++#define PERI_WK_CTRL1_8196 0x10 ++#define WC1_IS_EN_P2_96 BIT(0) ++ + /* mt2712 etc */ + #define PERI_SSUSB_SPM_CTRL 0x0 + #define SSC_IP_SLEEP_EN BIT(4) +@@ -59,6 +67,9 @@ enum ssusb_uwk_vers { + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ ++ SSUSB_UWK_V1_7, /* mt8196 IP0 */ ++ SSUSB_UWK_V1_8, /* mt8196 IP1 */ ++ SSUSB_UWK_V1_9, /* mt8196 IP2 */ + }; + + /* +@@ -100,6 +111,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; ++ case SSUSB_UWK_V1_7: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P0_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_8: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P1_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_9: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196; ++ msk = WC1_IS_EN_P2_96; ++ val = enable ? msk : 0; ++ break; + case SSUSB_UWK_V2: + reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; + msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; +diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c +--- a/drivers/usb/mtu3/mtu3_plat.c ++++ b/drivers/usb/mtu3/mtu3_plat.c +@@ -544,7 +544,8 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) + + ssusb_phy_power_off(ssusb); + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); +- ssusb_wakeup_set(ssusb, true); ++ if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg))) ++ ssusb_wakeup_set(ssusb, true); + return 0; + + sleep_err: +diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig +--- a/drivers/usb/musb/Kconfig ++++ b/drivers/usb/musb/Kconfig +@@ -116,7 +116,6 @@ config USB_MUSB_POLARFIRE_SOC + tristate "Microchip PolarFire SoC platforms" + depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on NOP_USB_XCEIV +- select USB_MUSB_DUAL_ROLE + help + Say Y here to enable support for USB on Microchip's PolarFire SoC. + +diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c +--- a/drivers/usb/musb/musb_host.c ++++ b/drivers/usb/musb/musb_host.c +@@ -1774,7 +1774,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) + status = -EPIPE; + + } else if (rx_csr & MUSB_RXCSR_H_ERROR) { +- dev_err(musb->controller, "ep%d RX three-strikes error", epnum); ++ dev_err_ratelimited(musb->controller, ++ "ep%d RX three-strikes error\n", epnum); + + /* + * The three-strikes error could only happen when the USB +@@ -1788,6 +1789,17 @@ void musb_host_rx(struct musb *musb, u8 epnum) + rx_csr &= ~MUSB_RXCSR_H_ERROR; + musb_writew(epio, MUSB_RXCSR, rx_csr); + ++ /* ++ * Unplugging a USB-Ethernet adapter while it is busy can make ++ * the controller keep re-asserting the three-strikes error for ++ * this endpoint before the disconnect is processed. That floods ++ * the log and can wedge the host port until reboot. Drop the ++ * stale pending RX interrupt on platforms that support it (e.g. ++ * AM335x/DSPS) to break the storm; the transfer is still ++ * aborted below via the fault path. ++ */ ++ musb_platform_clear_ep_rxintr(musb, epnum); ++ + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { + + if (USB_ENDPOINT_XFER_ISOC != qh->type) { +diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c +--- a/drivers/usb/serial/digi_acceleport.c ++++ b/drivers/usb/serial/digi_acceleport.c +@@ -32,10 +32,6 @@ + /* so we can be sure to send the full buffer in one urb */ + #define DIGI_OUT_BUF_SIZE 8 + +-/* port input buffer length -- must be >= transfer buffer length - 3 */ +-/* so we can be sure to hold at least one full buffer from one urb */ +-#define DIGI_IN_BUF_SIZE 64 +- + /* retry timeout while sleeping */ + #define DIGI_RETRY_TIMEOUT (HZ/10) + +@@ -176,10 +172,10 @@ + /* Structures */ + + struct digi_serial { +- spinlock_t ds_serial_lock; ++ struct mutex open_mutex; + struct usb_serial_port *ds_oob_port; /* out-of-band port */ + int ds_oob_port_num; /* index of out-of-band port */ +- int ds_device_started; ++ int open_count; + }; + + struct digi_port { +@@ -194,7 +190,6 @@ struct digi_port { + int dp_throttled; + int dp_throttle_restart; + wait_queue_head_t dp_flush_wait; +- wait_queue_head_t dp_close_wait; /* wait queue for close */ + wait_queue_head_t write_wait; + struct usb_serial_port *dp_port; + }; +@@ -227,9 +222,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty); + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); + static void digi_close(struct usb_serial_port *port); + static void digi_dtr_rts(struct usb_serial_port *port, int on); +-static int digi_startup_device(struct usb_serial *serial); + static int digi_startup(struct usb_serial *serial); +-static void digi_disconnect(struct usb_serial *serial); + static void digi_release(struct usb_serial *serial); + static int digi_port_probe(struct usb_serial_port *port); + static void digi_port_remove(struct usb_serial_port *port); +@@ -272,7 +265,7 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .dtr_rts = digi_dtr_rts, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -282,7 +275,6 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -301,7 +293,7 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .close = digi_close, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -311,7 +303,6 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -335,7 +326,6 @@ static struct usb_serial_driver * const serial_drivers[] = { + * interruptible_sleep_on_timeout is deprecated and has been replaced + * with the equivalent code. + */ +- + static long cond_wait_interruptible_timeout_irqrestore( + wait_queue_head_t *q, long timeout, + spinlock_t *lock, unsigned long flags) +@@ -351,6 +341,13 @@ __releases(lock) + return timeout; + } + ++static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ ++ return serial_priv->ds_oob_port; ++} ++ + /* + * Digi Write OOB Command + * +@@ -361,15 +358,14 @@ __releases(lock) + * the interruptible flag is true, or a negative error + * returned by usb_submit_urb. + */ +- + static int digi_write_oob_command(struct usb_serial_port *port, + unsigned char *buf, int count, int interruptible) + { +- int ret = 0; +- int len; +- struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, + "digi_write_oob_command: TOP: port=%d, count=%d\n", +@@ -406,10 +402,8 @@ static int digi_write_oob_command(struct usb_serial_port *port, + dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", + __func__, ret); + return ret; +- + } + +- + /* + * Digi Write In Band Command + * +@@ -421,16 +415,15 @@ static int digi_write_oob_command(struct usb_serial_port *port, + * timeout ticks. Returns 0 if successful, or a negative + * error returned by digi_write. + */ +- + static int digi_write_inb_command(struct usb_serial_port *port, + unsigned char *buf, int count, unsigned long timeout) + { +- int ret = 0; +- int len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + unsigned long expire; + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); +@@ -459,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, + /* len must be a multiple of 4 and small enough to */ + /* guarantee the write will send buffered data first, */ + /* so commands are in order with data and not split */ +- len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + if (len > 4) + len &= ~3; + +@@ -467,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port, + if (priv->dp_out_buf_len > 0) { + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; +- memcpy(data + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); + memcpy(data + 2 + priv->dp_out_buf_len, buf, len); +- port->write_urb->transfer_buffer_length +- = priv->dp_out_buf_len + 2 + len; ++ port->write_urb->transfer_buffer_length = ++ priv->dp_out_buf_len + 2 + len; + } else { + memcpy(data, buf, len); + port->write_urb->transfer_buffer_length = len; +@@ -484,7 +476,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + count -= len; + buf += len; + } +- + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + +@@ -495,7 +486,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + return ret; + } + +- + /* + * Digi Set Modem Signals + * +@@ -505,17 +495,15 @@ static int digi_write_inb_command(struct usb_serial_port *port, + * -EINTR if interrupted while sleeping, or a non-zero error + * returned by usb_submit_urb. + */ +- + static int digi_set_modem_signals(struct usb_serial_port *port, + unsigned int modem_signals, int interruptible) + { +- +- int ret; + struct digi_port *port_priv = usb_get_serial_port_data(port); +- struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned char *data = oob_port->write_urb->transfer_buffer; + unsigned long flags; ++ int ret; + + dev_dbg(&port->dev, + "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", +@@ -573,14 +561,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port, + * is only called from close, and only one process can be in close on a + * port at a time, so its ok. + */ +- + static int digi_transmit_idle(struct usb_serial_port *port, + unsigned long timeout) + { +- int ret; +- unsigned char buf[2]; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[2]; + unsigned long flags; ++ int ret; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_transmit_idle = 0; +@@ -607,16 +594,15 @@ static int digi_transmit_idle(struct usb_serial_port *port, + } + priv->dp_transmit_idle = 0; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return 0; +- + } + +- + static void digi_rx_throttle(struct tty_struct *tty) + { +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; + + /* stop receiving characters by not resubmitting the read urb */ + spin_lock_irqsave(&priv->dp_port_lock, flags); +@@ -625,13 +611,12 @@ static void digi_rx_throttle(struct tty_struct *tty) + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + } + +- + static void digi_rx_unthrottle(struct tty_struct *tty) + { +- int ret = 0; +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; ++ int ret = 0; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + +@@ -651,7 +636,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty) + __func__, ret, priv->dp_port_num); + } + +- + static void digi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +@@ -762,7 +746,6 @@ static void digi_set_termios(struct tty_struct *tty, + + /* set stop bits */ + if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { +- + if ((cflag & CSTOPB)) + arg = DIGI_STOP_BITS_2; + else +@@ -772,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty, + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; +- + } + + /* set input flow control */ +@@ -841,7 +823,6 @@ static void digi_set_termios(struct tty_struct *tty, + tty_encode_baud_rate(tty, baud, baud); + } + +- + static int digi_break_ctl(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; +@@ -855,50 +836,48 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state) + return digi_write_inb_command(port, buf, 4, 0); + } + +- + static int digi_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = priv->dp_modem_signals; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return val; + } + +- + static int digi_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = (priv->dp_modem_signals & ~clear) | set; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return digi_set_modem_signals(port, val, 1); + } + +- + static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) + { +- +- int ret, data_len, new_len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; ++ int ret, data_len, new_len; + unsigned long flags; + + dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); + + /* copy user data (which can sleep) before getting spin lock */ +- count = min(count, port->bulk_out_size-2); ++ count = min(count, port->bulk_out_size - 2); + count = min(64, count); + + /* be sure only one write proceeds at a time */ +@@ -920,7 +899,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + + /* allow space for any buffered data and for new data, up to */ + /* transfer buffer size - 2 (for command and length bytes) */ +- new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + data_len = new_len + priv->dp_out_buf_len; + + if (data_len == 0) { +@@ -928,7 +907,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + return 0; + } + +- port->write_urb->transfer_buffer_length = data_len+2; ++ port->write_urb->transfer_buffer_length = data_len + 2; + + *data++ = DIGI_CMD_SEND_DATA; + *data++ = data_len; +@@ -954,39 +933,22 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + dev_dbg(&port->dev, "digi_write: returning %d\n", ret); +- return ret; + ++ return ret; + } + + static void digi_write_bulk_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; +- struct usb_serial *serial; +- struct digi_port *priv; +- struct digi_serial *serial_priv; ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char *data = urb->transfer_buffer; + unsigned long flags; +- int ret = 0; +- int status = urb->status; + bool wakeup; +- +- /* port and serial sanity check */ +- if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { +- pr_err("%s: port or port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- serial = port->serial; +- if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { +- dev_err(&port->dev, +- "%s: serial or serial->private is NULL, status=%d\n", +- __func__, status); +- return; +- } ++ int ret = 0; + + /* handle oob callback */ + if (priv->dp_port_num == serial_priv->ds_oob_port_num) { +- dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + wake_up_interruptible(&priv->write_wait); +@@ -999,15 +961,13 @@ static void digi_write_bulk_callback(struct urb *urb) + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + if (priv->dp_out_buf_len > 0) { +- *((unsigned char *)(port->write_urb->transfer_buffer)) +- = (unsigned char)DIGI_CMD_SEND_DATA; +- *((unsigned char *)(port->write_urb->transfer_buffer) + 1) +- = (unsigned char)priv->dp_out_buf_len; +- port->write_urb->transfer_buffer_length = +- priv->dp_out_buf_len + 2; +- memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); +- ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); ++ data[0] = DIGI_CMD_SEND_DATA; ++ data[1] = priv->dp_out_buf_len; ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); ++ ++ urb->transfer_buffer_length = priv->dp_out_buf_len + 2; ++ ++ ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + priv->dp_out_buf_len = 0; +@@ -1041,8 +1001,8 @@ static unsigned int digi_write_room(struct tty_struct *tty) + + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room); +- return room; + ++ return room; + } + + static unsigned int digi_chars_in_buffer(struct tty_struct *tty) +@@ -1070,17 +1030,53 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) + digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1); + } + ++static int digi_open_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ int ret = 0; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count++ == 0) { ++ ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ++ ret); ++ serial_priv->open_count--; ++ } ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++ ++ return ret; ++} ++ ++static void digi_close_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count-- == 1) { ++ usb_kill_urb(oob_port->read_urb); ++ usb_kill_urb(oob_port->write_urb); ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++} ++ + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + { +- int ret; +- unsigned char buf[32]; + struct digi_port *priv = usb_get_serial_port_data(port); + struct ktermios not_termios; +- int throttled; ++ unsigned char buf[32]; ++ int ret; + +- /* be sure the device is started up */ +- if (digi_startup_device(port->serial) != 0) +- return -ENXIO; ++ ret = digi_open_oob_port(port->serial); ++ if (ret) ++ return ret; + + /* read modem signals automatically whenever they change */ + buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; +@@ -1106,32 +1102,34 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + } + + spin_lock_irq(&priv->dp_port_lock); +- throttled = priv->dp_throttle_restart; + priv->dp_throttled = 0; + priv->dp_throttle_restart = 0; + spin_unlock_irq(&priv->dp_port_lock); + +- if (throttled) { +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret) { +- dev_err(&port->dev, "failed to submit read urb: %d\n", ret); +- return ret; +- } ++ ret = usb_submit_urb(port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&port->dev, "failed to submit read urb: %d\n", ret); ++ goto err_close_oob; + } + + return 0; +-} + ++err_close_oob: ++ digi_close_oob_port(port->serial); ++ ++ return ret; ++} + + static void digi_close(struct usb_serial_port *port) + { ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[32]; + DEFINE_WAIT(wait); + int ret; +- unsigned char buf[32]; +- struct digi_port *priv = usb_get_serial_port_data(port); ++ ++ usb_kill_urb(port->read_urb); + + mutex_lock(&port->serial->disc_mutex); +- /* if disconnected, just clear flags */ + if (port->serial->disconnected) + goto exit; + +@@ -1177,57 +1175,18 @@ static void digi_close(struct usb_serial_port *port) + TASK_INTERRUPTIBLE); + schedule_timeout(DIGI_CLOSE_TIMEOUT); + finish_wait(&priv->dp_flush_wait, &wait); ++exit: ++ mutex_unlock(&port->serial->disc_mutex); + + /* shutdown any outstanding bulk writes */ + usb_kill_urb(port->write_urb); +-exit: +- spin_lock_irq(&priv->dp_port_lock); +- priv->dp_write_urb_in_use = 0; +- wake_up_interruptible(&priv->dp_close_wait); +- spin_unlock_irq(&priv->dp_port_lock); +- mutex_unlock(&port->serial->disc_mutex); +-} + +- +-/* +- * Digi Startup Device +- * +- * Starts reads on all ports. Must be called AFTER startup, with +- * urbs initialized. Returns 0 if successful, non-zero error otherwise. +- */ +- +-static int digi_startup_device(struct usb_serial *serial) +-{ +- int i, ret = 0; +- struct digi_serial *serial_priv = usb_get_serial_data(serial); +- struct usb_serial_port *port; +- +- /* be sure this happens exactly once */ +- spin_lock(&serial_priv->ds_serial_lock); +- if (serial_priv->ds_device_started) { +- spin_unlock(&serial_priv->ds_serial_lock); +- return 0; +- } +- serial_priv->ds_device_started = 1; +- spin_unlock(&serial_priv->ds_serial_lock); +- +- /* start reading from each bulk in endpoint for the device */ +- /* set USB_DISABLE_SPD flag for write bulk urbs */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- port = serial->port[i]; +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret != 0) { +- dev_err(&port->dev, +- "%s: usb_submit_urb failed, ret=%d, port=%d\n", +- __func__, ret, i); +- break; +- } +- } +- return ret; ++ digi_close_oob_port(port->serial); + } + + static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + { ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv; + + priv = kzalloc_obj(*priv); +@@ -1235,10 +1194,13 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + return -ENOMEM; + + spin_lock_init(&priv->dp_port_lock); ++ ++ if (port == serial_priv->ds_oob_port) ++ lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING); ++ + priv->dp_port_num = port_num; + init_waitqueue_head(&priv->dp_transmit_idle_wait); + init_waitqueue_head(&priv->dp_flush_wait); +- init_waitqueue_head(&priv->dp_close_wait); + init_waitqueue_head(&priv->write_wait); + priv->dp_port = port; + +@@ -1275,10 +1237,13 @@ static int digi_startup(struct usb_serial *serial) + if (!serial_priv) + return -ENOMEM; + +- spin_lock_init(&serial_priv->ds_serial_lock); ++ mutex_init(&serial_priv->open_mutex); ++ + serial_priv->ds_oob_port_num = oob_port_num; + serial_priv->ds_oob_port = serial->port[oob_port_num]; + ++ usb_set_serial_data(serial, serial_priv); ++ + ret = digi_port_init(serial_priv->ds_oob_port, + serial_priv->ds_oob_port_num); + if (ret) { +@@ -1286,24 +1251,9 @@ static int digi_startup(struct usb_serial *serial) + return ret; + } + +- usb_set_serial_data(serial, serial_priv); +- + return 0; + } + +- +-static void digi_disconnect(struct usb_serial *serial) +-{ +- int i; +- +- /* stop reads and writes on all ports */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- usb_kill_urb(serial->port[i]->read_urb); +- usb_kill_urb(serial->port[i]->write_urb); +- } +-} +- +- + static void digi_release(struct usb_serial *serial) + { + struct digi_serial *serial_priv; +@@ -1333,30 +1283,25 @@ static void digi_port_remove(struct usb_serial_port *port) + static void digi_read_bulk_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +- struct digi_port *priv; +- struct digi_serial *serial_priv; +- int ret; ++ struct usb_serial *serial = port->serial; ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); + int status = urb->status; +- +- /* port sanity check, do not resubmit if port is not valid */ +- if (port == NULL) +- return; +- priv = usb_get_serial_port_data(port); +- if (priv == NULL) { +- dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- if (port->serial == NULL || +- (serial_priv = usb_get_serial_data(port->serial)) == NULL) { +- dev_err(&port->dev, "%s: serial is bad or serial->private " +- "is NULL, status=%d\n", __func__, status); +- return; +- } ++ int ret; + + /* do not resubmit urb if it has any status error */ +- if (status) { +- dev_err(&port->dev, ++ switch (status) { ++ case 0: ++ break; ++ case -ENOENT: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ dev_err(&serial->interface->dev, ++ "%s: nonzero read bulk status: status=%d, port=%d\n", ++ __func__, status, priv->dp_port_num); ++ return; ++ default: ++ dev_err(&serial->interface->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; +@@ -1374,11 +1319,10 @@ static void digi_read_bulk_callback(struct urb *urb) + /* continue read */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) { +- dev_err(&port->dev, ++ dev_err(&serial->interface->dev, + "%s: failed resubmitting urb, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + } +- + } + + /* +@@ -1390,7 +1334,6 @@ static void digi_read_bulk_callback(struct urb *urb) + * It returns 0 if successful, 1 if successful but the port is + * throttled, and -1 if the sanity checks failed. + */ +- + static int digi_read_inb_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +@@ -1468,10 +1411,8 @@ static int digi_read_inb_callback(struct urb *urb) + dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); + + return throttled ? 1 : 0; +- + } + +- + /* + * Digi Read OOB Callback + * +@@ -1480,10 +1421,8 @@ static int digi_read_inb_callback(struct urb *urb) + * the port->serial is valid. It returns 0 if successful, and + * -1 if the sanity checks failed. + */ +- + static int digi_read_oob_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + struct tty_struct *tty; +@@ -1491,8 +1430,8 @@ static int digi_read_oob_callback(struct urb *urb) + unsigned char *buf = urb->transfer_buffer; + int opcode, line, status, val; + unsigned long flags; +- int i; + unsigned int rts; ++ int i; + + if (urb->actual_length < 4) + return -1; +@@ -1504,7 +1443,8 @@ static int digi_read_oob_callback(struct urb *urb) + status = buf[i + 2]; + val = buf[i + 3]; + +- dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", ++ dev_dbg(&serial->interface->dev, ++ "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + opcode, line, status, val); + + if (status != 0 || line >= serial->type->num_ports) +@@ -1562,8 +1502,8 @@ static int digi_read_oob_callback(struct urb *urb) + } + tty_kref_put(tty); + } +- return 0; + ++ return 0; + } + + module_usb_serial_driver(serial_drivers, id_table_combined); +diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c +--- a/drivers/usb/serial/keyspan_pda.c ++++ b/drivers/usb/serial/keyspan_pda.c +@@ -31,7 +31,6 @@ + struct keyspan_pda_private { + int tx_room; + struct work_struct unthrottle_work; +- struct usb_serial *serial; + struct usb_serial_port *port; + bool throttled; + bool throttle_req; +diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c +--- a/drivers/usb/serial/metro-usb.c ++++ b/drivers/usb/serial/metro-usb.c +@@ -36,6 +36,7 @@ + struct metrousb_private { + spinlock_t lock; + int throttled; ++ int throttle_req; + unsigned long control_state; + }; + +@@ -108,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb) + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + int throttled = 0; +- int result = 0; ++ int result; + + dev_dbg(&port->dev, "%s\n", __func__); + +@@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb) + + /* Set any port variables. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- throttled = metro_priv->throttled; ++ if (metro_priv->throttle_req) { ++ metro_priv->throttled = 1; ++ throttled = 1; ++ } + spin_unlock_irqrestore(&metro_priv->lock, flags); + + if (throttled) +@@ -169,12 +173,13 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) + struct usb_serial *serial = port->serial; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int result; + + /* Set the private data information for the port. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->control_state = 0; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + /* Clear the urb pipe. */ +@@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty) + + /* Set the private information for the port to stop reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- metro_priv->throttled = 1; ++ metro_priv->throttle_req = 1; + spin_unlock_irqrestore(&metro_priv->lock, flags); + } + +@@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty) + struct usb_serial_port *port = tty->driver_data; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int throttled; ++ int result; + + /* Set the private information for the port to resume reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); ++ throttled = metro_priv->throttled; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + +- /* Submit the urb to read from the port. */ +- result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); +- if (result) +- dev_err(&port->dev, +- "failed submitting interrupt in urb error code=%d\n", +- result); ++ if (throttled) { ++ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); ++ if (result) { ++ dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", ++ result); ++ } ++ } + } + + static struct usb_serial_driver metrousb_device = { +diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c +--- a/drivers/usb/serial/pl2303.c ++++ b/drivers/usb/serial/pl2303.c +@@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, ++ { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), +@@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define VENDOR_WRITE_REQUEST_TYPE 0x40 + #define VENDOR_WRITE_REQUEST 0x01 + #define VENDOR_WRITE_NREQUEST 0x80 ++#define VENDOR_WRITE_MPREQUEST 0x80 + + #define VENDOR_READ_REQUEST_TYPE 0xc0 + #define VENDOR_READ_REQUEST 0x01 + #define VENDOR_READ_NREQUEST 0x81 ++#define VENDOR_READ_MPREQUEST 0x80 ++ ++#define PL256X_RESET_REQUEST_TYPE 0x40 ++#define PL256X_RESET_REQUEST 0x96 + + #define UART_STATE_INDEX 8 + #define UART_STATE_MSR_MASK 0x8b +@@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 + #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + ++#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 ++#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 ++#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 ++#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 ++ ++#define PL256X_FLOWCTRL_MASK 0x43 ++#define PL256X_FLOWCTRL_XON_XOFF 0x40 ++#define PL256X_FLOWCTRL_RTS_CTS 0x03 ++#define PL256X_FLOWCTRL_NONE 0x00 ++ + static int pl2303_set_break(struct usb_serial_port *port, bool enable); + + enum pl2303_type { +@@ -181,6 +197,7 @@ enum pl2303_type { + TYPE_TB, + TYPE_HXD, + TYPE_HXN, ++ TYPE_MP, + TYPE_COUNT + }; + +@@ -196,6 +213,8 @@ struct pl2303_type_data { + struct pl2303_serial_private { + const struct pl2303_type_data *type; + unsigned long quirks; ++ u16 interface_num; ++ u16 flowctrl_reg; + }; + + struct pl2303_private { +@@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { + .max_baud_rate = 12000000, + .no_divisors = true, + }, ++ [TYPE_MP] = { ++ .name = "MP", ++ .max_baud_rate = 24000000, ++ .no_divisors = true, ++ }, + }; + ++static int pl256x_uart_reset(struct usb_serial *serial) ++{ ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); ++ struct device *dev = &serial->interface->dev; ++ int res; ++ ++ res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), ++ PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, ++ 0, spriv->interface_num, NULL, 0, 100); ++ if (res) { ++ dev_err(dev, "failed to reset device: %d\n", res); ++ return res; ++ } ++ ++ return 0; ++} ++ + static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + unsigned char buf[1]) + { +@@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_READ_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_READ_MPREQUEST; + else + request = VENDOR_READ_REQUEST; + +@@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_WRITE_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_WRITE_MPREQUEST; + else + request = VENDOR_WRITE_REQUEST; + +@@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) + if (!buf) + return -ENOMEM; + +- if (spriv->type == &pl2303_type_data[TYPE_HXN]) ++ if (spriv->type == &pl2303_type_data[TYPE_HXN] || ++ spriv->type == &pl2303_type_data[TYPE_MP]) { + ret = pl2303_vendor_read(serial, reg, buf); +- else ++ } else { + ret = pl2303_vendor_read(serial, reg | 0x80, buf); ++ } + + if (ret) + goto out_free; +@@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial) + case 0x905: /* GT-2AB */ + case 0x1005: /* GC-Q20 */ + return TYPE_HXN; ++ case 0x3302: /* PL2533 VC 2 Port */ ++ case 0x3304: /* PL2533 VC 4 Port */ ++ case 0x4302: /* PL2543 VC 2 Port */ ++ case 0x4304: /* PL2543 VC 4 Port */ ++ case 0x6502: /* PL2565 VC 2 Port */ ++ case 0x6504: /* PL2565 VC 4 Port */ ++ case 0x6506: /* PL2565 VC 4 Port QFN64 package */ ++ return TYPE_MP; + } + break; + } +@@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial) + struct pl2303_serial_private *spriv; + enum pl2303_type type; + unsigned char *buf; ++ unsigned int ifnum; + int ret; + + ret = pl2303_detect_type(serial); +@@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial) + type = ret; + dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + ++ ifnum = serial->interface->altsetting->desc.bInterfaceNumber; ++ + spriv = kzalloc_obj(*spriv); + if (!spriv) + return -ENOMEM; + ++ if (type == TYPE_MP) { ++ switch (ifnum) { ++ case 0: ++ spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; ++ break; ++ case 1: ++ spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; ++ break; ++ case 2: ++ spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; ++ break; ++ case 3: ++ spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; ++ break; ++ default: ++ kfree(spriv); ++ return -ENODEV; ++ } ++ } ++ + spriv->type = &pl2303_type_data[type]; + spriv->quirks = (unsigned long)usb_get_serial_data(serial); + spriv->quirks |= spriv->type->quirks; ++ spriv->interface_num = ifnum; + + if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) + spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; + + usb_set_serial_data(serial, spriv); + +- if (type != TYPE_HXN) { ++ if (type != TYPE_HXN && type != TYPE_MP) { + buf = kmalloc(1, GFP_KERNEL); + if (!buf) { + kfree(spriv); +@@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port) + static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) + { + struct usb_device *dev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int retval; + + dev_dbg(&port->dev, "%s - %02x\n", __func__, value); + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, +- value, 0, NULL, 0, 100); ++ value, spriv->interface_num, NULL, 0, 100); + if (retval) + dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); + +@@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port, + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret != 7) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + +@@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port, + unsigned char buf[7]) + { + struct usb_device *udev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int ret; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret < 0) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + return ret; +@@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_RTS_CTS); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_RTS_CTS); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); + } +@@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_XON_XOFF); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_XON_XOFF); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); + } +@@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_NONE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_NONE); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); + } +@@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) + pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, + PL2303_HXN_RESET_UPSTREAM_PIPE | + PL2303_HXN_RESET_DOWNSTREAM_PIPE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl256x_uart_reset(serial); + } else { + pl2303_vendor_write(serial, 8, 0); + pl2303_vendor_write(serial, 9, 0); +@@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable) + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + BREAK_REQUEST, BREAK_REQUEST_TYPE, state, +- 0, NULL, 0, 100); ++ spriv->interface_num, NULL, 0, 100); + if (result) { + dev_err(&port->dev, "error sending break = %d\n", result); + return result; +diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h +--- a/drivers/usb/serial/pl2303.h ++++ b/drivers/usb/serial/pl2303.h +@@ -26,7 +26,7 @@ + #define PL2303_PRODUCT_ID_HCR331 0x331a + #define PL2303_PRODUCT_ID_MOTOROLA 0x0307 + #define PL2303_PRODUCT_ID_ZTEK 0xe1f1 +- ++#define PL256X_PRODUCT_ID_4P 0x2533 + + #define ATEN_VENDOR_ID 0x0557 + #define ATEN_VENDOR_ID2 0x0547 +diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig +--- a/drivers/usb/typec/altmodes/Kconfig ++++ b/drivers/usb/typec/altmodes/Kconfig +@@ -5,6 +5,7 @@ menu "USB Type-C Alternate Mode drivers" + config TYPEC_DP_ALTMODE + tristate "DisplayPort Alternate Mode driver" + depends on DRM ++ select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + DisplayPort USB Type-C Alternate Mode allows DisplayPort + displays and adapters to be attached to the USB Type-C +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -764,6 +764,7 @@ int dp_altmode_probe(struct typec_altmode *alt) + struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + struct fwnode_handle *fwnode; + struct dp_altmode *dp; ++ u32 cap = DP_CAP_CAPABILITY(alt->vdo); + + /* Port can only be DFP_U. */ + if (typec_altmode_get_data_role(alt) != TYPEC_HOST) +@@ -778,6 +779,18 @@ int dp_altmode_probe(struct typec_altmode *alt) + return -ENODEV; + } + ++ /* ++ * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section ++ * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a ++ * USB-C receptacle must advertise at least one pin assignment for the ++ * capability it claims, otherwise Alt Mode can never be configured. ++ */ ++ if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) || ++ (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) { ++ typec_altmode_put_plug(plug); ++ return -ENODEV; ++ } ++ + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) { + typec_altmode_put_plug(plug); +@@ -790,7 +803,6 @@ int dp_altmode_probe(struct typec_altmode *alt) + dp->alt = alt; + + alt->desc = "DisplayPort"; +- typec_altmode_set_ops(alt, &dp_altmode_ops); + + if (plug) { + plug->desc = "Displayport"; +@@ -811,6 +823,10 @@ int dp_altmode_probe(struct typec_altmode *alt) + if (plug) + typec_altmode_set_drvdata(plug, dp); + ++ if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) ++ return 0; ++ ++ typec_altmode_set_ops(alt, &dp_altmode_ops); + if (!alt->mode_selection) { + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; + schedule_work(&dp->work); +diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c +--- a/drivers/usb/typec/altmodes/thunderbolt.c ++++ b/drivers/usb/typec/altmodes/thunderbolt.c +@@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); ++ ++ if (typec_cable_altmode_unsupported(alt)) ++ return 0; ++ + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (!alt->mode_selection && tbt_ready(alt)) { +diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c +--- a/drivers/usb/typec/class.c ++++ b/drivers/usb/typec/class.c +@@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) + } + EXPORT_SYMBOL_GPL(typec_cable_is_active); + ++enum typec_cable_altmode_support { ++ CABLE_SUPPORT_UNKNOWN, ++ CABLE_SUPPORTED, ++ CABLE_NOT_SUPPORTED, ++}; ++ ++static enum typec_cable_altmode_support ++typec_cable_check_altmode_support(struct typec_cable *cable, ++ struct typec_altmode *alt) ++{ ++ struct typec_altmode *plug; ++ u32 speed; ++ ++ /* ++ * Check if the cable has an e-marker, supports modal operation, and the ++ * SOP' altmode nodes are created. ++ */ ++ plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); ++ if (plug) { ++ typec_altmode_put_plug(plug); ++ return CABLE_SUPPORTED; ++ } ++ ++ /* The identity is not specified */ ++ if (!cable->identity) ++ return CABLE_SUPPORT_UNKNOWN; ++ ++ /* Non-e-marked cable */ ++ if (!cable->identity->id_header) ++ return CABLE_NOT_SUPPORTED; ++ ++ switch (PD_IDH_PTYPE(cable->identity->id_header)) { ++ case IDH_PTYPE_PCABLE: ++ speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); ++ if (speed == CABLE_USB2_ONLY) ++ return CABLE_NOT_SUPPORTED; ++ return CABLE_SUPPORTED; ++ case IDH_PTYPE_ACABLE: ++ /* ++ * Active cables must establish an SOP' communication ++ * node. Since that check failed at the beginning of ++ * this function, this active cable does not support ++ * this specific altmode. ++ */ ++ return CABLE_NOT_SUPPORTED; ++ } ++ ++ return CABLE_SUPPORT_UNKNOWN; ++} ++ ++/** ++ * typec_cable_altmode_unsupported - Check if a cable restricts altmode ++ * @alt: The Alternate Mode to evaluate ++ * ++ * Returns true if the connected cable is incapable of handling the altmode. ++ */ ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt) ++{ ++ enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; ++ struct typec_cable *cable; ++ ++ cable = typec_cable_get(typec_altmode2port(alt)); ++ if (cable) { ++ support = typec_cable_check_altmode_support(cable, alt); ++ typec_cable_put(cable); ++ } ++ ++ return support == CABLE_NOT_SUPPORTED; ++} ++EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); ++ + /** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values +diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c +--- a/drivers/usb/typec/mux/tusb1046.c ++++ b/drivers/usb/typec/mux/tusb1046.c +@@ -178,6 +178,7 @@ static const struct of_device_id tusb1046_match_table[] = { + {.compatible = "ti,tusb1046"}, + {}, + }; ++MODULE_DEVICE_TABLE(of, tusb1046_match_table); + + static struct i2c_driver tusb1046_driver = { + .driver = { +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -212,6 +213,24 @@ static const char * const tcpm_ams_str[] = { + FOREACH_AMS(GENERATE_STRING) + }; + ++#define FOREACH_VDM_DISCOVERY(S) \ ++ S(VDM_DISCOVERY_UNKNOWN), \ ++ S(VDM_DISCOVERY_PARTNER_IDENT), \ ++ S(VDM_DISCOVERY_CABLE_IDENT), \ ++ S(VDM_DISCOVERY_PARTNER_SVIDS), \ ++ S(VDM_DISCOVERY_PARTNER_MODES), \ ++ S(VDM_DISCOVERY_CABLE_SVIDS), \ ++ S(VDM_DISCOVERY_CABLE_MODES), \ ++ S(VDM_DISCOVERY_COMPLETE) ++ ++enum vdm_discovery_states { ++ FOREACH_VDM_DISCOVERY(GENERATE_ENUM) ++}; ++ ++static const char * const vdm_discovery_state_strings[] = { ++ FOREACH_VDM_DISCOVERY(GENERATE_STRING) ++}; ++ + enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, +@@ -232,7 +251,9 @@ enum pd_msg_request { + PD_MSG_DATA_SINK_CAP, + PD_MSG_DATA_SOURCE_CAP, + PD_MSG_DATA_REV, +- PD_MSG_EXT_SINK_CAP_EXT ++ PD_MSG_EXT_SINK_CAP_EXT, ++ PD_MSG_DATA_BATT_STATUS, ++ PD_MSG_EXT_BATT_CAP, + }; + + enum adev_actions { +@@ -274,7 +295,7 @@ enum frs_typec_current { + #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) + + #define GET_SINK_CAP_RETRY_MS 100 +-#define SEND_DISCOVER_RETRY_MS 100 ++#define SEND_DISCOVERY_VDM_RETRY_MS 100 + + struct pd_mode_data { + int svid_index; /* current SVID index */ +@@ -387,7 +408,15 @@ struct pd_timings { + }; + + /* Convert microwatt to watt */ +-#define UW_TO_W(pow) ((pow) / 1000000) ++#define UW_TO_W(pow) (div_u64((pow), 1000000)) ++ ++/* ++ * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries ++ * that a port can be queried is restricted to 4. ++ */ ++#define MAX_NUM_FIXED_BATT 4 ++ ++#define BATTERY_PROPERTY_UNKNOWN 0xffff + + /* + * struct pd_identifier - Contains info about PD identifiers +@@ -483,8 +512,6 @@ struct tcpm_port { + bool vbus_source; + bool vbus_charge; + +- /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */ +- bool send_discover; + bool op_vsafe5v; + + int try_role; +@@ -510,8 +537,8 @@ struct tcpm_port { + struct kthread_work vdm_state_machine; + struct hrtimer enable_frs_timer; + struct kthread_work enable_frs; +- struct hrtimer send_discover_timer; +- struct kthread_work send_discover_work; ++ struct hrtimer vdm_discovery_timer; ++ struct kthread_work vdm_discovery_work; + bool state_machine_running; + /* Set to true when VDM State Machine has following actions. */ + bool vdm_sm_running; +@@ -578,6 +605,9 @@ struct tcpm_port { + + u32 bist_request; + ++ /* VDM Discovery State to determine message sent */ ++ enum vdm_discovery_states vdm_discovery_state; ++ + /* PD state for Vendor Defined Messages */ + enum vdm_states vdm_state; + u32 vdm_retries; +@@ -641,12 +671,6 @@ struct tcpm_port { + bool potential_contaminant; + + /* SOP* Related Fields */ +- /* +- * Flag to determine if SOP' Discover Identity is available. The flag +- * is set if Discover Identity on SOP' does not immediately follow +- * Discover Identity on SOP. +- */ +- bool send_discover_prime; + /* + * tx_sop_type determines which SOP* a message is being sent on. + * For messages that are queued and not sent immediately such as in +@@ -683,6 +707,9 @@ struct tcpm_port { + + struct pd_identifier pd_ident; + struct sink_caps_ext_data sink_caps_ext; ++ struct power_supply **fixed_batt; ++ u32 fixed_batt_cnt; ++ u32 batt_request_id; + #ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + struct mutex logbuffer_lock; /* log buffer access lock */ +@@ -771,6 +798,9 @@ static const char * const pd_rev[] = { + #define tcpm_wait_for_discharge(port) \ + (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0) + ++#define tcpm_can_send_vdm(state) \ ++ ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST)) ++ + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) + { + if (port->port_type == TYPEC_PORT_DRP) { +@@ -1470,6 +1500,20 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + ++static void tcpm_get_fixed_batt(struct tcpm_port *port) ++{ ++ int ret; ++ ++ if (!port->self_powered || port->fixed_batt_cnt > 0) ++ return; ++ ++ ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt); ++ if (ret < 0) ++ tcpm_log(port, "Failed to get battery array, ret=%d", ret); ++ else ++ port->fixed_batt_cnt = ret; ++} ++ + static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + { + u16 operating_snk_watt = port->operating_snk_mw / 1000; +@@ -1482,6 +1526,8 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + if (!port->self_powered) + data->spr_op_pdp = operating_snk_watt; + ++ tcpm_get_fixed_batt(port); ++ + /* + * SPR Sink Minimum PDP indicates the minimum power required to operate + * a sink device in its lowest level of functionality without requiring +@@ -1507,6 +1553,7 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + skedb.load_step = data->load_step; + skedb.load_char = cpu_to_le16(data->load_char); + skedb.compliance = data->compliance; ++ skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT); + skedb.modes = data->modes; + skedb.spr_min_pdp = data->spr_min_pdp; + skedb.spr_op_pdp = data->spr_op_pdp; +@@ -1525,6 +1572,153 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + port->message_id, + data_obj_cnt, + 1 /* Denotes if ext header */)); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static u16 tcpm_charge_to_energy(int charge, int voltage) ++{ ++ u64 energy = div_u64((u64)charge * voltage, 1000000); ++ ++ /* Battery telemetry is reported in increments of 0.1Wh */ ++ return (u16)UW_TO_W(energy * 10); ++} ++ ++static int tcpm_pd_send_batt_status(struct tcpm_port *port) ++{ ++ u16 present_charge = BATTERY_PROPERTY_UNKNOWN; ++ bool batt_present = false, invalid_ref = true; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct power_supply *batt; ++ u8 charging_status = 0; ++ struct pd_message msg; ++ int ret, charge_now; ++ u32 bsdo; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_status; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val); ++ if (ret) ++ tcpm_log(port, ++ "Failed to fetch power_supply_prop_present ret %d", ++ ret); ++ else ++ batt_present = val.intval > 0; ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, ++ &val); ++ if (!ret) { ++ charge_now = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) ++ present_charge = tcpm_charge_to_energy(charge_now, ++ val.intval); ++ } ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); ++ if (!ret) { ++ switch (val.intval) { ++ case POWER_SUPPLY_STATUS_CHARGING: ++ charging_status = BSDO_BATTERY_INFO_CHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_DISCHARGING: ++ charging_status = BSDO_BATTERY_INFO_DISCHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_NOT_CHARGING: ++ case POWER_SUPPLY_STATUS_FULL: ++ charging_status = BSDO_BATTERY_INFO_IDLE; ++ break; ++ default: ++ charging_status = BSDO_BATTERY_INFO_RSVD; ++ break; ++ } ++ } ++ ++send_status: ++ ++ bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref); ++ msg.payload[0] = cpu_to_le32(bsdo); ++ msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS, ++ port->pwr_role, ++ port->data_role, ++ port->negotiated_rev, ++ port->message_id, ++ 1); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static int tcpm_pd_send_batt_cap(struct tcpm_port *port) ++{ ++ u16 design_cap = BATTERY_PROPERTY_UNKNOWN; ++ u16 charge_cap = BATTERY_PROPERTY_UNKNOWN; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct batt_cap_ext_msg bcdb; ++ struct power_supply *batt; ++ bool invalid_ref = true; ++ struct pd_message msg; ++ u8 data_obj_cnt; ++ int ret, vol; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_cap; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) { ++ vol = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, ++ &val); ++ if (!ret) ++ design_cap = tcpm_charge_to_energy(val.intval, vol); ++ ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ &val); ++ if (!ret) ++ charge_cap = tcpm_charge_to_energy(val.intval, vol); ++ } ++ ++send_cap: ++ ++ /* ++ * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the ++ * USB-IF) does not exist or an invalid battery reference is made by the ++ * requestor, then set the VID field to 0xffff. If the VID field is ++ * 0xffff, set the PID field to 0. ++ */ ++ bcdb.vid = BATTERY_PROPERTY_UNKNOWN; ++ bcdb.pid = 0; ++ bcdb.batt_design_cap = cpu_to_le16(design_cap); ++ bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap); ++ bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0; ++ memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb)); ++ msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb), ++ 0, /* Denotes if request chunk */ ++ 0, /* Chunk number */ ++ 1 /* Chunked */); ++ ++ data_obj_cnt = count_chunked_data_objs(sizeof(bcdb)); ++ msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role, ++ port->data_role, port->negotiated_rev, ++ port->message_id, data_obj_cnt); ++ + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + +@@ -1559,13 +1753,19 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + } + } + +-static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms) ++static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) ++{ ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_cancel_work_sync(&port->vdm_discovery_work); ++} ++ ++static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) + { + if (delay_ms) { +- hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); ++ hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { +- hrtimer_cancel(&port->send_discover_timer); +- kthread_queue_work(port->wq, &port->send_discover_work); ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + } + } + +@@ -1773,16 +1973,11 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, + WARN_ON(!mutex_is_locked(&port->lock)); + + /* If is sending discover_identity, handle received message first */ +- if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { +- if (tx_sop_type == TCPC_TX_SOP_PRIME) +- port->send_discover_prime = true; +- else +- port->send_discover = true; +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); +- } else { ++ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); ++ else + /* Make sure we are not still processing a previous VDM packet */ + WARN_ON(port->vdm_state > VDM_STATE_DONE); +- } + + port->vdo_count = cnt + 1; + port->vdo_data[0] = header; +@@ -1805,8 +2000,7 @@ static void tcpm_queue_vdm_work(struct kthread_work *work) + struct tcpm_port *port = event->port; + + mutex_lock(&port->lock); +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + tcpm_log_force(port, "dropping altmode_vdm_event"); + goto port_unlock; + } +@@ -2034,7 +2228,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) + if (!port->partner) + return; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_partner_register_altmode(port->partner, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2052,9 +2246,10 @@ static void tcpm_register_plug_altmodes(struct tcpm_port *port) + struct typec_altmode *altmode; + int i; + +- typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); ++ typec_plug_set_num_altmodes(port->plug_prime, ++ min(modep->altmodes, ALTMODE_DISCOVERY_MAX)); + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_plug_register_altmode(port->plug_prime, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2154,6 +2349,19 @@ static bool tcpm_cable_vdm_supported(struct tcpm_port *port) + tcpm_can_communicate_sop_prime(port); + } + ++static void tcpm_update_vdm_discovery_state(struct tcpm_port *port, ++ enum vdm_discovery_states new_state) ++{ ++ enum vdm_discovery_states old_state = port->vdm_discovery_state; ++ ++ if (old_state != new_state) ++ tcpm_log_force(port, "vdm discovery state changed: %s -> %s", ++ vdm_discovery_state_strings[old_state], ++ vdm_discovery_state_strings[new_state]); ++ ++ port->vdm_discovery_state = new_state; ++} ++ + static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) +@@ -2171,6 +2379,7 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + +@@ -2179,10 +2388,12 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(USB_SID_PD, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + modep = &port->mode_data_prime; + modep->svid_index++; +@@ -2193,11 +2404,13 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES); + return 1; + } + + tcpm_register_plug_altmodes(port); + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + return 0; +@@ -2376,18 +2589,18 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + typec_cable_set_svdm_version(port->cable, + svdm_version); + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT); ++ + /* 6.4.4.3.1 */ + svdm_consume_identity(port, p, cnt); + /* Attempt Vconn swap, delay SOP' discovery if necessary */ + if (tcpm_attempt_vconn_swap_discovery(port)) { +- port->send_discover_prime = true; + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (!ret) + return 0; + /* Cannot perform Vconn swap */ + port->upcoming_state = INVALID_STATE; +- port->send_discover_prime = false; + } + + /* +@@ -2398,7 +2611,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + if (IS_ERR_OR_NULL(port->cable) && + tcpm_can_communicate_sop_prime(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; +- port->send_discover_prime = true; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_IDENT); +@@ -2426,6 +2638,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + return 0; + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, +@@ -2445,16 +2658,27 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + rlen = 1; + } else { + if (rx_sop_type == TCPC_TX_SOP) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_PARTNER_SVIDS); + if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_CABLE_SVIDS); + if (modep_prime->nsvids) { + response[0] = VDO(modep_prime->svids[0], 1, + svdm_version, CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } + } +@@ -2505,8 +2729,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + case CMDT_RSP_NAK: + tcpm_ams_finish(port); + switch (cmd) { ++ /* ++ * The cable is not allowed to respond with NAK so this must've happened over SOP ++ */ + case CMD_DISCOVER_IDENT: + case CMD_DISCOVER_SVID: ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; + case CMD_DISCOVER_MODES: +@@ -2686,44 +2915,6 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, + port->vdm_sm_running = false; + } + +-static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, +- const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) +-{ +- int svdm_version; +- u32 header; +- +- switch (tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- /* +- * If the port partner is discovered, then the port partner's +- * SVDM Version will be returned +- */ +- svdm_version = typec_get_cable_svdm_version(port->typec_port); +- if (svdm_version < 0) +- svdm_version = SVDM_VER_MAX; +- break; +- case TCPC_TX_SOP: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- default: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- } +- +- if (WARN_ON(count > VDO_MAX_SIZE - 1)) +- count = VDO_MAX_SIZE - 1; +- +- /* set VDM header with VID & CMD */ +- header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? +- 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), +- svdm_version, cmd); +- tcpm_queue_vdm(port, header, data, count, tx_sop_type); +-} +- + static unsigned int vdm_ready_timeout(u32 vdm_hdr) + { + unsigned int timeout; +@@ -2769,8 +2960,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + port->vdm_sm_running = false; + break; + } +@@ -2780,22 +2970,10 @@ static void vdm_run_state_machine(struct tcpm_port *port) + switch (PD_VDO_CMD(vdo_hdr)) { + case CMD_DISCOVER_IDENT: + res = tcpm_ams_start(port, DISCOVER_IDENTITY); +- if (res == 0) { +- switch (port->tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- port->send_discover_prime = false; +- break; +- case TCPC_TX_SOP: +- port->send_discover = false; +- break; +- default: +- port->send_discover = false; +- break; +- } +- } else if (res == -EAGAIN) { ++ if (res == -EAGAIN) { + port->vdo_data[0] = 0; +- mod_send_discover_delayed_work(port, +- SEND_DISCOVER_RETRY_MS); ++ mod_vdm_discovery_delayed_work(port, ++ SEND_DISCOVERY_VDM_RETRY_MS); + } + break; + case CMD_DISCOVER_SVID: +@@ -2853,6 +3031,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + port->vdm_state = VDM_STATE_DONE; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* +@@ -2869,6 +3048,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + tcpm_ams_finish(port); + } else { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + if (port->tx_sop_type == TCPC_TX_SOP) + break; + /* Handle SOP' Transmission Errors */ +@@ -2878,11 +3058,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * discovery process on SOP only. + */ + case CMD_DISCOVER_IDENT: +- port->vdo_data[0] = 0; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version( + port->typec_port), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + tcpm_queue_vdm(port, response[0], &response[1], + 0, TCPC_TX_SOP); + break; +@@ -2892,9 +3072,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + case CMD_DISCOVER_SVID: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + case CMD_DISCOVER_MODES: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + default: + break; +@@ -3807,7 +3989,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3823,7 +4006,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3832,7 +4016,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + } + break; + case PD_CTRL_VCONN_SWAP: +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3891,6 +4076,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + { + enum pd_ext_msg_type type = pd_header_type_le(msg->header); + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); ++ const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg; + + /* stopping VDM state machine if interrupted by other Messages */ + if (tcpm_vdm_ams(port)) { +@@ -3899,7 +4085,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + mod_vdm_delayed_work(port, 0); + } + +- if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { ++ if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + tcpm_log(port, "Unchunked extended messages unsupported"); + return; +@@ -3924,9 +4110,25 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + NONE_AMS, 0); + } + break; +- case PD_EXT_SOURCE_CAP_EXT: +- case PD_EXT_GET_BATT_CAP: + case PD_EXT_GET_BATT_STATUS: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS, ++ GETTING_BATTERY_STATUS); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_GET_BATT_CAP: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP, ++ GETTING_BATTERY_CAPABILITIES); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_SOURCE_CAP_EXT: + case PD_EXT_BATT_CAP: + case PD_EXT_GET_MANUFACTURER_INFO: + case PD_EXT_MANUFACTURER_INFO: +@@ -4137,6 +4339,22 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) + ret); + tcpm_ams_finish(port); + break; ++ case PD_MSG_DATA_BATT_STATUS: ++ ret = tcpm_pd_send_batt_status(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery status ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; ++ case PD_MSG_EXT_BATT_CAP: ++ ret = tcpm_pd_send_batt_cap(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery cap ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; + default: + break; + } +@@ -4853,8 +5071,7 @@ static int tcpm_src_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + +@@ -4896,11 +5113,11 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) + struct pd_mode_data *modep_prime = &port->mode_data_prime; + int i; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->partner_altmode[i]); + port->partner_altmode[i] = NULL; + } +- for (i = 0; i < modep_prime->altmodes; i++) { ++ for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->plug_prime_altmode[i]); + port->plug_prime_altmode[i] = NULL; + } +@@ -4931,6 +5148,8 @@ static void tcpm_reset_port(struct tcpm_port *port) + port->in_ams = false; + port->ams = NONE_AMS; + port->vdm_sm_running = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + tcpm_unregister_altmodes(port); + tcpm_typec_disconnect(port); + port->attached = false; +@@ -5014,8 +5233,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + } +@@ -5422,16 +5640,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + /* +@@ -5814,16 +6027,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + power_supply_changed(port->psy); +@@ -5869,8 +6077,8 @@ static void run_state_machine(struct tcpm_port *port) + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_unregister_altmodes(port); + port->nr_sink_caps = 0; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, + PD_T_PS_HARD_RESET); +@@ -6017,25 +6225,15 @@ static void run_state_machine(struct tcpm_port *port) + /* DR_Swap states */ + case DR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case DR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); + break; + case DR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); +- port->send_discover = false; +- port->send_discover_prime = false; + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6047,6 +6245,8 @@ static void run_state_machine(struct tcpm_port *port) + else + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, + TYPEC_HOST); ++ if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6330,9 +6530,7 @@ static void run_state_machine(struct tcpm_port *port) + + /* Cable states */ + case SRC_VDM_IDENTITY_REQUEST: +- port->send_discover_prime = true; +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + port->upcoming_state = SRC_SEND_CAPABILITIES; + break; + +@@ -7052,8 +7250,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + goto unlock; + + /* Send when the state machine is idle */ +- if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || +- port->send_discover_prime) ++ if (port->state != SNK_READY || port->vdm_sm_running || ++ port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + goto resched; + + port->upcoming_state = GET_SINK_CAP; +@@ -7070,29 +7268,160 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + mutex_unlock(&port->lock); + } + +-static void tcpm_send_discover_work(struct kthread_work *work) ++static void tcpm_vdm_discovery_work(struct kthread_work *work) + { +- struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work); ++ struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work); ++ enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP; ++ struct typec_port *typec = port->typec_port; ++ struct pd_mode_data *modep, *modep_prime; ++ u32 msg[2] = { }; ++ int svdm_version; + + mutex_lock(&port->lock); +- /* No need to send DISCOVER_IDENTITY anymore */ +- if (!port->send_discover && !port->send_discover_prime) +- goto unlock; + +- if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_log_force(port, "%s state [%s]", __func__, ++ vdm_discovery_state_strings[port->vdm_discovery_state]); ++ ++ /* No need to perform work if Discovery process is complete */ ++ if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE) + goto unlock; +- } + + /* Retry if the port is not idle */ +- if ((port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); ++ if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) { ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + goto unlock; + } + +- tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); ++ modep = &port->mode_data; ++ modep_prime = &port->mode_data_prime; ++ ++ svdm_version = typec_get_negotiated_svdm_version(typec); ++ ++ switch (port->vdm_discovery_state) { ++ /* ++ * The port has not received a Discover Identity response from the port partner. ++ * ++ * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and ++ * SNK_READY states if there is an explicit contract ++ * 2. The port will send Discover Identity to the cable over SOP' in the ++ * SRC_VDM_IDENTITY_REQUEST state if capable of doing so. ++ */ ++ case VDM_DISCOVERY_UNKNOWN: ++ /* Can't send Discover Identity, VDM discovery is complete */ ++ if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ ++ if (port->state == SRC_VDM_IDENTITY_REQUEST) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = SVDM_VER_MAX; ++ } ++ ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the port partner. ++ * ++ * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and ++ * SNK_READY states if it did not previously discover the cable but is capable of doing ++ * so. ++ * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and ++ * SNK_READY states otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_IDENT: ++ if (tcpm_can_communicate_sop_prime(port) && !port->cable) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ } else { ++ if (tcpm_can_communicate_sop_prime(port)) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the cable. ++ * ++ * 1. The port will send Discover SVIDs to the partner over SOP. ++ */ ++ case VDM_DISCOVERY_CABLE_IDENT: ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported ++ * by the partner. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports ++ * modal operation and valid SVIDs were registered. ++ * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_SVIDS: ++ if (modep->nsvids && supports_modal(port)) { ++ msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the partner for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the port partner over SOP. ++ * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP' ++ * and the cable supports VDMs. ++ * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_MODES: ++ /* Not all modes have been discovered yet */ ++ if (modep->svid_index < modep->nsvids) { ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the cable over SOP'. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_SVIDS: ++ if (modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the cable for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the cable over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_MODES: ++ if (modep_prime->svid_index < modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ default: ++ goto unlock; ++ } ++ ++ if (svdm_version >= 0) ++ tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type); + + unlock: + mutex_unlock(&port->lock); +@@ -8508,12 +8837,12 @@ static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) + return HRTIMER_NORESTART; + } + +-static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer) ++static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer) + { +- struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer); ++ struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer); + + if (port->registered) +- kthread_queue_work(port->wq, &port->send_discover_work); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + return HRTIMER_NORESTART; + } + +@@ -8547,14 +8876,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) + kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); + kthread_init_work(&port->event_work, tcpm_pd_event_handler); + kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); +- kthread_init_work(&port->send_discover_work, tcpm_send_discover_work); ++ kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work); + hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); +- hrtimer_setup(&port->send_discover_timer, send_discover_timer_handler, CLOCK_MONOTONIC, ++ hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + spin_lock_init(&port->pd_event_lock); +@@ -8649,11 +8978,12 @@ void tcpm_unregister_port(struct tcpm_port *port) + port->registered = false; + kthread_destroy_worker(port->wq); + +- hrtimer_cancel(&port->send_discover_timer); ++ hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); + hrtimer_cancel(&port->vdm_state_machine_timer); + hrtimer_cancel(&port->state_machine_timer); + ++ power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt); + tcpm_reset_port(port); + + tcpm_port_unregister_pd(port); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -129,6 +129,7 @@ enum { + TPS_MODE_BIST, + TPS_MODE_DISC, + TPS_MODE_PTCH, ++ TPS_MODE_APP1, + }; + + static const char *const modes[] = { +@@ -137,6 +138,7 @@ static const char *const modes[] = { + [TPS_MODE_BIST] = "BIST", + [TPS_MODE_DISC] = "DISC", + [TPS_MODE_PTCH] = "PTCH", ++ [TPS_MODE_APP1] = "APP1", + }; + + /* Unrecognized commands will be replaced with "!CMD" */ +@@ -158,6 +160,7 @@ struct tipd_data { + int (*init)(struct tps6598x *tps); + int (*switch_power_state)(struct tps6598x *tps, u8 target_state); + bool (*read_data_status)(struct tps6598x *tps); ++ bool (*read_power_status)(struct tps6598x *tps); + int (*reset)(struct tps6598x *tps); + int (*connect)(struct tps6598x *tps, u32 status); + }; +@@ -629,6 +632,34 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) + return true; + } + ++/* ++ * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported ++ * and the remaining bits are redundant with STATUS register (0x1A). ++ * Synthesize pwr_status from the already-read STATUS register. ++ */ ++static bool tps66993_read_power_status(struct tps6598x *tps) ++{ ++ u16 pwr_status = 0; ++ ++ /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */ ++ if (tps->status & TPS_STATUS_PLUG_PRESENT) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1); ++ ++ /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */ ++ if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status)) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1); ++ ++ if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, ++ TPS_POWER_STATUS_TYPEC_CURRENT_PD); ++ ++ tps->pwr_status = pwr_status; ++ ++ tps->data->trace_power_status(pwr_status); ++ ++ return true; ++} ++ + static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) + { + int ret; +@@ -906,7 +937,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) + goto err_unlock; + + if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { + if (tps6598x_read_partner_identity(tps)) { +@@ -961,7 +992,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) +@@ -1034,17 +1065,25 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) + if (!tps6598x_read_status(tps, &status)) + goto err_unlock; + ++ tps->status = status; ++ + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + + if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) + if (!tps->data->read_data_status(tps)) + goto err_unlock; + +- /* Handle plug insert or removal */ +- if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) ++ /* ++ * Refresh power status before connect - needed for TPS66993 which ++ * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE. ++ */ ++ if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) { ++ if (!tps->data->read_power_status(tps)) ++ goto err_unlock; + tps6598x_handle_plug_event(tps, status); ++ } + + err_unlock: + mutex_unlock(&tps->lock); +@@ -1080,6 +1119,7 @@ static int tps6598x_check_mode(struct tps6598x *tps) + + switch (ret) { + case TPS_MODE_APP: ++ case TPS_MODE_APP1: + case TPS_MODE_PTCH: + return ret; + case TPS_MODE_BOOT: +@@ -1753,7 +1793,7 @@ static int tps6598x_probe(struct i2c_client *client) + struct tps6598x *tps; + struct fwnode_handle *fwnode; + u32 status; +- u32 vid; ++ u32 vid = 0; + int ret; + + data = i2c_get_match_data(client); +@@ -1781,8 +1821,11 @@ static int tps6598x_probe(struct i2c_client *client) + + if (!device_is_compatible(tps->dev, "ti,tps25750")) { + ret = tps6598x_read32(tps, TPS_REG_VID, &vid); +- if (ret < 0 || !vid) ++ if (ret < 0 || !vid) { ++ dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n", ++ ret, vid); + return -ENODEV; ++ } + } + + /* +@@ -1818,6 +1861,8 @@ static int tps6598x_probe(struct i2c_client *client) + goto err_clear_mask; + } + ++ tps->status = status; ++ + /* + * This fwnode has a "compatible" property, but is never populated as a + * struct device. Instead we simply parse it to read the properties. +@@ -1845,7 +1890,7 @@ static int tps6598x_probe(struct i2c_client *client) + + if (status & TPS_STATUS_PLUG_PRESENT) { + ret = -EINVAL; +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unregister_port; + if (!tps->data->read_data_status(tps)) + goto err_unregister_port; +@@ -1860,7 +1905,7 @@ static int tps6598x_probe(struct i2c_client *client) + IRQF_SHARED | IRQF_ONESHOT, + dev_name(&client->dev), tps); + } else { +- dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n"); ++ dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); + INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); + queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, + msecs_to_jiffies(POLL_INTERVAL)); +@@ -1987,6 +2032,7 @@ static const struct tipd_data cd321x_data = { + .trace_status = trace_tps6598x_status, + .init = cd321x_init, + .read_data_status = cd321x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, +@@ -2006,6 +2052,25 @@ static const struct tipd_data tps6598x_data = { + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, ++ .reset = tps6598x_reset, ++ .connect = tps6598x_connect, ++}; ++ ++static const struct tipd_data tps66993_data = { ++ .irq_handler = tps6598x_interrupt, ++ .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE | ++ TPS_REG_INT_PLUG_EVENT, ++ .tps_struct_size = sizeof(struct tps6598x), ++ .register_port = tps6598x_register_port, ++ .unregister_port = tps6598x_unregister_port, ++ .trace_data_status = trace_tps6598x_data_status, ++ .trace_power_status = trace_tps6598x_power_status, ++ .trace_status = trace_tps6598x_status, ++ .apply_patch = tps6598x_apply_patch, ++ .init = tps6598x_init, ++ .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps66993_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, + }; +@@ -2024,12 +2089,14 @@ static const struct tipd_data tps25750_data = { + .apply_patch = tps25750_apply_patch, + .init = tps25750_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = tps25750_reset, + .connect = tps6598x_connect, + }; + + static const struct of_device_id tps6598x_of_match[] = { + { .compatible = "ti,tps6598x", &tps6598x_data}, ++ { .compatible = "ti,tps66993", &tps66993_data}, + { .compatible = "apple,cd321x", &cd321x_data}, + { .compatible = "ti,tps25750", &tps25750_data}, + {} +diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h +--- a/drivers/usb/typec/tipd/tps6598x.h ++++ b/drivers/usb/typec/tipd/tps6598x.h +@@ -142,9 +142,13 @@ + #define TPS_SYSTEM_POWER_STATE_S4 0x04 + #define TPS_SYSTEM_POWER_STATE_S5 0x05 + +-/* TPS_REG_POWER_STATUS bits */ +-#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x)) +-#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x)) ++/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */ ++#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0) ++#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1) ++#define TPS_POWER_STATUS_CONNECTION(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x)) ++#define TPS_POWER_STATUS_SOURCESINK(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x)) + #define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x)) + + #define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2) +diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c +--- a/drivers/usb/typec/ucsi/debugfs.c ++++ b/drivers/usb/typec/ucsi/debugfs.c +@@ -82,8 +82,8 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) + if (ucsi->debugfs->status) + return ucsi->debugfs->status; + +- seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high, +- ucsi->debugfs->response.low); ++ seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext, ++ ucsi->debugfs->response.high, ucsi->debugfs->response.low); + return 0; + } + DEFINE_SHOW_ATTRIBUTE(ucsi_resp); +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1102,8 +1102,8 @@ static int ucsi_register_plug(struct ucsi_connector *con) + plug = typec_register_plug(con->cable, &desc); + if (IS_ERR(plug)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register plug (%ld)\n", con->num, +- PTR_ERR(plug)); ++ "con%d: failed to register plug (%pe)\n", con->num, ++ plug); + return PTR_ERR(plug); + } + +@@ -1161,8 +1161,8 @@ static int ucsi_register_cable(struct ucsi_connector *con) + cable = typec_register_cable(con->port, &desc); + if (IS_ERR(cable)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register cable (%ld)\n", con->num, +- PTR_ERR(cable)); ++ "con%d: failed to register cable (%pe)\n", con->num, ++ cable); + return PTR_ERR(cable); + } + +@@ -1291,8 +1291,8 @@ static int ucsi_register_partner(struct ucsi_connector *con) + partner = typec_register_partner(con->port, &desc); + if (IS_ERR(partner)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register partner (%ld)\n", con->num, +- PTR_ERR(partner)); ++ "con%d: failed to register partner (%pe)\n", con->num, ++ partner); + return PTR_ERR(partner); + } + +@@ -2221,7 +2221,7 @@ static void ucsi_init_work(struct work_struct *work) + return; + } + +- queue_delayed_work(system_long_wq, &ucsi->work, ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); + } + } +@@ -2345,7 +2345,7 @@ int ucsi_register(struct ucsi *ucsi) + UCSI_BCD_GET_MINOR(ucsi->version), + UCSI_BCD_GET_SUBMINOR(ucsi->version)); + +- queue_delayed_work(system_long_wq, &ucsi->work, 0); ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0); + + ucsi_debugfs_register(ucsi); + return 0; +diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h +--- a/drivers/usb/typec/ucsi/ucsi.h ++++ b/drivers/usb/typec/ucsi/ucsi.h +@@ -466,6 +466,7 @@ struct ucsi_debugfs_entry { + struct ucsi_data { + u64 low; + u64 high; ++ u64 ext; + } response; + int status; + u8 message_out[MESSAGE_OUT_MAX_LEN]; +diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h +--- a/include/linux/power_supply.h ++++ b/include/linux/power_supply.h +@@ -806,11 +806,26 @@ extern int power_supply_reg_notifier(struct notifier_block *nb); + extern void power_supply_unreg_notifier(struct notifier_block *nb); + #if IS_ENABLED(CONFIG_POWER_SUPPLY) + extern struct power_supply *power_supply_get_by_name(const char *name); ++extern int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys); ++extern void power_supply_put_system_batteries(struct power_supply **psys, int count); + extern void power_supply_put(struct power_supply *psy); + #else + static inline void power_supply_put(struct power_supply *psy) {} + static inline struct power_supply *power_supply_get_by_name(const char *name) + { return NULL; } ++static inline int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ if (psys) ++ *psys = NULL; ++ return 0; ++} ++ ++static inline void power_supply_put_system_batteries(struct power_supply **psys, ++ int count) ++{ ++} + #endif + extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, + const char *property); +diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h +--- a/include/linux/usb/pd.h ++++ b/include/linux/usb/pd.h +@@ -106,6 +106,9 @@ enum pd_ext_msg_type { + #define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) + ++#define PD_HEADER_EXT_LE(type, pwr, data, rev, id, cnt) \ ++ cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (1))) ++ + static inline unsigned int pd_header_cnt(u16 header) + { + return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; +@@ -219,6 +222,25 @@ static inline u8 count_chunked_data_objs(u32 size) + return ((size / 4) + (size % 4 ? 1 : 0)); + } + ++/** ++ * batt_cap_ext_msg - Battery capability extended PD message ++ * @vid: Battery Vendor ID (assigned by USB-IF) ++ * @pid: Battery Product ID (assigned by battery or device vendor) ++ * @batt_design_cap: Battery design capacity in 0.1Wh ++ * @batt_last_chg_cap: Battery last full charge capacity in 0.1Wh ++ * @batt_type: Battery Type. bit0 when set indicates invalid battery reference. ++ * Rest of the bits are reserved. ++ */ ++struct batt_cap_ext_msg { ++ __le16 vid; ++ __le16 pid; ++ __le16 batt_design_cap; ++ __le16 batt_last_chg_cap; ++ u8 batt_type; ++} __packed; ++ ++#define BATT_CAP_BATT_TYPE_INVALID_REF BIT(0) ++ + /* Sink Caps Extended Data Block Version */ + #define SKEDB_VER_1_0 1 + +@@ -724,4 +746,33 @@ void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct devi + + #endif /* CONFIG_TYPEC */ + ++/* Battery Status Data Object */ ++#define BSDO_PRESENT_CAPACITY GENMASK(31, 16) ++#define BSDO_CHG_STATUS GENMASK(11, 10) ++#define BSDO_BATTERY_PRESENT BIT(9) ++#define BSDO_INVALID_BATTERY_REFERENCE BIT(8) ++ ++/* ++ * Battery Charge Status: Battery Charging Status Values as defined in ++ * "USB PD Spec Rev3.1 Ver1.8", "Table 6-46 Battery Status Data Object (BSDO)". ++ */ ++#define BSDO_BATTERY_INFO_CHARGING 0x0 ++#define BSDO_BATTERY_INFO_DISCHARGING 0x1 ++#define BSDO_BATTERY_INFO_IDLE 0x2 ++#define BSDO_BATTERY_INFO_RSVD 0x3 ++ ++/** ++ * BSDO() - Pack data into Battery Status Data Object format. ++ * @batt_charge: Battery's present state of charge in 0.1WH increment. ++ * @chg_status: Battery charge status. ++ * @batt_present: Indicates that battery is present/attached when set else absent when unset. ++ * @invalid_ref: Indicates that an invalid battery reference was made in the Get_Battery_Status ++ * request. ++ */ ++#define BSDO(batt_charge, chg_status, batt_present, invalid_ref) \ ++ ((FIELD_PREP(BSDO_PRESENT_CAPACITY, batt_charge)) | \ ++ (FIELD_PREP(BSDO_CHG_STATUS, chg_status)) | \ ++ ((batt_present) ? BSDO_BATTERY_PRESENT : 0) | \ ++ ((invalid_ref) ? BSDO_INVALID_BATTERY_REFERENCE : 0)) ++ + #endif /* __LINUX_USB_PD_H */ +diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h +--- a/include/linux/usb/typec.h ++++ b/include/linux/usb/typec.h +@@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); + struct typec_cable *typec_cable_get(struct typec_port *port); + void typec_cable_put(struct typec_cable *cable); + int typec_cable_is_active(struct typec_cable *cable); ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt); + + struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc); +diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h +--- a/include/uapi/linux/usb/functionfs.h ++++ b/include/uapi/linux/usb/functionfs.h +@@ -25,6 +25,7 @@ enum functionfs_flags { + FUNCTIONFS_EVENTFD = 32, + FUNCTIONFS_ALL_CTRL_RECIP = 64, + FUNCTIONFS_CONFIG0_SETUP = 128, ++ FUNCTIONFS_RW_PROXY_EPS = 256, + }; + + /* Descriptor of an non-audio endpoint */ +@@ -414,4 +415,27 @@ struct usb_functionfs_event { + #define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \ + struct usb_ffs_dmabuf_transfer_req) + ++/* ++ * Enable or disable automatic zero-length packet (ZLP) appending for the ++ * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to ++ * enable. ++ * ++ * When enabled, the kernel will automatically append a ZLP at the end of ++ * a transfer if the payload length is an exact multiple of the endpoint's ++ * max packet size. ++ * ++ * This is useful for compatibility with legacy protocols which require ++ * automatic ZLP appending to data written from userspace. ++ * ++ * This ioctl can only be used on IN endpoints. It can be called at any time ++ * after the FunctionFS instance is active, even before the host has connected ++ * or enabled the endpoint. ++ * ++ * Returns zero on success, or a negative errno value on error: ++ * -ENODEV: The FunctionFS instance is not active. ++ * -EINVAL: The endpoint is not an IN endpoint. ++ * -EFAULT: Invalid user space pointer for the argument. ++ */ ++#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32) ++ + #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ +diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs +--- a/rust/kernel/usb.rs ++++ b/rust/kernel/usb.rs +@@ -24,11 +24,8 @@ + }; + use core::{ + marker::PhantomData, +- mem::{ +- offset_of, +- MaybeUninit, // +- }, +- ptr::NonNull, ++ mem::offset_of, ++ ptr::NonNull, // + }; + + /// An adapter for the registration of USB drivers. +@@ -130,8 +127,7 @@ pub const fn from_id(vendor: u16, product: u16) -> Self { + match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16, + idVendor: vendor, + idProduct: product, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -143,8 +139,7 @@ pub const fn from_device_ver(vendor: u16, product: u16, bcd_lo: u16, bcd_hi: u16 + idProduct: product, + bcdDevice_lo: bcd_lo, + bcdDevice_hi: bcd_hi, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -155,8 +150,7 @@ pub const fn from_device_info(class: u8, subclass: u8, protocol: u8) -> Self { + bDeviceClass: class, + bDeviceSubClass: subclass, + bDeviceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -167,8 +161,7 @@ pub const fn from_interface_info(class: u8, subclass: u8, protocol: u8) -> Self + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -180,8 +173,7 @@ pub const fn from_device_interface_class(vendor: u16, product: u16, class: u8) - + idVendor: vendor, + idProduct: product, + bInterfaceClass: class, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -193,8 +185,7 @@ pub const fn from_device_interface_protocol(vendor: u16, product: u16, protocol: + idVendor: vendor, + idProduct: product, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -206,8 +197,7 @@ pub const fn from_device_interface_number(vendor: u16, product: u16, number: u8) + idVendor: vendor, + idProduct: product, + bInterfaceNumber: number, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -227,8 +217,7 @@ pub const fn from_device_and_interface_info( + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + } +@@ -393,6 +382,7 @@ fn as_ref(&self) -> &Device { + + // SAFETY: Instances of `Interface` are always reference-counted. + unsafe impl AlwaysRefCounted for Interface { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Interface` guarantee that `self.as_raw()` + // returns a valid `struct usb_interface` pointer, for which we will +@@ -400,6 +390,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_intf(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) } +@@ -444,6 +435,7 @@ fn as_raw(&self) -> *mut bindings::usb_device { + + // SAFETY: Instances of `Device` are always reference-counted. + unsafe impl AlwaysRefCounted for Device { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Device` guarantee that `self.as_raw()` + // returns a valid `struct usb_device` pointer, for which we will +@@ -451,6 +443,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_dev(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) } diff --git a/pkgbuilds/linux-omarchy-bore/0603-usb-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0603-usb-updates.patch.sig new file mode 100644 index 0000000..a76936f Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0603-usb-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0604-xhci-fixes.patch b/pkgbuilds/linux-omarchy-bore/0604-xhci-fixes.patch new file mode 100644 index 0000000..8545f24 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0604-xhci-fixes.patch @@ -0,0 +1,35 @@ +diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -2301,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) + if (!segs) + segs = ERST_DEFAULT_SEGS; + +- max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; ++ max_segs = BIT(FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2)); + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -4328,11 +4328,16 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + check_interval(urb, ep_ctx); + + /* +- * Check if this starts the isoc data flow. Relies on hw setting ep ctx +- * state after doorbell ring. Consider adding list_empty(td_list) check ++ * Schedule the URB discontiguously if all previous URBs have completed. ++ * XXX core can't tell if completions are pending but not running yet. + */ +- if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ if (list_empty(&ep_ring->td_list) && ++ !hcd_periodic_completion_in_progress(xhci_to_hcd(xhci), urb->ep)) { ++ if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) ++ xhci_dbg(xhci, "Unexpected running ring at isoc stream start, uframe: %d\n", ++ xep->next_uframe); + xep->next_uframe = -1; ++ } + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } diff --git a/pkgbuilds/linux-omarchy-bore/0604-xhci-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0604-xhci-fixes.patch.sig new file mode 100644 index 0000000..7d0570f Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0604-xhci-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0605-thunderbolt-pcie-rescan-after-tunnel.patch b/pkgbuilds/linux-omarchy-bore/0605-thunderbolt-pcie-rescan-after-tunnel.patch new file mode 100644 index 0000000..a0b7740 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0605-thunderbolt-pcie-rescan-after-tunnel.patch @@ -0,0 +1,186 @@ +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -909,6 +909,10 @@ int tb_domain_init(void) + { + int ret; + ++ ret = tb_pci_rescan_init(); ++ if (ret) ++ return ret; ++ + tb_configfs_init(); + tb_debugfs_init(); + tb_acpi_init(); +@@ -927,6 +931,7 @@ err_xdomain: + err_acpi: + tb_acpi_exit(); + tb_debugfs_exit(); ++ tb_pci_rescan_exit(); + + return ret; + } +@@ -940,4 +945,5 @@ void tb_domain_exit(void) + tb_acpi_exit(); + tb_debugfs_exit(); + tb_configfs_exit(); ++ tb_pci_rescan_exit(); + } +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -9,7 +9,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + + #include "tb.h" +@@ -18,6 +20,7 @@ + + #define TB_TIMEOUT 100 /* ms */ + #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ ++#define TB_PCI_RESCAN_DELAY 300 /* ms */ + + /* + * How many time bandwidth allocation request from graphics driver is +@@ -49,6 +52,13 @@ MODULE_PARM_DESC(asym_threshold, + "threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: " + __MODULE_STRING(TB_ASYM_THRESHOLD) ")"); + ++struct tb_pci_rescan { ++ struct delayed_work work; ++ struct kref kref; ++ struct pci_bus *bus; ++ bool stopped; ++}; ++ + /** + * struct tb_cm - Simple Thunderbolt connection manager + * @tunnel_list: List of active tunnels +@@ -67,8 +77,11 @@ struct tb_cm { + bool hotplug_active; + struct delayed_work remove_work; + struct tb_bandwidth_group groups[MAX_GROUPS]; ++ struct tb_pci_rescan *pci_rescan; + }; + ++static struct workqueue_struct *tb_pci_rescan_wq; ++ + static inline struct tb *tcm_to_tb(struct tb_cm *tcm) + { + return ((void *)tcm - sizeof(struct tb)); +@@ -2282,6 +2295,82 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw) + return 0; + } + ++static void tb_pci_rescan_release(struct kref *kref) ++{ ++ struct tb_pci_rescan *rescan = container_of(kref, typeof(*rescan), kref); ++ ++ put_device(&rescan->bus->dev); ++ kfree(rescan); ++} ++ ++static void tb_pci_rescan_work_fn(struct work_struct *work) ++{ ++ struct tb_pci_rescan *rescan = ++ container_of(to_delayed_work(work), typeof(*rescan), work); ++ ++ pci_lock_rescan_remove(); ++ if (!READ_ONCE(rescan->stopped)) ++ pci_rescan_bus(rescan->bus); ++ pci_unlock_rescan_remove(); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_schedule(struct tb *tb) ++{ ++ struct tb_cm *tcm = tb_priv(tb); ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) { ++ struct pci_bus *bus; ++ ++ if (!dev_is_pci(tb->nhi->dev)) ++ return; ++ bus = to_pci_dev(tb->nhi->dev)->bus; ++ if (bus->parent) ++ bus = bus->parent; ++ ++ rescan = kzalloc_obj(*rescan); ++ if (!rescan) ++ return; ++ INIT_DELAYED_WORK(&rescan->work, tb_pci_rescan_work_fn); ++ kref_init(&rescan->kref); ++ get_device(&bus->dev); ++ rescan->bus = bus; ++ tcm->pci_rescan = rescan; ++ } ++ ++ kref_get(&rescan->kref); ++ if (mod_delayed_work(tb_pci_rescan_wq, &rescan->work, ++ msecs_to_jiffies(TB_PCI_RESCAN_DELAY))) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_stop(struct tb_cm *tcm) ++{ ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) ++ return; ++ ++ WRITE_ONCE(rescan->stopped, true); ++ if (cancel_delayed_work(&rescan->work)) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ tcm->pci_rescan = NULL; ++} ++ ++int tb_pci_rescan_init(void) ++{ ++ tb_pci_rescan_wq = alloc_workqueue("thunderbolt_pci_rescan", ++ WQ_FREEZABLE | WQ_UNBOUND, 0); ++ return tb_pci_rescan_wq ? 0 : -ENOMEM; ++} ++ ++void tb_pci_rescan_exit(void) ++{ ++ destroy_workqueue(tb_pci_rescan_wq); ++} ++ + static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + { + struct tb_port *up, *down, *port; +@@ -2323,6 +2412,7 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + tb_sw_warn(sw, "failed to connect xHCI\n"); + + list_add_tail(&tunnel->list, &tcm->tunnel_list); ++ tb_pci_rescan_schedule(tb); + return 0; + } + +@@ -2957,6 +3047,7 @@ static void tb_stop(struct tb *tb) + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); ++ tb_pci_rescan_stop(tcm); + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -759,6 +759,8 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, + + struct tb *icm_probe(struct tb_nhi *nhi); + struct tb *tb_probe(struct tb_nhi *nhi); ++int tb_pci_rescan_init(void); ++void tb_pci_rescan_exit(void); + + extern const struct device_type tb_domain_type; + extern const struct device_type tb_retimer_type; diff --git a/pkgbuilds/linux-omarchy-bore/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig b/pkgbuilds/linux-omarchy-bore/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig new file mode 100644 index 0000000..68db430 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0606-thunderbolt-fixes-2.patch b/pkgbuilds/linux-omarchy-bore/0606-thunderbolt-fixes-2.patch new file mode 100644 index 0000000..0d85afa --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0606-thunderbolt-fixes-2.patch @@ -0,0 +1,239 @@ +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -788,21 +788,6 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + transmit_ring, receive_path, receive_ring); + } + +-static void tb_domain_reset_interface(struct tb *tb) +-{ +- struct tb_nhi *nhi = tb->nhi; +- +- if (!nhi->ops->reset_interface) +- return; +- +- guard(mutex)(&tb->lock); +- +- /* The reset clears the ring state so stop the control channel */ +- tb_ctl_stop(tb->ctl); +- nhi->ops->reset_interface(nhi); +- tb_ctl_start(tb->ctl); +-} +- + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -825,20 +810,11 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { +- int ret; +- + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); +- if (ret) +- return ret; +- +- if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) +- tb_domain_reset_interface(tb); +- +- return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -1180,32 +1180,6 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + +-/** +- * nhi_reset_interface() - Reset the host interface +- * @nhi: Host interface to reset +- * +- * Brings the registers in the memory BAR back to their default state and +- * clears the End-to-End Flow Control state. The caller is responsible for +- * stopping the control channel over the reset because it clears the ring +- * state as well. +- */ +-void nhi_reset_interface(struct tb_nhi *nhi) +-{ +- u32 val; +- +- val = ioread32(nhi->iobase + REG_CAPS); +- /* Only v1 host interfaces implement the reset */ +- if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) +- return; +- +- dev_dbg(nhi->dev, "issuing host interface reset\n"); +- +- iowrite32(REG_HOST_INTERFACE_RESET_RST, +- nhi->iobase + REG_HOST_INTERFACE_RESET); +- /* Wait for tHIReset (10 ms) to complete */ +- usleep_range(10000, 20000); +-} +- + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,8 +36,6 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); +-void nhi_reset_interface(struct tb_nhi *nhi); +- + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -54,7 +52,6 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook +- * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -69,7 +66,6 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); +- void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -120,24 +116,11 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e +- + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) +-#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,10 +115,6 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + +-/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ +-#define REG_HOST_INTERFACE_RESET 0x39858 +-#define REG_HOST_INTERFACE_RESET_RST BIT(0) +- + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,27 +62,6 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } +- } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { +- switch (pdev->device) { +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: +- /* +- * These AMD hosts may hang the Tx ring when the +- * DMA paths are torn down so they need the host +- * interface reset after each teardown. +- */ +- nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; +- break; +- } + } + } + +@@ -279,7 +258,6 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -463,7 +441,6 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c +--- a/drivers/thunderbolt/quirks.c ++++ b/drivers/thunderbolt/quirks.c +@@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = { + quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, ++ /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */ ++ { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5, ++ quirk_clx_disable }, + /* + * Block Runtime PM in DP redrive mode for Intel Barlow Ridge host + * controllers. +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -980,6 +980,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) + } + + mutex_unlock(&sdev->lock); ++ ++ /* Stream handles IOCB_NOWAIT just fine */ ++ file->f_mode |= FMODE_NOWAIT; + return 0; + + err_unlock: +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, + + len += sizeof(res->hdr.xd_hdr) / 4; + len -= sizeof(*res) / 4; ++ if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) { ++ ret = -EINVAL; ++ goto err; ++ } + + if (res->offset != req.offset) { + ret = -EINVAL; diff --git a/pkgbuilds/linux-omarchy-bore/0606-thunderbolt-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-bore/0606-thunderbolt-fixes-2.patch.sig new file mode 100644 index 0000000..4a16c2a Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0606-thunderbolt-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0607-usb-fixes.patch b/pkgbuilds/linux-omarchy-bore/0607-usb-fixes.patch new file mode 100644 index 0000000..2e3ea13 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0607-usb-fixes.patch @@ -0,0 +1,1673 @@ +diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c +--- a/drivers/usb/cdns3/cdns3-gadget.c ++++ b/drivers/usb/cdns3/cdns3-gadget.c +@@ -3269,6 +3269,10 @@ static void cdns3_gadget_exit(struct cdns *cdns) + usb_del_gadget(&priv_dev->gadget); + devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + ++ /* The works can still be queued until the IRQ is freed. */ ++ cancel_work_sync(&priv_dev->pending_status_wq); ++ cancel_work_sync(&priv_dev->aligned_buf_wq); ++ + cdns3_free_all_eps(priv_dev); + + while (!list_empty(&priv_dev->aligned_buf_list)) { +diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c +--- a/drivers/usb/cdns3/cdns3-pci-wrap.c ++++ b/drivers/usb/cdns3/cdns3-pci-wrap.c +@@ -96,6 +96,11 @@ static int cdns3_pci_probe(struct pci_dev *pdev, + + if (pci_is_enabled(func)) { + wrap = pci_get_drvdata(func); ++ if (!wrap) { ++ dev_err(&pdev->dev, ++ "second function not initialized, retrying\n"); ++ return -EPROBE_DEFER; ++ } + } else { + wrap = kzalloc_obj(*wrap); + if (!wrap) +diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c +--- a/drivers/usb/chipidea/ci_hdrc_tegra.c ++++ b/drivers/usb/chipidea/ci_hdrc_tegra.c +@@ -306,8 +306,10 @@ static int tegra_usb_probe(struct platform_device *pdev) + + pm_runtime_enable(&pdev->dev); + err = pm_runtime_resume_and_get(&pdev->dev); +- if (err) ++ if (err) { ++ pm_runtime_disable(&pdev->dev); + return err; ++ } + + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) + usb->needs_double_reset = true; +diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c +--- a/drivers/usb/class/cdc-acm.c ++++ b/drivers/usb/class/cdc-acm.c +@@ -331,7 +331,6 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) + + spin_lock_irqsave(&acm->read_lock, flags); + acm->ctrlin = newctrl; +- acm->oldcount = acm->iocount; + + if (difference & USB_CDC_SERIAL_STATE_DSR) + acm->iocount.dsr++; +@@ -788,6 +787,9 @@ static void acm_port_shutdown(struct tty_port *port) + usb_autopm_put_interface_async(acm->control); + } + ++ if (acm->disconnected) ++ return; ++ + acm_unpoison_urbs(acm); + + if (acm->quirks & ALWAYS_POLL_CTRL) { +@@ -1027,11 +1029,16 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + +- do { ++ spin_lock_irq(&acm->read_lock); ++ old = acm->iocount; ++ spin_unlock_irq(&acm->read_lock); ++ ++ add_wait_queue(&acm->wioctl, &wait); ++ for (;;) { ++ set_current_state(TASK_INTERRUPTIBLE); ++ + spin_lock_irq(&acm->read_lock); +- old = acm->oldcount; + new = acm->iocount; +- acm->oldcount = new; + spin_unlock_irq(&acm->read_lock); + + if ((arg & TIOCM_DSR) && +@@ -1044,22 +1051,20 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + old.rng != new.rng) + break; + +- add_wait_queue(&acm->wioctl, &wait); +- set_current_state(TASK_INTERRUPTIBLE); +- schedule(); +- remove_wait_queue(&acm->wioctl, &wait); + if (acm->disconnected) { +- if (arg & TIOCM_CD) +- break; +- else +- rv = -ENODEV; +- } else { +- if (signal_pending(current)) +- rv = -ERESTARTSYS; ++ rv = -ENODEV; ++ break; + } +- } while (!rv); + +- ++ schedule(); ++ ++ if (signal_pending(current)) { ++ rv = -ERESTARTSYS; ++ break; ++ } ++ } ++ __set_current_state(TASK_RUNNING); ++ remove_wait_queue(&acm->wioctl, &wait); + + return rv; + } +diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h +--- a/drivers/usb/class/cdc-acm.h ++++ b/drivers/usb/class/cdc-acm.h +@@ -90,7 +90,6 @@ struct acm { + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ +- struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -73,8 +73,11 @@ + /* + * Give SS hubs 200ms time after wake to train downstream links before + * assuming no port activity and allowing hub to runtime suspend back. ++ * Root hubs have no upstream hub whose wake propagation needs to be ++ * accounted for, so they need less time, use 120ms for them. + */ + #define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ ++#define USB_SS_RH_PORT_U0_WAKE_TIME 120 /* ms */ + + /* Protect struct usb_device->state and ->children members + * Note: Both are also protected by ->dev.sem, except that ->state can +@@ -1358,7 +1361,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) + + queue_delayed_work(system_power_efficient_wq, + &hub->post_resume_work, +- msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); ++ msecs_to_jiffies(hdev->parent ? ++ USB_SS_PORT_U0_WAKE_TIME : ++ USB_SS_RH_PORT_U0_WAKE_TIME)); + return; + } + +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1337,7 +1337,8 @@ static void remove_intf_ep_devs(struct usb_interface *intf) + * + * Disables the endpoint for URB submission and nukes all pending URBs. + * If @reset_hardware is set then also deallocates hcd/hardware state +- * for the endpoint. ++ * for the endpoint (clearing both ep_in and ep_out pointers for ++ * bidirectional non-ep0 control endpoints). + */ + void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + bool reset_hardware) +@@ -1358,6 +1359,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + dev->ep_in[epnum] = NULL; + } + if (ep) { ++ if (reset_hardware && epnum != 0 && ++ usb_endpoint_xfer_control(&ep->desc)) { ++ dev->ep_out[epnum] = NULL; ++ dev->ep_in[epnum] = NULL; ++ } + ep->enabled = 0; + usb_hcd_flush_endpoint(dev, ep); + if (reset_hardware) +diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c +--- a/drivers/usb/dwc2/hcd.c ++++ b/drivers/usb/dwc2/hcd.c +@@ -5082,14 +5082,13 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) + } + + cancel_work_sync(&hsotg->phy_reset_work); +- +- timer_delete(&hsotg->wkp_timer); + } + + static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) + { + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); ++ timer_shutdown_sync(&hsotg->wkp_timer); + + dwc2_hcd_free(hsotg); + } +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -49,6 +49,7 @@ + #define DWC3_ENDPOINTS_NUM 32 + #define DWC3_XHCI_RESOURCES_NUM 2 + #define DWC3_ISOC_MAX_RETRIES 5 ++#define DWC3_ERR_RECOVERY_MAX 3 + + #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ + #define DWC3_EVENT_BUFFERS_SIZE 4096 +@@ -841,6 +842,12 @@ enum dwc3_link_state { + DWC3_LINK_STATE_MASK = 0x0f, + }; + ++enum dwc3_err_state { ++ DWC3_ERR_NONE = 0, ++ DWC3_ERR_RECOVERY, ++ DWC3_ERR_UNRECOVERABLE, ++}; ++ + /* TRB Length, PCM and Status */ + #define DWC3_TRB_SIZE_MASK (0x00ffffff) + #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) +@@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { + /** + * struct dwc3 - representation of our controller + * @drd_work: workqueue used for role swapping ++ * @err_recovery_work: workqueue used for controller error recovery + * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @setup_buf: used while precessing STD USB requests +@@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { + * @ep0_in_setup: one control transfer is completed and enter setup phase + * @lock: for synchronizing + * @mutex: for mode switching ++ * @connect_mutex: for the pull-up and err_recovery_work + * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device + * @xhci: pointer to our xHCI child +@@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { + * @ep0_next_event: hold the next expected event + * @ep0state: state of endpoint zero + * @link_state: link state ++ * @err_state: current error recovery state. ++ * @err_recovery_count: number of consecutive error recovery attempts until ++ * confirmed healthy and reset to 0 on reset event. + * @speed: device speed (super, high, full, low) + * @hwparams: copy of hwparams registers + * @regset: debugfs pointer to regdump file +@@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { + */ + struct dwc3 { + struct work_struct drd_work; ++ struct work_struct err_recovery_work; + struct dwc3_trb *ep0_trb; + void *bounce; + u8 *setup_buf; +@@ -1203,6 +1216,9 @@ struct dwc3 { + /* mode switching lock */ + struct mutex mutex; + ++ /* serializes pull-up run/stop vs error recovery */ ++ struct mutex connect_mutex; ++ + struct device *dev; + struct device *sysdev; + +@@ -1332,6 +1348,9 @@ struct dwc3 { + enum dwc3_ep0_next ep0_next_event; + enum dwc3_ep0_state ep0state; + enum dwc3_link_state link_state; ++ enum dwc3_err_state err_state; ++ ++ u32 err_recovery_count; + + u16 u2sel; + u16 u2pel; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -299,6 +299,7 @@ static int dwc3_ti_probe(struct platform_device *pdev) + + err_pm_disable: + clk_disable_unprepare(am62->usb2_refclk); ++ pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + return ret; +diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c +--- a/drivers/usb/dwc3/ep0.c ++++ b/drivers/usb/dwc3/ep0.c +@@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ ret = -ESHUTDOWN; ++ goto out; ++ } ++ + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { + dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", + dep->name); +@@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep0_set_halt(ep, value); + spin_unlock_irqrestore(&dwc->lock, flags); + +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, + return 0; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_queue(dep, req); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_set_halt(dep, value, false); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + dep->flags |= DWC3_EP_WEDGE; + + if (dep->number == 0 || dep->number == 1) +@@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + if (!dwc->gadget->wakeup_armed) { + dev_err(dwc->dev, "not armed for remote wakeup\n"); + spin_unlock_irqrestore(&dwc->lock, flags); +@@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + /* + * If the link is in U3, signal for remote wakeup and wait for the + * link to transition to U0 before sending device notification. +@@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) + + synchronize_irq(dwc->irq_gadget); + ++ /* Serialize against dwc3_err_recovery_work() */ ++ mutex_lock(&dwc->connect_mutex); + if (!is_on) + ret = dwc3_gadget_soft_disconnect(dwc); + else + ret = dwc3_gadget_soft_connect(dwc); ++ mutex_unlock(&dwc->connect_mutex); + + pm_runtime_put(dwc->dev); + +@@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) + + dwc->suspended = false; + ++ /* The controller is recovered. */ ++ if (dwc->err_state == DWC3_ERR_NONE) ++ dwc->err_recovery_count = 0; ++ + /* + * Ideally, dwc3_reset_gadget() would trigger the function + * drivers to stop any active transfers through ep disable. +@@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) + return ret; + } + ++static void dwc3_schedule_err_recovery(struct dwc3 *dwc) ++{ ++ dwc->err_state = DWC3_ERR_RECOVERY; ++ schedule_work(&dwc->err_recovery_work); ++} ++ + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + { + struct dwc3 *dwc = evt->dwc; +@@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + return IRQ_NONE; + + if (count > evt->length) { +- dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", ++ dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + count, evt->length); +- return IRQ_NONE; ++ /* ++ * This is a fatal error - the driver and controller are out of ++ * sync on which event has been consumed. Reinitializing the ++ * controller is required to recover. Write the bogus count back ++ * to GEVNTCOUNT to clear the IRQ source, consistent with the ++ * stale event clearing in dwc3_event_buffers_setup(), then ++ * schedule error recovery. ++ */ ++ spin_lock(&dwc->lock); ++ dwc3_schedule_err_recovery(dwc); ++ spin_unlock(&dwc->lock); ++ dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); ++ return IRQ_HANDLED; + } + + evt->count = count; +@@ -4735,6 +4784,57 @@ static void dwc_gadget_release(struct device *dev) + kfree(gadget); + } + ++static void dwc3_err_recovery_work(struct work_struct *work) ++{ ++ struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); ++ unsigned long flags; ++ int ret; ++ ++ /* serializes against dwc3_gadget_pullup() */ ++ mutex_lock(&dwc->connect_mutex); ++ ++ ret = dwc3_gadget_soft_disconnect(dwc); ++ ++ dwc3_disconnect_gadget_sleepable(dwc); ++ ++ if (ret) ++ goto err_unrecoverable; ++ ++ if (dwc->softconnect) { ++ u32 count; ++ /* ++ * Wait irq to finish before soft_connect resets evt->lpos and ++ * the event buffer registers to avoid racing with ++ * dwc3_process_event_buf(). ++ */ ++ synchronize_irq(dwc->irq_gadget); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ count = ++dwc->err_recovery_count; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ if (count > DWC3_ERR_RECOVERY_MAX) ++ goto err_unrecoverable; ++ ++ ret = dwc3_gadget_soft_connect(dwc); ++ if (ret) ++ goto err_unrecoverable; ++ } ++ mutex_unlock(&dwc->connect_mutex); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_NONE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return; ++ ++err_unrecoverable: ++ dev_err(dwc->dev, "Unable to recover the controller\n"); ++ mutex_unlock(&dwc->connect_mutex); ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_UNRECOVERABLE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++} ++ + /** + * dwc3_gadget_init - initializes gadget related registers + * @dwc: pointer to our controller context structure +@@ -4778,6 +4878,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) + } + + init_completion(&dwc->ep0_in_setup); ++ INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); ++ mutex_init(&dwc->connect_mutex); + dwc->gadget = kzalloc_obj(struct usb_gadget); + if (!dwc->gadget) { + ret = -ENOMEM; +@@ -4874,6 +4976,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) + if (!dwc->gadget) + return; + ++ cancel_work_sync(&dwc->err_recovery_work); ++ mutex_destroy(&dwc->connect_mutex); + dwc3_enable_susphy(dwc, true); + usb_del_gadget(dwc->gadget); + dwc3_gadget_free_endpoints(dwc); +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -1877,8 +1877,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + break; + case FUNCTIONFS_ENDPOINT_DESC: + { ++ const struct usb_endpoint_descriptor *desc; ++ struct usb_endpoint_descriptor desc1; + int desc_idx; +- struct usb_endpoint_descriptor desc1, *desc; + + switch (epfile->ffs->gadget->speed) { + case USB_SPEED_SUPER: +@@ -1892,7 +1893,17 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + desc_idx = 0; + } + +- desc = epfile->ep->descs[desc_idx]; ++ do { ++ desc = epfile->ep->descs[desc_idx]; ++ } while (!desc && --desc_idx >= 0); ++ ++ if (!desc) ++ desc = epfile->ep->ep->desc; ++ if (!desc) { ++ ret = -EINVAL; ++ break; ++ } ++ + memcpy(&desc1, desc, desc->bLength); + + spin_unlock_irq(&epfile->ffs->eps_lock); +diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c +--- a/drivers/usb/gadget/function/f_midi2.c ++++ b/drivers/usb/gadget/function/f_midi2.c +@@ -1633,8 +1633,8 @@ struct f_midi2_usb_config { + + /* MIDI 1.0 jacks */ + unsigned char jack_in, jack_out, jack_id; +- struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; +- struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; ++ struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES * 2]; ++ struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES * 2]; + }; + + static int append_config(struct f_midi2_usb_config *config, void *d) +diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c +--- a/drivers/usb/gadget/function/f_uac1_legacy.c ++++ b/drivers/usb/gadget/function/f_uac1_legacy.c +@@ -797,6 +797,9 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) + + static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + con->data[cmd] = value; + + return 0; +@@ -804,6 +807,9 @@ static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + + static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + return con->data[cmd]; + } + +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/core.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c +@@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev) + for (i = 0; i < vhub->max_ports; i++) + ast_vhub_del_dev(&vhub->ports[i].dev); + ++ /* Final drain; the worker takes vhub->lock, so stay outside of it */ ++ cancel_work_sync(&vhub->wake_work); ++ + spin_lock_irqsave(&vhub->lock, flags); + + /* Mask & ack all interrupts */ +@@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev) + return -ENOMEM; + + spin_lock_init(&vhub->lock); ++ INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + vhub->pdev = pdev; + vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, + VHUB_IRQ_DEV1_BIT); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +@@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) + int rc = -EINVAL; + + spin_lock_irqsave(&d->vhub->lock, flags); +- if (!d->wakeup_en) ++ if (!d->wakeup_en || !d->registered) + goto err; + + DDBG(d, "Device initiated wakeup\n"); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +@@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub, + ast_vhub_send_host_wakeup(vhub); + } + +-static void ast_vhub_wake_work(struct work_struct *work) ++void ast_vhub_wake_work(struct work_struct *work) + { + struct ast_vhub *vhub = container_of(work, + struct ast_vhub, +@@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work) + + void ast_vhub_hub_wake_all(struct ast_vhub *vhub) + { ++ lockdep_assert_held(&vhub->lock); ++ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain +@@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) + int ast_vhub_init_hub(struct ast_vhub *vhub) + { + vhub->speed = USB_SPEED_UNKNOWN; +- INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + + return ast_vhub_init_desc(vhub); + } +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +--- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h ++++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +@@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub); + void ast_vhub_hub_resume(struct ast_vhub *vhub); + void ast_vhub_hub_reset(struct ast_vhub *vhub); + void ast_vhub_hub_wake_all(struct ast_vhub *vhub); ++void ast_vhub_wake_work(struct work_struct *work); + + /* dev.c */ + int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); +diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c +--- a/drivers/usb/gadget/udc/dummy_hcd.c ++++ b/drivers/usb/gadget/udc/dummy_hcd.c +@@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, + { + bool fifo = req == &dum->fifo_req; + ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + usb_gadget_giveback_request(_ep, &req->req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + if (fifo) + dum->fifo_req_busy = 0; + } +@@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, + req->req.complete = fifo_complete; + + list_add_tail(&req->queue, &ep->queue); ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + _req->actual = _req->length; + _req->status = 0; + usb_gadget_giveback_request(_ep, _req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + } else + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&dum->lock, flags); +diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c +--- a/drivers/usb/host/octeon-hcd.c ++++ b/drivers/usb/host/octeon-hcd.c +@@ -378,8 +378,22 @@ struct octeon_hcd { + struct cvmx_usb_transaction *active_split; + struct cvmx_usb_tx_fifo periodic; + struct cvmx_usb_tx_fifo nonperiodic; ++ struct hrtimer sof_timer; + }; + ++static int cvmx_usb_poll(struct octeon_hcd *usb); ++ ++static enum hrtimer_restart octeon_usb_sof_timer(struct hrtimer *t) ++{ ++ struct octeon_hcd *usb = container_of(t, struct octeon_hcd, sof_timer); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&usb->lock, flags); ++ cvmx_usb_poll(usb); ++ spin_unlock_irqrestore(&usb->lock, flags); ++ return HRTIMER_NORESTART; ++} ++ + /* + * This macro logically sets a single field in a CSR. It does the sequence + * read, modify, and write +@@ -578,7 +592,7 @@ static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) + int result; + u64 address = CVMX_USBCX_GRSTCTL(usb->index); + u64 done = cvmx_get_cycle() + 100 * +- (u64)octeon_get_clock_rate / 1000000; ++ (u64)octeon_get_clock_rate() / 1000000; + union cvmx_usbcx_grstctl c; + + while (1) { +@@ -853,6 +867,14 @@ static int cvmx_usb_initialize(struct device *dev, + * USBC_GAHBCFG[PTXFEMPLVL] + * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 + */ ++ usbcx_gahbcfg.u32 = cvmx_usb_read_csr32(usb, ++ CVMX_USBCX_GHWCFG3(usb->index)); ++ if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { ++ dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", ++ usbcx_gahbcfg.u32); ++ return -ENODEV; ++ } ++ + usbcx_gahbcfg.u32 = 0; + usbcx_gahbcfg.s.dmaen = !(usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA); +@@ -1900,6 +1922,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + int channel; + struct cvmx_usb_pipe *pipe; + int need_sof; ++ u64 min_due; + enum cvmx_usb_transfer ttype; + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { +@@ -1940,15 +1963,33 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + * future that might need to be scheduled + */ + need_sof = 0; ++ min_due = ~0ull; + for (ttype = CVMX_USB_TRANSFER_CONTROL; + ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { + list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { +- if (pipe->next_tx_frame > usb->frame_number) { +- need_sof = 1; +- break; +- } ++ if (pipe->next_tx_frame > usb->frame_number && ++ pipe->next_tx_frame < min_due) ++ min_due = pipe->next_tx_frame; + } + } ++ if (min_due != ~0ull) { ++ u64 delta = min_due - usb->frame_number; ++ ++ /* ++ * frame_number is resynced from HFNUM on every poll, so a ++ * deadline that is many frames away does not need an ++ * interrupt on every SOF to count them down - sleep on the ++ * timer instead and keep SOF interrupts for deadlines within ++ * a few frames. Stay well below the 16383-frame wrap of ++ * HFNUM. One (micro)frame is 125us in high-speed mode. ++ */ ++ if (delta <= 4 || delta > 8000) ++ need_sof = 1; ++ else ++ hrtimer_start(&usb->sof_timer, ++ ns_to_ktime((delta - 2) * 125000), ++ HRTIMER_MODE_REL); ++ } + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, sofmsk, need_sof); + } +@@ -3638,6 +3679,8 @@ static int octeon_usb_probe(struct platform_device *pdev) + usb = (struct octeon_hcd *)hcd->hcd_priv; + + spin_lock_init(&usb->lock); ++ hrtimer_setup(&usb->sof_timer, octeon_usb_sof_timer, CLOCK_MONOTONIC, ++ HRTIMER_MODE_REL); + + usb->init_flags = initialize_flags; + +@@ -3688,6 +3731,7 @@ static void octeon_usb_remove(struct platform_device *pdev) + unsigned long flags; + + usb_remove_hcd(hcd); ++ hrtimer_cancel(&usb->sof_timer); + spin_lock_irqsave(&usb->lock, flags); + status = cvmx_usb_shutdown(usb); + spin_unlock_irqrestore(&usb->lock, flags); +diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c +--- a/drivers/usb/host/ohci-da8xx.c ++++ b/drivers/usb/host/ohci-da8xx.c +@@ -447,6 +447,7 @@ static int ohci_da8xx_probe(struct platform_device *pdev) + + err_remove_hcd: + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + err: + usb_put_hcd(hcd); + return error; +@@ -457,6 +458,7 @@ static void ohci_da8xx_remove(struct platform_device *pdev) + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + usb_put_hcd(hcd); + } + +diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c +--- a/drivers/usb/host/ohci-s3c2410.c ++++ b/drivers/usb/host/ohci-s3c2410.c +@@ -335,6 +335,7 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) + struct usb_hcd *hcd = platform_get_drvdata(dev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + s3c2410_stop_hc(dev); + usb_put_hcd(hcd); + } +diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c +--- a/drivers/usb/host/ohci-spear.c ++++ b/drivers/usb/host/ohci-spear.c +@@ -103,6 +103,7 @@ static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) + struct spear_ohci *sohci_p = to_spear_ohci(hcd); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + clk_disable_unprepare(sohci_p->clk); + + usb_put_hcd(hcd); +diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c +--- a/drivers/usb/host/ohci-st.c ++++ b/drivers/usb/host/ohci-st.c +@@ -235,6 +235,7 @@ static void st_ohci_platform_remove(struct platform_device *dev) + int clk; + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + + if (pdata->power_off) + pdata->power_off(dev); +diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c +--- a/drivers/usb/serial/bus.c ++++ b/drivers/usb/serial/bus.c +@@ -165,7 +165,11 @@ int usb_serial_bus_register(struct usb_serial_driver *driver) + + void usb_serial_bus_deregister(struct usb_serial_driver *driver) + { +- free_dynids(driver); + driver_unregister(&driver->driver); ++ free_dynids(driver); + } + ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver) ++{ ++ driver_remove_file(&driver->driver, &driver_attr_new_id); ++} +diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c +--- a/drivers/usb/serial/cp210x.c ++++ b/drivers/usb/serial/cp210x.c +@@ -223,6 +223,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ + { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ + { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ ++ { USB_DEVICE(0x1B1C, 0x1C02) }, /* Corsair AX1500i Power Supply */ + { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ + { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ + { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ +diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c +--- a/drivers/usb/serial/generic.c ++++ b/drivers/usb/serial/generic.c +@@ -266,6 +266,7 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); + void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + unsigned int bps; + unsigned long period; + unsigned long expire; +@@ -285,7 +286,14 @@ void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + __func__, jiffies_to_msecs(timeout), + jiffies_to_msecs(period)); + expire = jiffies + timeout; +- while (!port->serial->type->tx_empty(port)) { ++ for (;;) { ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty) || port->serial->type->tx_empty(port)) { ++ mutex_unlock(&tport->mutex); ++ break; ++ } ++ mutex_unlock(&tport->mutex); ++ + schedule_timeout_interruptible(period); + if (signal_pending(current)) + break; +diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c +--- a/drivers/usb/serial/option.c ++++ b/drivers/usb/serial/option.c +@@ -260,6 +260,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EM060K_12a 0x012a + #define QUECTEL_PRODUCT_EM060K_12b 0x012b + #define QUECTEL_PRODUCT_EM060K_12c 0x012c ++#define QUECTEL_PRODUCT_RG660QB 0x013d + #define QUECTEL_PRODUCT_EG91 0x0191 + #define QUECTEL_PRODUCT_EG95 0x0195 + #define QUECTEL_PRODUCT_BG96 0x0296 +@@ -280,6 +281,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EC200U 0x0901 + #define QUECTEL_PRODUCT_EG912Y 0x6001 + #define QUECTEL_PRODUCT_EC200S_CN 0x6002 ++#define QUECTEL_PRODUCT_EG060W 0x6004 + #define QUECTEL_PRODUCT_EC200A 0x6005 + #define QUECTEL_PRODUCT_EG916Q 0x6007 + #define QUECTEL_PRODUCT_EM061K_LWW 0x6008 +@@ -1268,12 +1270,15 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG060W, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0xff, 0x30) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, +@@ -2168,6 +2173,8 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ + .driver_info = RSVD(7) }, ++ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x902b, 0xff), ++ .driver_info = RSVD(4) }, + { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ +@@ -2429,6 +2436,13 @@ static const struct usb_device_id option_ids[] = { + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ + .driver_info = RSVD(3) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x01) }, /* Compal EXC-T1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x02) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x03) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x04) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, + { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ + .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, + { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ +diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c +--- a/drivers/usb/serial/quatech2.c ++++ b/drivers/usb/serial/quatech2.c +@@ -52,6 +52,7 @@ + #define QT2_XMIT_FLUSH 0x05 /* no following info */ + #define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 921600 + #define DEFAULT_BAUD_RATE 9600 + +@@ -156,7 +157,7 @@ static inline int calc_baud_divisor(int baudrate) + + static inline int qt2_set_port_config(struct usb_device *dev, + unsigned char port_number, +- u16 baudrate, u16 lcr) ++ speed_t baudrate, u16 lcr) + { + int divisor = calc_baud_divisor(baudrate); + u16 index = ((u16) (lcr << 8) | (u16) (port_number)); +@@ -257,9 +258,9 @@ static void qt2_set_termios(struct tty_struct *tty, + struct usb_device *dev = port->serial->dev; + struct qt2_port_private *port_priv; + struct ktermios *termios = &tty->termios; +- u16 baud; + unsigned int cflag = termios->c_cflag; + u16 new_lcr = 0; ++ speed_t baud; + int status; + + port_priv = usb_get_serial_port_data(port); +@@ -277,6 +278,18 @@ static void qt2_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + status = qt2_set_port_config(dev, port_priv->device_port, baud, + new_lcr); + if (status < 0) +@@ -373,7 +386,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) + port_priv->device_port = (u8) device_port; + + if (tty) +- qt2_set_termios(tty, port, &tty->termios); ++ qt2_set_termios(tty, port, NULL); + + return 0; + +diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c +--- a/drivers/usb/serial/ssu100.c ++++ b/drivers/usb/serial/ssu100.c +@@ -32,6 +32,7 @@ + + #define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 460800 + + #define ATC_DISABLED 0x00 +@@ -217,9 +218,10 @@ static void ssu100_set_termios(struct tty_struct *tty, + { + struct usb_device *dev = port->serial->dev; + struct ktermios *termios = &tty->termios; +- u16 baud, divisor, remainder; ++ speed_t baud, remainder; + unsigned int cflag = termios->c_cflag; + u16 urb_value = 0; /* will hold the new flags */ ++ u16 divisor; + int result; + + if (cflag & PARENB) { +@@ -235,6 +237,18 @@ static void ssu100_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); + + +@@ -310,7 +324,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) + dev_dbg(&port->dev, "%s - set uart failed\n", __func__); + + if (tty) +- ssu100_set_termios(tty, port, &tty->termios); ++ ssu100_set_termios(tty, port, NULL); + + return usb_serial_generic_open(tty, port); + } +diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c +--- a/drivers/usb/serial/usb-serial.c ++++ b/drivers/usb/serial/usb-serial.c +@@ -400,17 +400,13 @@ static unsigned int serial_chars_in_buffer(struct tty_struct *tty) + static void serial_wait_until_sent(struct tty_struct *tty, int timeout) + { + struct usb_serial_port *port = tty->driver_data; +- struct usb_serial *serial = port->serial; + + dev_dbg(&port->dev, "%s\n", __func__); + + if (!port->serial->type->wait_until_sent) + return; + +- mutex_lock(&serial->disc_mutex); +- if (!serial->disconnected) +- port->serial->type->wait_until_sent(tty, timeout); +- mutex_unlock(&serial->disc_mutex); ++ port->serial->type->wait_until_sent(tty, timeout); + } + + static void serial_throttle(struct tty_struct *tty) +@@ -438,8 +434,13 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + unsigned int close_delay, closing_wait; ++ int ret = 0; + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + close_delay = jiffies_to_msecs(tport->close_delay) / 10; + closing_wait = tport->closing_wait; +@@ -452,10 +453,10 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + + if (port->serial->type->get_serial) + port->serial->type->get_serial(tty, ss); +- ++out_unlock: + mutex_unlock(&tport->mutex); + +- return 0; ++ return ret; + } + + static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) +@@ -471,6 +472,10 @@ static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) + closing_wait = msecs_to_jiffies(closing_wait * 10); + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + if (!capable(CAP_SYS_ADMIN)) { + if (close_delay != tport->close_delay || +@@ -498,6 +503,7 @@ static int serial_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + int retval = -ENOIOCTLCMD; + + dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd); +@@ -508,8 +514,21 @@ static int serial_ioctl(struct tty_struct *tty, + retval = port->serial->type->tiocmiwait(tty, arg); + break; + default: +- if (port->serial->type->ioctl) ++ if (!port->serial->type->ioctl) ++ break; ++ ++ if (cmd == TIOCSRS485) ++ down_write(&tty->termios_rwsem); ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ retval = -EIO; ++ else + retval = port->serial->type->ioctl(tty, cmd, arg); ++ mutex_unlock(&tport->mutex); ++ ++ if (cmd == TIOCSRS485) ++ up_write(&tty->termios_rwsem); + } + + return retval; +@@ -519,25 +538,40 @@ static void serial_set_termios(struct tty_struct *tty, + const struct ktermios *old) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->set_termios) +- port->serial->type->set_termios(tty, port, old); +- else ++ if (!port->serial->type->set_termios) { + tty_termios_copy_hw(&tty->termios, old); ++ return; ++ } ++ ++ mutex_lock(&tport->mutex); ++ if (!tty_io_error(tty)) ++ port->serial->type->set_termios(tty, port, old); ++ mutex_unlock(&tport->mutex); + } + + static int serial_break(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->break_ctl) +- return port->serial->type->break_ctl(tty, break_state); ++ if (!port->serial->type->break_ctl) ++ return -ENOTTY; + +- return -ENOTTY; ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->break_ctl(tty, break_state); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_proc_show(struct seq_file *m, void *v) +@@ -578,24 +612,44 @@ static int serial_proc_show(struct seq_file *m, void *v) + static int serial_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmget) +- return port->serial->type->tiocmget(tty); +- return -ENOTTY; ++ if (!port->serial->type->tiocmget) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmget(tty); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmset) +- return port->serial->type->tiocmset(tty, set, clear); +- return -ENOTTY; ++ if (!port->serial->type->tiocmset) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmset(tty, set, clear); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_get_icount(struct tty_struct *tty, +@@ -1191,11 +1245,16 @@ static void usb_serial_disconnect(struct usb_interface *interface) + usb_serial_port_poison_urbs(port); + wake_up_interruptible(&port->port.delta_msr_wait); + cancel_work_sync(&port->work); ++ } ++ ++ if (serial->type->disconnect) ++ serial->type->disconnect(serial); ++ ++ for (i = 0; i < serial->num_ports; ++i) { ++ port = serial->port[i]; + if (device_is_registered(&port->dev)) + device_del(&port->dev); + } +- if (serial->type->disconnect) +- serial->type->disconnect(serial); + + release_sibling(serial, interface); + +@@ -1464,7 +1523,7 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + { + int rc; + struct usb_driver *udriver; +- struct usb_serial_driver * const *sd; ++ struct usb_serial_driver * const *sd, * const *s; + + /* + * udriver must be registered before any of the serial drivers, +@@ -1517,9 +1576,11 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + return 0; + + err_deregister_drivers: ++ for (s = serial_drivers; s < sd; ++s) ++ usb_serial_bus_remove_new_id(*s); ++ usb_deregister(udriver); + while (sd-- > serial_drivers) + usb_serial_deregister(*sd); +- usb_deregister(udriver); + err_free_driver: + kfree(udriver); + return rc; +@@ -1537,10 +1598,23 @@ EXPORT_SYMBOL_GPL(__usb_serial_register_drivers); + void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) + { + struct usb_driver *udriver = (*serial_drivers)->usb_driver; ++ struct usb_serial_driver * const *sd; ++ ++ /* ++ * udriver must be deregistered before the serial drivers so that ++ * I/O is stopped before unbinding the ports. ++ * ++ * Remove the new_id attributes to prevent ids from being added and ++ * triggering a probe of udriver after it has been deregistered. ++ */ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_bus_remove_new_id(*sd); + +- for (; *serial_drivers; ++serial_drivers) +- usb_serial_deregister(*serial_drivers); + usb_deregister(udriver); ++ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_deregister(*sd); ++ + kfree(udriver); + } + EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); +diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c +--- a/drivers/usb/serial/xr_serial.c ++++ b/drivers/usb/serial/xr_serial.c +@@ -850,12 +850,9 @@ static int xr_get_rs485_config(struct tty_struct *tty, + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + +- down_read(&tty->termios_rwsem); +- if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { +- up_read(&tty->termios_rwsem); ++ /* core holds port mutex */ ++ if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) + return -EFAULT; +- } +- up_read(&tty->termios_rwsem); + + return 0; + } +@@ -871,10 +868,9 @@ static int xr_set_rs485_config(struct tty_struct *tty, + return -EFAULT; + xr_sanitize_serial_rs485(&rs485); + +- down_write(&tty->termios_rwsem); ++ /* core holds termios rwsem and port mutex */ + data->rs485 = rs485; + xr_set_flow_mode(tty, port, NULL); +- up_write(&tty->termios_rwsem); + + if (copy_to_user(argp, &rs485, sizeof(rs485))) + return -EFAULT; +diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c +--- a/drivers/usb/storage/sierra_ms.c ++++ b/drivers/usb/storage/sierra_ms.c +@@ -77,6 +77,13 @@ static int sierra_get_swoc_info(struct usb_device *udev, + sizeof(struct swoc_info), /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + ++ /* ++ * A short IN transfer leaves the tail of swocInfo uninitialized; ++ * only a full transfer is valid. ++ */ ++ if (result != sizeof(struct swoc_info)) ++ return -EIO; ++ + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); + swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); + return result; +diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c +--- a/drivers/usb/typec/anx7411.c ++++ b/drivers/usb/typec/anx7411.c +@@ -1566,8 +1566,11 @@ static void anx7411_i2c_remove(struct i2c_client *client) + anx7411_partner_unregister_altmode(plat); + anx7411_unregister_partner(plat); + +- if (plat->workqueue) ++ if (plat->workqueue) { ++ disable_irq(plat->intp_irq); ++ cancel_work_sync(&plat->work); + destroy_workqueue(plat->workqueue); ++ } + + i2c_unregister_device(plat->spi_client); + +diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c +--- a/drivers/usb/typec/port-mapper.c ++++ b/drivers/usb/typec/port-mapper.c +@@ -42,6 +42,23 @@ static int usb4_port_compare(struct device *dev, void *fwnode) + return usb4_usb3_port_match(dev, fwnode); + } + ++static bool typec_has_usb4_host_interface(const struct fwnode_handle *fwnode) ++{ ++ if (!IS_REACHABLE(CONFIG_USB4)) ++ return false; ++ ++ struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = ++ fwnode_find_reference(fwnode, "usb4-host-interface", 0); ++ ++ /* ++ * The USB4 port can only appear if the host interface is enabled in ++ * the firmware and has been enumerated as a device. The latter is ++ * the same check as in usb_acpi_add_usb4_devlink(). ++ */ ++ return !IS_ERR(nhi_fwnode) && fwnode_device_is_available(nhi_fwnode) && ++ nhi_fwnode->dev; ++} ++ + static int typec_port_compare(struct device *dev, void *fwnode) + { + return device_match_fwnode(dev, fwnode); +@@ -64,11 +81,11 @@ static int typec_port_match(struct device *dev, void *data) + adev_fwnode); + + /* +- * If dev is USB 3.x port, it may have reference to the ++ * If dev is USB 3.x port, it may have reference to an available + * USB4 host interface in which case we can also link the + * Type-C port with the USB4 port. + */ +- if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) ++ if (typec_has_usb4_host_interface(adev_fwnode)) + component_match_add(&arg->port->dev, &arg->match, + usb4_port_compare, adev_fwnode); + } +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -1756,7 +1756,8 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) + { + hrtimer_cancel(&port->vdm_discovery_timer); +- kthread_cancel_work_sync(&port->vdm_discovery_work); ++ if (port->wq) ++ kthread_cancel_work_sync(&port->vdm_discovery_work); + } + + static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +@@ -7113,7 +7114,7 @@ static void tcpm_pd_event_handler(struct kthread_work *work) + port->upcoming_state = FR_SWAP_SEND; + ret = tcpm_ams_start(port, FAST_ROLE_SWAP); + if (ret == -EAGAIN) +- port->upcoming_state = INVALID_STATE; ++ tcpm_set_state(port, ERROR_RECOVERY, 0); + } else { + tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); + } +@@ -8977,6 +8978,7 @@ void tcpm_unregister_port(struct tcpm_port *port) + + port->registered = false; + kthread_destroy_worker(port->wq); ++ port->wq = NULL; + + hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -343,7 +343,7 @@ static void tps6598x_set_data_role(struct tps6598x *tps, + + static int tps6598x_connect(struct tps6598x *tps, u32 status) + { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + enum typec_pwr_opmode mode; + int ret; + +@@ -354,7 +354,6 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) + + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) { + ret = tps6598x_read_partner_identity(tps); +@@ -850,11 +849,10 @@ static void cd321x_update_work(struct work_struct *work) + + /* Set up partner if we were previously disconnected (or changed). */ + if (!tps->partner) { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + + desc.usb_pd = is_pd; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) + desc.identity = &st.partner_identity; +@@ -1792,6 +1790,7 @@ static int tps6598x_probe(struct i2c_client *client) + const struct tipd_data *data; + struct tps6598x *tps; + struct fwnode_handle *fwnode; ++ bool patch_loaded = false; + u32 status; + u32 vid = 0; + int ret; +@@ -1847,6 +1846,7 @@ static int tps6598x_probe(struct i2c_client *client) + return ret; + + if (ret == TPS_MODE_PTCH) { ++ patch_loaded = true; + ret = tps->data->init(tps); + if (ret) + return ret; +@@ -1937,7 +1937,8 @@ static int tps6598x_probe(struct i2c_client *client) + tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); + err_reset_controller: + /* Reset PD controller to remove any applied patch */ +- tps->data->reset(tps); ++ if (patch_loaded) ++ tps->data->reset(tps); + + return ret; + } +diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c +--- a/drivers/usb/typec/ucsi/displayport.c ++++ b/drivers/usb/typec/ucsi/displayport.c +@@ -71,11 +71,14 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) + if (ret < 0) { + if (ucsi->version > 0x0100) + goto err_unlock; +- cur = 0xff; + } + +- if (cur < UCSI_MAX_ALTMODES) { +- ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ if (cur != 0xff) { ++ if (cur < UCSI_MAX_ALTMODES) ++ ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ else ++ ret = -EINVAL; ++ + goto err_unlock; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1801,10 +1801,21 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) + { + struct fwnode_handle *fwnode; + int i = 1; ++ int ret; ++ u32 port; + +- device_for_each_child_node(con->ucsi->dev, fwnode) +- if (i++ == con->num) +- return fwnode; ++ device_for_each_child_node(con->ucsi->dev, fwnode) { ++ ret = fwnode_property_read_u32(fwnode, "reg", &port); ++ if (ret < 0) { ++ if (i == con->num) ++ return fwnode; ++ } else { ++ if (port == con->num - 1) ++ return fwnode; ++ } ++ ++ i++; ++ } + return NULL; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c +--- a/drivers/usb/typec/ucsi/ucsi_acpi.c ++++ b/drivers/usb/typec/ucsi/ucsi_acpi.c +@@ -121,6 +121,33 @@ static const struct ucsi_operations ucsi_acpi_ops = { + .async_control = ucsi_acpi_async_control + }; + ++static int ucsi_acer_read_version(struct ucsi *ucsi, u16 *version) ++{ ++ struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); ++ int ret; ++ ++ ret = ucsi_acpi_read_version(ucsi, version); ++ if (ret) ++ return ret; ++ ++ if (!*version) { ++ dev_warn(ua->dev, "UCSI version is zero, assuming 1.2\n"); ++ *version = UCSI_VERSION_1_2; ++ } ++ ++ return 0; ++} ++ ++static const struct ucsi_operations ucsi_acer_ops = { ++ .read_version = ucsi_acer_read_version, ++ .read_cci = ucsi_acpi_read_cci, ++ .poll_cci = ucsi_acpi_poll_cci, ++ .read_message_in = ucsi_acpi_read_message_in, ++ .write_message_out = ucsi_acpi_write_message_out, ++ .sync_control = ucsi_sync_control_common, ++ .async_control = ucsi_acpi_async_control ++}; ++ + static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, + void *val, size_t len, void *msg_out, + size_t msg_out_size) +@@ -162,6 +189,13 @@ static const struct ucsi_operations ucsi_gram_ops = { + }; + + static const struct dmi_system_id ucsi_acpi_quirks[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), ++ }, ++ .driver_data = (void *)&ucsi_acer_ops, ++ }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), +diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c +--- a/drivers/usb/typec/ucsi/ucsi_glink.c ++++ b/drivers/usb/typec/ucsi/ucsi_glink.c +@@ -362,6 +362,10 @@ static void pmic_glink_ucsi_destroy(void *data) + { + struct pmic_glink_ucsi *ucsi = data; + ++ /* Drain the work items before ucsi_destroy() frees the ucsi instance */ ++ cancel_work_sync(&ucsi->notify_work); ++ cancel_work_sync(&ucsi->register_work); ++ + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ + mutex_lock(&ucsi->lock); + ucsi_destroy(ucsi->ucsi); +@@ -467,8 +471,15 @@ static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) + { + struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); + +- /* Unregister first to stop having read & writes */ +- ucsi_unregister(ucsi->ucsi); ++ /* Callbacks can queue work until devres releases the client */ ++ disable_work_sync(&ucsi->notify_work); ++ disable_work_sync(&ucsi->register_work); ++ ++ /* register_work may have unregistered the instance already */ ++ if (ucsi->ucsi_registered) { ++ ucsi->ucsi_registered = false; ++ ucsi_unregister(ucsi->ucsi); ++ } + } + + static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { +diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h +--- a/include/linux/usb/serial.h ++++ b/include/linux/usb/serial.h +@@ -381,6 +381,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, + + int usb_serial_bus_register(struct usb_serial_driver *device); + void usb_serial_bus_deregister(struct usb_serial_driver *device); ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver); + + extern const struct bus_type usb_serial_bus_type; + extern struct tty_driver *usb_serial_tty_driver; diff --git a/pkgbuilds/linux-omarchy-bore/0607-usb-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0607-usb-fixes.patch.sig new file mode 100644 index 0000000..182c484 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0607-usb-fixes.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 deleted file mode 100644 index 3db7549..0000000 --- a/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch +++ /dev/null @@ -1,132 +0,0 @@ -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 deleted file mode 100644 index fd0bd60..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0610-typec-cable-altmode-check.patch.sig and /dev/null 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 index 8789452..3b51649 100644 --- a/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch +++ b/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch @@ -1,7 +1,7 @@ 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[] = { +@@ -819,6 +819,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, 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 index ceec933..61318b8 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig 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 deleted file mode 100644 index 539fe9a..0000000 --- a/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index a9714f1..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0661-rtw89-command-offload-source.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0663-btintel-pcie-serialize-reset-type.patch b/pkgbuilds/linux-omarchy-bore/0663-btintel-pcie-serialize-reset-type.patch new file mode 100644 index 0000000..2464f58 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0663-btintel-pcie-serialize-reset-type.patch @@ -0,0 +1,145 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -2499,8 +2499,6 @@ static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev, + } + } + +-static void btintel_pcie_reset(struct hci_dev *hdev); +- + static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data) + { + union acpi_object *obj, argv4; +@@ -2693,56 +2691,86 @@ static void btintel_pcie_reset_work(struct work_struct *wk) + pci_unlock_rescan_remove(); + } + +-static void btintel_pcie_reset(struct hci_dev *hdev) ++/* Schedule a device reset of the requested type. ++ * ++ * BTINTEL_PCIE_RECOVERY_IN_PROGRESS serializes all reset requesters ++ * (sysfs reset attribute, hci_cmd_timeout(), hw_error, resume error ++ * path, etc.) so that: ++ * ++ * - dev_data->reset_type is written by exactly one caller (the ++ * thread that wins test_and_set_bit), eliminating the race where ++ * a second hw_error could clobber an already-scheduled reset's ++ * type; ++ * - the write happens AFTER the bit is set, so reset_work observes ++ * it through schedule_work()'s memory ordering; ++ * - losers return without touching reset_type or scheduling the ++ * work, so concurrent triggers are silently coalesced into the ++ * in-flight one (whose recovery will reinitialize the device ++ * regardless of the dropped trigger's variant). ++ * ++ * The bit is cleared only by .remove() / re-probe via fresh devm ++ * allocation, which is the intended one-shot semantics: a reset ++ * tears down and re-probes 'data', so there is no "in-flight" ++ * reset to follow up after device_reprobe() succeeds. ++ */ ++static void btintel_pcie_request_reset(struct btintel_pcie_data *data, ++ enum btintel_pcie_reset_type type) + { +- struct btintel_pcie_data *data; +- +- data = hci_get_drvdata(hdev); +- + if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags)) + return; + + if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return; + ++ data->reset_type = type; ++ + pci_dev_get(data->pdev); + schedule_work(&data->reset_work); + } + ++static void btintel_pcie_hci_reset(struct hci_dev *hdev) ++{ ++ struct btintel_pcie_data *data = hci_get_drvdata(hdev); ++ ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); ++} ++ + static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) + { +- struct btintel_pcie_dev_recovery *data; ++ struct btintel_pcie_dev_recovery *rec; + struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev); + struct pci_dev *pdev = dev_data->pdev; ++ enum btintel_pcie_reset_type type; + time64_t retry_window; + ++ if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &dev_data->flags)) ++ return; ++ + btintel_pcie_dump_debug_registers(hdev); + +- data = btintel_pcie_get_recovery(pdev, &hdev->dev); +- if (!data) ++ rec = btintel_pcie_get_recovery(pdev, &hdev->dev); ++ if (!rec) + return; + +- if (code == 0x13) +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR; +- else +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; ++ type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR ++ : BTINTEL_PCIE_IOSF_PRR_FLR; + + bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code, +- dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); +- retry_window = ktime_get_boottime_seconds() - data->last_error; ++ type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); ++ retry_window = ktime_get_boottime_seconds() - rec->last_error; + + if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS && +- data->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { ++ rec->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { + bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d", +- BTINTEL_PCIE_FLR_MAX_RETRY, data->count); ++ BTINTEL_PCIE_FLR_MAX_RETRY, rec->count); + bt_dev_dbg(hdev, "Boot time: %lld seconds", + ktime_get_boottime_seconds()); + bt_dev_dbg(hdev, "last error at: %lld seconds", +- data->last_error); ++ rec->last_error); + return; + } + btintel_pcie_inc_recovery_count(pdev, &hdev->dev); +- btintel_pcie_reset(hdev); ++ btintel_pcie_request_reset(dev_data, type); + } + + static bool btintel_pcie_wakeup(struct hci_dev *hdev) +@@ -2832,7 +2860,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data) + hdev->hw_error = btintel_pcie_hw_error; + hdev->set_diag = btintel_set_diag; + hdev->set_bdaddr = btintel_set_bdaddr; +- hdev->reset = btintel_pcie_reset; ++ hdev->reset = btintel_pcie_hci_reset; + hdev->wakeup = btintel_pcie_wakeup; + hdev->hci_drv = &btintel_pcie_hci_drv; + +@@ -3124,8 +3152,7 @@ static int btintel_pcie_resume(struct device *dev) + if (data->pm_sx_event == PM_EVENT_FREEZE || + data->pm_sx_event == PM_EVENT_HIBERNATE) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; + } + +@@ -3156,7 +3183,7 @@ static int btintel_pcie_resume(struct device *dev) + queue_work(data->coredump_workqueue, &data->coredump_work); + } + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + } + return err; + } diff --git a/pkgbuilds/linux-omarchy-bore/0663-btintel-pcie-serialize-reset-type.patch.sig b/pkgbuilds/linux-omarchy-bore/0663-btintel-pcie-serialize-reset-type.patch.sig new file mode 100644 index 0000000..6469de5 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0663-btintel-pcie-serialize-reset-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch b/pkgbuilds/linux-omarchy-bore/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch new file mode 100644 index 0000000..54219c0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch @@ -0,0 +1,30 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, + BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + +- /* A hardware bug may cause the alive interrupt to be missed. +- * Check if the controller reached the expected state and retry +- * the operation only if it hasn't. ++ /* A hardware bug may cause the alive interrupt to be missed. Refresh ++ * boot_stage_cache from hardware, since only the interrupt handler ++ * updates it. Finally retry only if the state check still fails. + */ ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) + return 0; +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -50,6 +50,7 @@ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_DEVICE_HALTED (BIT(14)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_MAC_ACCESS_ON (BIT(16)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_ALIVE (BIT(23)) ++/* Reflects live D-state. Updated by hardware on every D-state transition. */ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY (BIT(24)) + + /* Registers for MSI-X */ diff --git a/pkgbuilds/linux-omarchy-bore/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig b/pkgbuilds/linux-omarchy-bore/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig new file mode 100644 index 0000000..216c5d1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch b/pkgbuilds/linux-omarchy-bore/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch new file mode 100644 index 0000000..2ec0e80 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch @@ -0,0 +1,145 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -3046,7 +3047,8 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0, status; ++ int retry = 0; ++ long status; + u32 dx_intr_timeout_ms = 200; + + do { +@@ -3098,18 +3100,23 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg) + + data = pci_get_drvdata(pdev); + +- dxstate = (mesg.event == PM_EVENT_SUSPEND ? +- BTINTEL_PCIE_STATE_D3_HOT : BTINTEL_PCIE_STATE_D3_COLD); +- +- data->pm_sx_event = mesg.event; ++ /* S0ix (s2idle) uses D3_HOT; S3, freeze and hibernate use D3_COLD. */ ++ if (mesg.event == PM_EVENT_SUSPEND && ++ pm_suspend_target_state == PM_SUSPEND_TO_IDLE) ++ dxstate = BTINTEL_PCIE_STATE_D3_HOT; ++ else ++ dxstate = BTINTEL_PCIE_STATE_D3_COLD; + + start = ktime_get(); + + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, dxstate); + +- if (err) ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set dxstate:%u (%d)", ++ dxstate, err); + return err; ++ } + + bt_dev_dbg(data->hdev, + "device entered into d3 state from d0 in %lld us", +@@ -3132,7 +3139,7 @@ static int btintel_pcie_freeze(struct device *dev) + return btintel_pcie_suspend_late(dev, PMSG_FREEZE); + } + +-static int btintel_pcie_resume(struct device *dev) ++static int btintel_pcie_resume_event(struct device *dev, pm_message_t mesg) + { + struct pci_dev *pdev = to_pci_dev(dev); + struct btintel_pcie_data *data; +@@ -3140,19 +3147,15 @@ static int btintel_pcie_resume(struct device *dev) + int err; + + data = pci_get_drvdata(pdev); +- data->gp0_received = false; + + start = ktime_get(); + +- /* When the system enters S4 (hibernate) mode, bluetooth device loses +- * power, which results in the erasure of its loaded firmware. +- * Consequently, function level reset (flr) is required on system +- * resume to bring the controller back into an operational state by +- * initiating a new firmware download. ++ /* S3 and S4 may cut power, erasing the firmware. Force FLR to recover ++ * instead of a normal D0 transition. + */ +- +- if (data->pm_sx_event == PM_EVENT_FREEZE || +- data->pm_sx_event == PM_EVENT_HIBERNATE) { ++ if (mesg.event == PM_EVENT_RESTORE || ++ (mesg.event == PM_EVENT_RESUME && ++ pm_suspend_target_state == PM_SUSPEND_MEM)) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; +@@ -3161,7 +3164,9 @@ static int btintel_pcie_resume(struct device *dev) + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, BTINTEL_PCIE_STATE_D0); + +- if (err == 0) { ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set D0 state (%d)", err); ++ } else { + bt_dev_dbg(data->hdev, + "device entered into d0 state from d3 in %lld us", + ktime_to_us(ktime_get() - start)); +@@ -3190,13 +3195,28 @@ static int btintel_pcie_resume(struct device *dev) + return err; + } + ++static int btintel_pcie_resume(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESUME); ++} ++ ++static int btintel_pcie_restore(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESTORE); ++} ++ ++static int btintel_pcie_thaw(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_THAW); ++} ++ + static const struct dev_pm_ops btintel_pcie_pm_ops = { + .suspend = btintel_pcie_suspend, + .resume = btintel_pcie_resume, + .freeze = btintel_pcie_freeze, +- .thaw = btintel_pcie_resume, ++ .thaw = btintel_pcie_thaw, + .poweroff = btintel_pcie_hibernate, +- .restore = btintel_pcie_resume, ++ .restore = btintel_pcie_restore, + }; + + static struct pci_driver btintel_pcie_driver = { +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -478,7 +478,6 @@ struct btintel_pcie_dump_header { + * @txq: TX Queue struct + * @rxq: RX Queue struct + * @alive_intr_ctxt: Alive interrupt context +- * @pm_sx_event: PM event on which system got suspended + */ + struct btintel_pcie_data { + struct pci_dev *pdev; +@@ -533,7 +532,6 @@ struct btintel_pcie_data { + enum btintel_pcie_reset_type reset_type; + struct btintel_pcie_dbgc dbgc; + struct btintel_pcie_dump_header dmp_hdr; +- u8 pm_sx_event; + u32 debug_evt_addr; + u32 debug_evt_size; + }; diff --git a/pkgbuilds/linux-omarchy-bore/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig b/pkgbuilds/linux-omarchy-bore/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig new file mode 100644 index 0000000..ee77a45 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0666-btintel-pcie-verify-serialize-dstate.patch b/pkgbuilds/linux-omarchy-bore/0666-btintel-pcie-verify-serialize-dstate.patch new file mode 100644 index 0000000..b1305d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0666-btintel-pcie-verify-serialize-dstate.patch @@ -0,0 +1,113 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1012,6 +1012,10 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + submit_rx = false; + signal_waitq = false; + ++ /* Serialize the alive_intr_ctxt claim against set_dxstate()'s ++ * register-based fallback, which can run concurrently. ++ */ ++ spin_lock(&data->irq_lock); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1067,6 +1071,7 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + data->alive_intr_ctxt); + break; + } ++ spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); +@@ -3047,7 +3052,7 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0; ++ int retry = 0, err; + long status; + u32 dx_intr_timeout_ms = 200; + +@@ -3059,30 +3064,66 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + status = wait_event_timeout(data->gp0_wait_q, data->gp0_received, + msecs_to_jiffies(dx_intr_timeout_ms)); + +- if (status) +- return 0; ++ if (!status) { ++ bt_dev_warn(data->hdev, ++ "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", ++ dx_intr_timeout_ms, dxstate, retry); + +- bt_dev_warn(data->hdev, +- "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", +- dx_intr_timeout_ms, dxstate, retry); ++ /* clear gp0 cause */ ++ btintel_pcie_clr_reg_bits(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ } + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); +- +- /* A hardware bug may cause the alive interrupt to be missed. Refresh +- * boot_stage_cache from hardware, since only the interrupt handler +- * updates it. Finally retry only if the state check still fails. ++ /* gp0_received is set at the top of the handler, before the switch on ++ * alive_intr_ctxt. Error and lockdown are filtered out above it, but a ++ * matched case can still complete without doing anything - D3 breaks ++ * unchanged while the controller has not reached D0 - and a hardware ++ * bug may drop the interrupt outright. Either way the flag says a gp0 ++ * was handled, not that the transition completed, and only the register ++ * knows. Refresh the cache here and retry only if the state check still ++ * fails. + */ + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { +- if (btintel_pcie_in_d0(data)) ++ if (btintel_pcie_in_d0(data)) { ++ bool armed; ++ ++ /* Claim the transition under irq_lock so a ++ * concurrent gp0 handler run cannot also arm RX. ++ */ ++ spin_lock(&data->irq_lock); ++ armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; ++ if (!armed) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D0; ++ spin_unlock(&data->irq_lock); ++ ++ if (armed) ++ return 0; ++ ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ if (err) ++ return err; ++ + return 0; ++ } + } else { +- if (btintel_pcie_in_d3(data)) ++ if (btintel_pcie_in_d3(data)) { ++ /* Do what the handler's D0 -> D3 branch ++ * would have done, unless it already has. ++ */ ++ spin_lock(&data->irq_lock); ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D3; ++ spin_unlock(&data->irq_lock); ++ + return 0; ++ } + } + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); diff --git a/pkgbuilds/linux-omarchy-bore/0666-btintel-pcie-verify-serialize-dstate.patch.sig b/pkgbuilds/linux-omarchy-bore/0666-btintel-pcie-verify-serialize-dstate.patch.sig new file mode 100644 index 0000000..11d5690 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0666-btintel-pcie-verify-serialize-dstate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0667-btintel-pcie-gp0-cause-w1c-write.patch b/pkgbuilds/linux-omarchy-bore/0667-btintel-pcie-gp0-cause-w1c-write.patch new file mode 100644 index 0000000..9927412 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0667-btintel-pcie-gp0-cause-w1c-write.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", + dx_intr_timeout_ms, dxstate, retry); + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ /* MSIX_HW_INT_CAUSES is W1C. Write only GP0 so other ++ * pending causes are not acknowledged here. ++ */ ++ btintel_pcie_wr_reg32(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + } + + /* gp0_received is set at the top of the handler, before the switch on diff --git a/pkgbuilds/linux-omarchy-bore/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig b/pkgbuilds/linux-omarchy-bore/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig new file mode 100644 index 0000000..0b81f4e Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0668-btintel-pcie-dstate-races.patch b/pkgbuilds/linux-omarchy-bore/0668-btintel-pcie-dstate-races.patch new file mode 100644 index 0000000..d25c602 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0668-btintel-pcie-dstate-races.patch @@ -0,0 +1,86 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1016,6 +1016,8 @@ static void btintel_pcie_msix_gp0_handle + * register-based fallback, which can run concurrently. + */ + spin_lock(&data->irq_lock); ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1071,12 +1073,12 @@ static void btintel_pcie_msix_gp0_handle + data->alive_intr_ctxt); + break; + } +- spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); + btintel_pcie_start_rx(data); + } ++ spin_unlock(&data->irq_lock); + + if (signal_waitq) { + bt_dev_dbg(data->hdev, "wake up gp0 wait_q"); +@@ -3086,40 +3088,34 @@ static int btintel_pcie_set_dxstate(stru + * knows. Refresh the cache here and retry only if the state check still + * fails. + */ ++ spin_lock(&data->irq_lock); + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) { +- bool armed; ++ err = 0; + + /* Claim the transition under irq_lock so a + * concurrent gp0 handler run cannot also arm RX. + */ +- spin_lock(&data->irq_lock); +- armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; +- if (!armed) ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D0) { + data->alive_intr_ctxt = BTINTEL_PCIE_D0; +- spin_unlock(&data->irq_lock); + +- if (armed) +- return 0; +- +- /* Do what the handler's D3 -> D0 branch +- * would have done, unless it already has. +- */ +- btintel_pcie_reset_ia(data); +- err = btintel_pcie_start_rx(data); +- if (err) +- return err; ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ } ++ spin_unlock(&data->irq_lock); + +- return 0; ++ return err; + } + } else { + if (btintel_pcie_in_d3(data)) { + /* Do what the handler's D0 -> D3 branch + * would have done, unless it already has. + */ +- spin_lock(&data->irq_lock); + if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) + data->alive_intr_ctxt = BTINTEL_PCIE_D3; + spin_unlock(&data->irq_lock); +@@ -3127,6 +3123,7 @@ static int btintel_pcie_set_dxstate(stru + return 0; + } + } ++ spin_unlock(&data->irq_lock); + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); + diff --git a/pkgbuilds/linux-omarchy-bore/0668-btintel-pcie-dstate-races.patch.sig b/pkgbuilds/linux-omarchy-bore/0668-btintel-pcie-dstate-races.patch.sig new file mode 100644 index 0000000..f910b39 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0668-btintel-pcie-dstate-races.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0669-btintel-version-tlv-parse-errors.patch b/pkgbuilds/linux-omarchy-bore/0669-btintel-version-tlv-parse-errors.patch new file mode 100644 index 0000000..164dbb6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0669-btintel-version-tlv-parse-errors.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -720,6 +720,7 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + { + struct sk_buff *skb; + const u8 param[1] = { 0xFF }; ++ int err; + + if (!version) + return -EINVAL; +@@ -738,10 +739,10 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + return -EIO; + } + +- btintel_parse_version_tlv(hdev, version, skb); ++ err = btintel_parse_version_tlv(hdev, version, skb); + + kfree_skb(skb); +- return 0; ++ return err; + } + + /* ------- REGMAP IBT SUPPORT ------- */ diff --git a/pkgbuilds/linux-omarchy-bore/0669-btintel-version-tlv-parse-errors.patch.sig b/pkgbuilds/linux-omarchy-bore/0669-btintel-version-tlv-parse-errors.patch.sig new file mode 100644 index 0000000..b8e7e45 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0669-btintel-version-tlv-parse-errors.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0670-bluetooth-fixes.patch b/pkgbuilds/linux-omarchy-bore/0670-bluetooth-fixes.patch new file mode 100644 index 0000000..14623cd --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0670-bluetooth-fixes.patch @@ -0,0 +1,485 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -288,7 +288,8 @@ void btintel_hw_error(struct hci_dev *hdev, u8 code) + goto unlock; + } + +- bt_dev_err(hdev, "Exception info %s", (char *)(skb->data + 1)); ++ bt_dev_err(hdev, "Exception info %.*s", (int)(skb->len - 1), ++ (char *)(skb->data + 1)); + + kfree_skb(skb); + +@@ -407,8 +408,16 @@ int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name) + /* DDC file contains one or more DDC structure which has + * Length (1 byte), DDC ID (2 bytes), and DDC value (Length - 2). + */ +- while (fw->size > fw_ptr - fw->data) { +- u8 cmd_plen = fw_ptr[0] + sizeof(u8); ++ while (fw->size > (size_t)(fw_ptr - fw->data)) { ++ size_t remaining = fw->size - (fw_ptr - fw->data); ++ unsigned int cmd_plen = fw_ptr[0] + 1U; ++ ++ if (cmd_plen < 3 || cmd_plen > U8_MAX || cmd_plen > remaining) { ++ bt_dev_err(hdev, "Malformed DDC record (plen=%u, remaining=%zu)", ++ cmd_plen, remaining); ++ release_firmware(fw); ++ return -EINVAL; ++ } + + skb = __hci_cmd_sync(hdev, 0xfc8b, cmd_plen, fw_ptr, + HCI_INIT_TIMEOUT); +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1200,7 +1200,7 @@ static int btintel_pcie_recv_frame(struct btintel_pcie_data *data, + { + int ret; + u8 pkt_type; +- u16 plen; ++ u32 plen; + u32 pcie_pkt_type; + void *pdata; + struct hci_dev *hdev = data->hdev; +@@ -2149,6 +2149,16 @@ static int btintel_pcie_send_frame(struct hci_dev *hdev, + if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return -ENODEV; + ++ /* Account for the 4-byte PCIe type header prepended before the ++ * DMA copy. Written as a subtraction to avoid wrap-around on ++ * attacker-controlled skb->len. ++ */ ++ if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { ++ bt_dev_err(hdev, "Packet too large: %u > %u", skb->len, ++ BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); ++ return -EMSGSIZE; ++ } ++ + /* Due to the fw limitation, the type header of the packet should be + * 4 bytes unlike 1 byte for UART. In UART, the firmware can read + * the first byte to get the packet type and redirect the rest of data +diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -62,7 +62,7 @@ struct inquiry_entry { + NAME_PENDING, + NAME_KNOWN, + } name_state; +- __u32 timestamp; ++ unsigned long timestamp; + struct inquiry_data data; + }; + +@@ -78,7 +78,7 @@ struct discovery_state { + struct list_head all; /* All devices found during inquiry */ + struct list_head unknown; /* Name state not known */ + struct list_head resolve; /* Name needs to be resolved */ +- __u32 timestamp; ++ unsigned long timestamp; + bdaddr_t last_adv_addr; + u8 last_adv_addr_type; + s8 last_adv_rssi; +@@ -561,6 +561,7 @@ struct hci_dev { + struct list_head link_keys; + struct list_head long_term_keys; + struct list_head identity_resolving_keys; ++ struct mutex remote_oob_lock; + struct list_head remote_oob_data; + struct list_head le_accept_list; + struct list_head le_resolv_list; +diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -302,11 +302,13 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.tx_bandwidth = cpu_to_le32(0x00001f40); + cp.rx_bandwidth = cpu_to_le32(0x00001f40); + ++ hci_dev_lock(hdev); ++ + switch (conn->codec.id) { + case BT_CODEC_MSBC: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x05; +@@ -332,7 +334,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + case BT_CODEC_TRANSPARENT: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x03; +@@ -359,11 +361,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + if (conn->parent && lmp_esco_capable(conn->parent)) { + if (!find_next_esco_param(conn, esco_param_cvsd, + ARRAY_SIZE(esco_param_cvsd))) +- return -EINVAL; ++ goto unlock; + param = &esco_param_cvsd[conn->attempt - 1]; + } else { + if (conn->attempt > ARRAY_SIZE(sco_param_cvsd)) +- return -EINVAL; ++ goto unlock; + param = &sco_param_cvsd[conn->attempt - 1]; + } + cp.tx_coding_format.id = 2; +@@ -386,9 +388,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.out_transport_unit_size = 16; + break; + default: +- return -EINVAL; ++ goto unlock; + } + ++ hci_dev_unlock(hdev); ++ + cp.retrans_effort = param->retrans_effort; + cp.pkt_type = __cpu_to_le16(param->pkt_type); + cp.max_latency = __cpu_to_le16(param->max_latency); +@@ -397,6 +401,10 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + return -EIO; + + return 0; ++ ++unlock: ++ hci_dev_unlock(hdev); ++ return -EINVAL; + } + + static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) +diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -1486,8 +1486,10 @@ int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s removing %pMR (%u)", hdev->name, bdaddr, bdaddr_type); + ++ mutex_lock(&hdev->remote_oob_lock); + list_del(&data->list); + kfree(data); ++ mutex_unlock(&hdev->remote_oob_lock); + + return 0; + } +@@ -1496,10 +1498,12 @@ void hci_remote_oob_data_clear(struct hci_dev *hdev) + { + struct oob_data *data, *n; + ++ mutex_lock(&hdev->remote_oob_lock); + list_for_each_entry_safe(data, n, &hdev->remote_oob_data, list) { + list_del(&data->list); + kfree(data); + } ++ mutex_unlock(&hdev->remote_oob_lock); + } + + int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, +@@ -1508,11 +1512,14 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + { + struct oob_data *data; + ++ mutex_lock(&hdev->remote_oob_lock); + data = hci_find_remote_oob_data(hdev, bdaddr, bdaddr_type); + if (!data) { + data = kmalloc_obj(*data); +- if (!data) ++ if (!data) { ++ mutex_unlock(&hdev->remote_oob_lock); + return -ENOMEM; ++ } + + bacpy(&data->bdaddr, bdaddr); + data->bdaddr_type = bdaddr_type; +@@ -1545,6 +1552,8 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s for %pMR", hdev->name, bdaddr); + ++ mutex_unlock(&hdev->remote_oob_lock); ++ + return 0; + } + +@@ -2482,6 +2491,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) + mutex_init(&hdev->lock); + mutex_init(&hdev->req_lock); + mutex_init(&hdev->mgmt_pending_lock); ++ mutex_init(&hdev->remote_oob_lock); + + ida_init(&hdev->unset_handle_ida); + +@@ -2554,8 +2564,10 @@ int hci_register_dev(struct hci_dev *hdev) + return id; + + error = dev_set_name(&hdev->dev, "hci%u", id); +- if (error) ++ if (error) { ++ ida_free(&hci_index_ida, id); + return error; ++ } + + hdev->name = dev_name(&hdev->dev); + hdev->id = id; +@@ -3323,6 +3335,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + skb_shinfo(skb)->frag_list = NULL; + ++ spin_lock_bh(&queue->lock); ++ + __skb_queue_tail(queue, skb); + + do { +@@ -3337,6 +3351,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + __skb_queue_tail(queue, skb); + } while (list); ++ ++ spin_unlock_bh(&queue->lock); + } + + bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(queue)); +@@ -3400,9 +3416,6 @@ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, + struct hci_conn *conn = NULL, *c; + unsigned int num = 0, min = ~0; + +- /* We don't have to lock device here. Connections are always +- * added and removed with TX task disabled. */ +- + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +@@ -3607,13 +3620,15 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt, u8 type) + } + + /* Schedule SCO */ +-static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++static void __hci_sched_sco(struct hci_dev *hdev, __u8 type) + { + struct hci_conn *conn; + struct sk_buff *skb; + int quote, *cnt; + unsigned int pkts = hdev->sco_pkts; + ++ lockdep_assert_held(&hdev->lock); ++ + bt_dev_dbg(hdev, "type %u", type); + + if (!hci_conn_num(hdev, type) || !pkts) +@@ -3648,6 +3663,13 @@ static void hci_sched_sco(struct hci_dev *hdev, __u8 type) + queue_work(hdev->workqueue, &hdev->tx_work); + } + ++static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++{ ++ hci_dev_lock(hdev); ++ __hci_sched_sco(hdev, type); ++ hci_dev_unlock(hdev); ++} ++ + static void hci_sched_acl_pkt(struct hci_dev *hdev) + { + unsigned int cnt = hdev->acl_cnt; +@@ -3657,6 +3679,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + + __check_timeout(hdev, cnt, ACL_LINK); + ++ hci_dev_lock(hdev); ++ + while (hdev->acl_cnt && + (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3681,13 +3705,15 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (cnt != hdev->acl_cnt) + hci_prio_recalculate(hdev, ACL_LINK); ++ ++ hci_dev_unlock(hdev); + } + + static void hci_sched_acl(struct hci_dev *hdev) +@@ -3716,6 +3742,8 @@ static void hci_sched_le(struct hci_dev *hdev) + + __check_timeout(hdev, *cnt, LE_LINK); + ++ hci_dev_lock(hdev); ++ + tmp = *cnt; + while (*cnt && (chan = hci_chan_sent(hdev, LE_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3737,13 +3765,15 @@ static void hci_sched_le(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (*cnt != tmp) + hci_prio_recalculate(hdev, LE_LINK); ++ ++ hci_dev_unlock(hdev); + } + + /* Schedule iso */ +@@ -3762,6 +3792,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + + __check_timeout(hdev, *cnt, type); + ++ hci_dev_lock(hdev); ++ + while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); +@@ -3775,6 +3807,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + (*cnt)--; + } + } ++ ++ hci_dev_unlock(hdev); + } + + static void hci_tx_work(struct work_struct *work) +diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c +--- a/net/bluetooth/hci_debugfs.c ++++ b/net/bluetooth/hci_debugfs.c +@@ -364,7 +364,7 @@ static int inquiry_cache_show(struct seq_file *f, void *p) + + list_for_each_entry(e, &cache->all, all) { + struct inquiry_data *data = &e->data; +- seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n", ++ seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %lu\n", + &data->bdaddr, + data->pscan_rep_mode, data->pscan_period_mode, + data->pscan_mode, data->dev_class[2], +diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -312,6 +312,10 @@ static void hci_cmd_sync_work(struct work_struct *work) + while (1) { + struct hci_cmd_sync_work_entry *entry; + ++ /* Leave the backlog to hci_cmd_sync_clear() */ ++ if (hci_dev_test_flag(hdev, HCI_UNREGISTER)) ++ break; ++ + mutex_lock(&hdev->cmd_sync_work_lock); + entry = list_first_entry_or_null(&hdev->cmd_sync_work_list, + struct hci_cmd_sync_work_entry, +@@ -658,6 +662,8 @@ void hci_cmd_sync_clear(struct hci_dev *hdev) + { + struct hci_cmd_sync_work_entry *entry, *tmp; + ++ /* cmd_work is disabled, the pending request can only time out */ ++ hci_cmd_sync_cancel_sync(hdev, ENODEV); + cancel_work_sync(&hdev->cmd_sync_work); + cancel_work_sync(&hdev->reenable_adv_work); + +@@ -5678,6 +5684,7 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + { + struct discovery_state *d = &hdev->discovery; + struct inquiry_entry *e; ++ bdaddr_t addr; + int err; + + bt_dev_dbg(hdev, "state %u", hdev->discovery.state); +@@ -5713,15 +5720,21 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + return 0; + + if (d->state == DISCOVERY_RESOLVING || d->state == DISCOVERY_STOPPING) { ++ hci_dev_lock(hdev); + e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, + NAME_PENDING); +- if (!e) ++ if (!e) { ++ hci_dev_unlock(hdev); + return 0; ++ } ++ ++ bacpy(&addr, &e->data.bdaddr); ++ hci_dev_unlock(hdev); + + /* Ignore cancel errors since it should interfere with stopping + * of the discovery. + */ +- hci_remote_name_cancel_sync(hdev, &e->data.bdaddr); ++ hci_remote_name_cancel_sync(hdev, &addr); + } + + return 0; +@@ -7151,6 +7164,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + bacpy(&cp.bdaddr, &conn->dst); + cp.pscan_rep_mode = 0x02; + ++ hci_dev_lock(hdev); + ie = hci_inquiry_cache_lookup(hdev, &conn->dst); + if (ie) { + if (inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { +@@ -7162,6 +7176,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + + memcpy(conn->dev_class, ie->data.dev_class, 3); + } ++ hci_dev_unlock(hdev); + + cp.pkt_type = cpu_to_le16(conn->pkt_type); + if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) +diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c +--- a/net/bluetooth/rfcomm/tty.c ++++ b/net/bluetooth/rfcomm/tty.c +@@ -462,7 +462,7 @@ static int __rfcomm_release_dev(void __user *arg) + /* Shut down TTY synchronously before freeing rfcomm_dev */ + tty_port_tty_vhangup(&dev->port); + +- if (!test_bit(RFCOMM_TTY_OWNED, &dev->status)) ++ if (!test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); + + tty_port_put(&dev->port); +@@ -724,10 +724,9 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty) + * when the last process closes the tty. The behaviour is expected by + * userspace. + */ +- if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { +- set_bit(RFCOMM_TTY_OWNED, &dev->status); ++ if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags) && ++ !test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); +- } + + return 0; + } +diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c +--- a/net/bluetooth/smp.c ++++ b/net/bluetooth/smp.c +@@ -661,6 +661,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + else + bdaddr_type = BDADDR_LE_RANDOM; + ++ mutex_lock(&hdev->remote_oob_lock); + oob_data = hci_find_remote_oob_data(hdev, &hcon->dst, + bdaddr_type); + if (oob_data && oob_data->present) { +@@ -671,6 +672,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + SMP_DBG("OOB Remote Confirmation: %16phN", smp->pcnf); + SMP_DBG("OOB Remote Random: %16phN", smp->rr); + } ++ mutex_unlock(&hdev->remote_oob_lock); + + } else { + authreq &= ~SMP_AUTH_SC; diff --git a/pkgbuilds/linux-omarchy-bore/0670-bluetooth-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0670-bluetooth-fixes.patch.sig new file mode 100644 index 0000000..a680e2e Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0670-bluetooth-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0701-pci-amd-enhanced-atomics.patch b/pkgbuilds/linux-omarchy-bore/0701-pci-amd-enhanced-atomics.patch new file mode 100644 index 0000000..c76cb96 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0701-pci-amd-enhanced-atomics.patch @@ -0,0 +1,109 @@ +diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c +--- a/arch/x86/pci/fixup.c ++++ b/arch/x86/pci/fixup.c +@@ -886,6 +886,105 @@ static void quirk_clear_strap_no_soft_reset_dev2_f0(struct pci_dev *dev) + } + } + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15b8, quirk_clear_strap_no_soft_reset_dev2_f0); ++ ++/* ++ * Enhanced atomic operations can cause corruption with 64-bit IOVA ++ * on these devices. ++ */ ++#define RX_ENH_ATOMIC_EN BIT(8) ++ ++static const u32 nbio_7_7_pcie_smn_addrs[] = { ++ 0x111401d0, ++ 0x111411d0, ++ 0x111421d0, ++ 0x111431d0, ++ 0x111441d0, ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x114401d0, ++}; ++ ++static const u32 nbio_7_11_pcie_smn_addrs[] = { ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x113411d0, ++ 0x113421d0, ++ 0x113431d0, ++ 0x113441d0, ++ 0x113451d0, ++}; ++ ++static void quirk_amd_nbio_enhanced_atomic(struct pci_dev *host_bridge, ++ const u32 *smn_addrs, ++ size_t nr_smn_addrs) ++{ ++ bool changed = false; ++ size_t i; ++ u32 data; ++ int ret; ++ ++ for (i = 0; i < nr_smn_addrs; i++) { ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (!(data & RX_ENH_ATOMIC_EN)) ++ continue; ++ data = data & ~RX_ENH_ATOMIC_EN; ++ ret = amd_smn_write(0, smn_addrs[i], data); ++ if (ret) ++ continue; ++ if (changed) ++ continue; ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (data & RX_ENH_ATOMIC_EN) ++ continue; ++ changed = true; ++ } ++ ++ if (changed) ++ pci_info(host_bridge, "enhanced atomics disabled\n"); ++} ++ ++static void quirk_amd_nbio_7_7_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_7_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_7_pcie_smn_addrs)); ++} ++ ++static void quirk_amd_nbio_7_11_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_11_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_11_pcie_smn_addrs)); ++} ++ ++/* Phoenix, Hawk Point (NBIO 7.7) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++ ++/* Strix, Krackan, Strix Halo (NBIO 7.11) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++ + #endif + + /* diff --git a/pkgbuilds/linux-omarchy-bore/0701-pci-amd-enhanced-atomics.patch.sig b/pkgbuilds/linux-omarchy-bore/0701-pci-amd-enhanced-atomics.patch.sig new file mode 100644 index 0000000..f5360e4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0701-pci-amd-enhanced-atomics.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0702-ahci-jmb585-32bit-dma-revert.patch b/pkgbuilds/linux-omarchy-bore/0702-ahci-jmb585-32bit-dma-revert.patch new file mode 100644 index 0000000..9794028 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0702-ahci-jmb585-32bit-dma-revert.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c +--- a/drivers/ata/ahci.c ++++ b/drivers/ata/ahci.c +@@ -68,7 +68,6 @@ enum board_ids { + /* board IDs for specific chipsets in alphabetical order */ + board_ahci_al, + board_ahci_avn, +- board_ahci_jmb585, + board_ahci_mcp65, + board_ahci_mcp77, + board_ahci_mcp89, +@@ -213,15 +212,6 @@ static const struct ata_port_info ahci_port_info[] = { + .udma_mask = ATA_UDMA6, + .port_ops = &ahci_avn_ops, + }, +- /* JMicron JMB582/585: 64-bit DMA is broken, force 32-bit */ +- [board_ahci_jmb585] = { +- AHCI_HFLAGS (AHCI_HFLAG_IGN_IRQ_IF_ERR | +- AHCI_HFLAG_32BIT_ONLY), +- .flags = AHCI_FLAG_COMMON, +- .pio_mask = ATA_PIO4, +- .udma_mask = ATA_UDMA6, +- .port_ops = &ahci_ops, +- }, + [board_ahci_mcp65] = { + AHCI_HFLAGS (AHCI_HFLAG_NO_FPDMA_AA | AHCI_HFLAG_NO_PMP | + AHCI_HFLAG_YES_NCQ), +@@ -954,14 +944,6 @@ static const struct pci_device_id ahci_pci_tbl[] = { + /* Elkhart Lake AHCI */ + PCI_VDEVICE(INTEL, 0x4b63), + .driver_data = board_ahci_pcs_quirk, +- }, { +- /* JMicron JMB582/585: force 32-bit DMA (broken 64-bit implementation) */ +- PCI_VDEVICE(JMICRON, 0x0582), +- .driver_data = board_ahci_jmb585, +- +- }, { +- PCI_VDEVICE(JMICRON, 0x0585), +- .driver_data = board_ahci_jmb585, + }, { + /* JMicron 360/1/3/5/6, match class to avoid IDE function */ + PCI_DEVICE(PCI_VENDOR_ID_JMICRON, PCI_ANY_ID), diff --git a/pkgbuilds/linux-omarchy-bore/0702-ahci-jmb585-32bit-dma-revert.patch.sig b/pkgbuilds/linux-omarchy-bore/0702-ahci-jmb585-32bit-dma-revert.patch.sig new file mode 100644 index 0000000..b59d6ba Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0702-ahci-jmb585-32bit-dma-revert.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0703-pci-vmd-scan-bios-enumerated-devices.patch b/pkgbuilds/linux-omarchy-bore/0703-pci-vmd-scan-bios-enumerated-devices.patch new file mode 100644 index 0000000..0fe6c04 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0703-pci-vmd-scan-bios-enumerated-devices.patch @@ -0,0 +1,297 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -37,6 +37,12 @@ + #define MB2_SHADOW_OFFSET 0x2000 + #define MB2_SHADOW_SIZE 16 + ++/* DMR BAR4 register offsets */ ++#define SHADOW_MEMBAR1_28C1 0x2818 /* MEMBAR1 physical address */ ++#define SHADOW_MEMBAR2_28C1 0x2820 /* MEMBAR2 physical address */ ++#define BASE_ID_REG_28C1 0x2840 ++#define MEMBAR2_OFFSET_28C1 0x30d0 ++ + enum vmd_features { + /* + * Device may contain registers which hint the physical location of the +@@ -77,6 +83,15 @@ enum vmd_features { + * proper power management of the SoC. + */ + VMD_FEAT_BIOS_PM_QUIRK = (1 << 5), ++ ++ /* ++ * Newer VMD with device ID 0x28C1 has unique settings compared to its ++ * predecessor where BIOS enumerates the entire VMD device tree and ++ * stores respective configurations including bus start range and ++ * shadow registers in VMD MMIO space in VMD BAR4/BAR5, otherwise ++ * referred to as MEMBAR2 or MSI-X BAR. ++ */ ++ VMD_FEAT_USE_BIOS_INFO = (1 << 6), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -142,6 +157,7 @@ struct vmd_dev { + u8 first_vec; + char *name; + int instance; ++ unsigned long features; + }; + + static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) +@@ -366,6 +382,9 @@ static void vmd_set_msi_remapping(struct vmd_dev *vmd, bool enable) + { + u16 reg; + ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return; ++ + pci_read_config_word(vmd->dev, PCI_REG_VMCONFIG, ®); + reg = enable ? (reg & ~VMCONFIG_MSI_REMAP) : + (reg | VMCONFIG_MSI_REMAP); +@@ -389,11 +408,22 @@ static void vmd_remove_irq_domain(struct vmd_dev *vmd) + } + } + ++static unsigned int vmd_bus_to_ecam(struct vmd_dev *vmd, unsigned int busnr) ++{ ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return busnr; ++ ++ return busnr - vmd->busn_start; ++} ++ + static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, + unsigned int devfn, int reg, int len) + { +- unsigned int busnr_ecam = bus->number - vmd->busn_start; +- u32 offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); ++ unsigned int busnr_ecam; ++ u32 offset; ++ ++ busnr_ecam = vmd_bus_to_ecam(vmd, bus->number); ++ offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); + + if (offset + len >= resource_size(&vmd->dev->resource[VMD_CFGBAR])) + return NULL; +@@ -518,22 +548,37 @@ static inline void vmd_acpi_begin(void) { } + static inline void vmd_acpi_end(void) { } + #endif /* CONFIG_ACPI */ + ++static resource_size_t vmd_cfgbar_ecam_space(struct vmd_dev *vmd) ++{ ++ resource_size_t cfgbar_buses; ++ unsigned int ecam_start; ++ ++ cfgbar_buses = resource_size(&vmd->dev->resource[VMD_CFGBAR]) >> 20; ++ ecam_start = vmd_bus_to_ecam(vmd, vmd->resources[0].start); ++ if (ecam_start >= cfgbar_buses) ++ return 0; ++ ++ return cfgbar_buses - ecam_start; ++} + static void vmd_domain_reset(struct vmd_dev *vmd) + { + u16 bus, max_buses = resource_size(&vmd->resources[0]); + u8 dev, functions, fn, hdr_type; ++ unsigned int ecam_bus; + char __iomem *base; + ++ max_buses = min_t(u16, max_buses, vmd_cfgbar_ecam_space(vmd)); + for (bus = 0; bus < max_buses; bus++) { ++ ecam_bus = vmd_bus_to_ecam(vmd, vmd->resources[0].start + bus); + for (dev = 0; dev < 32; dev++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, 0), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE); + + functions = (hdr_type & PCI_HEADER_TYPE_MFD) ? 8 : 1; + for (fn = 0; fn < functions; fn++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, fn), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE) & +@@ -661,6 +706,46 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_get_bus_info_from_bar4(struct vmd_dev *vmd, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ u64 phys1, phys2, bar4_2840; ++ void __iomem *bar4; ++ u32 base_id; ++ u8 base_bus; ++ ++ bar4 = pci_ioremap_bar(vmd->dev, 4); ++ if (!bar4) ++ return -ENOMEM; ++ ++ /* Read shadow registers for MEMBAR1 and MEMBAR2 physical addresses */ ++ phys1 = readq(bar4 + SHADOW_MEMBAR1_28C1); ++ phys2 = readq(bar4 + SHADOW_MEMBAR2_28C1); ++ ++ /* ++ * Read and set bus start number from Base ID register. 24-bit Base ID ++ * register is part of 64-bit shadowed reqid hide range register and ++ * holds segment, bus, device and function. ++ */ ++ bar4_2840 = readq(bar4 + BASE_ID_REG_28C1); ++ base_id = bar4_2840 & 0xFFFFFF; ++ base_bus = base_id >> 8; ++ vmd->busn_start = base_bus; ++ ++ /* Calculate offsets like vmd_get_phys_offsets() does */ ++ if (phys1) ++ *offset1 = vmd->dev->resource[VMD_MEMBAR1].start - ++ (phys1 & PCI_BASE_ADDRESS_MEM_MASK); ++ if (phys2) ++ *offset2 = vmd->dev->resource[VMD_MEMBAR2].start - ++ (phys2 & PCI_BASE_ADDRESS_MEM_MASK); ++ ++ pci_iounmap(vmd->dev, bar4); ++ ++ return 0; ++} ++ + static irqreturn_t vmd_irq(int irq, void *data) + { + struct vmd_irq_list *irqs = data; +@@ -711,6 +796,52 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_prepare_offsets_and_bus(struct vmd_dev *vmd, ++ unsigned long features, ++ resource_size_t *membar2_offset, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ int ret; ++ ++ /* ++ * Shadow registers may exist in certain VMD device IDs which allow ++ * guests to correctly assign host physical addresses to the root ports ++ * and child devices. These registers will either return the host value ++ * or 0, depending on an enable bit in the VMD device. ++ * ++ * For certain VMD devices (i.e. 0x28C1), BIOS places device info ++ * in BAR4 shadow registers to determine the base bus number and memory ++ * offsets. ++ */ ++ if (features & VMD_FEAT_USE_BIOS_INFO) { ++ *membar2_offset = MEMBAR2_OFFSET_28C1; ++ ret = vmd_get_bus_info_from_bar4(vmd, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { ++ *membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; ++ ret = vmd_get_phys_offsets(vmd, true, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { ++ ret = vmd_get_phys_offsets(vmd, false, offset1, offset2); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Certain VMD devices may have a root port configuration option which ++ * limits the bus range to between 0-127, 128-255, or 224-255. ++ */ ++ if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { ++ ret = vmd_get_bus_number_start(vmd); ++ if (ret) ++ return ret; ++ } ++ return 0; ++} ++ + /* + * Since VMD is an aperture to regular PCIe root ports, only allow it to + * control features that the OS is allowed to control on the physical PCI bus. +@@ -780,42 +911,24 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) + LIST_HEAD(resources); + resource_size_t offset[2] = {0}; + resource_size_t membar2_offset = 0x2000; ++ resource_size_t busn_end; + struct pci_bus *child; + struct pci_dev *dev; + int ret; + +- /* +- * Shadow registers may exist in certain VMD device ids which allow +- * guests to correctly assign host physical addresses to the root ports +- * and child devices. These registers will either return the host value +- * or 0, depending on an enable bit in the VMD device. +- */ +- if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { +- membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; +- ret = vmd_get_phys_offsets(vmd, true, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { +- ret = vmd_get_phys_offsets(vmd, false, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } +- +- /* +- * Certain VMD devices may have a root port configuration option which +- * limits the bus range to between 0-127, 128-255, or 224-255 +- */ +- if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { +- ret = vmd_get_bus_number_start(vmd); +- if (ret) +- return ret; +- } ++ ret = vmd_prepare_offsets_and_bus(vmd, features, &membar2_offset, ++ &offset[0], &offset[1]); ++ if (ret) ++ return ret; + ++ /* Do not let resource[0] end go out of bounds */ + res = &vmd->dev->resource[VMD_CFGBAR]; ++ busn_end = vmd->busn_start + (resource_size(res) >> 20) - 1; ++ busn_end = min_t(resource_size_t, busn_end, 0xff); + vmd->resources[0] = (struct resource) { + .name = "VMD CFGBAR", + .start = vmd->busn_start, +- .end = vmd->busn_start + (resource_size(res) >> 20) - 1, ++ .end = busn_end, + .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, + }; + +@@ -998,6 +1111,7 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) + + vmd->dev = dev; + vmd->sysdata.domain = PCI_DOMAIN_NR_NOT_SET; ++ vmd->features = features; + vmd->instance = ida_alloc(&vmd_instance_ida, GFP_KERNEL); + if (vmd->instance < 0) + return vmd->instance; +@@ -1114,6 +1228,10 @@ static const struct pci_device_id vmd_ids[] = { + .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | + VMD_FEAT_HAS_BUS_RESTRICTIONS | + VMD_FEAT_CAN_BYPASS_MSI_REMAP,}, ++ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_28C1), ++ .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | ++ VMD_FEAT_CAN_BYPASS_MSI_REMAP | ++ VMD_FEAT_USE_BIOS_INFO,}, + {PCI_VDEVICE(INTEL, 0x467f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x4c3d), +diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h +--- a/include/linux/pci_ids.h ++++ b/include/linux/pci_ids.h +@@ -2892,6 +2892,7 @@ + #define PCI_DEVICE_ID_INTEL_HDA_ICH8 0x284b + #define PCI_DEVICE_ID_INTEL_ICH8_6 0x2850 + #define PCI_DEVICE_ID_INTEL_VMD_28C0 0x28c0 ++#define PCI_DEVICE_ID_INTEL_VMD_28C1 0x28c1 + #define PCI_DEVICE_ID_INTEL_ICH9_0 0x2910 + #define PCI_DEVICE_ID_INTEL_ICH9_2 0x2912 + #define PCI_DEVICE_ID_INTEL_ICH9_3 0x2913 diff --git a/pkgbuilds/linux-omarchy-bore/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig b/pkgbuilds/linux-omarchy-bore/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig new file mode 100644 index 0000000..5969dea Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0704-pci-vmd-meteor-lake-mtl016-flush.patch b/pkgbuilds/linux-omarchy-bore/0704-pci-vmd-meteor-lake-mtl016-flush.patch new file mode 100644 index 0000000..22be8d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0704-pci-vmd-meteor-lake-mtl016-flush.patch @@ -0,0 +1,120 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -92,6 +92,22 @@ enum vmd_features { + * referred to as MEMBAR2 or MSI-X BAR. + */ + VMD_FEAT_USE_BIOS_INFO = (1 << 6), ++ ++ /* ++ * Meteor Lake VMD (device ID 0x7d0b) is affected by erratum MTL016: ++ * the VMD may signal its MSI before the posted writes that carry the ++ * child device's DMA data have landed in memory. The demuxed handler ++ * then runs against a not-yet-coherent completion queue and misses the ++ * completion entirely, so the I/O is only recovered when the block ++ * layer timeout fires and polls the queue ("timeout, completion ++ * polled"). Intel's documented workaround is to issue a dummy read ++ * to the MSI initiator (the child device) before handling the ++ * interrupt: the read completion cannot pass the device's earlier ++ * posted writes, so it pulls them into memory per PCIe ordering ++ * rules. A read that terminates at the VMD itself does not order ++ * against the child's writes and is not sufficient (measured). ++ */ ++ VMD_FEAT_INTERRUPT_QUIRK = (1 << 7), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -114,6 +130,9 @@ static DEFINE_RAW_SPINLOCK(list_lock); + * @irq: back pointer to parent. + * @enabled: true if driver enabled IRQ + * @virq: the virtual IRQ value provided to the requesting driver. ++ * @flush_addr: config space address of the initiating device, read before ++ * demuxing to flush its posted writes (MTL016); NULL if the ++ * VMD is not affected. + * + * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to + * a VMD IRQ using this structure. +@@ -123,8 +142,14 @@ struct vmd_irq { + struct vmd_irq_list *irq; + bool enabled; + unsigned int virq; ++ void __iomem *flush_addr; + }; + ++struct vmd_dev; ++ ++static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, ++ unsigned int devfn, int reg, int len); ++ + /** + * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector + * @irq_list: the list of irq's the VMD one demuxes to. +@@ -132,12 +157,15 @@ struct vmd_irq { + * @count: number of child IRQs assigned to this vector; used to track + * sharing. + * @virq: The underlying VMD Linux interrupt number ++ * @vmd: back pointer to the owning VMD device; serializes the MTL016 ++ * flush read against other config space access. + */ + struct vmd_irq_list { + struct list_head irq_list; + struct srcu_struct srcu; + unsigned int count; + unsigned int virq; ++ struct vmd_dev *vmd; + }; + + struct vmd_dev { +@@ -295,6 +323,13 @@ static int vmd_msi_alloc(struct irq_domain *domain, unsigned int virq, + INIT_LIST_HEAD(&vmdirq->node); + vmdirq->irq = vmd_next_irq(vmd, desc); + vmdirq->virq = virq + i; ++ if (vmd->features & VMD_FEAT_INTERRUPT_QUIRK) { ++ struct pci_dev *pdev = msi_desc_to_pci_dev(desc); ++ ++ vmdirq->flush_addr = vmd_cfg_addr(vmd, pdev->bus, ++ pdev->devfn, ++ PCI_VENDOR_ID, 2); ++ } + + irq_domain_set_info(domain, virq + i, vmdirq->irq->virq, + &vmd_msi_controller, vmdirq, +@@ -753,8 +788,20 @@ static irqreturn_t vmd_irq(int irq, void *data) + int idx; + + idx = srcu_read_lock(&irqs->srcu); +- list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) ++ list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) { ++ /* ++ * MTL016: the MSI may have outrun the initiating device's ++ * posted writes (e.g. its NVMe completion entry). A read ++ * that completes at the initiator flushes them, so the ++ * demuxed handler observes a coherent completion queue. ++ * The value is discarded; only the ordering matters. ++ */ ++ if (vmdirq->flush_addr) { ++ guard(raw_spinlock)(&irqs->vmd->cfg_lock); ++ readw(vmdirq->flush_addr); ++ } + generic_handle_irq(vmdirq->virq); ++ } + srcu_read_unlock(&irqs->srcu, idx); + + return IRQ_HANDLED; +@@ -785,6 +832,7 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return err; + + INIT_LIST_HEAD(&vmd->irqs[i].irq_list); ++ vmd->irqs[i].vmd = vmd; + vmd->irqs[i].virq = pci_irq_vector(dev, i); + err = devm_request_irq(&dev->dev, vmd->irqs[i].virq, + vmd_irq, IRQF_NO_THREAD, +@@ -1239,7 +1287,7 @@ static const struct pci_device_id vmd_ids[] = { + {PCI_VDEVICE(INTEL, 0xa77f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x7d0b), +- .driver_data = VMD_FEATS_CLIENT,}, ++ .driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,}, + {PCI_VDEVICE(INTEL, 0xad0b), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), diff --git a/pkgbuilds/linux-omarchy-bore/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig b/pkgbuilds/linux-omarchy-bore/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig new file mode 100644 index 0000000..63aecd3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch b/pkgbuilds/linux-omarchy-bore/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch new file mode 100644 index 0000000..49806cf --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -4370,6 +4370,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_JMICRON, + DECLARE_PCI_FIXUP_HEADER(0x1c28, /* Lite-On */ + 0x0122, /* Plextor M6E (Marvell 88SS9183)*/ + quirk_dma_func1_alias); ++/* Toshiba AHCI SSD controller in MacBookAir6,1 and MacBookAir7,1 */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA, 0x010b, ++ quirk_dma_func1_alias); + + /* + * Some devices DMA with the wrong devfn, not just the wrong function. diff --git a/pkgbuilds/linux-omarchy-bore/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig b/pkgbuilds/linux-omarchy-bore/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig new file mode 100644 index 0000000..678cdb3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0711-iommu-amd-perfopt-support.patch b/pkgbuilds/linux-omarchy-bore/0711-iommu-amd-perfopt-support.patch new file mode 100644 index 0000000..4b13fed --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0711-iommu-amd-perfopt-support.patch @@ -0,0 +1,413 @@ +diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h +--- a/drivers/iommu/amd/amd_iommu.h ++++ b/drivers/iommu/amd/amd_iommu.h +@@ -48,6 +48,9 @@ extern u8 amd_iommu_hpt_vasize; + extern unsigned long amd_iommu_pgsize_bitmap; + extern bool amd_iommu_hatdis; + ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu); ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu); ++ + /* Protection domain ops */ + void amd_iommu_init_identity_domain(void); + struct protection_domain *protection_domain_alloc(void); +diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h +--- a/drivers/iommu/amd/amd_iommu_types.h ++++ b/drivers/iommu/amd/amd_iommu_types.h +@@ -65,6 +65,7 @@ + #define MMIO_MSI_ADDR_LO_OFFSET 0x015C + #define MMIO_MSI_ADDR_HI_OFFSET 0x0160 + #define MMIO_MSI_DATA_OFFSET 0x0164 ++#define MMIO_PERF_OPT_OFFSET 0x016C + #define MMIO_INTCAPXT_EVT_OFFSET 0x0170 + #define MMIO_INTCAPXT_PPR_OFFSET 0x0178 + #define MMIO_INTCAPXT_GALOG_OFFSET 0x0180 +@@ -99,6 +100,8 @@ + #define FEATURE_GLX GENMASK_ULL(15, 14) + #define FEATURE_GAM_VAPIC BIT_ULL(21) + #define FEATURE_PASMAX GENMASK_ULL(36, 32) ++#define FEATURE_PERF_OPT BIT_ULL(45) ++#define PERF_OPT_EN BIT(13) + #define FEATURE_GIOSUP BIT_ULL(48) + #define FEATURE_HASUP BIT_ULL(49) + #define FEATURE_EPHSUP BIT_ULL(50) +@@ -663,6 +666,9 @@ struct amd_iommu { + /* Extended features 2 */ + u64 features2; + ++ /* Devices requesting PerfOpt; the shared PERF_OPT_EN bit is on while >0. Protected by @lock. */ ++ int perfopt_refcount; ++ + /* PCI device id of the IOMMU device */ + u16 devid; + +@@ -824,6 +830,7 @@ struct iommu_dev_data { + u8 ppr :1; /* Enable device PPR support */ + bool use_vapic; /* Enable device to use vapic mode */ + bool defer_attach; ++ bool perfopt; + + struct ratelimit_state rs; /* Ratelimit IOPF messages */ + }; +diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c +--- a/drivers/iommu/amd/init.c ++++ b/drivers/iommu/amd/init.c +@@ -1945,6 +1945,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h, + if (!iommu->mmio_base) + return -ENOMEM; + ++ if (amd_iommu_perfopt_clear(iommu)) ++ pr_err("IOMMU%d: failed to clear PerfOpt\n", iommu->index); ++ + return init_iommu_from_acpi(iommu, h); + } + +@@ -3035,10 +3038,46 @@ static void enable_iommus_vapic(void) + #endif + } + ++static int clear_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_clear(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ ++static int restore_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_restore(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ + static void disable_iommus(void) + { + struct amd_iommu *iommu; + ++ /* ++ * PerfOpt is an optional performance bit, so a failure to clear it must ++ * not skip the mandatory disable below. This also runs from the void ++ * amd_iommu_disable() shutdown/kexec path, which cannot report an error. ++ */ ++ if (clear_perfopt_all()) ++ pr_err("Failed to clear PerfOpt while disabling IOMMUs\n"); ++ + for_each_iommu(iommu) + iommu_disable(iommu); + +@@ -3064,6 +3103,10 @@ static void amd_iommu_resume(void *data) + for_each_iommu(iommu) + early_enable_iommu(iommu); + ++ /* early_enable_iommu() cleared PERF_OPT_EN; re-assert it from the refcount. */ ++ if (restore_perfopt_all()) ++ pr_err("Failed to restore PerfOpt after IOMMU resume\n"); ++ + iommu_enable_event_buffer(); + amd_iommu_enable_interrupts(); + } +diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c +--- a/drivers/iommu/amd/iommu.c ++++ b/drivers/iommu/amd/iommu.c +@@ -2352,6 +2352,9 @@ static int attach_device(struct device *dev, + if (ret) + goto out; + ++ if (dev_data->perfopt) ++ goto skip_caps; ++ + /* Setup GCR3 table */ + if (pdom_is_sva_capable(domain)) { + ret = init_gcr3_table(dev_data, domain); +@@ -2376,6 +2379,7 @@ static int attach_device(struct device *dev, + pdev_enable_cap_ats(pdev); + } + ++skip_caps: + /* Update data structures */ + dev_data->domain = domain; + spin_lock_irqsave(&domain->lock, flags); +@@ -2444,6 +2448,192 @@ static void detach_device(struct device *dev) + mutex_unlock(&dev_data->mutex); + } + ++/* Program the per-IOMMU PerfOpt enable bit. Caller must hold iommu->lock. */ ++static int __perfopt_write(struct amd_iommu *iommu, bool enable) ++{ ++ u32 old, val, readback; ++ ++ if (!(readq(iommu->mmio_base + MMIO_EXT_FEATURES) & FEATURE_PERF_OPT)) ++ return enable ? -ENODEV : 0; ++ ++ old = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (old == U32_MAX) ++ return -EIO; ++ ++ val = enable ? old | PERF_OPT_EN : old & ~PERF_OPT_EN; ++ if (val != old) ++ writel(val, iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ readback = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (readback == U32_MAX || ++ (readback & PERF_OPT_EN) != (val & PERF_OPT_EN)) ++ return -EIO; ++ return 0; ++} ++ ++/* ++ * PERF_OPT_EN is a single bit shared by every device behind @iommu, so it is ++ * reference counted: armed on the first requesting device, cleared on the last. ++ */ ++static int perfopt_get(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount == 0) { ++ ret = __perfopt_write(iommu, true); ++ if (ret) ++ goto out; ++ } ++ iommu->perfopt_refcount++; ++out: ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++static int perfopt_put(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount > 0 && --iommu->perfopt_refcount == 0) ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Force PERF_OPT_EN off without touching the refcount (used on init, shutdown, ++ * and suspend). The count is preserved so amd_iommu_perfopt_restore() can ++ * re-arm on resume. ++ */ ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Re-assert PERF_OPT_EN from the refcount after the hardware was reprogrammed on ++ * resume, so devices armed before suspend keep the optimization without each ++ * consumer driver re-arming. ++ */ ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, iommu->perfopt_refcount > 0); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ int ret; ++ ++ if (!iommu || !dev_data) ++ return -ENODEV; ++ ++ if (!(iommu->features & FEATURE_PERF_OPT)) ++ return -ENODEV; ++ ++ domain = dev_data->domain; ++ if (!domain) ++ return -ENODEV; ++ ++ /* Already armed for this device (e.g. re-entry on resume). */ ++ if (dev_data->perfopt) ++ return 0; ++ ++ /* ++ * The bit is only architecturally valid while the device is untranslated: ++ * identity domain with ATS/PRI/PASID off. The identity domain is ++ * SVA-capable so attach_device() enabled ATS/PRI/PASID and built a GCR3 ++ * table. Re-home the device onto the same identity domain with ++ * perfopt set, so the attach_device() skip_caps path leaves ++ * ATS/PRI/PASID off and no GCR3 table. This follows the detach/attach ++ * pattern used by amd_iommu_attach_device(). ++ * ++ * Locking: this and amd_iommu_disable_perfopt() run only from the ++ * consumer driver's bind/unbind path. group->mutex is not exposed to ++ * drivers, but a device bound to its native driver cannot have its domain ++ * changed concurrently by the core (VFIO ownership is mutually exclusive; ++ * sysfs domain changes require an unused group), so the detach/attach pair ++ * is serialized without it. ++ */ ++ dev_data->perfopt = true; ++ detach_device(&pdev->dev); ++ ret = attach_device(&pdev->dev, domain); ++ if (ret) ++ goto err_restore; ++ ++ ret = perfopt_get(iommu); ++ if (ret) ++ goto err_rearm; ++ ++ dev_info_once(&pdev->dev, "PerfOpt armed on IOMMU%d\n", iommu->index); ++ return 0; ++ ++err_rearm: ++ detach_device(&pdev->dev); ++err_restore: ++ dev_data->perfopt = false; ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore state after PerfOpt setup; device left detached\n"); ++ dev_err_once(&pdev->dev, "PerfOpt failed to arm on IOMMU%d (%d)\n", ++ iommu->index, ret); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(amd_iommu_enable_perfopt); ++ ++void amd_iommu_disable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ ++ if (!iommu || !dev_data || !dev_data->perfopt || !dev_data->domain) ++ return; ++ ++ if (WARN_ON(perfopt_put(iommu))) ++ pci_err(pdev, "failed to clear PerfOpt\n"); ++ ++ /* ++ * Restore ATS/PRI/PASID (and thus SVA) by re-homing the device onto its ++ * identity domain with the flag cleared, so a later bind without PerfOpt ++ * sees a normally-capable device. See the locking note in ++ * amd_iommu_enable_perfopt(). ++ */ ++ domain = dev_data->domain; ++ dev_data->perfopt = false; ++ detach_device(&pdev->dev); ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore caps after PerfOpt disable\n"); ++} ++EXPORT_SYMBOL_GPL(amd_iommu_disable_perfopt); + static struct iommu_device *amd_iommu_probe_device(struct device *dev) + { + struct iommu_device *iommu_dev; +@@ -2511,6 +2701,14 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) + static void amd_iommu_release_device(struct device *dev) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt on release\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + WARN_ON(dev_data->domain); + +@@ -2885,6 +3083,19 @@ static int blocked_domain_attach_device(struct iommu_domain *domain, + struct iommu_domain *old) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ /* ++ * blocked_domain is also the .release_domain, so this is the normal ++ * teardown path: drop the reference and clear the flag here too, and ++ * don't fail teardown if the WARN-guarded write doesn't stick. ++ */ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt for blocked domain\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + if (dev_data->domain) + detach_device(dev); +@@ -2953,6 +3164,9 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev, + struct amd_iommu *iommu = get_amd_iommu_from_dev(dev); + int ret; + ++ if (dev_data->perfopt && !pdom_is_in_pt_mode(domain)) ++ return -EBUSY; ++ + /* + * Skip attach device to domain if new domain is same as + * devices current domain +diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h +--- a/include/linux/amd-iommu.h ++++ b/include/linux/amd-iommu.h +@@ -11,11 +11,11 @@ + #include + + struct amd_iommu; ++struct pci_dev; + + #ifdef CONFIG_AMD_IOMMU + + struct task_struct; +-struct pci_dev; + + extern void amd_iommu_detect(void); + +@@ -76,4 +76,15 @@ static inline int amd_iommu_snp_disable(void) { return 0; } + static inline bool amd_iommu_sev_tio_supported(void) { return false; } + #endif + ++#ifdef CONFIG_AMD_IOMMU ++int amd_iommu_enable_perfopt(struct pci_dev *pdev); ++void amd_iommu_disable_perfopt(struct pci_dev *pdev); ++#else ++static inline int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ return 0; ++} ++static inline void amd_iommu_disable_perfopt(struct pci_dev *pdev) { } ++#endif ++ + #endif /* _ASM_X86_AMD_IOMMU_H */ diff --git a/pkgbuilds/linux-omarchy-bore/0711-iommu-amd-perfopt-support.patch.sig b/pkgbuilds/linux-omarchy-bore/0711-iommu-amd-perfopt-support.patch.sig new file mode 100644 index 0000000..c4b7be2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0711-iommu-amd-perfopt-support.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0712-drm-amdgpu-perfopt-in-identity-domain.patch b/pkgbuilds/linux-omarchy-bore/0712-drm-amdgpu-perfopt-in-identity-domain.patch new file mode 100644 index 0000000..5c74198 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0712-drm-amdgpu-perfopt-in-identity-domain.patch @@ -0,0 +1,128 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h +@@ -160,6 +160,7 @@ struct amdgpu_watchdog_timer { + * Modules parameters. + */ + extern int amdgpu_modeset; ++extern int amdgpu_iommu_perfopt; + extern unsigned int amdgpu_vram_limit; + extern int amdgpu_vis_vram_limit; + extern int amdgpu_gart_size; +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -3842,6 +3843,28 @@ amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px) + apple_gmux_detect(NULL, NULL))); + } + ++static inline bool amdgpu_device_identity(struct amdgpu_device *adev) ++{ ++ struct pci_dev *pdev = adev->pdev; ++ struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev); ++ ++ if (!domain) ++ return false; ++ ++ return domain->type == IOMMU_DOMAIN_IDENTITY; ++} ++ ++static bool amdgpu_device_use_perfopt(struct amdgpu_device *adev) ++{ ++ if (amdgpu_iommu_perfopt == 0) ++ return false; ++ ++ if (!(adev->flags & AMD_IS_APU)) ++ return false; ++ ++ return amdgpu_device_identity(adev); ++} ++ + /** + * amdgpu_device_init - initialize the driver + * +@@ -4048,6 +4071,16 @@ int amdgpu_device_init(struct amdgpu_device *adev, + if (r) + return r; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(pdev); ++ ++ /* Optional optimization; a failure to arm it must not abort probe. */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + /* + * No need to remove conflicting FBs for non-display class devices. + * This prevents the sysfb from being freed accidently. +@@ -4416,6 +4449,9 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) + + amdgpu_gart_dummy_page_fini(adev); + ++ if (amdgpu_device_use_perfopt(adev)) ++ amd_iommu_disable_perfopt(adev->pdev); ++ + if (pci_dev_is_disconnected(adev->pdev)) + amdgpu_device_unmap_mmio(adev); + +@@ -4779,6 +4815,20 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients) + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(adev->pdev); ++ ++ /* ++ * Must not return on failure: a bare return would leak the ++ * SR-IOV VF exclusive-mode acquisition taken above (released ++ * via the exit: path). ++ */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + if (adev->in_s0ix) + amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D0Entry); + +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -185,6 +185,7 @@ char *amdgpu_disable_cu; + char *amdgpu_virtual_display; + int amdgpu_enforce_isolation = -1; + int amdgpu_modeset = -1; ++int amdgpu_iommu_perfopt = -1; + + /* Specifies the default granularity for SVM, used in buffer + * migration and restoration of backing memory when handling +@@ -393,6 +394,17 @@ module_param_named(fw_load_type, amdgpu_fw_load_type, int, 0444); + MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)"); + module_param_named(aspm, amdgpu_aspm, int, 0444); + ++/** ++ * DOC: iommu_perfopt (int) ++ * Control the AMD IOMMU PerfOpt DMA-latency optimization ++ * (0 = disable; -1 = enable on supported devices). ++ * This arms the IOMMU PerfOpt control (IOMMU spec, MMIO Offset 016Ch, EFR PerfOptSup / PerfOptEn) ++ * Arming it disables ATS, PRI, PASID and SVA for the GPU and removes IOMMU DMA containment for it, ++ * trading isolation for lower DMA latency. ++ */ ++MODULE_PARM_DESC(iommu_perfopt, "Control IOMMU PerfOpt DMA-latency optimization (-1 = enable on supported devices, 0 = disable)"); ++module_param_named(iommu_perfopt, amdgpu_iommu_perfopt, int, 0444); ++ + /** + * DOC: runpm (int) + * Override for runtime power management control for dGPUs. The amdgpu driver can dynamically power down diff --git a/pkgbuilds/linux-omarchy-bore/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig b/pkgbuilds/linux-omarchy-bore/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig new file mode 100644 index 0000000..8d77e46 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0740-hwmon-updates.patch b/pkgbuilds/linux-omarchy-bore/0740-hwmon-updates.patch new file mode 100644 index 0000000..a66a783 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0740-hwmon-updates.patch @@ -0,0 +1,7419 @@ +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -224,6 +224,7 @@ config SENSORS_ADT7462 + config SENSORS_ADT7470 + tristate "Analog Devices ADT7470" + depends on I2C ++ depends on PWM || PWM=n + select REGMAP_I2C + help + If you say yes here you get support for the Analog Devices +@@ -292,11 +293,11 @@ config SENSORS_ASC7621 + will be called asc7621. + + config SENSORS_ASUS_ROG_RYUJIN +- tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver" ++ tristate "ASUS ROG RYUJIN hardware monitoring driver" + depends on HID + help + If you say yes here you get support for the fans and sensors of +- the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler. ++ supported ASUS ROG RYUJIN II and III AIO CPU liquid coolers. + + This driver can also be built as a module. If so, the module + will be called asus_rog_ryujin. +@@ -335,6 +336,17 @@ config SENSORS_K10TEMP + This driver can also be built as a module. If so, the module + will be called k10temp. + ++config SENSORS_KB9002 ++ tristate "Kandou KB9002 PCIe retimer" ++ depends on I2C ++ help ++ If you say yes here you get support for the integrated ++ temperature sensor and firmware version readout on Kandou ++ KB9002 PCIe 5.0 retimers, accessed over SMBus. ++ ++ This driver can also be built as a module. If so, the module ++ will be called kb9002. ++ + config SENSORS_KBATT + tristate "KEBA battery controller support" + depends on KEBA_CP500 +@@ -403,7 +415,6 @@ config SENSORS_ARCTIC_FAN_CONTROLLER + config SENSORS_ARM_SCMI + tristate "ARM SCMI Sensors" + depends on ARM_SCMI_PROTOCOL +- depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on SCMI based platforms. The actual +@@ -415,7 +426,6 @@ config SENSORS_ARM_SCMI + config SENSORS_ARM_SCPI + tristate "ARM SCPI Sensors" + depends on ARM_SCPI_PROTOCOL +- depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on ARM Ltd's SCP based platforms. The +@@ -435,7 +445,6 @@ config SENSORS_ASB100 + config SENSORS_ASPEED + tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver" + depends on ARCH_ASPEED || COMPILE_TEST +- depends on THERMAL || THERMAL=n + select REGMAP + help + This driver provides support for ASPEED AST2400/AST2500 PWM +@@ -506,8 +515,8 @@ config SENSORS_CORSAIR_PSU + If you say yes here you get support for Corsair PSUs with a HID + interface. + Currently this driver supports the (RM/HX)550i, (RM/HX)650i, +- (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies +- by Corsair. ++ (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i, HX1200i and HX1500i power ++ supplies by Corsair. + + This driver can also be built as a module. If so, the module + will be called corsair-psu. +@@ -788,7 +797,6 @@ config SENSORS_G762 + config SENSORS_GPIO_FAN + tristate "GPIO fan" + depends on GPIOLIB || COMPILE_TEST +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to GPIO lines. + +@@ -802,7 +810,7 @@ config SENSORS_GXP_FAN_CTRL + If you say yes here you get support for GXP fan control functionality. + + The GXP controls fan function via the CPLD through the use of PWM +- registers. This driver reports status and pwm setting of the fans. ++ registers. This driver enables pwm setting of the fans. + + config SENSORS_HIH6130 + tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor" +@@ -942,6 +950,19 @@ config SENSORS_JC42 + This driver can also be built as a module. If so, the module + will be called jc42. + ++config SENSORS_POLARFIRE_SOC_TVS ++ tristate "PolarFire SoC (MPFS) temperature and voltage sensor" ++ depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST ++ depends on MFD_SYSCON ++ help ++ This driver adds support for the PolarFire SoC (MPFS) Temperature and ++ Voltage Sensor. ++ ++ To compile this driver as a module, choose M here. the ++ module will be called tvs-mpfs. ++ ++ If unsure, say N. ++ + config SENSORS_POWERZ + tristate "ChargerLAB POWER-Z USB-C tester" + depends on USB +@@ -1357,7 +1378,6 @@ config SENSORS_MAX6639 + config SENSORS_MAX6650 + tristate "Maxim MAX6650 sensor chip" + depends on I2C +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for the MAX6650 / MAX6651 + sensor chips. +@@ -1732,7 +1752,6 @@ config SENSORS_PC87427 + config SENSORS_NTC_THERMISTOR + tristate "NTC thermistor support" + depends on IIO +- depends on THERMAL || !THERMAL_OF + help + This driver supports NTC thermistors sensor reading and its + interpretation. The driver can also monitor the temperature and +@@ -1933,7 +1952,6 @@ config SENSORS_PT5161L + config SENSORS_PWM_FAN + tristate "PWM fan" + depends on PWM || COMPILE_TEST +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to PWM lines. + The driver uses the generic PWM interface, thus it will work on a +@@ -1945,7 +1963,6 @@ config SENSORS_PWM_FAN + config SENSORS_QNAP_MCU_HWMON + tristate "QNAP MCU hardware monitoring" + depends on MFD_QNAP_MCU +- depends on THERMAL || THERMAL=n + help + Say yes here to enable support for fan and temperature sensor + connected to a QNAP MCU, as found in a number of QNAP network +@@ -2010,8 +2027,9 @@ config SENSORS_SHT3x + depends on I2C + select CRC8 + help +- If you say yes here you get support for the Sensiron SHT30, SHT31 and +- SHT85 humidity and temperature sensors. ++ If you say yes here you get support for the Sensiron SHT30, SHT31, ++ SHT85 and the compatible GXCAS GXHT30 humidity and temperature ++ sensors. + + This driver can also be built as a module. If so, the module + will be called sht3x. +@@ -2070,6 +2088,17 @@ config SENSORS_DME1737 + This driver can also be built as a module. If so, the module + will be called dme1737. + ++config SENSORS_EIC7700_PVT ++ tristate "Eswin EIC7700 Voltage, Temperature sensor driver" ++ depends on ARCH_ESWIN || COMPILE_TEST ++ select POLYNOMIAL ++ help ++ If you say yes here you get support for Eswin EIC7700 PVT sensor ++ embedded into the SoC. ++ ++ This driver can also be built as a module. If so, the module will be ++ called eic7700-pvt. ++ + config SENSORS_EMC1403 + tristate "SMSC EMC1403/23 thermal sensor" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737) += dme1737.o + obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o + obj-$(CONFIG_SENSORS_DS620) += ds620.o + obj-$(CONFIG_SENSORS_DS1621) += ds1621.o ++obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o + obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o + obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o + obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o +@@ -112,6 +113,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o + obj-$(CONFIG_SENSORS_JC42) += jc42.o + obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o + obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o ++obj-$(CONFIG_SENSORS_KB9002) += kb9002.o + obj-$(CONFIG_SENSORS_KBATT) += kbatt.o + obj-$(CONFIG_SENSORS_KFAN) += kfan.o + obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o +@@ -197,6 +199,7 @@ obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o + obj-$(CONFIG_SENSORS_PC87360) += pc87360.o + obj-$(CONFIG_SENSORS_PC87427) += pc87427.o + obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o ++obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o + obj-$(CONFIG_SENSORS_POWERZ) += powerz.o + obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o + obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o +diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c +--- a/drivers/hwmon/acpi_power_meter.c ++++ b/drivers/hwmon/acpi_power_meter.c +@@ -21,8 +21,6 @@ + #include + + #define ACPI_POWER_METER_NAME "power_meter" +-#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" +-#define ACPI_POWER_METER_CLASS "pwr_meter_resource" + + #define NUM_SENSORS 17 + +@@ -877,7 +875,7 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, ++ acpi_bus_generate_netlink_event("pwr_meter_resource", + dev_name(&resource->acpi_dev->dev), + event, 0); + } +@@ -899,8 +897,6 @@ static int acpi_power_meter_probe(struct platform_device *pdev) + resource->sensors_valid = 0; + resource->acpi_dev = device; + mutex_init(&resource->lock); +- strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); + + platform_set_drvdata(pdev, resource); + +diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c +--- a/drivers/hwmon/adt7470.c ++++ b/drivers/hwmon/adt7470.c +@@ -22,6 +22,9 @@ + #include + #include + #include ++#include ++#include ++#include + + /* Addresses to scan */ + static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; +@@ -100,6 +103,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; + #define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2)) + + #define ADT7470_PWM_COUNT 4 ++#define ADT7470_PWM_MAX 255 + #define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x)) +@@ -163,6 +167,7 @@ struct adt7470_data { + char limits_valid; + unsigned long sensors_last_updated; /* In jiffies */ + unsigned long limits_last_updated; /* In jiffies */ ++ bool use_pwm_framework; + + int num_temp_sensors; /* -1 = probe */ + int temperatures_probed; +@@ -850,7 +855,7 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + + switch (attr) { + case hwmon_pwm_input: +- val = clamp_val(val, 0, 255); ++ val = clamp_val(val, 0, ADT7470_PWM_MAX); + mutex_lock(&data->lock); + err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), + val); +@@ -886,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + return err; + } + ++struct adt7470_pwm_wfhw { ++ u8 val; ++}; ++ ++static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const struct pwm_waveform *wf, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ u64 actual_period; ++ ++ if (wf->duty_length_ns == 0) { ++ wfhw->val = 0; ++ return 0; ++ } ++ ++ /* ++ * The PWM frequency (period) is a single chip-wide setting shared by ++ * all 4 channels, so it cannot be changed on a per-pwm_device basis ++ * through this API. The duty cycle is rounded against the currently ++ * configured hardware period rather than the period requested in ++ * @wf; round_waveform_fromhw() reports the actual resulting ++ * waveform back so the core/consumer can detect a mismatch. ++ */ ++ actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ ++ if (actual_period > wf->period_length_ns) ++ /* period too short */ ++ return 1; ++ ++ if (wf->duty_length_ns >= actual_period) { ++ wfhw->val = ADT7470_PWM_MAX; ++ } else { ++ wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns, ++ ADT7470_PWM_MAX, ++ actual_period); ++ } ++ ++ return 0; ++} ++ ++static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw, ++ struct pwm_waveform *wf) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ wf->duty_offset_ns = 0; ++ wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns, ++ ADT7470_PWM_MAX); ++ return 0; ++} ++ ++static int adt7470_pwm_read_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct device *dev = regmap_get_device(data->regmap); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ data = adt7470_update_device(dev); ++ if (IS_ERR(data)) ++ return PTR_ERR(data); ++ ++ /* ++ * No lock needed: like the other hwmon_ops read callbacks in this ++ * driver (e.g. adt7470_pwm_read()), this only does a single byte ++ * read from the cache populated by adt7470_update_device(). ++ */ ++ wfhw->val = data->pwm[pwm->hwpwm]; ++ ++ return 0; ++} ++ ++static int adt7470_pwm_write_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ unsigned int pwm_auto_reg_mask; ++ int err; ++ ++ if (pwm->hwpwm % 2) ++ pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; ++ else ++ pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; ++ ++ guard(mutex)(&data->lock); ++ ++ if (data->pwm[pwm->hwpwm] == wfhw->val && ++ data->pwm_automatic[pwm->hwpwm] == 0) ++ return 0; ++ ++ /* Put the PWM channel in manual mode before updating it. */ ++ err = regmap_update_bits(data->regmap, ++ ADT7470_REG_PWM_CFG(pwm->hwpwm), ++ pwm_auto_reg_mask, 0); ++ if (err < 0) ++ return err; ++ ++ data->pwm_automatic[pwm->hwpwm] = 0; ++ ++ err = regmap_write(data->regmap, ++ ADT7470_REG_PWM(pwm->hwpwm), wfhw->val); ++ if (err < 0) ++ return err; ++ ++ data->pwm[pwm->hwpwm] = wfhw->val; ++ ++ return 0; ++} ++ ++static const struct pwm_ops adt7470_pwm_ops = { ++ .sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw), ++ .round_waveform_tohw = adt7470_pwm_round_waveform_tohw, ++ .round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw, ++ .read_waveform = adt7470_pwm_read_waveform, ++ .write_waveform = adt7470_pwm_write_waveform, ++}; ++ + static ssize_t pwm_max_show(struct device *dev, + struct device_attribute *devattr, char *buf) + { +@@ -910,7 +1042,7 @@ static ssize_t pwm_max_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_max[attr->index] = temp; +@@ -945,7 +1077,7 @@ static ssize_t pwm_min_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_min[attr->index] = temp; +@@ -1106,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = { + &dev_attr_alarm_mask.attr, + &dev_attr_num_temp_sensors.attr, + &dev_attr_auto_update_interval.attr, ++ NULL ++}; ++ ++static struct attribute *adt7470_pwm_attrs[] = { + &sensor_dev_attr_force_pwm_max.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, +@@ -1130,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = { + NULL + }; + +-ATTRIBUTE_GROUPS(adt7470); ++static const struct attribute_group adt7470_group = { ++ .attrs = adt7470_attrs, ++}; ++ ++static umode_t adt7470_pwm_is_visible(struct kobject *kobj, ++ struct attribute *attr, int index) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct adt7470_data *data = dev_get_drvdata(dev); ++ ++ if (data->use_pwm_framework) ++ return 0; ++ ++ return attr->mode; ++} ++ ++static const struct attribute_group adt7470_pwm_group = { ++ .attrs = adt7470_pwm_attrs, ++ .is_visible = adt7470_pwm_is_visible, ++}; ++ ++static const struct attribute_group *adt7470_groups[] = { ++ &adt7470_group, ++ &adt7470_pwm_group, ++ NULL, ++}; + + static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +@@ -1165,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a + static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) + { ++ const struct adt7470_data *data = _data; + umode_t mode = 0; + + switch (type) { +@@ -1197,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ + } + break; + case hwmon_pwm: ++ /* Hide all pwm attributes if this device is exposed to the PWM ++ * framework ++ */ ++ if (data->use_pwm_framework) { ++ mode = 0; ++ break; ++ } ++ + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: +@@ -1226,6 +1396,8 @@ static const struct hwmon_ops adt7470_hwmon_ops = { + }; + + static const struct hwmon_channel_info * const adt7470_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, +@@ -1327,6 +1499,29 @@ static int adt7470_probe(struct i2c_client *client) + + data->pwm_freq = (u32)freq_val; + ++ data->use_pwm_framework = false; ++ ++ if (device_property_present(dev, "#pwm-cells")) { ++ if (IS_REACHABLE(CONFIG_PWM)) { ++ struct pwm_chip *chip; ++ ++ chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0); ++ if (IS_ERR(chip)) ++ return PTR_ERR(chip); ++ ++ chip->ops = &adt7470_pwm_ops; ++ pwmchip_set_drvdata(chip, data); ++ ++ err = devm_pwmchip_add(dev, chip); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register PWM chip\n"); ++ ++ data->use_pwm_framework = true; ++ } else { ++ dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n"); ++ } ++ } ++ + /* Register sysfs hooks */ + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &adt7470_chip_info, +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + /* data port used by Apple SMC */ + #define APPLESMC_DATA_PORT 0x300 +@@ -133,6 +134,7 @@ static struct applesmc_registers { + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ ++ char fan_positions[10][17]; /* cached fan position labels */ + } smcreg = { + .mutex = __MUTEX_INITIALIZER(smcreg.mutex), + }; +@@ -143,8 +145,8 @@ static s16 rest_x; + static s16 rest_y; + static u8 backlight_state[2]; + +-static struct device *hwmon_dev; + static struct input_dev *applesmc_idev; ++static struct device *hwmon_dev; + + /* + * Last index written to key_at_index sysfs file, and value to use for all other +@@ -372,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + __be32 be; + int ret = 0; + +- if (cache->valid) ++ /* Pairs with smp_store_release() to ensure cache contents are visible */ ++ if (smp_load_acquire(&cache->valid)) + return cache; + + mutex_lock(&smcreg.mutex); +@@ -391,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + cache->len = info[0]; + memcpy(cache->type, &info[1], 4); + cache->flags = info[5]; +- cache->valid = true; ++ /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ ++ smp_store_release(&cache->valid, true); + + out: + mutex_unlock(&smcreg.mutex); +@@ -566,7 +570,7 @@ static int applesmc_init_smcreg_try(void) + { + struct applesmc_registers *s = &smcreg; + bool left_light_sensor = false, right_light_sensor = false; +- unsigned int count; ++ unsigned int count, i; + u8 tmp[1]; + int ret; + +@@ -597,6 +601,16 @@ static int applesmc_init_smcreg_try(void) + if (s->fan_count > 10) + s->fan_count = 10; + ++ for (i = 0; i < s->fan_count; i++) { ++ char newkey[5]; ++ ++ scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); ++ ret = applesmc_read_key(newkey, s->fan_positions[i], 16); ++ s->fan_positions[i][16] = 0; ++ if (ret) ++ scnprintf(s->fan_positions[i], 17, " Fan %d", i); ++ } ++ + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); + if (ret) + return ret; +@@ -808,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, + return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); + } + +-/* Displays sensor key as label */ +-static ssize_t applesmc_show_sensor_label(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- +- return sysfs_emit(sysfsbuf, "%s\n", key); +-} +- +-/* Displays degree Celsius * 1000 */ +-static ssize_t applesmc_show_temperature(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- int ret; +- s16 value; +- int temp; +- +- ret = applesmc_read_s16(key, &value); +- if (ret) +- return ret; +- +- temp = 250 * (value >> 6); +- +- return sysfs_emit(sysfsbuf, "%d\n", temp); +-} +- + static ssize_t applesmc_show_fan_speed(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -854,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, + return sysfs_emit(sysfsbuf, "%u\n", speed); + } + +-static ssize_t applesmc_store_fan_speed(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- unsigned long speed; +- char newkey[5]; +- u8 buffer[2]; +- +- if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) +- return -EINVAL; /* Bigger than a 14-bit value */ +- +- scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], +- to_index(attr)); +- +- buffer[0] = (speed >> 6) & 0xff; +- buffer[1] = (speed << 2) & 0xff; +- ret = applesmc_write_key(newkey, buffer, 2); +- +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_manual(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- u16 manual = 0; +- u8 buffer[2]; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- return ret; +- +- manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; +- return sysfs_emit(sysfsbuf, "%d\n", manual); +-} +- +-static ssize_t applesmc_store_fan_manual(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- u8 buffer[2]; +- unsigned long input; +- u16 val; +- +- if (kstrtoul(sysfsbuf, 10, &input) < 0) +- return -EINVAL; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- goto out; +- +- val = (buffer[0] << 8 | buffer[1]); +- +- if (input) +- val = val | (0x01 << to_index(attr)); +- else +- val = val & ~(0x01 << to_index(attr)); +- +- buffer[0] = (val >> 8) & 0xFF; +- buffer[1] = val & 0xFF; +- +- ret = applesmc_write_key(FANS_MANUAL, buffer, 2); +- +-out: +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_position(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- char newkey[5]; +- u8 buffer[17]; +- +- scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); +- +- ret = applesmc_read_key(newkey, buffer, 16); +- buffer[16] = 0; +- +- if (ret) +- return ret; +- +- return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); +-} +- + static ssize_t applesmc_calibrate_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -1094,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { + { } + }; + +-static struct applesmc_node_group fan_group[] = { +- { "fan%d_label", applesmc_show_fan_position }, +- { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, +- { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, +- { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, +- { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, +- { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, +- { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, +- { } +-}; + +-static struct applesmc_node_group temp_group[] = { +- { "temp%d_label", applesmc_show_sensor_label }, +- { "temp%d_input", applesmc_show_temperature }, +- { } +-}; + + /* Module stuff */ + +@@ -1312,8 +1191,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { + }; + MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); + ++static struct applesmc_dev_attr *fan_safe_attrs; ++static struct attribute **fan_safe_attr_list; ++static struct attribute_group fan_safe_group; ++static const struct attribute_group *applesmc_extra_groups[2]; ++ ++static u32 *applesmc_temp_config; ++static u32 *applesmc_fan_config; ++static u32 *applesmc_pwm_config; ++static struct hwmon_channel_info *applesmc_info_temp; ++static struct hwmon_channel_info *applesmc_info_fan; ++static struct hwmon_channel_info *applesmc_info_pwm; ++static const struct hwmon_channel_info **applesmc_info_arr; ++static struct hwmon_chip_info *applesmc_chip; ++ ++static void applesmc_free_hwmon(void) ++{ ++ kfree(applesmc_temp_config); ++ kfree(applesmc_fan_config); ++ kfree(applesmc_pwm_config); ++ kfree(applesmc_info_temp); ++ kfree(applesmc_info_fan); ++ kfree(applesmc_info_pwm); ++ kfree(applesmc_info_arr); ++ kfree(applesmc_chip); ++ kfree(fan_safe_attrs); ++ kfree(fan_safe_attr_list); ++} ++ ++static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input || attr == hwmon_temp_label) ++ return 0444; ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: ++ case hwmon_fan_label: ++ case hwmon_fan_max: ++ return 0444; ++ case hwmon_fan_min: ++ case hwmon_fan_target: ++ return 0644; ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) ++ return 0644; ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input) { ++ const char *key = smcreg.index[channel]; ++ s16 value; ++ ++ ret = applesmc_read_s16(key, &value); ++ if (ret) ++ return ret; ++ *val = 250 * (value >> 6); ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dAc", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_min: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMn", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_max: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMx", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_target: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dTg", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ ++ ret = applesmc_read_key(FANS_MANUAL, buffer, 2); ++ if (ret) ++ return ret; ++ *val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_fan: ++ if (attr == hwmon_fan_min || attr == hwmon_fan_target) { ++ char key[5]; ++ u8 buffer[2]; ++ const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; ++ ++ if (val < 0 || val >= 0x4000) ++ return -EINVAL; ++ scnprintf(key, sizeof(key), fmt, channel); ++ buffer[0] = (val >> 6) & 0xff; ++ buffer[1] = (val << 2) & 0xff; ++ return applesmc_write_key(key, buffer, 2); ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ u16 manual_val; ++ const struct applesmc_entry *entry; ++ ++ if (val != 1 && val != 2) ++ return -EINVAL; ++ ++ entry = applesmc_get_entry_by_key(FANS_MANUAL); ++ if (IS_ERR(entry)) ++ return PTR_ERR(entry); ++ ++ mutex_lock(&smcreg.mutex); ++ ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); ++ if (ret) ++ goto out_unlock; ++ manual_val = (buffer[0] << 8 | buffer[1]); ++ if (val == 1) ++ manual_val |= (0x01 << channel); ++ else ++ manual_val &= ~(0x01 << channel); ++ buffer[0] = (manual_val >> 8) & 0xff; ++ buffer[1] = manual_val & 0xff; ++ ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); ++out_unlock: ++ mutex_unlock(&smcreg.mutex); ++ return ret; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = smcreg.index[channel]; ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ if (attr == hwmon_fan_label) { ++ *str = smcreg.fan_positions[channel] + 4; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops applesmc_hwmon_ops = { ++ .is_visible = applesmc_hwmon_is_visible, ++ .read = applesmc_hwmon_read, ++ .write = applesmc_hwmon_write, ++ .read_string = applesmc_hwmon_read_string, ++}; ++ + static int __init applesmc_init(void) + { ++ int i; + int ret; + + if (!dmi_check_system(applesmc_whitelist)) { +@@ -1348,17 +1457,97 @@ static int __init applesmc_init(void) + if (ret) + goto out_smcreg; + +- ret = applesmc_create_nodes(fan_group, smcreg.fan_count); +- if (ret) ++ /* allocate hwmon channel configs */ ++ applesmc_temp_config = kcalloc(smcreg.index_count + 1, ++ sizeof(*applesmc_temp_config), GFP_KERNEL); ++ applesmc_fan_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_fan_config), GFP_KERNEL); ++ applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_pwm_config), GFP_KERNEL); ++ if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { ++ ret = -ENOMEM; + goto out_info; ++ } + +- ret = applesmc_create_nodes(temp_group, smcreg.index_count); +- if (ret) +- goto out_fans; ++ for (i = 0; i < smcreg.index_count; i++) ++ applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; ++ applesmc_temp_config[smcreg.index_count] = 0; ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | ++ HWMON_F_MAX | HWMON_F_TARGET; ++ applesmc_pwm_config[i] = HWMON_PWM_ENABLE; ++ } ++ applesmc_fan_config[smcreg.fan_count] = 0; ++ applesmc_pwm_config[smcreg.fan_count] = 0; ++ ++ applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); ++ applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); ++ applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); ++ if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_temp->type = hwmon_temp; ++ applesmc_info_temp->config = applesmc_temp_config; ++ ++ applesmc_info_fan->type = hwmon_fan; ++ applesmc_info_fan->config = applesmc_fan_config; ++ ++ applesmc_info_pwm->type = hwmon_pwm; ++ applesmc_info_pwm->config = applesmc_pwm_config; ++ ++ applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ if (!applesmc_info_arr) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_arr[0] = applesmc_info_temp; ++ applesmc_info_arr[1] = applesmc_info_fan; ++ applesmc_info_arr[2] = applesmc_info_pwm; ++ applesmc_info_arr[3] = NULL; ++ ++ applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ if (!applesmc_chip) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_chip->ops = &applesmc_hwmon_ops; ++ applesmc_chip->info = applesmc_info_arr; ++ ++ /* Create non-standard fanX_safe attributes group */ ++ fan_safe_attrs = kcalloc(smcreg.fan_count, ++ sizeof(*fan_safe_attrs), GFP_KERNEL); ++ fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, ++ sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ if (!fan_safe_attrs || !fan_safe_attr_list) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ struct applesmc_dev_attr *node = &fan_safe_attrs[i]; ++ ++ scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); ++ node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ ++ node->sda.dev_attr.show = applesmc_show_fan_speed; ++ node->sda.dev_attr.store = NULL; ++ sysfs_attr_init(&node->sda.dev_attr.attr); ++ node->sda.dev_attr.attr.name = node->name; ++ node->sda.dev_attr.attr.mode = 0444; ++ fan_safe_attr_list[i] = &node->sda.dev_attr.attr; ++ } ++ fan_safe_attr_list[smcreg.fan_count] = NULL; ++ fan_safe_group.attrs = fan_safe_attr_list; ++ applesmc_extra_groups[0] = &fan_safe_group; ++ applesmc_extra_groups[1] = NULL; + + ret = applesmc_create_accelerometer(); + if (ret) +- goto out_temperature; ++ goto out_info; + + ret = applesmc_create_light_sensor(); + if (ret) +@@ -1368,7 +1557,8 @@ static int __init applesmc_init(void) + if (ret) + goto out_light_sysfs; + +- hwmon_dev = hwmon_device_register(&pdev->dev); ++ hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, ++ applesmc_chip, applesmc_extra_groups); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + goto out_light_ledclass; +@@ -1382,11 +1572,8 @@ static int __init applesmc_init(void) + applesmc_release_light_sensor(); + out_accelerometer: + applesmc_release_accelerometer(); +-out_temperature: +- applesmc_destroy_nodes(temp_group); +-out_fans: +- applesmc_destroy_nodes(fan_group); + out_info: ++ applesmc_free_hwmon(); + applesmc_destroy_nodes(info_group); + out_smcreg: + applesmc_destroy_smcreg(); +@@ -1407,13 +1594,12 @@ static void __exit applesmc_exit(void) + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); + applesmc_release_accelerometer(); +- applesmc_destroy_nodes(temp_group); +- applesmc_destroy_nodes(fan_group); + applesmc_destroy_nodes(info_group); + applesmc_destroy_smcreg(); + platform_device_unregister(pdev); + platform_driver_unregister(&applesmc_driver); + release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); ++ applesmc_free_hwmon(); + } + + module_init(applesmc_init); +diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c +--- a/drivers/hwmon/asus-ec-sensors.c ++++ b/drivers/hwmon/asus-ec-sensors.c +@@ -185,6 +185,20 @@ enum ec_sensors { + #define SENSOR_TEMP_SENSOR_EXTRA_2 BIT(ec_sensor_temp_sensor_extra_2) + #define SENSOR_TEMP_SENSOR_EXTRA_3 BIT(ec_sensor_temp_sensor_extra_3) + ++/* ++ * The values for temperature sensor readings without physical sensors connected. ++ * The value varies across generations and is seemingly defined by the EC chip ++ * used in the given board. ++ */ ++static const s32 temperature_blank_values[] = {-62, -60, -40}; ++ ++static const s32 environment_temp_sensors = ++ SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_ALT1 | ++ SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | ++ SENSOR_TEMP_WATER_BLOCK_IN | SENSOR_TEMP_WATER_BLOCK_OUT | ++ SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_SENSOR_EXTRA_1 | ++ SENSOR_TEMP_SENSOR_EXTRA_2 | SENSOR_TEMP_SENSOR_EXTRA_3; ++ + enum board_family { + family_unknown, + family_amd_400_series, +@@ -386,6 +400,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), ++ [ec_sensor_fan_cpu_opt] = ++ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe), + [ec_sensor_temp_water_in] = +@@ -487,6 +503,14 @@ static const struct ec_board_info board_info_crosshair_x670e_hero = { + .family = family_amd_600_series, + }; + ++static const struct ec_board_info board_info_crosshair_x870e_hero = { ++ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, ++ .family = family_amd_800_series, ++}; ++ + static const struct ec_board_info board_info_maximus_vi_hero = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | +@@ -527,6 +551,14 @@ static const struct ec_board_info board_info_maximus_z790_extreme = { + .family = family_intel_700_series, + }; + ++static const struct ec_board_info board_info_maximus_z790_hero = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, ++ .family = family_intel_700_series, ++}; ++ + static const struct ec_board_info board_info_prime_x470_pro = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | +@@ -590,6 +622,13 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { + .family = family_amd_800_series, + }; + ++static const struct ec_board_info board_info_pro_art_z690_creator_wifi = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, ++ .family = family_intel_600_series, ++}; ++ + static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = { + /* Board also has a nct6798 */ + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME | +@@ -743,7 +782,7 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { + + static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | +- SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +@@ -859,6 +898,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_pro_art_x670E_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI", + &board_info_pro_art_x870E_creator_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt Z690-CREATOR WIFI", ++ &board_info_pro_art_z690_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI", + &board_info_pro_ws_trx50_sage_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI A", +@@ -883,6 +924,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_crosshair_x670e_gene), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", + &board_info_crosshair_x670e_hero), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X870E HERO", ++ &board_info_crosshair_x870e_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO", + &board_info_maximus_xi_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", +@@ -893,6 +936,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_maximus_z690_formula), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME", + &board_info_maximus_z790_extreme), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 HERO", ++ &board_info_maximus_z790_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", + &board_info_strix_b550_e_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", +@@ -927,8 +972,12 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_strix_x870_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI", + &board_info_strix_x870e_e_gaming_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI7 R2", ++ &board_info_strix_x870e_e_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7", + &board_info_strix_x870e_h_gaming_wifi7), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING", ++ &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", + &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", +@@ -955,6 +1004,7 @@ static const struct dmi_system_id dmi_table[] = { + }; + + struct ec_sensor { ++ /* this is ec_sensors enum value */ + unsigned int info_index; + s32 cached_value; + }; +@@ -1047,6 +1097,12 @@ get_sensor_info(const struct ec_sensors_data *state, int index) + return state->sensors_info + state->sensors[index].info_index; + } + ++static enum ec_sensors ++get_ec_sensor_type(const struct ec_sensors_data *state, int index) ++{ ++ return state->sensors[index].info_index; ++} ++ + static int find_ec_sensor_index(const struct ec_sensors_data *ec, + enum hwmon_sensor_types type, int channel) + { +@@ -1290,6 +1346,17 @@ static int get_cached_value_or_update(const struct device *dev, + return 0; + } + ++static bool is_blank_temperature_value(s32 value) ++{ ++ size_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(temperature_blank_values); ++i) { ++ if (value == temperature_blank_values[i]) ++ return true; ++ } ++ return false; ++} ++ + /* + * Now follow the functions that implement the hwmon interface + */ +@@ -1297,6 +1364,8 @@ static int get_cached_value_or_update(const struct device *dev, + static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { ++ const struct ec_sensor_info *sensor_info; ++ enum ec_sensors ec_sensor; + int ret; + s32 value = 0; + +@@ -1308,12 +1377,19 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + } + + ret = get_cached_value_or_update(dev, sidx, state, &value); +- if (!ret) { +- *val = scale_sensor_value(value, +- get_sensor_info(state, sidx)->type); +- } ++ if (ret) ++ return ret; + +- return ret; ++ sensor_info = get_sensor_info(state, sidx); ++ if (sensor_info->type == hwmon_temp) { ++ ec_sensor = get_ec_sensor_type(state, sidx); ++ if ((environment_temp_sensors & BIT(ec_sensor)) && ++ is_blank_temperature_value(value)) ++ return -ENODATA; ++ } ++ *val = scale_sensor_value(value, sensor_info->type); ++ ++ return 0; + } + + static int asus_ec_hwmon_read_string(struct device *dev, +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. ++ * hwmon driver for Asus ROG Ryujin AIO coolers. + * + * Copyright 2024 Aleksa Savic + */ +@@ -17,6 +17,9 @@ + + #define USB_VENDOR_ID_ASUS_ROG 0x0b05 + #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ ++#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb ++#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade ++#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada + + struct rog_ryujin_device_info { + u8 temp_offset; +@@ -34,6 +37,14 @@ static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = { + .has_controller = true, + }; + ++static const struct rog_ryujin_device_info rog_ryujin_iii_info = { ++ .temp_offset = 5, ++ .pump_speed_offset = 7, ++ .fan_speed_offset = 10, ++ .duty_channel = 1, ++ .has_controller = false, ++}; ++ + #define STATUS_VALIDITY 1500 /* ms */ + #define MAX_REPORT_LENGTH 65 + +@@ -173,7 +184,6 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { +- unsigned long flags; + int ret; + + /* +@@ -181,9 +191,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ spin_lock_bh(&priv->status_report_request_lock); + reinit_completion(status_completion); +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); ++ spin_unlock_bh(&priv->status_report_request_lock); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -411,18 +421,15 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); +- unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + +- spin_lock_irqsave(&priv->status_report_request_lock, flags); +- + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- goto unlock; ++ return 0; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -436,7 +443,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- goto unlock; ++ return 0; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -448,7 +455,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- goto unlock; ++ return 0; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -467,7 +474,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- goto unlock; ++ return 0; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -477,7 +484,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- goto unlock; ++ return 0; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -496,7 +503,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- goto unlock; ++ return 0; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -505,8 +512,6 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + +-unlock: +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +@@ -596,6 +601,12 @@ static void rog_ryujin_remove(struct hid_device *hdev) + static const struct hid_device_id rog_ryujin_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO), + .driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EXTREME), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { } + }; + +@@ -625,4 +636,4 @@ module_exit(rog_ryujin_exit); + + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Aleksa Savic "); +-MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); ++MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin AIO coolers"); +diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c +--- a/drivers/hwmon/axi-fan-control.c ++++ b/drivers/hwmon/axi-fan-control.c +@@ -508,8 +508,7 @@ static int axi_fan_control_probe(struct platform_device *pdev) + IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + NULL, ctl); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "failed to request an irq\n"); ++ return ret; + + return 0; + } +diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c +--- a/drivers/hwmon/chipcap2.c ++++ b/drivers/hwmon/chipcap2.c +@@ -22,6 +22,8 @@ + #include + #include + #include ++#include ++#include + + #define CC2_START_CM 0xA0 + #define CC2_START_NOM 0x80 +@@ -83,6 +85,7 @@ struct cc2_data { + struct i2c_client *client; + struct regulator *regulator; + const char *name; ++ const char *label; + int irq_ready; + int irq_low; + int irq_high; +@@ -444,6 +447,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_humidity_input: + return 0444; ++ case hwmon_humidity_label: ++ return cc2->label ? 0444 : 0; + case hwmon_humidity_min_alarm: + return cc2->rh_alarm.low_alarm_visible ? 0444 : 0; + case hwmon_humidity_max_alarm: +@@ -461,6 +466,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_temp_input: + return 0444; ++ case hwmon_temp_label: ++ return cc2->label ? 0444 : 0; + default: + return 0; + } +@@ -547,6 +554,16 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) + return 0; + } + ++static int cc2_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ struct cc2_data *data = dev_get_drvdata(dev); ++ ++ *str = data->label; ++ ++ return 0; ++} ++ + static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) + { +@@ -665,8 +682,9 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) + } + + static const struct hwmon_channel_info *cc2_info[] = { +- HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), +- HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX | ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_LABEL | ++ HWMON_H_MIN | HWMON_H_MAX | + HWMON_H_MIN_HYST | HWMON_H_MAX_HYST | + HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM), + NULL +@@ -675,6 +693,7 @@ static const struct hwmon_channel_info *cc2_info[] = { + static const struct hwmon_ops cc2_hwmon_ops = { + .is_visible = cc2_is_visible, + .read = cc2_read, ++ .read_string = cc2_read_string, + .write = cc2_write, + }; + +@@ -705,6 +724,8 @@ static int cc2_probe(struct i2c_client *client) + return dev_err_probe(dev, PTR_ERR(data->regulator), + "Failed to get regulator\n"); + ++ device_property_read_string(dev, "label", &data->label); ++ + ret = cc2_request_ready_irq(data, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request ready irq\n"); +diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c +--- a/drivers/hwmon/coretemp.c ++++ b/drivers/hwmon/coretemp.c +@@ -68,7 +68,7 @@ enum coretemp_attr_index { + * This value is passed as "id" field to rdmsr/wrmsr functions. + * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, + * from where the temperature values should be read. +- * @attr_size: Total number of pre-core attrs displayed in the sysfs. ++ * @attr_size: Number of sysfs attributes in this temperature data group. + */ + struct temp_data { + int temp; +diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c +--- a/drivers/hwmon/corsair-psu.c ++++ b/drivers/hwmon/corsair-psu.c +@@ -884,7 +884,7 @@ static const struct hid_device_id corsairpsu_idtable[] = { + { HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */ + { HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */ +- { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */ ++ { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy, Series 2023 and 2025 */ + { HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */ + { HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */ + { }, +diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c +--- a/drivers/hwmon/cros_ec_hwmon.c ++++ b/drivers/hwmon/cros_ec_hwmon.c +@@ -148,9 +148,17 @@ static bool cros_ec_hwmon_is_error_temp(u8 temp) + temp == EC_TEMP_SENSOR_NOT_CALIBRATED; + } + ++/* This differs slightly from the variant in units.h to avoid rounding inconsistencies. */ ++#define CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS (-273000) ++ ++static long cros_ec_hwmon_kelvin_to_millicelsius(long t) ++{ ++ return t * MILLIDEGREE_PER_DEGREE + CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS; ++} ++ + static long cros_ec_hwmon_temp_to_millicelsius(u8 temp) + { +- return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); ++ return cros_ec_hwmon_kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); + } + + static bool cros_ec_hwmon_attr_is_temp_threshold(u32 attr) +@@ -228,8 +236,13 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + ret = cros_ec_hwmon_read_temp_threshold(priv->cros_ec, channel, + cros_ec_hwmon_attr_to_thres(attr), + &threshold); +- if (ret == 0) +- *val = kelvin_to_millicelsius(threshold); ++ if (ret == 0) { ++ /* Limit to sensible, non-overflowing values. */ ++ if (threshold > 255 + 273) ++ *val = 255000; ++ else ++ *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); ++ } + } + } + +diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c +--- a/drivers/hwmon/da9055-hwmon.c ++++ b/drivers/hwmon/da9055-hwmon.c +@@ -256,11 +256,8 @@ static int da9055_hwmon_probe(struct platform_device *pdev) + NULL, da9055_auxadc_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "adc-irq", hwmon); +- if (ret != 0) { +- dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", +- ret); ++ if (ret != 0) + return ret; +- } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", + hwmon, +diff --git a/drivers/hwmon/eic7700-pvt.c b/drivers/hwmon/eic7700-pvt.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.c +@@ -0,0 +1,509 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ * ++ * Authors: ++ * Yulin Lu ++ * Huan He ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "eic7700-pvt.h" ++ ++static const struct pvt_sensor_info pvt_info[] = { ++ PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP), ++ PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT), ++}; ++ ++static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"}; ++ ++/* ++ * The original translation formulae of the temperature (in degrees of Celsius) ++ * to PVT data and vice-versa are following: ++ * N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) + ++ * 1.6198e2, ++ * T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) + ++ * 3.2843e-1*(N^1) - 4.8690e1, ++ * where T = [-40, 125]C and N = [27, 771]. ++ * They must be accordingly altered to be suitable for the integer arithmetics. ++ * The technique is called 'factor redistribution', which just makes sure the ++ * multiplications and divisions are made so to have a result of the operations ++ * within the integer numbers limit. In addition we need to translate the ++ * formulae to accept millidegrees of Celsius. Here what they look like after ++ * the alterations: ++ * N = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T + ++ * 16198e2) / 1e4, ++ * T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N - ++ * 48690, ++ * where T = [-40000, 125000] mC and N = [27, 771]. ++ */ ++static const struct polynomial poly_N_to_temp = { ++ .total_divider = 1, ++ .terms = { ++ {4, -18439, 1000, 1}, ++ {3, 80705, 1000, 1}, ++ {2, -185010, 1000, 1}, ++ {1, 328430, 1000, 1}, ++ {0, -48690, 1, 1} ++ } ++}; ++ ++/* ++ * Similar alterations are performed for the voltage conversion equations. ++ * The original formulae are: ++ * N = 1.3905e3*V - 5.7685e2, ++ * V = (N + 5.7685e2) / 1.3905e3, ++ * where V = [0.72, 0.88] V and N = [424, 646]. ++ * After the optimization they looks as follows: ++ * N = (13905e-3*V - 5768.5) / 10, ++ * V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10. ++ * where V = [720, 880] mV and N = [424, 646]. ++ */ ++static const struct polynomial poly_N_to_volt = { ++ .total_divider = 10, ++ .terms = { ++ {1, 100000, 13905, 1}, ++ {0, 57685000, 1, 13905} ++ } ++}; ++ ++static inline u32 eic7700_pvt_update(void __iomem *reg, u32 mask, u32 data) ++{ ++ u32 old; ++ ++ old = readl_relaxed(reg); ++ writel((old & ~mask) | (data & mask), reg); ++ ++ return old & mask; ++} ++ ++static inline void eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) ++{ ++ u32 old; ++ ++ mode = FIELD_PREP(PVT_MODE_MASK, mode); ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) ++{ ++ u32 old; ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ writel(val, pvt->regs + PVT_TRIM); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ u32 stat, val; ++ int active; ++ ++ if (IS_ENABLED(CONFIG_PM)) { ++ active = pm_runtime_get_if_active(pvt->dev); ++ if (active <= 0) ++ return IRQ_NONE; ++ } ++ ++ stat = readl(pvt->regs + PVT_INT); ++ if (!(stat & PVT_INT_STAT)) { ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ return IRQ_NONE; ++ } ++ ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ /* ++ * Read the data, update the cache and notify a waiter of this event. ++ */ ++ val = readl(pvt->regs + PVT_DATA); ++ WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val)); ++ complete(&pvt->conversion); ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ ++ return IRQ_HANDLED; ++} ++ ++static int eic7700_pvt_read_data(struct pvt_hwmon *pvt, ++ enum pvt_sensor_type type, long *val) ++{ ++ unsigned long timeout; ++ u32 data; ++ int ret; ++ ++ /* ++ * Wait for PVT conversion to complete and update the data cache. The ++ * data read procedure is following: set the requested PVT sensor mode, ++ * enable conversion, wait until conversion is finished, then disable ++ * conversion and IRQ, and read the cached data. ++ */ ++ reinit_completion(&pvt->conversion); ++ ++ eic7700_pvt_set_mode(pvt, pvt_info[type].mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN); ++ ++ /* ++ * Wait with timeout since in case if the sensor is suddenly powered ++ * down the request won't be completed and the caller will hang up on ++ * this procedure until the power is back up again. Multiply the ++ * timeout by the factor of two to prevent a false timeout. ++ */ ++ timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); ++ ret = wait_for_completion_timeout(&pvt->conversion, timeout); ++ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ ++ if (!ret) ++ synchronize_irq(pvt->irq); ++ ++ data = READ_ONCE(pvt->data_cache); ++ ++ if (!ret) ++ return -ETIMEDOUT; ++ ++ if (type == PVT_TEMP) ++ *val = polynomial_calc(&poly_N_to_temp, data); ++ else ++ *val = polynomial_calc(&poly_N_to_volt, data); ++ ++ return 0; ++} ++ ++static const struct hwmon_channel_info *pvt_channel_info[] = { ++ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), ++ NULL ++}; ++ ++static umode_t eic7700_pvt_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int ch) ++{ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ } ++ break; ++ case hwmon_in: ++ switch (attr) { ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, long *val) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = pm_runtime_get_sync(pvt->dev); ++ if (ret < 0) { ++ dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret); ++ pm_runtime_put_noidle(pvt->dev); ++ return ret; ++ } ++ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ ret = eic7700_pvt_read_data(pvt, ch, val); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_input) ++ ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val); ++ else ++ ret = -EOPNOTSUPP; ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ ++ pm_runtime_mark_last_busy(pvt->dev); ++ pm_runtime_put_autosuspend(pvt->dev); ++ return ret; ++} ++ ++static int eic7700_pvt_hwmon_read_string(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = pvt_info[ch].label; ++ return 0; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_label) { ++ *str = pvt_info[PVT_VOLT + ch].label; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops pvt_hwmon_ops = { ++ .is_visible = eic7700_pvt_hwmon_is_visible, ++ .read = eic7700_pvt_hwmon_read, ++ .read_string = eic7700_pvt_hwmon_read_string ++}; ++ ++static const struct hwmon_chip_info pvt_hwmon_info = { ++ .ops = &pvt_hwmon_ops, ++ .info = pvt_channel_info ++}; ++ ++static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct pvt_hwmon *pvt; ++ ++ pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); ++ if (!pvt) ++ return ERR_PTR(-ENOMEM); ++ ++ pvt->dev = dev; ++ init_completion(&pvt->conversion); ++ ++ return pvt; ++} ++ ++static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt) ++{ ++ /* ++ * Make sure controller are disabled so not to accidentally have ISR ++ * executed before the driver data is fully initialized. Clear the IRQ ++ * status as well. ++ */ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ readl(pvt->regs + PVT_INT); ++ readl(pvt->regs + PVT_DATA); ++ ++ /* Setup default sensor mode and temperature trim. */ ++ eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode); ++ ++ /* ++ * Max conversion latency (~333 µs) derived from PVT spec: ++ * maximum sampling rate = 3000 samples/sec. ++ */ ++ pvt->timeout = ns_to_ktime(PVT_TOUT_MIN); ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int eic7700_pvt_request_irq(struct pvt_hwmon *pvt) ++{ ++ struct platform_device *pdev = to_platform_device(pvt->dev); ++ int ret; ++ ++ pvt->irq = platform_get_irq(pdev, 0); ++ if (pvt->irq < 0) ++ return pvt->irq; ++ ++ ret = devm_request_threaded_irq(pvt->dev, pvt->irq, ++ eic7700_pvt_hard_isr, NULL, ++ IRQF_TRIGGER_HIGH, "pvt", pvt); ++ if (ret) { ++ dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_create_hwmon(struct pvt_hwmon *pvt) ++{ ++ pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", ++ pvt, &pvt_hwmon_info, ++ NULL); ++ if (IS_ERR(pvt->hwmon)) { ++ dev_err(pvt->dev, "Couldn't create hwmon device\n"); ++ return PTR_ERR(pvt->hwmon); ++ } ++ ++ return 0; ++} ++ ++static void eic7700_pvt_disable_pm_runtime(void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ ++ pm_runtime_dont_use_autosuspend(pvt->dev); ++ pm_runtime_disable(pvt->dev); ++ ++ if (!pm_runtime_status_suspended(pvt->dev)) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ pm_runtime_set_suspended(pvt->dev); ++ } ++} ++ ++static int eic7700_pvt_probe(struct platform_device *pdev) ++{ ++ struct reset_control *rst; ++ struct pvt_hwmon *pvt; ++ int i, ret; ++ ++ pvt = eic7700_pvt_create_data(pdev); ++ if (IS_ERR(pvt)) ++ return PTR_ERR(pvt); ++ ++ platform_set_drvdata(pdev, pvt); ++ ++ pvt->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(pvt->regs)) ++ return PTR_ERR(pvt->regs); ++ ++ for (i = 0; i < PVT_CLK_NUM; i++) ++ pvt->clks[i].id = pvt_clk_names[i]; ++ ++ ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Couldn't get clock descriptors\n"); ++ ++ rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); ++ if (IS_ERR(rst)) ++ return dev_err_probe(pvt->dev, PTR_ERR(rst), ++ "Couldn't get reset control\n"); ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(pvt->dev, ret, ++ "Failed to enable clocks\n"); ++ ++ ret = eic7700_pvt_init_iface(pvt); ++ if (ret) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ return ret; ++ } ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ pm_runtime_enable(&pdev->dev); ++ pm_runtime_set_autosuspend_delay(&pdev->dev, 3000); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ pm_runtime_get_noresume(&pdev->dev); ++ ++ ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime, ++ pvt); ++ if (ret) { ++ pm_runtime_put_noidle(&pdev->dev); ++ return dev_err_probe(&pdev->dev, ret, ++ "Can't register PM cleanup\n"); ++ } ++ ++ ret = eic7700_pvt_request_irq(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ ret = eic7700_pvt_create_hwmon(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ pm_runtime_put_autosuspend(&pdev->dev); ++ ++ return 0; ++ ++err_put_pm_runtime: ++ pm_runtime_put_noidle(&pdev->dev); ++ return ret; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) { ++ dev_err(dev, "Failed to enable clocks: %d\n", ret); ++ return ret; ++ } ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops eic7700_pvt_pm_ops = { ++ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) ++ RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume, ++ NULL) ++}; ++ ++static const struct of_device_id pvt_of_match[] = { ++ { .compatible = "eswin,eic7700-pvt"}, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, pvt_of_match); ++ ++static struct platform_driver pvt_driver = { ++ .probe = eic7700_pvt_probe, ++ .driver = { ++ .name = "eic7700-pvt", ++ .of_match_table = pvt_of_match, ++ .pm = pm_ptr(&eic7700_pvt_pm_ops), ++ }, ++}; ++module_platform_driver(pvt_driver); ++ ++MODULE_AUTHOR("Yulin Lu "); ++MODULE_AUTHOR("Huan He "); ++MODULE_DESCRIPTION("Eswin eic7700 PVT driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/eic7700-pvt.h b/drivers/hwmon/eic7700-pvt.h +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.h +@@ -0,0 +1,99 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ */ ++#ifndef __HWMON_EIC7700_PVT_H__ ++#define __HWMON_EIC7700_PVT_H__ ++ ++#include ++#include ++#include ++#include ++ ++/* ESWIN EIC7700 PVT registers and their bitfields */ ++#define PVT_TRIM 0x04 ++#define PVT_MODE 0x08 ++#define PVT_MODE_MASK GENMASK(2, 0) ++#define PVT_CTRL_MODE_TEMP 0x0 ++#define PVT_CTRL_MODE_VOLT 0x4 ++#define PVT_ENA 0x0c ++#define PVT_ENA_EN BIT(0) ++#define PVT_INT 0x10 ++#define PVT_INT_STAT BIT(0) ++#define PVT_INT_CLR BIT(1) ++#define PVT_DATA 0x14 ++#define PVT_DATA_OUT GENMASK(9, 0) ++ ++/* ++ * PVT sensors-related limits and default values ++ * @PVT_TEMP_CHS: Number of temperature hwmon channels. ++ * @PVT_VOLT_CHS: Number of voltage hwmon channels. ++ * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value ++ * when one is determined for ESWIN EIC7700 SoC). ++ * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. ++ */ ++#define PVT_TEMP_CHS 1 ++#define PVT_VOLT_CHS 1 ++#define PVT_TRIM_DEF 0 ++#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) ++ ++/* ++ * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT ++ * sampling mode) ++ * @PVT_TEMP: PVT Temperature sensor. ++ * @PVT_VOLT: PVT Voltage sensor. ++ */ ++enum pvt_sensor_type { ++ PVT_TEMP = 0, ++ PVT_VOLT ++}; ++ ++#define PVT_CLK_NUM 2 ++ ++/* ++ * struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure ++ * @channel: Sensor channel ID. ++ * @label: hwmon sensor label. ++ * @mode: PVT mode corresponding to the channel. ++ * @type: Sensor type. ++ */ ++struct pvt_sensor_info { ++ int channel; ++ const char *label; ++ u32 mode; ++ enum hwmon_sensor_types type; ++}; ++ ++#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \ ++ { \ ++ .channel = _ch, \ ++ .label = _label, \ ++ .mode = PVT_CTRL_MODE_ ##_mode, \ ++ .type = _type, \ ++ } ++ ++/* ++ * struct pvt_hwmon - Eswin EIC7700 PVT private data ++ * @dev: device structure of the PVT platform device. ++ * @hwmon: hwmon device structure. ++ * @regs: pointer to the Eswin EIC7700 PVT registers region. ++ * @irq: PVT events IRQ number. ++ * @clks: PVT clock descriptors. ++ * @data_cache: data cache in raw format. ++ * @conversion: data conversion completion. ++ * @timeout: conversion timeout. ++ */ ++struct pvt_hwmon { ++ struct device *dev; ++ struct device *hwmon; ++ void __iomem *regs; ++ int irq; ++ struct clk_bulk_data clks[PVT_CLK_NUM]; ++ u32 data_cache; ++ struct completion conversion; ++ ktime_t timeout; ++}; ++ ++#endif /* __HWMON_EIC7700_PVT_H__ */ +diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c +--- a/drivers/hwmon/emc1403.c ++++ b/drivers/hwmon/emc1403.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + + #define THERMAL_PID_REG 0xfd +@@ -654,14 +655,20 @@ static int emc1403_probe(struct i2c_client *client) + { + struct thermal_data *data; + struct device *hwmon_dev; +- const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client); ++ int ret; ++ ++ ret = devm_regulator_get_enable(&client->dev, "vdd"); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to enable regulator\n"); + + data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + +- data->chip = id->driver_data; ++ data->chip = (uintptr_t)i2c_get_match_data(client); ++ + data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); +@@ -677,10 +684,20 @@ static const unsigned short emc1403_address_list[] = { + 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END + }; + ++static const struct of_device_id emc1403_of_match[] = { ++ { .compatible = "smsc,emc1402", .data = (void *)emc1402 }, ++ { .compatible = "smsc,emc1403", .data = (void *)emc1403 }, ++ { .compatible = "smsc,emc1404", .data = (void *)emc1404 }, ++ { .compatible = "smsc,emc1428", .data = (void *)emc1428 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, emc1403_of_match); ++ + static struct i2c_driver sensor_emc1403 = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc1403", ++ .of_match_table = emc1403_of_match, + }, + .detect = emc1403_detect, + .probe = emc1403_probe, +diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c +--- a/drivers/hwmon/gxp-fan-ctrl.c ++++ b/drivers/hwmon/gxp-fan-ctrl.c +@@ -1,59 +1,16 @@ + // SPDX-License-Identifier: GPL-2.0-only +-/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ ++/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */ + +-#include + #include + #include + #include + #include + #include + +-#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ +-#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */ +-#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */ +-#define POWER_BIT 24 +- + struct gxp_fan_ctrl_drvdata { + void __iomem *base; +- void __iomem *plreg; +- void __iomem *fn2; + }; + +-static bool fan_installed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_INST); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_failed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_FAIL); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_enabled(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 val; +- +- /* +- * Check the power status as if the platform is off the value +- * reported for the PWM will be incorrect. Report fan as +- * disabled. +- */ +- val = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); +-} +- + static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +@@ -80,37 +37,11 @@ static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, + } + } + +-static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) +-{ +- switch (attr) { +- case hwmon_fan_enable: +- *val = fan_enabled(dev, channel); +- return 0; +- case hwmon_fan_fault: +- *val = fan_failed(dev, channel); +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- + static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 reg; + +- /* +- * Check the power status of the platform. If the platform is off +- * the value reported for the PWM will be incorrect. In this case +- * report a PWM of zero. +- */ +- +- reg = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- if (reg & BIT(POWER_BIT)) +- *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; +- else +- *val = 0; ++ *val = readb(drvdata->base + channel); + + return 0; + } +@@ -119,8 +50,6 @@ static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { + switch (type) { +- case hwmon_fan: +- return gxp_fan_read(dev, attr, channel, val); + case hwmon_pwm: + return gxp_pwm_read(dev, attr, channel, val); + default: +@@ -135,16 +64,6 @@ static umode_t gxp_fan_ctrl_is_visible(const void *_data, + umode_t mode = 0; + + switch (type) { +- case hwmon_fan: +- switch (attr) { +- case hwmon_fan_enable: +- case hwmon_fan_fault: +- mode = 0444; +- break; +- default: +- break; +- } +- break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: +@@ -168,15 +87,6 @@ static const struct hwmon_ops gxp_fan_ctrl_ops = { + }; + + static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { +- HWMON_CHANNEL_INFO(fan, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, +@@ -211,18 +121,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(drvdata->base), + "failed to map base\n"); + +- drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, +- "pl"); +- if (IS_ERR(drvdata->plreg)) +- return dev_err_probe(dev, PTR_ERR(drvdata->plreg), +- "failed to map plreg\n"); +- +- drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, +- "fn2"); +- if (IS_ERR(drvdata->fn2)) +- return dev_err_probe(dev, PTR_ERR(drvdata->fn2), +- "failed to map fn2\n"); +- + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "hpe_gxp_fan_ctrl", + drvdata, +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -57,8 +57,8 @@ struct hwmon_device_attribute { + }; + + #define to_hwmon_attr(d) \ +- container_of(d, struct hwmon_device_attribute, dev_attr) +-#define to_dev_attr(a) container_of(a, struct device_attribute, attr) ++ container_of_const(d, struct hwmon_device_attribute, dev_attr) ++#define to_dev_attr(a) container_of_const(a, struct device_attribute, attr) + + /* + * Thermal zone information +@@ -71,27 +71,27 @@ struct hwmon_thermal_data { + }; + + static ssize_t +-name_show(struct device *dev, struct device_attribute *attr, char *buf) ++name_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->name); + } +-static DEVICE_ATTR_RO(name); ++static const DEVICE_ATTR_RO(name); + + static ssize_t +-label_show(struct device *dev, struct device_attribute *attr, char *buf) ++label_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); + } +-static DEVICE_ATTR_RO(label); ++static const DEVICE_ATTR_RO(label); + +-static struct attribute *hwmon_dev_attrs[] = { ++static const struct attribute *const hwmon_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_label.attr, + NULL + }; + + static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, +- struct attribute *attr, int n) ++ const struct attribute *attr, int n) + { + struct device *dev = kobj_to_dev(kobj); + struct hwmon_device *hdev = to_hwmon_device(dev); +@@ -106,8 +106,8 @@ static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, + } + + static const struct attribute_group hwmon_dev_attr_group = { +- .attrs = hwmon_dev_attrs, +- .is_visible = hwmon_dev_attr_is_visible, ++ .attrs_const = hwmon_dev_attrs, ++ .is_visible_const = hwmon_dev_attr_is_visible, + }; + + static const struct attribute_group *hwmon_dev_attr_groups[] = { +@@ -115,13 +115,13 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = { + NULL + }; + +-static void hwmon_free_attrs(struct attribute **attrs) ++static void hwmon_free_attrs(const struct attribute *const *attrs) + { + int i; + + for (i = 0; attrs[i]; i++) { +- struct device_attribute *dattr = to_dev_attr(attrs[i]); +- struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); ++ const struct device_attribute *dattr = to_dev_attr(attrs[i]); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); + + kfree(hattr); + } +@@ -132,8 +132,8 @@ static void hwmon_dev_release(struct device *dev) + { + struct hwmon_device *hwdev = to_hwmon_device(dev); + +- if (hwdev->group.attrs) +- hwmon_free_attrs(hwdev->group.attrs); ++ if (hwdev->group.attrs_const) ++ hwmon_free_attrs(hwdev->group.attrs_const); + kfree(hwdev->groups); + kfree(hwdev->label); + kfree(hwdev); +@@ -341,7 +341,7 @@ static int hwmon_match_device(struct device *dev, const void *data) + return dev->class == &hwmon_class; + } + +-static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, ++static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, + char *buf) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -349,7 +349,7 @@ static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, + return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); + } + +-static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, ++static ssize_t pec_store(struct device *dev, const struct device_attribute *devattr, + const char *buf, size_t count) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -389,7 +389,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, + return err; + } + +-static DEVICE_ATTR_RW(pec); ++static const DEVICE_ATTR_RW(pec); + + static void hwmon_remove_pec(void *dev) + { +@@ -424,9 +424,9 @@ static int hwmon_pec_register(struct device *hdev) + /* sysfs attribute management */ + + static ssize_t hwmon_attr_show(struct device *dev, +- struct device_attribute *devattr, char *buf) ++ const struct device_attribute *devattr, char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + s64 val64; + long val; +@@ -449,10 +449,10 @@ static ssize_t hwmon_attr_show(struct device *dev, + } + + static ssize_t hwmon_attr_show_string(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + enum hwmon_sensor_types type = hattr->type; + const char *s; +@@ -472,10 +472,10 @@ static ssize_t hwmon_attr_show_string(struct device *dev, + } + + static ssize_t hwmon_attr_store(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + const char *buf, size_t count) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + long val; + int ret; +@@ -509,12 +509,12 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) + (type == hwmon_fan && attr == hwmon_fan_label); + } + +-static struct attribute *hwmon_genattr(const void *drvdata, +- enum hwmon_sensor_types type, +- u32 attr, +- int index, +- const char *template, +- const struct hwmon_ops *ops) ++static const struct attribute *hwmon_genattr(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, ++ int index, ++ const char *template, ++ const struct hwmon_ops *ops) + { + struct hwmon_device_attribute *hattr; + struct device_attribute *dattr; +@@ -551,8 +551,8 @@ static struct attribute *hwmon_genattr(const void *drvdata, + hattr->ops = ops; + + dattr = &hattr->dev_attr; +- dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; +- dattr->store = hwmon_attr_store; ++ dattr->show_const = is_string ? hwmon_attr_show_string : hwmon_attr_show; ++ dattr->store_const = hwmon_attr_store; + + a = &dattr->attr; + sysfs_attr_init(a); +@@ -830,7 +830,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) + } + + static int hwmon_genattrs(const void *drvdata, +- struct attribute **attrs, ++ const struct attribute **attrs, + const struct hwmon_ops *ops, + const struct hwmon_channel_info *info) + { +@@ -849,7 +849,7 @@ static int hwmon_genattrs(const void *drvdata, + u32 attr; + + while (attr_mask) { +- struct attribute *a; ++ const struct attribute *a; + + attr = __ffs(attr_mask); + attr_mask &= ~BIT(attr); +@@ -868,11 +868,11 @@ static int hwmon_genattrs(const void *drvdata, + return aindex; + } + +-static struct attribute ** ++static const struct attribute ** + __hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip) + { + int ret, i, aindex = 0, nattrs = 0; +- struct attribute **attrs; ++ const struct attribute **attrs; + + for (i = 0; chip->info[i]; i++) + nattrs += hwmon_num_channel_attrs(chip->info[i]); +@@ -927,7 +927,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + hdev = &hwdev->dev; + + if (chip) { +- struct attribute **attrs; ++ const struct attribute **attrs; + int ngroups = 2; /* terminating NULL plus &hwdev->groups */ + + if (groups) +@@ -946,7 +946,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + goto free_hwmon; + } + +- hwdev->group.attrs = attrs; ++ hwdev->group.attrs_const = attrs; + ngroups = 0; + hwdev->groups[ngroups++] = &hwdev->group; + +diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c +--- a/drivers/hwmon/ina2xx.c ++++ b/drivers/hwmon/ina2xx.c +@@ -124,6 +124,7 @@ static const struct regmap_config ina2xx_regmap_config = { + enum ina2xx_ids { + ina219, + ina226, ++ ina232, + ina234, + ina260, + sy24655 +@@ -210,6 +211,20 @@ static const struct ina2xx_config ina2xx_config[] = { + .current_shift = 4, + .has_update_interval = true, + }, ++ [ina232] = { ++ .config_default = INA226_CONFIG_DEFAULT, ++ .calibration_value = 2048, ++ .shunt_div = 400, ++ .shunt_voltage_shift = 0, ++ .bus_voltage_shift = 0, ++ .bus_voltage_lsb = 1600, ++ .power_lsb_factor = 32, ++ .has_alerts = true, ++ .has_ishunt = false, ++ .has_power_average = false, ++ .current_shift = 0, ++ .has_update_interval = true, ++ }, + [ina260] = { + .config_default = INA260_CONFIG_DEFAULT, + .shunt_div = 400, +@@ -1123,6 +1138,7 @@ static const struct i2c_device_id ina2xx_id[] = { + { .name = "ina226", .driver_data = ina226 }, + { .name = "ina230", .driver_data = ina226 }, + { .name = "ina231", .driver_data = ina226 }, ++ { .name = "ina232", .driver_data = ina232 }, + { .name = "ina234", .driver_data = ina234 }, + { .name = "ina260", .driver_data = ina260 }, + { .name = "sy24655", .driver_data = sy24655 }, +@@ -1155,6 +1171,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { + .compatible = "ti,ina231", + .data = (void *)ina226 + }, ++ { ++ .compatible = "ti,ina232", ++ .data = (void *)ina232 ++ }, + { + .compatible = "ti,ina234", + .data = (void *)ina234 +diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c +--- a/drivers/hwmon/k10temp.c ++++ b/drivers/hwmon/k10temp.c +@@ -207,6 +207,10 @@ static const char *k10temp_temp_label[] = { + "Tccd10", + "Tccd11", + "Tccd12", ++ "Tccd13", ++ "Tccd14", ++ "Tccd15", ++ "Tccd16", + }; + + static int k10temp_read_labels(struct device *dev, +@@ -243,7 +247,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, + if (*val < 0 && !data->disp_negative) + *val = 0; + break; +- case 2 ... 13: /* Tccd{1-12} */ ++ case 2 ... 17: /* Tccd{1-16} */ + ret = read_ccd_temp_reg(data, channel - 2, ®val); + + if (ret) +@@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const k10temp_info[] = { + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL + }; +@@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) + } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { ++ case 0x00 ... 0x2f: /* Zen5 Turin */ ++ data->ccd_offset = 0x1F0; ++ k10temp_get_ccd_support(data, 16); ++ break; + case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ + data->ccd_offset = 0x308; + k10temp_get_ccd_support(data, 8); +diff --git a/drivers/hwmon/kb9002.c b/drivers/hwmon/kb9002.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/kb9002.c +@@ -0,0 +1,460 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Kandou KB9002 PCIe 5.0 retimer hwmon driver. ++ * ++ * The retimer exposes a system management bus (SMBus 3.0 with PEC) ++ * target for firmware-managed status registers. This driver assumes ++ * the chip is strapped to SMBus mode and exports the aggregated ++ * maximum die temperature as hwmon temp1_input (millidegrees Celsius) ++ * plus the firmware version and boot status under debugfs. ++ * ++ * The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch ++ * the host interface) carries the 32-bit register address and data ++ * big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read) ++ * carries the register address and returned data little-endian. ++ * ++ * Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD). ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define KB9002_DEV_NAME "kb9002" ++ ++/* ++ * SMBus read command codes. Each read is a two-phase PEC-protected ++ * transaction: prime writes the target address, data reads it back with ++ * the register contents. FW reads use a 16-bit address, HW reads 32-bit. ++ */ ++#define KB9002_CC_FW_READ_PRIME 0x82 ++#define KB9002_CC_FW_READ_DATA 0x81 ++#define KB9002_CC_HW_READ_PRIME 0x8a ++#define KB9002_CC_HW_READ_DATA 0x89 ++ ++/* Firmware register offsets (16-bit). */ ++#define KB9002_FW_REG_VID 0x0004 ++#define KB9002_FW_REG_FW_VERSION 0x0500 ++#define KB9002_FW_REG_TEMP_MAXIMUM 0x0550 ++ ++#define KB9002_VID_MASK GENMASK(31, 16) ++#define KB9002_VID_KANDOU 0x1e6f ++ ++/* Firmware boot status: 0xe8 in the top byte means init completed OK. */ ++#define KB9002_HW_REG_FW_BOOT_STATUS 0xe0090008 ++#define KB9002_FW_BOOT_STATUS_OK_MSB 0xe8 ++ ++/* ++ * Hardware registers reached over raw I2C (32-bit addressing). The ++ * host-interface bit selects SMBus (set) vs raw-I2C target; parts ++ * strapped to raw I2C need it set before SMBus access works. ++ */ ++#define KB9002_HW_REG_REVID 0x00480004 ++#define KB9002_HW_REG_HOST_IF 0x00480008 ++#define KB9002_HOST_IF_SMBUS BIT(1) ++ ++#define KB9002_REVID_MASK GENMASK(7, 0) ++#define KB9002_REVID_B0 0x10 ++#define KB9002_REVID_B1 0x11 ++ ++/* Retries to drain a stray leading 0xff from the raw-I2C FIFO. */ ++#define KB9002_REVID_READ_RETRIES 16 ++ ++/* Temperature: 32-bit Q16.16 absolute Kelvin. */ ++#define KB9002_TEMP_FRAC_BITS 16 ++#define KB9002_ABS_ZERO_MILLI_C (-273150) ++ ++/* Firmware takes up to ~2s to respond after a host-interface change. */ ++#define KB9002_FW_READY_POLL_US (25 * USEC_PER_MSEC) ++#define KB9002_FW_READY_TIMEOUT_US (2 * USEC_PER_SEC) ++ ++struct kb9002_data { ++ struct i2c_client *client; ++ struct device *hwmon_dev; ++}; ++ ++/* Raw-I2C read: write the 32-bit BE address, then read 4 BE data bytes. */ ++static int kb9002_i2c_read(struct i2c_client *client, u32 reg, u32 *val) ++{ ++ u8 addr[4]; ++ u8 rbuf[4]; ++ struct i2c_msg msgs[2] = { ++ { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(addr), ++ .buf = addr, ++ }, ++ { ++ .addr = client->addr, ++ .flags = I2C_M_RD, ++ .len = sizeof(rbuf), ++ .buf = rbuf, ++ }, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, addr); ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) ++ return ret; ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *val = get_unaligned_be32(rbuf); ++ return 0; ++} ++ ++/* Raw-I2C write: 4 BE address bytes followed by 4 BE data bytes. */ ++static int kb9002_i2c_write(struct i2c_client *client, u32 reg, u32 val) ++{ ++ u8 buf[8]; ++ struct i2c_msg msg = { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(buf), ++ .buf = buf, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, &buf[0]); ++ put_unaligned_be32(val, &buf[4]); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) ++ return ret; ++ if (ret != 1) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* ++ * Read the silicon revision ID. A fresh FIFO may start with a stray ++ * 0xff that shifts the result, so drain one byte between retries until ++ * the top byte is no longer 0xff. ++ */ ++static int kb9002_read_revid(struct i2c_client *client, u32 *revid) ++{ ++ u8 dummy; ++ int ret; ++ int i; ++ ++ for (i = 0; i < KB9002_REVID_READ_RETRIES; i++) { ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_REVID, revid); ++ if (ret) ++ return ret; ++ if ((*revid >> 24) != 0xff) ++ return 0; ++ /* Drain one byte from the chip to re-align the I2C FIFO. */ ++ i2c_master_recv(client, &dummy, 1); ++ } ++ ++ return -EIO; ++} ++ ++/* ++ * Read a 32-bit firmware register over SMBus: block-write the 16-bit LE ++ * address, then block-read the echoed address plus 4 LE data bytes. ++ */ ++static int kb9002_fw_read(struct kb9002_data *data, u16 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[2]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le16(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_FW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_FW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* Like kb9002_fw_read but for a hardware register (32-bit LE address). */ ++static int kb9002_smbus_hw_read(struct kb9002_data *data, u32 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[4]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le32(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_HW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_HW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* ++ * Switch the host interface from raw-I2C to SMBus and wait for firmware ++ * to come back up. Called only when SMBus access failed in probe, i.e. ++ * the chip is strapped to raw-I2C mode. Confirms the revision, sets the ++ * SMBus-mode bit, then polls until firmware responds again. ++ */ ++static int kb9002_enable_smbus_target(struct kb9002_data *data) ++{ ++ struct i2c_client *client = data->client; ++ u32 revid; ++ u32 val; ++ int op_ret; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "raw I2C required to switch to SMBus mode\n"); ++ ++ ret = kb9002_read_revid(client, &revid); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "revision ID read failed\n"); ++ ++ switch (FIELD_GET(KB9002_REVID_MASK, revid)) { ++ case KB9002_REVID_B0: ++ case KB9002_REVID_B1: ++ break; ++ default: ++ return dev_err_probe(&client->dev, -ENODEV, ++ "unsupported revision ID 0x%08x\n", revid); ++ } ++ ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_HOST_IF, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface read failed\n"); ++ ++ val |= KB9002_HOST_IF_SMBUS; ++ ++ ret = kb9002_i2c_write(client, KB9002_HW_REG_HOST_IF, val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface write failed\n"); ++ ++ /* Wait until firmware re-initialisation completes. */ ++ ret = read_poll_timeout(kb9002_fw_read, op_ret, op_ret == 0, ++ KB9002_FW_READY_POLL_US, ++ KB9002_FW_READY_TIMEOUT_US, true, ++ data, KB9002_FW_REG_VID, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "firmware not responding over SMBus\n"); ++ ++ return 0; ++} ++ ++/* Convert Q16.16 absolute Kelvin to millidegrees Celsius. */ ++static long kb9002_temp_to_milli_c(u32 raw) ++{ ++ s64 milli_k = ((s64)raw * 1000) >> KB9002_TEMP_FRAC_BITS; ++ ++ return (long)milli_k + KB9002_ABS_ZERO_MILLI_C; ++} ++ ++static int kb9002_read_temp(struct kb9002_data *data, long *val) ++{ ++ u32 raw; ++ int ret; ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_TEMP_MAXIMUM, &raw); ++ if (ret) ++ return ret; ++ ++ *val = kb9002_temp_to_milli_c(raw); ++ return 0; ++} ++ ++static umode_t kb9002_is_visible(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ return 0444; ++} ++ ++static int kb9002_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct kb9002_data *data = dev_get_drvdata(dev); ++ ++ if (type == hwmon_temp && attr == hwmon_temp_input) ++ return kb9002_read_temp(data, val); ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops kb9002_hwmon_ops = { ++ .is_visible = kb9002_is_visible, ++ .read = kb9002_read, ++}; ++ ++static const struct hwmon_channel_info * const kb9002_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), ++ NULL, ++}; ++ ++static const struct hwmon_chip_info kb9002_chip_info = { ++ .ops = &kb9002_hwmon_ops, ++ .info = kb9002_hwmon_info, ++}; ++ ++static int kb9002_fw_version_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 ver; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_FW_VERSION, &ver); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%u.%02u.%02u.%u\n", ++ (ver >> 24) & 0xff, (ver >> 16) & 0xff, ++ (ver >> 8) & 0xff, (ver >> 0) & 0xff); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_version); ++ ++static int kb9002_fw_load_status_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 status; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_smbus_hw_read(data, KB9002_HW_REG_FW_BOOT_STATUS, &status); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%s\n", ++ (status >> 24) == KB9002_FW_BOOT_STATUS_OK_MSB ? ++ "normal" : "abnormal"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_load_status); ++ ++static void kb9002_debugfs_init(struct kb9002_data *data) ++{ ++ struct dentry *dir = data->client->debugfs; ++ ++ debugfs_create_file("fw_ver", 0444, dir, data, ++ &kb9002_fw_version_fops); ++ debugfs_create_file("fw_load_status", 0444, dir, data, ++ &kb9002_fw_load_status_fops); ++} ++ ++static int kb9002_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct kb9002_data *data; ++ struct device *hwmon_dev; ++ u32 vid; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_BLOCK_DATA | ++ I2C_FUNC_SMBUS_PEC)) ++ return -ENODEV; ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->client = client; ++ ++ /* All firmware register accesses are PEC-protected. */ ++ client->flags |= I2C_CLIENT_PEC; ++ ++ i2c_set_clientdata(client, data); ++ ++ /* ++ * Try SMBus first. If the chip is strapped to raw-I2C mode it ++ * will not respond to SMBus framing, so fall back to switching ++ * the host interface over raw I2C and retry. ++ */ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) { ++ dev_dbg(dev, "SMBus probe failed (%d), trying raw-I2C host-interface switch\n", ++ ret); ++ ret = kb9002_enable_smbus_target(data); ++ if (ret) ++ return ret; ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "VID read failed after host-interface switch\n"); ++ } ++ if (FIELD_GET(KB9002_VID_MASK, vid) != KB9002_VID_KANDOU) ++ return dev_err_probe(dev, -ENODEV, ++ "unexpected VID 0x%08x\n", vid); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(dev, KB9002_DEV_NAME, ++ data, ++ &kb9002_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++ data->hwmon_dev = hwmon_dev; ++ kb9002_debugfs_init(data); ++ return 0; ++} ++ ++static const struct i2c_device_id kb9002_id[] = { ++ { .name = KB9002_DEV_NAME }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, kb9002_id); ++ ++static const struct of_device_id kb9002_of_match[] = { ++ { .compatible = "kandou,kb9002" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, kb9002_of_match); ++ ++static struct i2c_driver kb9002_driver = { ++ .driver = { ++ .name = KB9002_DEV_NAME, ++ .of_match_table = kb9002_of_match, ++ }, ++ .probe = kb9002_probe, ++ .id_table = kb9002_id, ++}; ++module_i2c_driver(kb9002_driver); ++ ++MODULE_AUTHOR("Andy Chung "); ++MODULE_DESCRIPTION("Kandou KB9002 PCIe retimer hwmon driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c +--- a/drivers/hwmon/lm90.c ++++ b/drivers/hwmon/lm90.c +@@ -101,6 +101,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -108,7 +109,7 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include +@@ -295,7 +296,7 @@ static const struct i2c_device_id lm90_id[] = { + }; + MODULE_DEVICE_TABLE(i2c, lm90_id); + +-static const struct of_device_id __maybe_unused lm90_of_match[] = { ++static const struct of_device_id lm90_of_match[] = { + { + .compatible = "adi,adm1032", + .data = (void *)adm1032 +@@ -2602,7 +2603,6 @@ static void lm90_stop_work(void *_data) + + static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + { +- struct device_node *np = client->dev.of_node; + int config, convrate; + + if (data->flags & LM90_HAVE_CONVRATE) { +@@ -2626,7 +2626,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + + /* Check Temperature Range Select */ + if (data->flags & LM90_HAVE_EXTENDED_TEMP) { +- if (of_property_read_bool(np, "ti,extended-range-enable")) ++ if (device_property_read_bool(&client->dev, "ti,extended-range-enable")) + config |= 0x04; + if (!(config & 0x04)) + data->flags &= ~LM90_HAVE_EXTENDED_TEMP; +@@ -2692,36 +2692,41 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id) + return IRQ_NONE; + } + +-static int lm90_probe_channel_from_dt(struct i2c_client *client, +- struct device_node *child, +- struct lm90_data *data) ++static int lm90_probe_channel(struct i2c_client *client, ++ struct fwnode_handle *child, ++ struct lm90_data *data) + { + u32 id; + s32 val; + int err; + struct device *dev = &client->dev; + +- err = of_property_read_u32(child, "reg", &id); ++ err = fwnode_property_read_u32(child, "reg", &id); + if (err) { +- dev_err(dev, "missing reg property of %pOFn\n", child); ++ dev_err(dev, "missing reg property of %pfw\n", child); + return err; + } + + if (id >= MAX_CHANNELS) { +- dev_err(dev, "invalid reg property value %d in %pOFn\n", id, child); ++ dev_err(dev, "invalid reg property value %d in %pfw\n", id, child); + return -EINVAL; + } + +- err = of_property_read_string(child, "label", &data->channel_label[id]); ++ err = fwnode_property_read_string(child, "label", &data->channel_label[id]); + if (err == -ENODATA || err == -EILSEQ) { +- dev_err(dev, "invalid label property in %pOFn\n", child); ++ dev_err(dev, "invalid label property in %pfw\n", child); + return err; + } + + if (data->channel_label[id]) + data->channel_config[id] |= HWMON_T_LABEL; + +- err = of_property_read_s32(child, "temperature-offset-millicelsius", &val); ++ /* ++ * fwnode_property_read_u32() has no signed equivalent. ++ * temperature-offset-millicelsius is signed, so read and reinterpret it as s32 to ++ * preserve negative offsets values (same behavior as the old of_property_read_s32()). ++ */ ++ err = fwnode_property_read_u32(child, "temperature-offset-millicelsius", (u32 *)&val); + if (!err) { + if (id == 0) { + dev_err(dev, "temperature-offset-millicelsius can't be set for internal channel\n"); +@@ -2739,18 +2744,17 @@ static int lm90_probe_channel_from_dt(struct i2c_client *client, + return 0; + } + +-static int lm90_parse_dt_channel_info(struct i2c_client *client, +- struct lm90_data *data) ++static int lm90_parse_channel_info(struct i2c_client *client, ++ struct lm90_data *data) + { + int err; + struct device *dev = &client->dev; +- const struct device_node *np = dev->of_node; + +- for_each_child_of_node_scoped(np, child) { +- if (strcmp(child->name, "channel")) ++ device_for_each_child_node_scoped(dev, child) { ++ if (!fwnode_name_eq(child, "channel")) + continue; + +- err = lm90_probe_channel_from_dt(client, child, data); ++ err = lm90_probe_channel(client, child, data); + if (err) + return err; + } +@@ -2887,12 +2891,10 @@ static int lm90_probe(struct i2c_client *client) + /* Set maximum conversion rate */ + data->max_convrate = lm90_params[data->kind].max_convrate; + +- /* Parse device-tree channel information */ +- if (client->dev.of_node) { +- err = lm90_parse_dt_channel_info(client, data); +- if (err) +- return err; +- } ++ /* Parse channel information */ ++ err = lm90_parse_channel_info(client, data); ++ if (err) ++ return err; + + /* Initialize the LM90 chip */ + err = lm90_init_client(client, data); +@@ -2918,10 +2920,8 @@ static int lm90_probe(struct i2c_client *client) + err = devm_request_threaded_irq(dev, client->irq, + NULL, lm90_irq_thread, + IRQF_ONESHOT, "lm90", client); +- if (err < 0) { +- dev_err(dev, "cannot request IRQ %d\n", client->irq); ++ if (err < 0) + return err; +- } + } + + return 0; +@@ -2985,7 +2985,7 @@ static struct i2c_driver lm90_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm90", +- .of_match_table = of_match_ptr(lm90_of_match), ++ .of_match_table = lm90_of_match, + .pm = pm_sleep_ptr(&lm90_pm_ops), + }, + .probe = lm90_probe, +diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c +--- a/drivers/hwmon/nct6775-core.c ++++ b/drivers/hwmon/nct6775-core.c +@@ -33,6 +33,7 @@ + * (0xd451) + * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * (0xd429) ++ * nct5585d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3 + * nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3 + * +diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c +--- a/drivers/hwmon/nct6775-platform.c ++++ b/drivers/hwmon/nct6775-platform.c +@@ -34,7 +34,7 @@ static const char * const nct6775_sio_names[] __initconst = { + [nct6795] = "NCT6795D", + [nct6796] = "NCT6796D", + [nct6797] = "NCT6797D", +- [nct6798] = "NCT6798D", ++ [nct6798] = "NCT6798D/NCT5585D", + [nct6799] = "NCT6796D-S/NCT6799D-R", + }; + +diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c +--- a/drivers/hwmon/npcm750-pwm-fan.c ++++ b/drivers/hwmon/npcm750-pwm-fan.c +@@ -996,10 +996,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) + sprintf(name, "NPCM7XX-FAN-MD%d", i); + ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr, + 0, name, (void *)data); +- if (ret) { +- dev_err(dev, "register IRQ fan%d failed\n", i); ++ if (ret) + return ret; +- } + } + + for_each_child_of_node_scoped(np, child) { +diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c +--- a/drivers/hwmon/nzxt-kraken3.c ++++ b/drivers/hwmon/nzxt-kraken3.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. ++ * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73, 2023/2023 Elite, and ++ * 2024 Elite all in one coolers. + * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). + * 2023 models use the Z53 code paths. + * +@@ -25,6 +26,7 @@ + #define USB_PRODUCT_ID_Z53 0x3008 + #define USB_PRODUCT_ID_KRAKEN2023 0x300E + #define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C ++#define USB_PRODUCT_ID_KRAKEN2024_ELITE 0x3012 + + enum kinds { X53, Z53, KRAKEN2023 } __packed; + enum pwm_enable { off, manual, curve } __packed; +@@ -927,6 +929,10 @@ static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id + priv->kind = KRAKEN2023; + device_name = "kraken2023elite"; + break; ++ case USB_PRODUCT_ID_KRAKEN2024_ELITE: ++ priv->kind = KRAKEN2023; ++ device_name = "kraken2024elite"; ++ break; + default: + ret = -ENODEV; + goto fail_and_close; +@@ -994,6 +1000,7 @@ static const struct hid_device_id kraken3_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2024_ELITE) }, + { } + }; + +diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h +--- a/drivers/hwmon/peci/common.h ++++ b/drivers/hwmon/peci/common.h +@@ -31,7 +31,7 @@ struct peci_sensor_data { + + /** + * peci_sensor_need_update() - check whether sensor update is needed or not +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + * + * Return: true if update is needed, false if not. + */ +@@ -44,7 +44,7 @@ static inline bool peci_sensor_need_update(struct peci_sensor_state *state) + + /** + * peci_sensor_mark_updated() - mark the sensor is updated +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + */ + static inline void peci_sensor_mark_updated(struct peci_sensor_state *state) + { +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -52,8 +52,8 @@ config SENSORS_ADM1275 + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, +- ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and +- Digital Power Monitors. ++ ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C ++ Hot-Swap Controller and Digital Power Monitors. + + This driver can also be built as a module. If so, the module will + be called adm1275. +@@ -394,10 +394,10 @@ config SENSORS_MAX20751 + be called max20751. + + config SENSORS_MAX20830 +- tristate "Analog Devices MAX20830" ++ tristate "Analog Devices MAX20830 and compatibles" + help + If you say yes here you get hardware monitoring support for Analog +- Devices MAX20830. ++ Devices MAX20830, MAX20830C, and MAX20840C. + + This driver can also be built as a module. If so, the module will + be called max20830. +@@ -434,8 +434,9 @@ config SENSORS_MAX34440 + tristate "Maxim MAX34440 and compatibles" + help + If you say yes here you get hardware monitoring support for Maxim +- MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. +- Other compatible are ADPM12160, and ADPM12200. ++ MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and ++ MAX34461. Other compatible devices are ADPM12160, ADPM12200, and ++ ADPM12250. + + This driver can also be built as a module. If so, the module will + be called max34440. +@@ -607,6 +608,15 @@ config SENSORS_MPQ7932 + This driver can also be built as a module. If so, the module will + be called mpq7932. + ++config SENSORS_MPQ82D00 ++ tristate "MPS MPQ82D00" ++ help ++ If you say yes here you get hardware monitoring functionality support ++ for power management IC MPS MPQ82D00. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mpq82d00. ++ + config SENSORS_MPQ8785 + tristate "MPS MPQ8785" + help +@@ -616,6 +626,37 @@ config SENSORS_MPQ8785 + This driver can also be built as a module. If so, the module will + be called mpq8785. + ++config SENSORS_MPQ8646 ++ tristate "MPS MPQ8646" ++ depends on REGULATOR || !REGULATOR ++ depends on NVMEM || !NVMEM ++ help ++ If you say yes here you get hardware monitoring support for the ++ Monolithic Power Systems MPQ8646. ++ ++ This driver can also be built as a module. If so, the module ++ will be called mpq8646. ++ ++config SENSORS_MPQ8646_DEBUG_UNSAFE ++ bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)" ++ depends on SENSORS_MPQ8646 && DEBUG_FS ++ default n ++ help ++ Expose additional WRITE-able debugfs files in the client's ++ pmbus debugfs directory, ++ /sys/kernel/debug/i2c/i2c-/-/ ++ ++ These are provisioning and bring-up aids. On many designs the ++ MPQ8646 powers the SoC core rail, so a wrong write can brown out ++ or permanently mis-provision the board. ++ ++ WARNING: Wrong register writes can and likely will physically ++ damage or destroy the chip and/or the board. ++ ++ It shall never be set in a production, shipping, or default configuration. ++ ++ If unsure, say N. ++ + config SENSORS_PIM4328 + tristate "Flex PIM4328 and compatibles" + help +@@ -677,6 +718,25 @@ config SENSORS_STEF48H28 + This driver can also be built as a module. If so, the module will + be called stef48h28. + ++config SENSORS_SQ24860 ++ tristate "Silergy SQ24860" ++ help ++ If you say yes here you get hardware monitoring support for Silergy ++ SQ24860 eFuse. ++ ++ This driver can also be built as a module. If so, the module will ++ be called sq24860. ++ ++config SENSORS_SQ24860_REGULATOR ++ bool "Regulator support for SQ24860" ++ depends on SENSORS_SQ24860 && REGULATOR ++ default SENSORS_SQ24860 ++ help ++ If you say yes here you get regulator support for Silergy SQ24860. ++ The regulator is registered through the PMBus regulator framework and ++ can be used to control the output exposed by the device. ++ This option is only useful if regulator framework support is needed. ++ + config SENSORS_STPDDC60 + tristate "ST STPDDC60" + help +@@ -767,6 +827,15 @@ config SENSORS_UCD9200 + This driver can also be built as a module. If so, the module will + be called ucd9200. + ++config SENSORS_VT7505 ++ tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505" ++ help ++ If you say yes here you get hardware monitoring support for Analog ++ Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers. ++ ++ This driver can also be built as a module. If so, the module will ++ be called vt7505. ++ + config SENSORS_XDP710 + tristate "Infineon XDP710 family" + help +@@ -803,6 +872,16 @@ config SENSORS_XDPE1A2G7B + This driver can also be built as a module. If so, the module will + be called xdpe1a2g7b. + ++config SENSORS_XDPE1A2G7B_REGULATOR ++ bool "Regulator support for XDPE1A2G7B and compatibles" ++ depends on SENSORS_XDPE1A2G7B && REGULATOR ++ help ++ If you say yes here you get regulator support for Infineon ++ XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers. ++ ++ This enables the controllers to be used as regulator devices, ++ providing voltage control through the regulator framework. ++ + config SENSORS_XDPE122 + tristate "Infineon XDPE122 family" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -59,12 +59,15 @@ obj-$(CONFIG_SENSORS_MP5990) += mp5990.o + obj-$(CONFIG_SENSORS_MP9941) += mp9941.o + obj-$(CONFIG_SENSORS_MP9945) += mp9945.o + obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o ++obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o + obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o ++obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.o + obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o + obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o + obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o + obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o + obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o ++obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o + obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o + obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o + obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o +@@ -73,6 +76,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o + obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o + obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o + obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o ++obj-$(CONFIG_SENSORS_VT7505) += vt7505.o + obj-$(CONFIG_SENSORS_XDP710) += xdp710.o + obj-$(CONFIG_SENSORS_XDP720) += xdp720.o + obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o +diff --git a/drivers/hwmon/pmbus/acbel-fsg032.c b/drivers/hwmon/pmbus/acbel-fsg032.c +--- a/drivers/hwmon/pmbus/acbel-fsg032.c ++++ b/drivers/hwmon/pmbus/acbel-fsg032.c +@@ -52,6 +52,7 @@ static const struct i2c_device_id acbel_fsg032_id[] = { + { .name = "acbel_fsg032" }, + { } + }; ++MODULE_DEVICE_TABLE(i2c, acbel_fsg032_id); + + static struct pmbus_driver_info acbel_fsg032_info = { + .pages = 1, +diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c +--- a/drivers/hwmon/pmbus/adm1275.c ++++ b/drivers/hwmon/pmbus/adm1275.c +@@ -19,7 +19,7 @@ + #include "pmbus.h" + + enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, +- adm1293, adm1294, sq24905c }; ++ adm1293, adm1294, bd12780, bd12790, sq24905c }; + + #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) + #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) +@@ -47,6 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, + #define ADM1278_VOUT_EN BIT(1) + + #define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) ++/* The BD127[89]0 data sheets mark TSFILT bit as reserved. */ ++#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN) + + #define ADM1293_IRANGE_25 0 + #define ADM1293_IRANGE_50 BIT(6) +@@ -134,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = { + + }; + ++/* ++ * BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18) ++ * and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m. ++ * ++ * Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V) ++ * -> m = 6770, R=-2 / m = 4062, R=-2 ++ * Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV) ++ * -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R ++ * Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72 ++ * -> m = round(k * 10^(-3-R)), R=-2 for 60V/15mV, R=-3 for the other three ++ * Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1 ++ */ ++static const struct coefficients bd12790_coefficients[] = { ++ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ ++ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ ++ [2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */ ++ [3] = { 664, 20480, -1 }, /* current, vsense range 30mV */ ++ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ ++ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ ++ [6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */ ++ [7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */ ++ [8] = { 42, 31880, -1 }, /* temperature */ ++}; ++ + static const struct coefficients adm1275_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ +@@ -487,6 +513,8 @@ static const struct i2c_device_id adm1275_id[] = { + { .name = "adm1281", .driver_data = adm1281 }, + { .name = "adm1293", .driver_data = adm1293 }, + { .name = "adm1294", .driver_data = adm1294 }, ++ { .name = "bd12780", .driver_data = bd12780 }, ++ { .name = "bd12790", .driver_data = bd12790 }, + { .name = "mc09c", .driver_data = sq24905c }, + { } + }; +@@ -494,12 +522,13 @@ MODULE_DEVICE_TABLE(i2c, adm1275_id); + + /* Enable VOUT & TEMP1 if not enabled (disabled by default) */ + static int adm1275_enable_vout_temp(struct adm1275_data *data, +- struct i2c_client *client, int config) ++ struct i2c_client *client, int config, ++ u16 defconfig) + { + int ret; + +- if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) { +- config |= ADM1278_PMON_DEFCONFIG; ++ if ((config & defconfig) != defconfig) { ++ config |= defconfig; + ret = adm1275_write_pmon_config(data, client, config); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n"); +@@ -524,23 +553,19 @@ static int adm1275_probe(struct i2c_client *client) + u32 shunt; + u32 avg; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) && +- (ret != 2 || strncmp(block_buffer, "SY", 2))) { ++ (ret != 2 || strncmp(block_buffer, "SY", 2)) && ++ (ret != 4 || strncmp(block_buffer, "ROHM", 4))) { + dev_err(&client->dev, "Unsupported Manufacturer ID\n"); + return -ENODEV; + } + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; +@@ -562,6 +587,7 @@ static int adm1275_probe(struct i2c_client *client) + if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || + mid->driver_data == adm1278 || mid->driver_data == adm1281 || + mid->driver_data == adm1293 || mid->driver_data == adm1294 || ++ mid->driver_data == bd12780 || mid->driver_data == bd12790 || + mid->driver_data == sq24905c) + config_read_fn = i2c_smbus_read_word_data; + else +@@ -642,6 +668,7 @@ static int adm1275_probe(struct i2c_client *client) + data->have_power_sampling = true; + + coefficients = adm1272_coefficients; ++ + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ +@@ -666,7 +693,53 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ break; ++ ++ /* ++ * The BD12790 is almost identical to the adm1272. Only the defconfig ++ * and coefficients have minor differences. ++ */ ++ case bd12790: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = bd12790_coefficients; ++ ++ vindex = (config & ADM1275_VRANGE) ? 1 : 0; ++ cindex = (config & ADM1272_IRANGE) ? 3 : 2; ++ /* pindex depends on the combination of the above */ ++ switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { ++ case 0: ++ default: ++ pindex = 4; ++ break; ++ case ADM1275_VRANGE: ++ pindex = 5; ++ break; ++ case ADM1272_IRANGE: ++ pindex = 6; ++ break; ++ case ADM1275_VRANGE | ADM1272_IRANGE: ++ pindex = 7; ++ break; ++ } ++ tindex = 8; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); + if (ret) + return ret; + +@@ -728,13 +801,45 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; ++ ++ /* ++ * The BD12780 is almost functionally identical with the adm1278 above. ++ * Only differences visible to the driver are lack of TSFILT bits and ++ * different identification register contents. ++ */ ++ case bd12780: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = adm1278_coefficients; ++ vindex = 0; ++ cindex = 1; ++ pindex = 2; ++ tindex = 3; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ ++ break; + case adm1293: + case adm1294: + data->have_iout_min = true; +@@ -870,9 +975,27 @@ static int adm1275_probe(struct i2c_client *client) + return pmbus_do_probe(client, info); + } + ++static const struct of_device_id adm1275_of_match[] = { ++ { .compatible = "adi,adm1075", }, ++ { .compatible = "adi,adm1272", }, ++ { .compatible = "adi,adm1273", }, ++ { .compatible = "adi,adm1275", }, ++ { .compatible = "adi,adm1276", }, ++ { .compatible = "adi,adm1278", }, ++ { .compatible = "adi,adm1281", }, ++ { .compatible = "adi,adm1293", }, ++ { .compatible = "adi,adm1294", }, ++ { .compatible = "rohm,bd12780", }, ++ { .compatible = "rohm,bd12790", }, ++ { .compatible = "silergy,mc09c", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, adm1275_of_match); ++ + static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", ++ .of_match_table = adm1275_of_match, + }, + .probe = adm1275_probe, + .id_table = adm1275_id, +diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c +--- a/drivers/hwmon/pmbus/ir36021.c ++++ b/drivers/hwmon/pmbus/ir36021.c +@@ -31,21 +31,13 @@ static int ir36021_probe(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_READ_WORD_DATA +- | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf); +- if (ret < 0) { +- dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); +- return ret; +- } +- if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) { +- dev_err(&client->dev, "MFR_MODEL unrecognised\n"); +- return -ENODEV; +- } ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read PMBUS_MFR_MODEL\n"); ++ if (ret != 1 || buf[0] != 0x2d) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "MFR_MODEL unrecognised\n"); + + return pmbus_do_probe(client, &ir36021_info); + } +diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c +--- a/drivers/hwmon/pmbus/lm25066.c ++++ b/drivers/hwmon/pmbus/lm25066.c +@@ -35,6 +35,7 @@ enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; + #define LM25066_READ_AVG_PIN 0xdf + + #define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ ++#define LM25066_DEV_SETUP_CL_CFG BIT(2) /* Current limit configuration */ + + #define LM25066_SAMPLES_FOR_AVG_MAX 4096 + +@@ -485,6 +486,42 @@ static int lm25066_probe(struct i2c_client *client) + + data->id = (enum chips)(unsigned long)i2c_get_match_data(client); + ++ if (data->id != lm25056) { ++ int config_new = config; ++ const char *cl_setting; ++ int ret; ++ ++ if (!of_property_read_string(client->dev.of_node, ++ "ti,current-range", &cl_setting)) { ++ config_new |= LM25066_DEV_SETUP_CL_CFG; ++ if (strcmp(cl_setting, "high") == 0) { ++ if (data->id == lm25066) ++ config_new |= LM25066_DEV_SETUP_CL; ++ else ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ } else if (strcmp(cl_setting, "low") == 0) { ++ if (data->id == lm25066) ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ else ++ config_new |= LM25066_DEV_SETUP_CL; ++ } else { ++ dev_err(&client->dev, ++ "invalid current-range setting: %s\n", ++ cl_setting); ++ return -EINVAL; ++ } ++ } ++ ++ if (config_new != config) { ++ ret = i2c_smbus_write_byte_data(client, ++ LM25066_DEVICE_SETUP, ++ config_new); ++ if (ret < 0) ++ return ret; ++ config = config_new; ++ } ++ } ++ + info = &data->info; + + info->pages = 1; +diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c +--- a/drivers/hwmon/pmbus/ltc2978.c ++++ b/drivers/hwmon/pmbus/ltc2978.c +@@ -611,17 +611,13 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) + return ret; + if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3))) + return -ENODEV; + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return ret; + for (id = <c2978_id[0]; strlen(id->name); id++) { +@@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- sizeof(buf), buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, ++ buf); + if (ret < 0) + return ret; + +- if (!strncmp(buf + 1, "LT7170", 6) || +- !strncmp(buf + 1, "LT7170-1", 8)) ++ if (ret < 6) ++ return -ENODEV; ++ ++ if (!strncmp(buf, "LT7170", 6)) + return lt7170; +- if (!strncmp(buf + 1, "LT7171", 6) || +- !strncmp(buf + 1, "LT7171-1", 8)) ++ if (!strncmp(buf, "LT7171", 6)) + return lt7171; + + return -ENODEV; +diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c +--- a/drivers/hwmon/pmbus/max20830.c ++++ b/drivers/hwmon/pmbus/max20830.c +@@ -7,12 +7,69 @@ + + #include + #include ++#include + #include + #include + #include "pmbus.h" + + #define MAX20830_IC_DEVICE_ID_LENGTH 9 + ++struct max20830_data { ++ struct pmbus_driver_info info; ++ u32 vout_rfb1; ++ u32 vout_rfb2; ++}; ++ ++static const char * const supported_chip_ids[] = { ++ "MAX20830", ++ "MAX20830C", ++ "MAX20840C", ++}; ++ ++/* ++ * MAX20830 only supports READ_VOUT for VOUT monitoring. ++ * ++ * Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not ++ * supported by this driver and return -ENODATA. This means sysfs attributes ++ * like in1_max, in1_crit, etc. will not be available. Only in1_input (the ++ * scaled output voltage) is supported. ++ * ++ * MAX20830 uses an external resistor divider for voltage sensing: ++ * - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin ++ * - READ_VOUT reports the feedback voltage, which needs to be scaled for actual ++ * output voltage ++ * ++ * Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2) ++ * ++ * If regulator support is added in the future, some adjustments are needed to ++ * ensure correct feedback voltages are set. ++ */ ++static int max20830_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ const struct max20830_data *data = container_of(info, struct max20830_data, info); ++ int ret; ++ u64 temp; ++ ++ switch (reg) { ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ /* Apply voltage divider scaling if resistors are non-zero */ ++ if (data->vout_rfb1 && data->vout_rfb2) { ++ temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2; ++ temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2); ++ ret = clamp_val(temp, 0, 0xFFFF); ++ } ++ return ret; ++ default: ++ return -ENODATA; ++ } ++} ++ + static struct pmbus_driver_info max20830_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, +@@ -23,61 +80,53 @@ static struct pmbus_driver_info max20830_info = { + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, ++ .read_word_data = max20830_read_word_data, ++ .have_pmbus_revision = true, ++ .pmbus_revision = PMBUS_REV_13, + }; + + static int max20830_probe(struct i2c_client *client) + { + u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {}; +- int ret; ++ struct max20830_data *data; ++ int i, ret; + +- if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) && +- !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) +- return -ENODEV; ++ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; + +- /* +- * Use i2c_smbus_read_block_data() if supported, otherwise fall back +- * to i2c_smbus_read_i2c_block_data() to support I2C controllers +- * which do not support SMBus block reads. +- */ +- if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { +- /* Reads 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- } else { +- /* Reads 1 length byte + 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- MAX20830_IC_DEVICE_ID_LENGTH + 1, +- buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- /* +- * Moves data forward, removing the length byte, this is to +- * match the format of i2c_smbus_read_block_data(). +- * Also adjust return value to reflect length byte removal. +- */ +- memmove(buf, buf + 1, MAX20830_IC_DEVICE_ID_LENGTH); +- ret = ret - 1; +- } ++ data->info = max20830_info; + +- /* +- * MAX20830 IC_DEVICE_ID sends string data "MAX20830\0". +- * Return value should at least be 9 bytes of data. +- */ ++ /* Read optional voltage divider resistor values */ ++ device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1); ++ device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2); ++ ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read IC_DEVICE_ID\n"); ++ ++ /* Verify we read the expected number of bytes */ + if (ret < MAX20830_IC_DEVICE_ID_LENGTH) + return dev_err_probe(&client->dev, -ENODEV, +- "IC_DEVICE_ID too short: expected at least 9 bytes, got %d\n", +- ret); ++ "IC_DEVICE_ID too short: expected %d bytes, got %d\n", ++ MAX20830_IC_DEVICE_ID_LENGTH, ret); + +- /* 9 bytes of data, buf[0]-buf[7] = "MAX20830", buf[8] = '\0' */ +- buf[MAX20830_IC_DEVICE_ID_LENGTH - 1] = '\0'; +- if (strncmp(buf, "MAX20830", MAX20830_IC_DEVICE_ID_LENGTH - 1)) ++ /* Null-terminate the string */ ++ buf[ret] = '\0'; ++ ++ /* Verify the device ID matches what we expect */ ++ for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) { ++ if (!strcmp(buf, supported_chip_ids[i])) ++ break; ++ } ++ ++ /* No match found - unsupported device */ ++ if (i == ARRAY_SIZE(supported_chip_ids)) + return dev_err_probe(&client->dev, -ENODEV, +- "Unsupported device: '%s'\n", buf); ++ "Unsupported device: '%*pE'\n", ret, buf); + +- return pmbus_do_probe(client, &max20830_info); ++ return pmbus_do_probe(client, &data->info); + } + + static const struct i2c_device_id max20830_id[] = { +diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c +--- a/drivers/hwmon/pmbus/max34440.c ++++ b/drivers/hwmon/pmbus/max34440.c +@@ -23,6 +23,7 @@ enum chips { + max34441, + max34446, + max34451, ++ max34452, + max34460, + max34461, + }; +@@ -59,6 +60,7 @@ enum chips { + #define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A + + #define MAX34451ETNA6_MFR_REV 0x0012 ++#define MAX34451ETNA8_MFR_REV 0x0014 + + #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 + #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f +@@ -105,16 +107,21 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_MFR_MAX_TEMP_1: + /* +- * MAX34451/ADPM family do not support VIN/IIN limit registers, +- * manufacturer-specific min/max registers, or undercurrent/ +- * undertemperature fault limits. Accessing these triggers CML +- * error and asserts ALERT. ++ * MAX34451/MAX34452/ADPM family do not support VIN/IIN limit ++ * registers, manufacturer-specific min/max registers, or ++ * undercurrent/undertemperature fault limits. Accessing these ++ * triggers CML error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + ret = -ENODATA; + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_VOUT_MIN); +@@ -125,8 +132,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446 && data->id != max34451 && +- data->id != adpm12160 && data->id != adpm12200 && +- data->id != adpm12250) ++ data->id != max34452 && data->id != adpm12160 && ++ data->id != adpm12200 && data->id != adpm12250) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_IOUT_AVG); +@@ -191,6 +198,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit, + word); + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); +@@ -211,8 +222,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && (data->id == max34446 || data->id == max34451 || +- data->id == adpm12160 || data->id == adpm12200 || +- data->id == adpm12250)) ++ data->id == max34452 || data->id == adpm12160 || ++ data->id == adpm12200 || data->id == adpm12250)) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + +@@ -280,12 +291,13 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg) + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Accessing them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -303,12 +315,13 @@ static int max34451_write_byte_data(struct i2c_client *client, int page, + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Writing to them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -339,16 +352,28 @@ static int max34451_set_supported_funcs(struct i2c_client *client, + if (rv < 0) + return rv; + +- if (rv >= MAX34451ETNA6_MFR_REV) { ++ if (data->id == max34451 && rv >= MAX34451ETNA6_MFR_REV) { + max34451_na6 = true; + data->info.format[PSC_VOLTAGE_IN] = direct; + data->info.format[PSC_CURRENT_IN] = direct; +- data->info.m[PSC_VOLTAGE_IN] = 1; ++ + data->info.b[PSC_VOLTAGE_IN] = 0; +- data->info.R[PSC_VOLTAGE_IN] = 3; +- data->info.m[PSC_CURRENT_IN] = 1; + data->info.b[PSC_CURRENT_IN] = 0; +- data->info.R[PSC_CURRENT_IN] = 2; ++ if (rv >= MAX34451ETNA8_MFR_REV) { ++ data->info.m[PSC_VOLTAGE_IN] = 125; ++ data->info.R[PSC_VOLTAGE_IN] = 0; ++ data->info.m[PSC_VOLTAGE_OUT] = 125; ++ data->info.R[PSC_VOLTAGE_OUT] = 0; ++ data->info.m[PSC_CURRENT_IN] = 250; ++ data->info.R[PSC_CURRENT_IN] = -1; ++ data->info.m[PSC_CURRENT_OUT] = 250; ++ data->info.R[PSC_CURRENT_OUT] = -1; ++ } else { ++ data->info.m[PSC_VOLTAGE_IN] = 1; ++ data->info.R[PSC_VOLTAGE_IN] = 3; ++ data->info.m[PSC_CURRENT_IN] = 1; ++ data->info.R[PSC_CURRENT_IN] = 2; ++ } + data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; + } +@@ -665,6 +690,32 @@ static struct pmbus_driver_info max34440_info[] = { + .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, + }, ++ [max34452] = { ++ .pages = 21, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .m[PSC_CURRENT_OUT] = 1, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 2, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 2, ++ /* func 0-15 is set dynamically before probing */ ++ .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .read_byte_data = max34451_read_byte_data, ++ .read_word_data = max34440_read_word_data, ++ .write_byte_data = max34451_write_byte_data, ++ .write_word_data = max34440_write_word_data, ++ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, ++ }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, +@@ -748,7 +799,7 @@ static int max34440_probe(struct i2c_client *client) + data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; + +- if (data->id == max34451) { ++ if (data->id == max34451 || data->id == max34452) { + rv = max34451_set_supported_funcs(client, data); + if (rv) + return rv; +@@ -769,6 +820,7 @@ static const struct i2c_device_id max34440_id[] = { + { .name = "max34441", .driver_data = max34441 }, + { .name = "max34446", .driver_data = max34446 }, + { .name = "max34451", .driver_data = max34451 }, ++ { .name = "max34452", .driver_data = max34452 }, + { .name = "max34460", .driver_data = max34460 }, + { .name = "max34461", .driver_data = max34461 }, + { } +diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c +--- a/drivers/hwmon/pmbus/mp2975.c ++++ b/drivers/hwmon/pmbus/mp2975.c +@@ -176,7 +176,6 @@ mp2975_vid2direct(int vrf, int val) + static u16 mp2975_data2reg_linear11(s64 val) + { + s16 exponent = 0, mantissa; +- bool negative = false; + + /* simple case */ + if (val == 0) +@@ -196,10 +195,6 @@ static u16 mp2975_data2reg_linear11(s64 val) + /* Convert mantissa from milli-units to units */ + mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); + +- /* restore sign */ +- if (negative) +- mantissa = -mantissa; +- + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); + } +diff --git a/drivers/hwmon/pmbus/mpq82d00.c b/drivers/hwmon/pmbus/mpq82d00.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mpq82d00.c +@@ -0,0 +1,249 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00) ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define MPQ82D00_VOUT_DIV 64 ++ ++#define MPQ82D00_PAGE_NUM 1 ++ ++#define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \ ++ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \ ++ PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_INPUT) ++ ++struct mpq82d00_data { ++ struct pmbus_driver_info info; ++ int vout_scale; ++}; ++ ++#define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info) ++ ++static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent) ++{ ++ s16 exponent, mantissa, target_exponent; ++ ++ exponent = ((s16)word) >> 11; ++ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; ++ target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11; ++ ++ /* ++ * The MPQ82D00 does not support negtive limit value, if a negtive ++ * limit value is written, the limit value will become to 0. And ++ * the maximum positive limit value is limitted to 0x3FF. ++ */ ++ if (mantissa < 0) { ++ mantissa = 0; ++ } else { ++ if (exponent > target_exponent) { ++ mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ? ++ mantissa << (exponent - target_exponent) : ++ 0x3FF; ++ } else { ++ mantissa = clamp_val(mantissa >> (target_exponent - exponent), ++ 0, 0x3FF); ++ } ++ } ++ ++ return mantissa | ((expect_exponent << 11) & 0xf800); ++} ++ ++static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ /* ++ * The MPQ82D00 does not follow standard PMBus protocol completely, ++ * and the calculation of vout in this driver is based on direct ++ * format. As a result, the format of vout is enforced to direct. ++ */ ++ ret = PB_VOUT_MODE_DIRECT; ++ break; ++ default: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_STATUS_WORD: ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_POUT: ++ case PMBUS_READ_PIN: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale, ++ MPQ82D00_VOUT_DIV); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other telemetry and limit ++ * value reading, so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg, ++ u16 word) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b00000), so the ++ * exponent of word parameter should be converted to 5'b00000. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x00)); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ /* ++ * The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b11100), so the ++ * exponent of word parameter should be converted to 5'b11100. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x1C)); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other limit value configuration, ++ * so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info) ++{ ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE ++ * bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5 ++ * is 0, it is linear mode, the vout scale is 1.953125mv/LSB. ++ */ ++ if (FIELD_GET(GENMASK(5, 5), ret)) ++ data->vout_scale = 320; ++ else ++ data->vout_scale = 125; ++ ++ return 0; ++} ++ ++static struct pmbus_driver_info mpq82d00_info = { ++ .pages = MPQ82D00_PAGE_NUM, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_CURRENT_OUT] = linear, ++ .format[PSC_TEMPERATURE] = linear, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_POWER] = linear, ++ ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ ++ .func[0] = MPQ82D00_RAIL1_FUNC, ++ .read_word_data = mpq82d00_read_word_data, ++ .read_byte_data = mpq82d00_read_byte_data, ++ .write_word_data = mpq82d00_write_word_data, ++ .identify = mpq82d00_identify, ++}; ++ ++static int mpq82d00_probe(struct i2c_client *client) ++{ ++ struct mpq82d00_data *data; ++ ++ data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info)); ++ ++ return pmbus_do_probe(client, &data->info); ++} ++ ++static const struct i2c_device_id mpq82d00_id[] = { ++ {.name = "mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mpq82d00_id); ++ ++static const struct of_device_id __maybe_unused mpq82d00_of_match[] = { ++ {.compatible = "mps,mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq82d00_of_match); ++ ++static struct i2c_driver mpq82d00_driver = { ++ .driver = { ++ .name = "mpq82d00", ++ .of_match_table = mpq82d00_of_match, ++ }, ++ .probe = mpq82d00_probe, ++ .id_table = mpq82d00_id, ++}; ++ ++module_i2c_driver(mpq82d00_driver); ++ ++MODULE_AUTHOR("Wensheng Wang ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++/* Default cadence for the in-driver alarm-poll fallback (ms) */ ++#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 ++ ++/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ ++#define MPS_CLEAR_LAST_FAULT 0x08 ++#define MPS_MFR_CFG_EXT 0xF5 ++#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6) ++#define MPS_PROTECTION_LAST 0xFB ++ ++/* ++ * PMBus 1.3 NVM commit / revert commands. MPS equivalent of ++ * STORE_ALL (15h) and RESTORE_ALL (16h). ++ */ ++#define PMBUS_STORE_USER_ALL 0x15 ++#define PMBUS_RESTORE_USER_ALL 0x16 ++ ++/* PMBus 1.3 timing / UVLO command codes */ ++#define PMBUS_VIN_ON 0x35 ++#define PMBUS_VIN_OFF 0x36 ++#define PMBUS_TON_DELAY 0x60 ++#define PMBUS_TON_RISE 0x61 ++#define PMBUS_TOFF_DELAY 0x64 ++#define PMBUS_TOFF_FALL 0x65 ++ ++/* MPS vendor-extended observability / identity registers */ ++#define MPS_MFR_CONFIG_ID 0xC0 ++#define MPS_MFR_CONFIG_CODE_REV 0xC1 ++#define MPS_MFR_PRODUCT_REV_USER 0xC2 ++#define MPS_MFR_SILICON_REV 0xC3 ++#define MPS_MFR_RETRY_TIMES 0xF4 ++#define MPS_MFR_VBOOT_CFG 0xFC ++ ++/* ++ * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate ++ * subsequent PMBus writes ++ * bits[1:0] = 00 -- unlocked (POR default) ++ * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21) ++ * so the operator can still DVFS the rail ++ * 11 -- lock all writes ++ * A negative-going PG edge resets these bits to 00, the lock ++ * is operationally reversible without a full chip POR. ++ */ ++#define MPS_MFR_PMBUS_LOCK 0xEE ++ ++/* ++ * Retry parameters for the MFR_CFG_EXT gate-close write after ++ * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this ++ * silicon is about 1 ms; the datasheet does not have information. ++ */ ++#define MPQ8646_NVM_RETRY_MAX 5 ++#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000 ++#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000 ++ ++#define MPQ8646_DEBUG(client, fmt, ...) \ ++ dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) ++ ++/* ++ * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon ++ * The chip uses an 11-bit VOUT feedback-divider scale register. ++ */ ++#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0) ++ ++/* Forward declaration */ ++struct mpq8646_dbg_reg_ctx; ++ ++/* Per-instance state */ ++struct mpq8646_priv { ++ struct pmbus_driver_info info; /* must be first, container_of target */ ++ struct i2c_client *client; ++ ++ /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ ++ struct mutex mps_lock; ++ ++ /* Set alarm_poll_interval_ms = 0 to disable. */ ++ struct delayed_work alarm_poll_work; ++ u32 alarm_poll_interval_ms; ++ ++#ifdef CONFIG_DEBUG_FS ++ /* the only debugfs file that can re-arm the poll worker */ ++ struct dentry *dbg_poll; ++ struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; ++#endif ++}; ++ ++static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ ++ return container_of(info, struct mpq8646_priv, info); ++} ++ ++/* ++ * VID-mode m/b/R coefficients, same values as the mpq8785 driver for ++ * the MPS VID encoding. Per the PMBus Direct formula used by ++ * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields ++ * 1.5625 mV per LSB. ++ */ ++#define MPQ8646_VID_M 64 ++#define MPQ8646_VID_B 0 ++#define MPQ8646_VID_R 1 ++ ++static int mpq8646_identify(struct i2c_client *client, ++ struct pmbus_driver_info *info) ++{ ++ int vout_mode; ++ ++ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); ++ if (vout_mode < 0) ++ return vout_mode; ++ ++ switch (vout_mode & PB_VOUT_MODE_MODE_MASK) { ++ case PB_VOUT_MODE_LINEAR: ++ info->format[PSC_VOLTAGE_OUT] = linear; ++ break; ++ case PB_VOUT_MODE_VID: ++ case PB_VOUT_MODE_DIRECT: ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M; ++ info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B; ++ info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R; ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ return 0; ++}; ++ ++static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg); ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ ret = pmbus_read_byte_data(client, page, reg); ++ MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret); ++ if (ret < 0) ++ return ret; ++ ++ if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID) ++ return PB_VOUT_MODE_DIRECT; ++ ++ return ret; ++ case PMBUS_STATUS_BYTE: ++ case PMBUS_STATUS_CML: ++ case PMBUS_STATUS_OTHER: ++ case PMBUS_STATUS_MFR_SPECIFIC: ++ case PMBUS_STATUS_FAN_12: ++ case PMBUS_STATUS_FAN_34: ++ return -ENXIO; ++ case PMBUS_MFR_LOCATION: ++ case PMBUS_MFR_DATE: ++ case PMBUS_MFR_SERIAL: ++ case PMBUS_IC_DEVICE_ID: ++ case PMBUS_IC_DEVICE_REV: ++ MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); ++ return -ENXIO; ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * Reference: ltc2978.c::ltc2978_write_word_data which uses the ++ * same virtual-register channel for its real chip-side peak reset. ++ */ ++static int mpq8646_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ int rc; ++ ++ switch (reg) { ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n", ++ reg); ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); ++ if (rc < 0) ++ MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc); ++ return rc < 0 ? rc : 0; ++ default: ++ return -ENODATA; /* let pmbus_core do the direct write */ ++ } ++} ++ ++static int mpq8646_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ int rc; ++ ++ MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n", ++ page, phase, reg); ++ ++ switch (reg) { ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_VOUT: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_STATUS_WORD: ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_MFR_VIN_MAX: ++ case PMBUS_MFR_VOUT_MAX: ++ case PMBUS_MFR_IOUT_MAX: ++ case PMBUS_MFR_MAX_TEMP_1: ++ break; ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++ ++ rc = i2c_smbus_read_word_data(client, reg); ++ ++ MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc); ++ return rc; ++} ++ ++static struct pmbus_driver_info mpq8646_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_VOLTAGE_IN] = 4, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 1, ++ .m[PSC_CURRENT_OUT] = 16, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 0, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, ++}; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++static const struct regulator_desc mpq8646_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++}; ++#endif /* CONFIG_REGULATOR */ ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++/* ++ * Expose using ++ * /sys/bus/nvmem/devices//nvmem ++ * ++ * Layout (16 bytes): ++ * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM ++ * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM ++ * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM ++ * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM ++ * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM ++ * offset 9..15 reserved (zero-fill, leaves room for additions) ++ */ ++#define MPQ8646_NVMEM_SIZE 16 ++ ++struct mpq8646_nvmem_entry { ++ unsigned int off; ++ u8 reg; ++ bool is_word; ++}; ++ ++static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = { ++ { 0, MPS_PROTECTION_LAST, true }, ++ { 2, MPS_MFR_RETRY_TIMES, true }, ++ { 4, MPS_MFR_CONFIG_ID, true }, ++ { 6, MPS_MFR_VBOOT_CFG, true }, ++ { 8, MPS_MFR_SILICON_REV, false }, ++}; ++ ++static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val, ++ size_t bytes) ++{ ++ struct mpq8646_priv *priv = data; ++ u8 *out = val; ++ size_t i; ++ ++ if (offset >= MPQ8646_NVMEM_SIZE) ++ return -EINVAL; ++ if (offset + bytes > MPQ8646_NVMEM_SIZE) ++ bytes = MPQ8646_NVMEM_SIZE - offset; ++ ++ memset(out, 0, bytes); ++ ++ guard(mutex)(&priv->mps_lock); ++ for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { ++ const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i]; ++ unsigned int e_start = e->off; ++ unsigned int e_end = e_start + (e->is_word ? 2 : 1); ++ u8 raw[2]; ++ int rc; ++ unsigned int j; ++ ++ if (e_end <= offset || e_start >= offset + bytes) ++ continue; /* outside requested slice */ ++ ++ if (e->is_word) ++ rc = i2c_smbus_read_word_data(priv->client, e->reg); ++ else ++ rc = i2c_smbus_read_byte_data(priv->client, e->reg); ++ if (rc < 0) ++ continue; /* leave the zero-fill in place */ ++ ++ raw[0] = rc & 0xff; ++ raw[1] = (rc >> 8) & 0xff; ++ ++ for (j = 0; j < e_end - e_start; j++) { ++ unsigned int abs = e_start + j; ++ ++ if (abs >= offset && abs < offset + bytes) ++ out[abs - offset] = raw[j]; ++ } ++ } ++ return 0; ++} ++#endif /* CONFIG_NVMEM */ ++ ++static const struct i2c_device_id mpq8646_id[] = { ++ { .name = "mpq8646" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(i2c, mpq8646_id); ++ ++static const struct of_device_id __maybe_unused mpq8646_of_match[] = { ++ { .compatible = "mps,mpq8646" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq8646_of_match); ++ ++static struct pmbus_platform_data mpq8646_no_pec_pdata = { ++ .flags = PMBUS_NO_CAPABILITY, ++}; ++ ++#ifdef CONFIG_DEBUG_FS ++/* ++ * Read-only decode cases in the client's pmbus debugfs directory: ++ * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the ++ * identity/timing registers. ++ */ ++ ++struct mpq_status_bit { ++ u16 mask; ++ const char *name; ++}; ++ ++static const struct mpq_status_bit mpq8646_status_word_bits[] = { ++ { PB_STATUS_VOUT, "VOUT" }, ++ { PB_STATUS_IOUT_POUT, "IOUT_POUT" }, ++ { PB_STATUS_INPUT, "INPUT" }, ++ { PB_STATUS_WORD_MFR, "NVM_SUMMARY" }, ++ { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" }, ++ { PB_STATUS_FANS, "FANS" }, ++ { PB_STATUS_OTHER, "OTHER" }, ++ { PB_STATUS_UNKNOWN, "WATCH_DOG" }, ++ { PB_STATUS_BUSY, "BUSY" }, ++ { PB_STATUS_OFF, "OFF" }, ++ { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" }, ++ { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" }, ++ { PB_STATUS_VIN_UV, "VIN_UV_FAULT" }, ++ { PB_STATUS_TEMPERATURE, "TEMP" }, ++ { PB_STATUS_CML, "CML" }, ++ { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" }, ++ { /* sentinel */ } ++}; ++ ++/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */ ++static const struct mpq_status_bit mpq8646_protection_last_bits[] = { ++ { BIT(15), "INIT_FAULT" }, ++ { BIT(14), "NVM_CRC_ERROR" }, ++ { BIT(13), "NVM_FAULT" }, ++ { BIT(12), "OC_PHASE_FAULT" }, ++ { BIT(11), "OTP_SELF_FAULT" }, ++ { BIT(9), "SWITCH_PRD_FAULT" }, ++ { BIT(8), "VIN_OV_FAULT" }, ++ { BIT(7), "VOUT_OV_FAULT" }, ++ { BIT(6), "VOUT_UV_FAULT" }, ++ { BIT(5), "OC_TOT_FAULT" }, ++ { BIT(4), "VIN_UVLO_FAULT" }, ++ { BIT(3), "DRMOS_OTP" }, ++ { /* sentinel */ } ++}; ++ ++static void mpq8646_print_bits(struct seq_file *s, u16 v, ++ const struct mpq_status_bit *tab) ++{ ++ const struct mpq_status_bit *t; ++ bool first = true; ++ ++ seq_printf(s, "0x%04x", v); ++ if (!v) { ++ seq_puts(s, " [clean]\n"); ++ return; ++ } ++ seq_puts(s, " ["); ++ for (t = tab; t->mask; t++) { ++ if (v & t->mask) { ++ if (!first) ++ seq_putc(s, ' '); ++ seq_puts(s, t->name); ++ first = false; ++ } ++ } ++ seq_puts(s, "]\n"); ++} ++ ++static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ scoped_guard(pmbus_lock, priv->client) ++ rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); ++ if (rc < 0) { ++ seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "STATUS_WORD: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits); ++ seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); ++ ++static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "PROTECTION_LAST: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits); ++ seq_puts(s, "(NVM-backed, survives chip POR)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); ++ ++struct mpq8646_dbg_reg { ++ u8 reg; ++ bool is_word; ++ const char *name; ++}; ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = { ++ /* MPS vendor extensions (read-only identity / observability) */ ++ { MPS_MFR_CONFIG_ID, true, "mfr_config_id" }, ++ { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" }, ++ { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" }, ++ { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" }, ++ { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" }, ++ /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ ++ { PMBUS_VIN_ON, true, "vin_on" }, ++ { PMBUS_VIN_OFF, true, "vin_off" }, ++ { PMBUS_TON_DELAY, true, "ton_delay" }, ++ { PMBUS_TON_RISE, true, "ton_rise" }, ++ { PMBUS_TOFF_DELAY, true, "toff_delay" }, ++ { PMBUS_TOFF_FALL, true, "toff_fall" }, ++}; ++ ++struct mpq8646_dbg_reg_ctx { ++ struct mpq8646_priv *priv; ++ const struct mpq8646_dbg_reg *desc; ++}; ++ ++static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n", ++ ctx->desc->reg, ctx->desc->name, rc); ++ return 0; ++ } ++ seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); ++ ++#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE ++/* ++ * Write/provisioning data, disabled by default: NVM commit and ++ * revert, the CLEAR_LAST_FAULT sequences and a small set of named ++ * writable registers. ++ */ ++ ++static void mpq8646_unsafe_banner(struct mpq8646_priv *priv) ++{ ++ dev_warn(&priv->client->dev, ++ "**********************************************************\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "** **\n" ++ "** The MPQ8646 provisioning debugfs writes are enabled. **\n" ++ "** Wrong register writes can and likely will physically **\n" ++ "** damage or destroy the chip and/or the board. **\n" ++ "** **\n" ++ "** If you see this message and you are not debugging **\n" ++ "** the kernel, report this immediately to your system **\n" ++ "** administrator! **\n" ++ "** **\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "**********************************************************\n"); ++} ++ ++static int mpq8646_dbg_clear_protection_last(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops, ++ NULL, mpq8646_dbg_clear_protection_last, "%llu\n"); ++ ++static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc, ret; ++ int wp_orig, cfg_orig; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ guard(mutex)(&priv->mps_lock); ++ ++ wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); ++ if (wp_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n", ++ wp_orig); ++ return wp_orig; ++ } ++ cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); ++ if (cfg_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n", ++ cfg_orig); ++ return cfg_orig; ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, 0); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP clear failed (%d), aborting\n", ++ rc); ++ return rc; ++ } ++ } ++ ++ ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, ++ (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); ++ if (ret < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: gate open failed (%d)\n", ++ ret); ++ } else { ++ ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (ret < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n", ++ ret); ++ ++ for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { ++ rc = i2c_smbus_write_word_data(priv->client, ++ MPS_MFR_CFG_EXT, ++ (u16)cfg_orig); ++ if (rc >= 0) ++ break; ++ usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN, ++ MPQ8646_NVM_RETRY_DELAY_US_MAX); ++ } ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, ++ (u8)wp_orig); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP restore failed (%d)\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ return ret; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops, ++ NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n"); ++ ++static int mpq8646_dbg_store_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "store_all: STORE_USER_ALL (0x15) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops, ++ NULL, mpq8646_dbg_store_all, "%llu\n"); ++ ++static int mpq8646_dbg_restore_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, ++ NULL, mpq8646_dbg_restore_all, "%llu\n"); ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = { ++ /* PMBus 1.3 control / margin */ ++ { PMBUS_ON_OFF_CONFIG, false, "on_off_config" }, ++ { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" }, ++ { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" }, ++ /* MPS PMBus-level write-protect */ ++ { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" }, ++ /* MPS user-writable product revision */ ++ { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" }, ++}; ++ ++static int mpq8646_dbg_reg_get(void *data, u64 *val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ if (rc < 0) ++ return rc; ++ *val = rc; ++ return 0; ++} ++ ++static int mpq8646_dbg_reg_set(void *data, u64 val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_write_word_data(ctx->priv->client, ++ ctx->desc->reg, (u16)val); ++ else ++ rc = i2c_smbus_write_byte_data(ctx->priv->client, ++ ctx->desc->reg, (u8)val); ++ return rc < 0 ? rc : 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops, ++ mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n"); ++ ++static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx; ++ size_t i; ++ ++ mpq8646_unsafe_banner(priv); ++ ++ debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv, ++ &mpq8646_dbg_clear_protection_last_fops); ++ debugfs_create_file_unsafe("clear_protection_last_force", 0200, root, ++ priv, ++ &mpq8646_dbg_clear_protection_last_force_fops); ++ debugfs_create_file_unsafe("store_all", 0200, root, priv, ++ &mpq8646_dbg_store_all_fops); ++ debugfs_create_file_unsafe("restore_all", 0200, root, priv, ++ &mpq8646_dbg_restore_all_fops); ++ ++ ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs_unsafe), ++ sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) { ++ ctx[i].priv = priv; ++ ctx[i].desc = &mpq8646_dbg_regs_unsafe[i]; ++ debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name, ++ 0600, root, &ctx[i], ++ &mpq8646_dbg_reg_rw_fops); ++ } ++} ++#else ++static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) {} ++#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */ ++ ++static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) ++{ ++ struct mpq8646_priv *priv = data; ++ ++ *val = priv->alarm_poll_interval_ms; ++ return 0; ++} ++ ++static int mpq8646_dbg_poll_interval_set(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ bool was_off = !priv->alarm_poll_interval_ms; ++ ++ priv->alarm_poll_interval_ms = (u32)val; ++ ++ if (val && was_off && !priv->client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies((u32)val)); ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops, ++ mpq8646_dbg_poll_interval_get, ++ mpq8646_dbg_poll_interval_set, "%llu\n"); ++ ++static void mpq8646_debugfs_register(struct mpq8646_priv *priv) ++{ ++ struct dentry *root; ++ size_t i; ++ ++ root = pmbus_get_debugfs_dir(priv->client); ++ if (!root) ++ return; ++ ++ /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */ ++ debugfs_create_file("status_decoded", 0400, root, priv, ++ &mpq8646_dbg_status_decoded_fops); ++ debugfs_create_file("protection_last", 0400, root, priv, ++ &mpq8646_dbg_protection_last_fops); ++ priv->dbg_poll = ++ debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600, ++ root, priv, ++ &mpq8646_dbg_poll_interval_fops); ++ ++ priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs), ++ sizeof(*priv->dbg_reg_ctx), ++ GFP_KERNEL); ++ if (!priv->dbg_reg_ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { ++ priv->dbg_reg_ctx[i].priv = priv; ++ priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i]; ++ debugfs_create_file(mpq8646_dbg_regs[i].name, 0400, ++ root, &priv->dbg_reg_ctx[i], ++ &mpq8646_dbg_reg_fops); ++ } ++ ++ mpq8646_debugfs_register_unsafe(priv, root); ++} ++ ++static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) ++{ ++ debugfs_remove(priv->dbg_poll); ++} ++#else ++static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {} ++static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {} ++#endif /* CONFIG_DEBUG_FS */ ++ ++static void mpq8646_alarm_poll_work(struct work_struct *work) ++{ ++ struct mpq8646_priv *priv = container_of(to_delayed_work(work), ++ struct mpq8646_priv, ++ alarm_poll_work); ++ ++ if (priv->client->irq) ++ return; /* SMBALERT# wired; polling not needed */ ++ ++ if (!priv->alarm_poll_interval_ms) ++ return; /* polling disabled; don't re-arm */ ++ ++ pmbus_check_and_notify_faults(priv->client); ++ ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++} ++ ++static int mpq8646_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ struct mpq8646_priv *priv; ++ u32 voltage_scale; ++ int ret; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ priv->client = client; ++ mutex_init(&priv->mps_lock); ++ memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); ++ info = &priv->info; ++ ++ info->identify = mpq8646_identify; ++ info->read_byte_data = mpq8646_read_byte_data; ++ info->read_word_data = mpq8646_read_word_data; ++ info->write_word_data = mpq8646_write_word_data; ++ dev->platform_data = &mpq8646_no_pec_pdata; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++ info->reg_desc = mpq8646_reg_desc; ++ info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc); ++#endif ++ ++ INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); ++ priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT; ++ ++ if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", ++ &voltage_scale)) { ++ if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX) ++ return -EINVAL; ++ ++ ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, ++ voltage_scale); ++ if (ret) ++ return ret; ++ } ++ ++ ret = pmbus_do_probe(client, info); ++ if (ret) ++ return ret; ++ ++ mpq8646_debugfs_register(priv); ++ ++ if (!client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++ { ++ struct nvmem_config cfg = { ++ .dev = &client->dev, ++ .name = dev_name(&client->dev), ++ .owner = THIS_MODULE, ++ .read_only = true, ++ .root_only = true, ++ .word_size = 1, ++ .stride = 1, ++ .size = MPQ8646_NVMEM_SIZE, ++ .reg_read = mpq8646_nvmem_read, ++ .priv = priv, ++ }; ++ struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg); ++ ++ if (IS_ERR(nv)) ++ dev_warn(&client->dev, ++ "nvmem snapshot register failed (%pe)\n", ++ nv); ++ } ++#endif ++ return 0; ++}; ++ ++static void mpq8646_remove(struct i2c_client *client) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ ++ mpq8646_debugfs_unregister(priv); ++ cancel_delayed_work_sync(&priv->alarm_poll_work); ++} ++ ++static struct i2c_driver mpq8646_driver = { ++ .driver = { ++ .name = "mpq8646", ++ .of_match_table = of_match_ptr(mpq8646_of_match), ++ }, ++ .probe = mpq8646_probe, ++ .remove = mpq8646_remove, ++ .id_table = mpq8646_id, ++}; ++ ++module_i2c_driver(mpq8646_driver); ++ ++MODULE_AUTHOR("Vincent Jardin "); ++MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h +--- a/drivers/hwmon/pmbus/pmbus.h ++++ b/drivers/hwmon/pmbus/pmbus.h +@@ -421,10 +421,12 @@ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; + enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; + + /* PMBus revision identifiers */ +-#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ +-#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ +-#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ +-#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ ++#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ ++#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ ++#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_131 0x44 /* PMBus revision 1.3.1 */ ++#define PMBUS_REV_14 0x55 /* PMBus revision 1.4 */ + + /* Operation type flags for pmbus_update_ts */ + #define PMBUS_OP_WRITE BIT(0) +@@ -489,6 +491,16 @@ struct pmbus_driver_info { + int access_delay; /* in microseconds */ + int write_delay; /* in microseconds */ + int page_change_delay; /* in microseconds */ ++ ++ /* ++ * Some chips do not support the PMBUS_REVISION command. ++ * Drivers for such chips can report the supported PMBus revision here. ++ * ++ * Drivers must set have_pmbus_revision to true and provide the ++ * supported PMBus version in pmbus_revision. ++ */ ++ bool have_pmbus_revision; /* true if pmbus_revision is valid */ ++ u8 pmbus_revision; /* PMBus revision */ + }; + + /* Regulator ops */ +@@ -550,6 +562,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); + void pmbus_wait(struct i2c_client *client); + void pmbus_update_ts(struct i2c_client *client, int op); + int pmbus_set_page(struct i2c_client *client, int page, int phase); ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf); + int pmbus_read_word_data(struct i2c_client *client, int page, int phase, + u8 reg); + int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, +@@ -561,6 +574,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, + int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + u8 mask, u8 value); + void pmbus_clear_faults(struct i2c_client *client); ++void pmbus_check_and_notify_faults(struct i2c_client *client); + bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); + bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); + int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -96,7 +96,8 @@ struct pmbus_data { + + u32 flags; /* from platform data */ + +- u8 revision; /* The PMBus revision the device is compliant with */ ++ bool have_pmbus_revision; ++ u8 revision; /* The PMBus revision the device is compliant with */ + + int exponent[PMBUS_PAGES]; + /* linear mode: exponent for output voltages */ +@@ -181,7 +182,12 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); + void pmbus_wait(struct i2c_client *client) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); ++ s64 delay; ++ ++ if (!data) ++ return; ++ ++ delay = ktime_us_delta(data->next_access_backoff, ktime_get()); + + if (delay > 0) + fsleep(delay); +@@ -192,8 +198,14 @@ EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); + void pmbus_update_ts(struct i2c_client *client, int op) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- const struct pmbus_driver_info *info = data->info; +- int delay = info->access_delay; ++ const struct pmbus_driver_info *info; ++ int delay; ++ ++ if (!data) ++ return; ++ ++ info = data->info; ++ delay = info->access_delay; + + if (op & PMBUS_OP_WRITE) + delay = max(delay, info->write_delay); +@@ -517,6 +529,81 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + } + EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS"); + ++/** ++ * pmbus_read_smbus_i2c_block_data() - Read SMBus/I2C block data ++ * @client: Handle to slave device ++ * @reg: Byte interpreted by slave ++ * @data_buf: Byte array into which data will be read ++ * Return: Negative errno or number of bytes read ++ * ++ * PMBus internal function to read a SMBus block from a PMBus chip. ++ * ++ * PMBus chips report various properties using SMBus block read operations. ++ * However, not all I2C controllers support this operation. ++ * ++ * Execute SMBus block read if supported. If not supported, but SMBus I2C block ++ * read is supported, use it instead. Note that at most 31 data bytes can be ++ * read from the device if i2c_smbus_read_i2c_block_data() is used to read the ++ * data. This is a SMBUs protocol limit which can not be avoided. ++ * ++ * Return -EOPNOTSUPP if neither I2C_FUNC_SMBUS_READ_BLOCK_DATA nor ++ * I2C_FUNC_SMBUS_READ_I2C_BLOCK is supported. ++ * ++ * Callers must hold pmbus_lock or execute calls from the probe function. ++ */ ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf) ++{ ++ u8 buf[I2C_SMBUS_BLOCK_MAX]; ++ int blen, len, ret; ++ ++ if (i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { ++ pmbus_wait(client); ++ ret = i2c_smbus_read_block_data(client, reg, data_buf); ++ pmbus_update_ts(client, 0); ++ return ret; ++ } ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { ++ dev_err_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_I2C_BLOCK\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ /* ++ * If the returned data is valid SMBus block data, the first byte ++ * must be the data length. ++ * ++ * i2c_smbus_read_i2c_block_data() may return an error if the chip ++ * sends NACK before the number of requested bytes is received. ++ * Handle this by reading the data length first, then reading the ++ * entire message up to I2C_SMBUS_BLOCK_MAX bytes. This ensures ++ * that requested number of bytes never exceeds the number of ++ * bytes sent by the chip. ++ */ ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ ++ len = buf[0]; ++ if (len == 0) ++ return 0; ++ blen = len; ++ if (len >= I2C_SMBUS_BLOCK_MAX) ++ len = I2C_SMBUS_BLOCK_MAX - 1; ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, len + 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ if (buf[0] != blen) ++ return -EIO; ++ memcpy(data_buf, buf + 1, len); ++ return len; ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_read_smbus_i2c_block_data, "PMBUS"); ++ + static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + char *data_buf) + { +@@ -526,11 +613,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + if (rv < 0) + return rv; + +- pmbus_wait(client); +- rv = i2c_smbus_read_block_data(client, reg, data_buf); +- pmbus_update_ts(client, 0); +- +- return rv; ++ return pmbus_read_smbus_i2c_block_data(client, reg, data_buf); + } + + static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, +@@ -2849,9 +2932,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, + if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) + pmbus_init_wp(client, data); + +- ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); +- if (ret >= 0) +- data->revision = ret; ++ if (info->have_pmbus_revision) { ++ data->have_pmbus_revision = true; ++ data->revision = info->pmbus_revision; ++ } else { ++ ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); ++ if (ret >= 0) { ++ data->have_pmbus_revision = true; ++ data->revision = ret; ++ } ++ } + + if (data->info->pages) + pmbus_clear_faults(client); +@@ -3455,10 +3545,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, + return ret; + } + +-static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++void pmbus_check_and_notify_faults(struct i2c_client *client) + { +- struct pmbus_data *data = pdata; +- struct i2c_client *client = to_i2c_client(data->dev); ++ struct pmbus_data *data = i2c_get_clientdata(client); + int i, status, event; + + guard(pmbus_lock)(client); +@@ -3471,6 +3560,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) + } + + pmbus_clear_faults(client); ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS"); ++ ++static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++{ ++ struct pmbus_data *data = pdata; ++ struct i2c_client *client = to_i2c_client(data->dev); ++ ++ pmbus_check_and_notify_faults(client); + + return IRQ_HANDLED; + } +@@ -3514,10 +3612,8 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data) + /* Register notifiers */ + err = devm_request_threaded_irq(dev, client->irq, NULL, pmbus_fault_handler, + IRQF_ONESHOT, "pmbus-irq", data); +- if (err) { +- dev_err(dev, "failed to request an irq %d\n", err); ++ if (err) + return err; +- } + + return 0; + } +@@ -3543,6 +3639,17 @@ static int pmbus_debugfs_get(void *data, u64 *val) + DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, + "0x%02llx\n"); + ++static int pmbus_debugfs_get_revision(void *data, u64 *val) ++{ ++ struct pmbus_data *pdata = data; ++ ++ *val = pdata->revision; ++ ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_revision_ops, pmbus_debugfs_get_revision, NULL, ++ "0x%02llx\n"); ++ + static int pmbus_debugfs_get_status(void *data, u64 *val) + { + struct pmbus_debugfs_entry *entry = data; +@@ -3698,14 +3805,9 @@ static void pmbus_init_debugfs(struct i2c_client *client, + &entries[idx++], + &pmbus_debugfs_ops); + } +- if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) { +- entries[idx].client = client; +- entries[idx].page = 0; +- entries[idx].reg = PMBUS_REVISION; +- debugfs_create_file("pmbus_revision", 0444, debugfs, +- &entries[idx++], +- &pmbus_debugfs_ops); +- } ++ if (data->have_pmbus_revision) ++ debugfs_create_file("pmbus_revision", 0444, debugfs, data, ++ &pmbus_debugfs_revision_ops); + + for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) { + const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i]; +diff --git a/drivers/hwmon/pmbus/sq24860.c b/drivers/hwmon/pmbus/sq24860.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/sq24860.c +@@ -0,0 +1,430 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Author: Ziming Zhu ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define SQ24860_IIN_CAL_GAIN 0x38 ++#define SQ24860_READ_VAUX 0xd0 ++#define SQ24860_READ_VIN_MIN 0xd1 ++#define SQ24860_READ_VIN_PEAK 0xd2 ++#define SQ24860_READ_IIN_PEAK 0xd4 ++#define SQ24860_READ_PIN_PEAK 0xd5 ++#define SQ24860_READ_TEMP_AVG 0xd6 ++#define SQ24860_READ_TEMP_PEAK 0xd7 ++#define SQ24860_READ_VOUT_MIN 0xda ++#define SQ24860_READ_VIN_AVG 0xdc ++#define SQ24860_READ_VOUT_AVG 0xdd ++#define SQ24860_READ_IIN_AVG 0xde ++#define SQ24860_READ_PIN_AVG 0xdf ++#define SQ24860_VIREF 0xe0 ++#define SQ24860_PK_MIN_AVG 0xea ++#define PK_MIN_AVG_RST_PEAK BIT(7) ++#define PK_MIN_AVG_RST_AVG BIT(6) ++#define PK_MIN_AVG_RST_MIN BIT(5) ++#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0) ++#define SQ24860_MFR_WRITE_PROTECT 0xf8 ++#define SQ24860_UNLOCKED BIT(7) ++ ++#define SQ24860_8B_SHIFT 2 ++#define SQ24860_IIN_OCF_NUM 1000000 ++#define SQ24860_IIN_OCF_DIV 129278 ++#define SQ24860_IIN_OCF_OFF 165 ++ ++#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ ++ PK_MIN_AVG_RST_AVG | \ ++ PK_MIN_AVG_RST_MIN) ++#define SQ24860_MAX_SAMPLES BIT(FIELD_MAX(PK_MIN_AVG_AVG_CNT)) ++/* ++ * Arbitrary default Rimon value: 1.6kOhm ++ */ ++#define SQ24860_DEFAULT_RIMON 1600000000 ++#define SQ24860_GIMON 18180 ++ ++#define SQ24860_VAUX_DIV 20 ++ ++static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon) ++{ ++ u64 temp = 6400ULL * 1000000000ULL * 1000ULL; ++ u64 denom; ++ u64 word; ++ ++ if (!rimon) ++ return -EINVAL; ++ ++ denom = (u64)rimon * SQ24860_GIMON; ++ word = div64_u64(temp, denom); ++ if (!word || word > U16_MAX) ++ return -EINVAL; ++ ++ return i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN, ++ (u16)word); ++} ++ ++static int sq24860_mfr_write_protect_set(struct i2c_client *client, ++ u8 protect) ++{ ++ u8 val; ++ ++ switch (protect) { ++ case 0: ++ val = 0xa2; ++ break; ++ case PB_WP_ALL: ++ val = 0x0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT, ++ val); ++} ++ ++static int sq24860_mfr_write_protect_get(struct i2c_client *client) ++{ ++ int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT); ++ ++ if (ret < 0) ++ return ret; ++ ++ return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL; ++} ++ ++static int sq24860_read_word_data(struct i2c_client *client, ++ int page, int phase, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VMON: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VAUX); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV); ++ break; ++ ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ /* ++ * These registers provide an 8 bits value instead of a ++ * 10bits one. Just shifting twice the register value is ++ * enough to make the sensor type conversion work, even ++ * if the datasheet provides different m, b and R for ++ * those. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ break; ++ ret <<= SQ24860_8B_SHIFT; ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ /* ++ * VIREF directly sets the over-current limit at which the eFuse ++ * will turn the FET off and trigger a fault. Expose it through ++ * this generic property instead of a manufacturer specific one. ++ */ ++ ret = pmbus_read_byte_data(client, page, SQ24860_VIREF); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM, ++ SQ24860_IIN_OCF_DIV); ++ ret += SQ24860_IIN_OCF_OFF; ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG); ++ if (ret < 0) ++ break; ++ ret = BIT(FIELD_GET(PK_MIN_AVG_AVG_CNT, ret)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ ret = 0; ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_word_data(struct i2c_client *client, ++ int page, int reg, u16 value) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ value = max_t(s16, (s16)value, 0); ++ value >>= SQ24860_8B_SHIFT; ++ value = clamp_val(value, 0, 0xff); ++ ret = pmbus_write_word_data(client, page, reg, value); ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ value = max_t(s16, (s16)value, SQ24860_IIN_OCF_OFF); ++ value -= SQ24860_IIN_OCF_OFF; ++ value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV, ++ SQ24860_IIN_OCF_NUM); ++ value = clamp_val(value, 0, 0x3f); ++ ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value); ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ value = clamp_val(value, 1, SQ24860_MAX_SAMPLES); ++ value = ilog2(value); ++ ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_AVG_CNT, ++ FIELD_PREP(PK_MIN_AVG_AVG_CNT, value)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ /* ++ * SQ24860 has history resets based on MIN/AVG/PEAK instead of per ++ * sensor type. Exposing this quirk in hwmon is not desirable so ++ * reset MIN, AVG and PEAK together. Even is there effectively only ++ * one reset, which resets everything, expose the 5 entries so ++ * userspace is not required map a sensor type to another to trigger ++ * a reset ++ */ ++ ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_RST_MASK, ++ PK_MIN_AVG_RST_MASK); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_read_byte_data(struct i2c_client *client, ++ int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_get(client); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_byte_data(struct i2c_client *client, ++ int page, int reg, u8 byte) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_set(client, byte); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++static const struct regulator_desc sq24860_reg_desc[] = { ++ PMBUS_REGULATOR_ONE_NODE("vout"), ++}; ++#endif ++ ++static const struct pmbus_driver_info sq24860_base_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .m[PSC_VOLTAGE_IN] = 64, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 0, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 64, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 0, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ /* ++ * Current and power measurements depend on the calibration gain ++ * programmed from the board-specific IMON resistor value. ++ */ ++ .format[PSC_CURRENT_IN] = direct, ++ .m[PSC_CURRENT_IN] = 16, ++ .b[PSC_CURRENT_IN] = 0, ++ .R[PSC_CURRENT_IN] = 0, ++ .format[PSC_POWER] = direct, ++ .m[PSC_POWER] = 2, ++ .b[PSC_POWER] = 0, ++ .R[PSC_POWER] = 0, ++ .func[0] = PMBUS_HAVE_VIN | ++ PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_VMON | ++ PMBUS_HAVE_IIN | ++ PMBUS_HAVE_PIN | ++ PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_SAMPLES, ++ .read_word_data = sq24860_read_word_data, ++ .write_word_data = sq24860_write_word_data, ++ .read_byte_data = sq24860_read_byte_data, ++ .write_byte_data = sq24860_write_byte_data, ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++ .reg_desc = sq24860_reg_desc, ++ .num_regulators = ARRAY_SIZE(sq24860_reg_desc), ++#endif ++}; ++ ++static const struct i2c_device_id sq24860_i2c_id[] = { ++ { "sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id); ++ ++static const struct of_device_id sq24860_of_match[] = { ++ { .compatible = "silergy,sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, sq24860_of_match); ++ ++static int sq24860_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ u32 rimon; ++ int ret; ++ ++ if (device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon)) ++ rimon = SQ24860_DEFAULT_RIMON; ++ ret = sq24860_write_iin_cal_gain(client, rimon); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to set gain\n"); ++ info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static struct i2c_driver sq24860_driver = { ++ .driver = { ++ .name = "sq24860", ++ .of_match_table = sq24860_of_match, ++ }, ++ .probe = sq24860_probe, ++ .id_table = sq24860_i2c_id, ++}; ++module_i2c_driver(sq24860_driver); ++ ++MODULE_AUTHOR("Ziming Zhu "); ++MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/vt7505.c b/drivers/hwmon/pmbus/vt7505.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/vt7505.c +@@ -0,0 +1,287 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and ++ * Volterra VT7505 PMBus controllers. ++ * ++ * Copyright 2026 Hewlett Packard Enterprise Development LP ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define VT7505_MFR_CONFIG 0xd0 ++#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14) ++ ++#define VT7505_MFR_PEAK_VIN 0xd1 ++#define VT7505_MFR_PEAK_IOUT 0xd2 ++#define VT7505_MFR_PEAK_PIN 0xd3 ++#define VT7505_MFR_PEAK_TEMP 0xd4 ++#define VT7505_MFR_CLEAR_PEAKS 0xd5 ++#define VT7505_MFR_PEAK_VOUT 0xfd ++ ++#define VT7505_RLOAD_DEFAULT 4750 ++/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */ ++#define VT7505_RLOAD_MAX 283383959U ++ ++struct vt7505_chip_data { ++ int temp_m; ++ int temp_b; ++ bool has_ocp_filter; ++}; ++ ++static const struct vt7505_chip_data max16545_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++}; ++ ++static const struct vt7505_chip_data max16550_data = { ++ .temp_m = 199, ++ .temp_b = 7046, ++ .has_ocp_filter = true, ++}; ++ ++static const struct vt7505_chip_data vt7505_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++ .has_ocp_filter = true, ++}; ++ ++static int vt7505_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VIN); ++ case PMBUS_VIRT_READ_IOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_IOUT); ++ case PMBUS_VIRT_READ_PIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_PIN); ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_TEMP); ++ case PMBUS_VIRT_READ_VOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VOUT); ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++} ++ ++static int vt7505_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ switch (reg) { ++ /* ++ * A single reset command clears all peak values. CLEAR_PEAKS is a ++ * send-byte command; the device NAKs a word or byte-data write to it. ++ */ ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return pmbus_write_byte(client, page, ++ VT7505_MFR_CLEAR_PEAKS); ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * None of these controllers implement the standard PMBus WRITE_PROTECT ++ * (0x10) register, so tell the core not to access it. ++ */ ++static struct pmbus_platform_data vt7505_pdata = { ++ .flags = PMBUS_NO_WRITE_PROTECT, ++}; ++ ++static int vt7505_set_ocp_filter(struct i2c_client *client) ++{ ++ u32 ocp_us; ++ u8 field; ++ int ret; ++ u16 word; ++ ++ if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us", ++ &ocp_us)) ++ return 0; ++ ++ switch (ocp_us) { ++ case 0: ++ field = 0; ++ break; ++ case 1: ++ field = 1; ++ break; ++ case 2: ++ field = 2; ++ break; ++ case 10: ++ field = 3; ++ break; ++ default: ++ return dev_err_probe(&client->dev, -EINVAL, ++ "invalid adi,ocp-severe-filter-us value %u\n", ++ ocp_us); ++ } ++ ++ ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to read MFR_CONFIG\n"); ++ ++ word = ret & ~VT7505_CFG_OCP_S_FILT_MASK; ++ word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field); ++ ++ ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to write MFR_CONFIG\n"); ++ ++ return 0; ++} ++ ++static void vt7505_set_m(int *m, u32 rload) ++{ ++ u64 val = (u64)*m * rload; ++ ++ /* rload is range-checked in probe, so the result fits in int. */ ++ *m = DIV_ROUND_CLOSEST_ULL(val, 1000); ++} ++ ++static int vt7505_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ const struct vt7505_chip_data *chip; ++ struct pmbus_driver_info *info; ++ u32 rload; ++ int ret; ++ ++ chip = i2c_get_match_data(client); ++ if (!chip) ++ return -ENODEV; ++ ++ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ dev->platform_data = &vt7505_pdata; ++ ++ /* ++ * The m coefficient used in the direct-format current and power ++ * calculations depends on RLOAD, the external current-report resistor ++ * connected between the ILOAD pin and ground. Use the default value if ++ * none is specified. ++ */ ++ if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload)) ++ rload = VT7505_RLOAD_DEFAULT; ++ ++ if (!rload || rload > VT7505_RLOAD_MAX) ++ return dev_err_probe(dev, -EINVAL, ++ "adi,rload-ohms must be 1-%u\n", ++ VT7505_RLOAD_MAX); ++ ++ info->pages = 1; ++ info->read_word_data = vt7505_read_word_data; ++ info->write_word_data = vt7505_write_word_data; ++ ++ info->format[PSC_VOLTAGE_IN] = direct; ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->format[PSC_CURRENT_IN] = direct; ++ info->format[PSC_CURRENT_OUT] = direct; ++ info->format[PSC_POWER] = direct; ++ info->format[PSC_TEMPERATURE] = direct; ++ ++ /* ++ * Direct data format coefficients from the device datasheet ("PMBus ++ * Equation Parameters"). The current and power m coefficients scale ++ * with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving ++ * m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The ++ * temperature coefficients are chip specific (see the chip data). ++ */ ++ info->m[PSC_VOLTAGE_IN] = 7578; ++ info->R[PSC_VOLTAGE_IN] = -2; ++ info->m[PSC_VOLTAGE_OUT] = 7578; ++ info->R[PSC_VOLTAGE_OUT] = -2; ++ info->m[PSC_CURRENT_IN] = 3824; ++ info->b[PSC_CURRENT_IN] = -4300; ++ info->R[PSC_CURRENT_IN] = -3; ++ info->m[PSC_CURRENT_OUT] = 3824; ++ info->b[PSC_CURRENT_OUT] = -4300; ++ info->R[PSC_CURRENT_OUT] = -3; ++ info->m[PSC_POWER] = 895; ++ info->b[PSC_POWER] = -9100; ++ info->R[PSC_POWER] = -2; ++ ++ vt7505_set_m(&info->m[PSC_CURRENT_IN], rload); ++ vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload); ++ vt7505_set_m(&info->m[PSC_POWER], rload); ++ ++ info->m[PSC_TEMPERATURE] = chip->temp_m; ++ info->b[PSC_TEMPERATURE] = chip->temp_b; ++ info->R[PSC_TEMPERATURE] = -2; ++ ++ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_IIN | PMBUS_HAVE_PIN; ++ ++ /* ++ * The severe OCP deglitch filter is programmable on the MAX16550 and ++ * the VT7505, but fixed on the MAX16545. ++ */ ++ if (chip->has_ocp_filter) { ++ ret = vt7505_set_ocp_filter(client); ++ if (ret) ++ return ret; ++ } ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id vt7505_id[] = { ++ { .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data }, ++ { .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data }, ++ { .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, vt7505_id); ++ ++static const struct of_device_id vt7505_of_match[] = { ++ { .compatible = "adi,max16545", .data = &max16545_data }, ++ { .compatible = "adi,max16550", .data = &max16550_data }, ++ { .compatible = "adi,vt7505", .data = &vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, vt7505_of_match); ++ ++static struct i2c_driver vt7505_driver = { ++ .driver = { ++ .name = "vt7505", ++ .of_match_table = vt7505_of_match, ++ }, ++ .probe = vt7505_probe, ++ .id_table = vt7505_id, ++}; ++module_i2c_driver(vt7505_driver); ++ ++MODULE_AUTHOR("Georgi Vlaev "); ++MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c +--- a/drivers/hwmon/pmbus/xdpe1a2g7b.c ++++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c +@@ -57,6 +57,13 @@ static int xdpe1a2g7b_identify(struct i2c_client *client, + return 0; + } + ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++static const struct regulator_desc xdpe1a2g7b_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++ PMBUS_REGULATOR("vout", 1), ++}; ++#endif ++ + static struct pmbus_driver_info xdpe1a2g7b_info = { + .pages = XDPE1A2G7B_PAGE_NUM, + .identify = xdpe1a2g7b_identify, +@@ -72,6 +79,10 @@ static struct pmbus_driver_info xdpe1a2g7b_info = { + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++ .num_regulators = XDPE1A2G7B_PAGE_NUM, ++ .reg_desc = xdpe1a2g7b_reg_desc, ++#endif + }; + + static int xdpe1a2g7b_probe(struct i2c_client *client) +diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c +--- a/drivers/hwmon/pwm-fan.c ++++ b/drivers/hwmon/pwm-fan.c +@@ -634,12 +634,8 @@ static int pwm_fan_probe(struct platform_device *pdev) + if (tach->irq > 0) { + ret = devm_request_irq(dev, tach->irq, pulse_handler, + IRQF_NO_THREAD, pdev->name, tach); +- if (ret) { +- dev_err(dev, +- "Failed to request interrupt: %d\n", +- ret); ++ if (ret) + return ret; +- } + } + + if (!ctx->pulses_per_revolution[i]) { +diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c +--- a/drivers/hwmon/sht15.c ++++ b/drivers/hwmon/sht15.c +@@ -981,10 +981,8 @@ static int sht15_probe(struct platform_device *pdev) + IRQF_TRIGGER_FALLING, + "sht15 data", + data); +- if (ret) { +- dev_err(&pdev->dev, "failed to get irq for data line\n"); ++ if (ret) + goto err_release_reg; +- } + disable_irq_nosync(gpiod_to_irq(data->data)); + ret = sht15_connection_reset(data); + if (ret) +diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c +--- a/drivers/hwmon/sht3x.c ++++ b/drivers/hwmon/sht3x.c +@@ -62,7 +62,7 @@ static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 }; + #define SHT3X_MAX_HUMIDITY 100000 + + enum sht3x_chips { +- sht3x, ++ sht3x = 1, + sts3x, + }; + +@@ -940,8 +940,19 @@ static const struct i2c_device_id sht3x_ids[] = { + + MODULE_DEVICE_TABLE(i2c, sht3x_ids); + ++static const struct of_device_id sht3x_of_match[] = { ++ { .compatible = "sensirion,sht30", .data = (void *)(uintptr_t)sht3x }, ++ { .compatible = "sensirion,sts30", .data = (void *)(uintptr_t)sts3x }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(of, sht3x_of_match); ++ + static struct i2c_driver sht3x_i2c_driver = { +- .driver.name = "sht3x", ++ .driver = { ++ .name = "sht3x", ++ .of_match_table = sht3x_of_match, ++ }, + .probe = sht3x_probe, + .id_table = sht3x_ids, + }; +diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c +--- a/drivers/hwmon/tmp102.c ++++ b/drivers/hwmon/tmp102.c +@@ -396,6 +396,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume + + static const struct i2c_device_id tmp102_id[] = { + { .name = "tmp102" }, ++ { .name = "tmp110" }, ++ { .name = "tmp113" }, + { } + }; + MODULE_DEVICE_TABLE(i2c, tmp102_id); +diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c +--- a/drivers/hwmon/tmp401.c ++++ b/drivers/hwmon/tmp401.c +@@ -695,7 +695,7 @@ static int tmp401_probe(struct i2c_client *client) + info->type = hwmon_chip; + info->config = data->chip_channel_config; + +- data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; ++ data->chip_channel_config[0] = HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL; + + info = &data->temp_info; + info->type = hwmon_temp; +diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/tvs-mpfs.c +@@ -0,0 +1,390 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Author: Lars Randers ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MPFS_TVS_CTRL 0x08 ++#define MPFS_TVS_OUTPUT0 0x24 ++#define MPFS_TVS_OUTPUT1 0x28 ++ ++#define MPFS_TVS_CTRL_TEMP_VALID BIT(19) ++#define MPFS_TVS_CTRL_V2P5_VALID BIT(18) ++#define MPFS_TVS_CTRL_V1P8_VALID BIT(17) ++#define MPFS_TVS_CTRL_V1P05_VALID BIT(16) ++ ++#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3) ++#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2) ++#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1) ++#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0) ++#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0) ++ ++/* ++ * For all of these the value in millivolts is stored in 16 bits, with an upper ++ * sign bit and a lower 3 bits of decimal. These masks discard the sign bit and ++ * decimal places, because if Linux is running these voltages cannot be negative ++ * and so avoid having to convert to two's complement. ++ */ ++#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19) ++#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3) ++#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3) ++ ++/* ++ * The register map claims that the temperature is stored in bits 31:16, but ++ * application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor" ++ * says that 31 is reserved. Temperature is in kelvin, so what's probably a ++ * sign bit has no value anyway. ++ */ ++#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16) ++ ++#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8) ++#define MPFS_TVS_INTERVAL_OFFSET 8 ++/* The interval register is in increments of 32 us */ ++#define MPFS_TVS_INTERVAL_SCALE 32 ++/* with 254 usable increments of 32 us available, 8 ms is the integer limit */ ++#define MPFS_TVS_INTERVAL_MAX_MS 8 ++ ++/* 273.1875 in 11.4 fixed-point notation */ ++#define MPFS_TVS_K_TO_C 0x1113 ++ ++enum mpfs_tvs_sensors { ++ SENSOR_V1P05 = 0, ++ SENSOR_V1P8, ++ SENSOR_V2P5, ++}; ++ ++static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" }; ++ ++struct mpfs_tvs { ++ struct regmap *regmap; ++}; ++ ++static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr, ++ int channel, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_in_input && attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V2P5_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp); ++ break; ++ case SENSOR_V1P8: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P8_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp); ++ break; ++ case SENSOR_V1P05: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P05_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr, ++ int channel, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V2P5_ENABLE, tmp); ++ break; ++ case SENSOR_V1P8: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P8_ENABLE, tmp); ++ break; ++ case SENSOR_V1P05: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P05_ENABLE, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_temp_input && attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ if (attr == hwmon_temp_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control); ++ return 0; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_TEMP_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp); ++ *val -= MPFS_TVS_K_TO_C; ++ *val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */ ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_TEMP_ENABLE, tmp); ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp); ++ *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp); ++ *val *= MPFS_TVS_INTERVAL_SCALE; ++ *val = roundup(*val, 1000); ++ *val /= 1000; ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ long temp = val; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS); ++ ++ temp *= 1000; ++ temp /= MPFS_TVS_INTERVAL_SCALE; ++ ++ temp <<= MPFS_TVS_INTERVAL_OFFSET; ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_INTERVAL_MASK, temp); ++ ++ return 0; ++} ++ ++static umode_t mpfs_tvs_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ if (type == hwmon_chip && attr == hwmon_chip_update_interval) ++ return 0644; ++ ++ if (type == hwmon_temp) { ++ switch (attr) { ++ case hwmon_temp_enable: ++ return 0644; ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ if (type == hwmon_in) { ++ switch (attr) { ++ case hwmon_in_enable: ++ return 0644; ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_read(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_read(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_read(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_write(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_write(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_write(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_read_labels(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, ++ const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ *str = "Die Temp"; ++ return 0; ++ case hwmon_in: ++ *str = mpfs_tvs_voltage_labels[channel]; ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static const struct hwmon_ops mpfs_tvs_ops = { ++ .is_visible = mpfs_tvs_is_visible, ++ .read_string = mpfs_tvs_read_labels, ++ .read = mpfs_tvs_read, ++ .write = mpfs_tvs_write, ++}; ++ ++static const struct hwmon_channel_info *mpfs_tvs_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_ENABLE), ++ HWMON_CHANNEL_INFO(in, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE), ++ NULL ++}; ++ ++static const struct hwmon_chip_info mpfs_tvs_chip_info = { ++ .ops = &mpfs_tvs_ops, ++ .info = mpfs_tvs_info, ++}; ++ ++static int mpfs_tvs_probe(struct platform_device *pdev) ++{ ++ struct device *hwmon_dev; ++ struct mpfs_tvs *data; ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->regmap = device_node_to_regmap(pdev->dev.parent->of_node); ++ if (IS_ERR(data->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap), ++ "Failed to find syscon regmap\n"); ++ ++ /* ++ * It's an MMIO regmap with no resources, there's nothing that can fail ++ * and return an error ++ */ ++ regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs", ++ data, ++ &mpfs_tvs_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev), ++ "hwmon device registration failed.\n"); ++ ++ return 0; ++} ++ ++static struct platform_driver mpfs_tvs_driver = { ++ .probe = mpfs_tvs_probe, ++ .driver = { ++ .name = "mpfs-tvs", ++ }, ++}; ++module_platform_driver(mpfs_tvs_driver); ++ ++MODULE_AUTHOR("Lars Randers "); ++MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c +--- a/drivers/hwmon/xgene-hwmon.c ++++ b/drivers/hwmon/xgene-hwmon.c +@@ -133,10 +133,6 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) + init_completion(&ctx->rd_complete); + ctx->resp_pending = true; + +- /* Write signature for subspace */ +- WRITE_ONCE(generic_comm_base->signature, +- cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx)); +- + /* Write to the shared command region */ + WRITE_ONCE(generic_comm_base->command, + cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR)); +diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c +--- a/drivers/hwmon/yogafan.c ++++ b/drivers/hwmon/yogafan.c +@@ -53,6 +53,7 @@ struct yoga_fan_data { + }; + + /* Specific configurations mapped via DMI */ ++ + static const struct yogafan_config yoga_8bit_fans_cfg = { + .multiplier = 100, + .fan_count = 1, +@@ -71,6 +72,24 @@ static const struct yogafan_config legion_16bit_dual_cfg = { + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } + }; + ++static const struct yogafan_config loq_15iax9_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config xiaoxin_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config yoga_pro_7_14iah10_cfg = { ++ .multiplier = 100, ++ .fan_count = 1, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL } ++}; ++ + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) + { + ktime_t now = ktime_get_boottime(); +@@ -170,6 +189,54 @@ static const struct hwmon_chip_info yoga_fan_chip_info = { + }; + + static const struct dmi_system_id yogafan_quirks[] = { ++ { ++ .ident = "Lenovo LOQ 15IAX9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), ++ }, ++ .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo XiaoXin Pro 13ARE 2020", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "XiaoXinPro-13ARE 2020"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo IdeaPad 3 15ALC6", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6"), ++ }, ++ .driver_data = (void *)&ideapad_8bit_fan0_cfg, ++ }, ++ { ++ .ident = "Lenovo Legion Pro 7 16AFR10H", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion Pro 7 16AFR10H"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga Pro 7 14IAH10", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga Pro 7 14IAH10"), ++ }, ++ .driver_data = (void *)&yoga_pro_7_14iah10_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga 7 16ARP8", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 7 16ARP8"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, + { + .ident = "Lenovo Yoga", + .matches = { +diff --git a/include/linux/hwmon-sysfs.h b/include/linux/hwmon-sysfs.h +--- a/include/linux/hwmon-sysfs.h ++++ b/include/linux/hwmon-sysfs.h +@@ -15,10 +15,10 @@ struct sensor_device_attribute{ + int index; + }; + #define to_sensor_dev_attr(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute, dev_attr) + +-#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ ++#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ + .index = _index } + + #define SENSOR_ATTR_RO(_name, _func, _index) \ +@@ -49,11 +49,11 @@ struct sensor_device_attribute_2 { + u8 nr; + }; + #define to_sensor_dev_attr_2(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute_2, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute_2, dev_attr) + +-#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ +- .index = _index, \ ++#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ ++ .index = _index, \ + .nr = _nr } + + #define SENSOR_ATTR_2_RO(_name, _func, _nr, _index) \ diff --git a/pkgbuilds/linux-omarchy-bore/0740-hwmon-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0740-hwmon-updates.patch.sig new file mode 100644 index 0000000..00d8812 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0740-hwmon-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0741-hwmon-fixes-1.patch b/pkgbuilds/linux-omarchy-bore/0741-hwmon-fixes-1.patch new file mode 100644 index 0000000..15533e0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0741-hwmon-fixes-1.patch @@ -0,0 +1,179 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -1481,9 +1481,9 @@ static int __init applesmc_init(void) + applesmc_fan_config[smcreg.fan_count] = 0; + applesmc_pwm_config[smcreg.fan_count] = 0; + +- applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); +- applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); +- applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); ++ applesmc_info_temp = kzalloc_obj(*applesmc_info_temp); ++ applesmc_info_fan = kzalloc_obj(*applesmc_info_fan); ++ applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm); + if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { + ret = -ENOMEM; + goto out_info; +@@ -1498,7 +1498,7 @@ static int __init applesmc_init(void) + applesmc_info_pwm->type = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + +- applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ applesmc_info_arr = kzalloc_objs(*applesmc_info_arr, 4); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; +@@ -1509,7 +1509,7 @@ static int __init applesmc_init(void) + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + +- applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ applesmc_chip = kzalloc_obj(*applesmc_chip); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; +@@ -1519,10 +1519,9 @@ static int __init applesmc_init(void) + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ +- fan_safe_attrs = kcalloc(smcreg.fan_count, +- sizeof(*fan_safe_attrs), GFP_KERNEL); +- fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, +- sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ fan_safe_attrs = kzalloc_objs(*fan_safe_attrs, smcreg.fan_count); ++ fan_safe_attr_list = kzalloc_objs(*fan_safe_attr_list, ++ smcreg.fan_count + 1); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -184,6 +184,7 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { ++ unsigned long flags; + int ret; + + /* +@@ -191,9 +192,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_bh(&priv->status_report_request_lock); ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); + reinit_completion(status_completion); +- spin_unlock_bh(&priv->status_report_request_lock); ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -421,15 +422,18 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); ++ unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- return 0; ++ goto unlock; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -443,7 +447,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- return 0; ++ goto unlock; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -455,7 +459,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- return 0; ++ goto unlock; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -474,7 +478,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- return 0; ++ goto unlock; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -484,7 +488,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- return 0; ++ goto unlock; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -503,7 +507,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- return 0; ++ goto unlock; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -512,6 +516,8 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + ++unlock: ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -318,6 +318,11 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + return 1; + } + ++static bool is_hwmon_device(struct device *dev) ++{ ++ return dev->class == &hwmon_class; ++} ++ + #if IS_REACHABLE(CONFIG_I2C) + + /* +@@ -338,7 +343,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + + static int hwmon_match_device(struct device *dev, const void *data) + { +- return dev->class == &hwmon_class; ++ return is_hwmon_device(dev); + } + + static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, +@@ -781,6 +786,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, + const char *template; + int base; + ++ if (WARN(!is_hwmon_device(dev), "%s is not a hardware monitoring device\n", ++ dev_name(dev))) ++ return -EINVAL; + if (type >= ARRAY_SIZE(__templates)) + return -EINVAL; + if (attr >= __templates_size[type]) diff --git a/pkgbuilds/linux-omarchy-bore/0741-hwmon-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-bore/0741-hwmon-fixes-1.patch.sig new file mode 100644 index 0000000..76f3485 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0741-hwmon-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0742-hwmon-fixes-2.patch b/pkgbuilds/linux-omarchy-bore/0742-hwmon-fixes-2.patch new file mode 100644 index 0000000..9552822 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0742-hwmon-fixes-2.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c +--- a/drivers/hwmon/k10temp.c ++++ b/drivers/hwmon/k10temp.c +@@ -523,7 +523,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) + } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { +- case 0x00 ... 0x2f: /* Zen5 Turin */ ++ case 0x00 ... 0x1f: /* Zen5 Turin */ + data->ccd_offset = 0x1F0; + k10temp_get_ccd_support(data, 16); + break; diff --git a/pkgbuilds/linux-omarchy-bore/0742-hwmon-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-bore/0742-hwmon-fixes-2.patch.sig new file mode 100644 index 0000000..dcf7ede Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0742-hwmon-fixes-2.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 deleted file mode 100644 index bd8dd7c..0000000 --- a/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch +++ /dev/null @@ -1,31 +0,0 @@ -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 deleted file mode 100644 index 6a23cb7..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0750-applesmc-cache-race.patch.sig and /dev/null 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 deleted file mode 100644 index 6eda26b..0000000 --- a/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index d4cd8fb..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/0751-applesmc-key-backlight-workqueue-leak.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch index bb6a9de..c6018e3 100644 --- a/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch +++ b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch @@ -364,78 +364,6 @@ diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/am 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 @@ -520,20 +448,6 @@ diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig 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 @@ -1795,19 +1709,6 @@ diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsm + */ +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 @@ -3763,41 +3664,6 @@ diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-w { 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 @@ -3832,30 +3698,6 @@ diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c /* 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 @@ -4164,43 +4006,6 @@ diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell 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 @@ -4251,35 +4056,6 @@ diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/del } 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 @@ -4415,21 +4191,6 @@ diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujits 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 @@ -4463,22 +4224,17 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c #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 = { +@@ -133,24 +142,41 @@ 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; + struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; + const struct hp_wmi_fan_profile_params *fan_profile; +}; + @@ -4487,41 +4243,30 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c +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[] = { + static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params *active_board_params; +@@ -181,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", "88F7", "88FD", "88FE", "88FF", "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", @@ -4530,33 +4275,21 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c "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 = { - { +@@ -217,6 +243,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, -- .driver_data = (void *)&omen_v1_legacy_thermal_params, -+ .driver_data = (void *)&omen_v1_legacy_board_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, - }, - { + .driver_data = (void *)&omen_v1_legacy_board_params, +@@ -225,6 +255,14 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, -- .driver_data = (void *)&omen_v1_legacy_thermal_params, -+ .driver_data = (void *)&omen_v1_legacy_board_params, -+ }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, + .driver_data = (void *)&omen_v1_board_params, @@ -4564,107 +4297,33 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c + { + .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, - }, - { + .driver_data = (void *)&omen_v1_board_params, +@@ -233,6 +271,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, -- .driver_data = (void *)&victus_s_thermal_params, -+ .driver_data = (void *)&victus_s_board_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, - }, - { + .driver_data = (void *)&victus_s_board_params, +@@ -289,6 +331,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, -- .driver_data = (void *)&omen_v1_no_ec_thermal_params, -+ .driver_data = (void *)&omen_v1_no_ec_board_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 { + .driver_data = (void *)&omen_v1_no_ec_board_params, +@@ -360,6 +406,7 @@ enum hp_wmi_commandtype { HPWMI_POSTCODEERROR_QUERY = 0x2a, HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, @@ -4672,7 +4331,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c }; struct victus_power_limits { -@@ -504,9 +575,9 @@ struct hp_wmi_hwmon_priv { +@@ -528,9 +575,9 @@ struct hp_wmi_hwmon_priv { struct mutex lock; /* protects mode, pwm */ u8 min_rpm; u8 max_rpm; @@ -4684,7 +4343,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c struct delayed_work keep_alive_dwork; }; -@@ -833,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) +@@ -857,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) return enabled; } @@ -4719,7 +4378,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -891,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) { @@ -4730,7 +4389,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c } 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) +@@ -1152,12 +1197,134 @@ static int camera_shutter_input_setup(void) return err; } @@ -4865,7 +4524,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c static struct attribute *hp_wmi_attrs[] = { &dev_attr_display.attr, -@@ -1142,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { +@@ -1166,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { &dev_attr_dock.attr, &dev_attr_tablet.attr, &dev_attr_postcode.attr, @@ -4873,7 +4532,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c NULL, }; ATTRIBUTE_GROUPS(hp_wmi); -@@ -1793,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) +@@ -1817,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) return is_victus_s_board; } @@ -4912,77 +4571,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -2436,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) switch (priv->mode) { case PWM_MODE_MAX: @@ -4991,7 +4580,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -2448,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_MANUAL: @@ -5011,7 +4600,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2481,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, { switch (type) { case hwmon_pwm: @@ -5027,7 +4616,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2510,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, priv = dev_get_drvdata(dev); switch (type) { case hwmon_fan: @@ -5038,7 +4627,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2520,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, return 0; case hwmon_pwm: if (attr == hwmon_pwm_input) { @@ -5051,7 +4640,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2550,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct hp_wmi_hwmon_priv *priv; @@ -5069,7 +4658,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2566,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, /* ensure PWM input is within valid fan speeds */ rpm = pwm_to_rpm(val, priv); rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); @@ -5081,7 +4670,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2574,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, priv->mode = PWM_MODE_MAX; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_MANUAL: @@ -5107,7 +4696,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2599,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, static const struct hwmon_channel_info * const info[] = { HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), @@ -5116,7 +4705,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c NULL }; -@@ -2608,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) +@@ -2640,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) struct victus_s_fan_table *fan_table; u8 min_rpm, max_rpm; u8 cpu_rpm, gpu_rpm, noise_db; @@ -5134,7 +4723,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -2686,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) if (min_rpm == U8_MAX || max_rpm == 0) return -EINVAL; @@ -5145,54 +4734,6 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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 @@ -6042,7 +5583,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c #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"); +@@ -78,26 +88,29 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #define WMBB_USB_CHARGE 0x10B #define WMBB_BATT_LIMIT 0x10C @@ -6084,8 +5625,8 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c -#define INIT_SPARSE_KEYMAP 0x80 static int battery_limit_use_wmbb; -+static bool kbd_backlight_available; - static struct led_classdev kbd_backlight; + static bool kbd_backlight_available; +@@ -105,16 +118,65 @@ static struct led_classdev kbd_backlight; static enum led_brightness get_kbd_backlight_level(struct device *dev); static const struct key_entry wmi_keymap[] = { @@ -6158,7 +5699,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c static int ggov(u32 arg0) { union acpi_object args[1]; -@@ -154,30 +217,97 @@ static int ggov(u32 arg0) +@@ -155,30 +217,97 @@ static int ggov(u32 arg0) return res; } @@ -6273,13 +5814,14 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } 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 +@@ -212,76 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u return (union acpi_object *)buffer.pointer; } -static void wmi_notify(union acpi_object *obj, void *context) -{ - long data = (long)context; +- unsigned int brightness; - - pr_debug("event guid %li\n", data); - if (!obj) @@ -6290,8 +5832,11 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c - struct key_entry *key; - - if (eventcode == 0x10000000) { -- led_classdev_notify_brightness_hw_changed( -- &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); +- if (kbd_backlight_available) { +- brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); +- led_classdev_notify_brightness_hw_changed(&kbd_backlight, +- brightness); +- } - } else { - key = sparse_keymap_entry_from_scancode( - wmi_input_dev, eventcode); @@ -6346,7 +5891,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c int ret; ret = kstrtoul(buffer, 10, &value); -@@ -285,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, +@@ -290,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, if (value >= 3) return -EINVAL; @@ -6361,7 +5906,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c return count; } -@@ -296,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, +@@ -301,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr, char *buffer) { @@ -6390,7 +5935,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } static ssize_t usb_charge_store(struct device *dev, -@@ -361,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, +@@ -366,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, const char *buffer, size_t count) { bool value; @@ -6441,7 +5986,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } static ssize_t fn_lock_store(struct device *dev, -@@ -403,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, +@@ -408,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, const char *buffer, size_t count) { bool value; @@ -6491,7 +6036,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } 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, +@@ -458,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, if (value == 100 || value == 80) { union acpi_object *r; @@ -6516,7 +6061,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c return count; } -@@ -471,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, +@@ -476,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, struct device_attribute *attr, char *buf) { @@ -6527,7 +6072,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c 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, +@@ -490,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, } status = r->buffer.pointer[0x10]; @@ -6552,7 +6097,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c if (status != 80 && status != 100) status = 0; -@@ -563,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { +@@ -568,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { static void tpad_led_set(struct led_classdev *cdev, enum led_brightness brightness) { @@ -6564,7 +6109,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } static enum led_brightness tpad_led_get(struct led_classdev *cdev) -@@ -579,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); +@@ -584,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); static void kbd_backlight_set(struct led_classdev *cdev, enum led_brightness brightness) { @@ -6644,7 +6189,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } 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) +@@ -634,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); @@ -6822,42 +6367,36 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c 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) +@@ -865,10 +1036,6 @@ 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 = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); +- if (ret) +- goto out_platform_device; +- + /* LEDs are optional */ + ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); + if (ret < 0) +@@ -878,7 +1045,14 @@ static int acpi_probe(struct platform_device *pdev) + + devm_led_classdev_register(&pdev->dev, &tpad_led); + +- wmi_input_setup(); + ret = lg_laptop_input_init(&pdev->dev, device->handle); + if (ret < 0) + goto out_platform_device; + - ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); - if (ret) - goto out_platform_device; - -- /* LEDs are optional */ -- led_classdev_register(&pf_device->dev, &kbd_backlight); -- led_classdev_register(&pf_device->dev, &tpad_led); -- -- wmi_input_setup(); ++ ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); ++ if (ret) ++ goto out_platform_device; ++ battery_hook_register(&battery_hook); return 0; -@@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) - { +@@ -895,7 +1069,6 @@ 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); diff --git a/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch.sig b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch.sig index 03807cf..58371ed 100644 Binary files a/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch.sig and b/pkgbuilds/linux-omarchy-bore/0800-platform-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0802-platform-fixes.patch b/pkgbuilds/linux-omarchy-bore/0802-platform-fixes.patch new file mode 100644 index 0000000..b7d82cc --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0802-platform-fixes.patch @@ -0,0 +1,240 @@ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -17,7 +17,6 @@ + #include + #include "pmf.h" + +-#ifdef CONFIG_AMD_PMF_DEBUG + u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { +@@ -31,6 +30,7 @@ u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + } + EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + ++#ifdef CONFIG_AMD_PMF_DEBUG + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) + { + int i; +diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c +--- a/drivers/platform/x86/x86-android-tablets/core.c ++++ b/drivers/platform/x86/x86-android-tablets/core.c +@@ -156,7 +156,6 @@ static struct platform_device **pdevs; + static struct serdev_device **serdevs; + static const struct software_node **gpio_button_swnodes; + static const struct software_node **swnode_group; +-static const struct software_node **gpiochip_node_group; + static void (*exit_handler)(void); + + static __init struct i2c_adapter * +@@ -362,6 +361,15 @@ static const struct software_node *cherryview_gpiochip_node_group[] = { + NULL + }; + ++const struct software_node crystalcove_gpiochip_node = { ++ .name = "INT33FD:00", ++}; ++ ++static const struct software_node *crystalcove_gpiochip_node_group[] = { ++ &crystalcove_gpiochip_node, ++ NULL ++}; ++ + static void gpio_secondary_unset(void *data) + { + struct device *dev = data; +@@ -377,27 +385,28 @@ static void gpio_secondary_unregister_node_group(void *data) + software_node_unregister_node_group(nodes); + } + +-static int gpio_secondary_fwnode_init(struct device *parent) ++static int gpio_secondary_fwnode_init(struct device *parent, ++ const struct software_node * const *node_group) + { + const struct software_node *const *swnode; + struct fwnode_handle *fwnode; + struct device *phys_dev; + int ret; + +- if (!gpiochip_node_group) ++ if (!node_group) + return 0; + +- ret = software_node_register_node_group(gpiochip_node_group); ++ ret = software_node_register_node_group(node_group); + if (ret) + return ret; + + ret = devm_add_action_or_reset(parent, + gpio_secondary_unregister_node_group, +- gpiochip_node_group); ++ (void *)node_group); + if (ret) + return ret; + +- for (swnode = gpiochip_node_group; *swnode; swnode++) { ++ for (swnode = node_group; *swnode; swnode++) { + struct device *dev __free(put_device) = + acpi_bus_find_device_by_name((*swnode)->name); + if (!dev) +@@ -460,6 +469,7 @@ static void x86_android_tablet_remove(struct platform_device *pdev) + + static __init int x86_android_tablet_probe(struct platform_device *pdev) + { ++ const struct software_node * const *gpiochip_node_group; + const struct x86_dev_info *dev_info; + const struct dmi_system_id *id; + int i, ret = 0; +@@ -491,12 +501,20 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) + break; + } + +- ret = gpio_secondary_fwnode_init(&pdev->dev); ++ ret = gpio_secondary_fwnode_init(&pdev->dev, gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + ++ if (dev_info->has_crystalcove) { ++ ret = gpio_secondary_fwnode_init(&pdev->dev, crystalcove_gpiochip_node_group); ++ if (ret) { ++ x86_android_tablet_remove(pdev); ++ return ret; ++ } ++ } ++ + ret = software_node_register_node_group(dev_info->swnode_group); + if (ret) { + x86_android_tablet_remove(pdev); +diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c +--- a/drivers/platform/x86/x86-android-tablets/lenovo.c ++++ b/drivers/platform/x86/x86-android-tablets/lenovo.c +@@ -61,13 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { + .initial_brightness = 128, + }; + +-static const struct software_node arizona_gpiochip_node = { +- .name = "arizona", +-}; +- +-static const struct software_node crystalcove_gpiochip_node = { +- .name = "gpio_crystalcove", +-}; + + /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ + +@@ -416,15 +409,17 @@ static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __ini + + #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" + ++static const struct software_node lenovo_yoga_tab2_830_1050_wm5102; ++ + static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", + &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", +- &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ &lenovo_yoga_tab2_830_1050_wm5102, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", +- &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), ++ &lenovo_yoga_tab2_830_1050_wm5102, 4, GPIO_ACTIVE_LOW), + { } + }; + +@@ -432,14 +427,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { + .properties = lenovo_yoga_tab2_830_1050_wm1502_props, + }; + +-static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { +- &crystalcove_gpiochip_node, +- &arizona_gpiochip_node, +- &lenovo_yoga_tab2_830_1050_wm5102, +- &generic_lipo_hv_4v35_battery_node, +- NULL +-}; +- + static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); + static void lenovo_yoga_tab2_830_1050_exit(void); + +@@ -455,8 +442,9 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { + .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_830_1050_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -800,8 +788,9 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { + .pdev_info = lenovo_yoga_tab2_1380_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_1380_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -985,13 +974,15 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { + }, + }; + ++static const struct software_node lenovo_yt3_wm5102; ++ + static const struct property_entry lenovo_yt3_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", + &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), +- PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &lenovo_yt3_wm5102, 2, GPIO_ACTIVE_HIGH), + { } + }; + +@@ -1000,11 +991,6 @@ static const struct software_node lenovo_yt3_wm5102 = { + .name = "wm5102", + }; + +-static const struct software_node *lenovo_yt3_swnodes[] = { +- &arizona_gpiochip_node, +- &lenovo_yt3_wm5102, +- NULL +-}; + + static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { + { +@@ -1072,7 +1058,6 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { + .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), + .spi_dev_info = lenovo_yt3_spi_devs, + .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), +- .swnode_group = lenovo_yt3_swnodes, + .modules = lenovo_yt3_modules, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, + .init = lenovo_yt3_init, +diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +--- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h ++++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +@@ -96,6 +96,7 @@ struct x86_dev_info { + int (*init)(struct device *dev); + void (*exit)(void); + bool use_pci; ++ bool has_crystalcove; + enum x86_gpiochip_type gpiochip_type; + }; + +@@ -107,6 +108,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); + /* Software nodes representing GPIO chips used by various tablets */ + extern const struct software_node baytrail_gpiochip_nodes[]; + extern const struct software_node cherryview_gpiochip_nodes[]; ++extern const struct software_node crystalcove_gpiochip_node; + + /* + * Extern declarations of x86_dev_info structs so there can be a single diff --git a/pkgbuilds/linux-omarchy-bore/0802-platform-fixes.patch.sig b/pkgbuilds/linux-omarchy-bore/0802-platform-fixes.patch.sig new file mode 100644 index 0000000..3e19e33 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0802-platform-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0803-acpi-battery-hook-api-acpi-prefix.patch b/pkgbuilds/linux-omarchy-bore/0803-acpi-battery-hook-api-acpi-prefix.patch new file mode 100644 index 0000000..c3246f0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0803-acpi-battery-hook-api-acpi-prefix.patch @@ -0,0 +1,412 @@ +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -736,7 +736,7 @@ static LIST_HEAD(acpi_battery_list); + static LIST_HEAD(battery_hook_list); + static DEFINE_MUTEX(hook_mutex); + +-static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -753,7 +753,7 @@ static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + pr_info("hook unregistered: %s\n", hook->name); + } + +-void battery_hook_unregister(struct acpi_battery_hook *hook) ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) + { + mutex_lock(&hook_mutex); + /* +@@ -762,13 +762,13 @@ void battery_hook_unregister(struct acpi_battery_hook *hook) + * adding a new battery. + */ + if (!list_empty(&hook->list)) +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_unregister); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); + +-void battery_hook_register(struct acpi_battery_hook *hook) ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -789,7 +789,7 @@ void battery_hook_register(struct acpi_battery_hook *hook) + * hooks. + */ + pr_err("hook failed to load: %s", hook->name); +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + goto end; + } + +@@ -799,22 +799,22 @@ void battery_hook_register(struct acpi_battery_hook *hook) + end: + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_register); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); + +-static void devm_battery_hook_unregister(void *data) ++static void devm_acpi_battery_hook_unregister(void *data) + { + struct acpi_battery_hook *hook = data; + +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) ++int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) + { +- battery_hook_register(hook); ++ acpi_battery_hook_register(hook); + +- return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook); ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); + } +-EXPORT_SYMBOL_GPL(devm_battery_hook_register); ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); + + /* + * This function gets called right after the battery sysfs +@@ -843,7 +843,7 @@ static void battery_hook_add_battery(struct acpi_battery *battery) + */ + pr_err("error in hook, unloading: %s", + hook_node->name); +- battery_hook_unregister_unlocked(hook_node); ++ acpi_battery_hook_unregister_unlocked(hook_node); + } + } + mutex_unlock(&hook_mutex); +@@ -876,7 +876,7 @@ static void __exit battery_hook_exit(void) + * need to remove the hooks. + */ + list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + mutex_destroy(&hook_mutex); + } +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -1681,14 +1681,14 @@ static void asus_wmi_battery_init(struct asus_wmi *asus) + asus->battery_rsoc_available = false; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) { + asus->battery_rsoc_available = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + } + } + + static void asus_wmi_battery_exit(struct asus_wmi *asus) + { + if (asus->battery_rsoc_available) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* LEDs ***********************************************************************/ +diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c +--- a/drivers/platform/x86/ayaneo-ec.c ++++ b/drivers/platform/x86/ayaneo-ec.c +@@ -493,7 +493,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev) + data->battery_hook.add_battery = ayaneo_add_battery; + data->battery_hook.remove_battery = ayaneo_remove_battery; + data->battery_hook.name = "Ayaneo Battery"; +- ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook); ++ ret = devm_acpi_battery_hook_register(&pdev->dev, &data->battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c +--- a/drivers/platform/x86/dell/dell-laptop.c ++++ b/drivers/platform/x86/dell/dell-laptop.c +@@ -2446,13 +2446,13 @@ static void __init dell_battery_init(struct device *dev) + battery_supported_modes = battery_get_supported_modes(); + + if (battery_supported_modes != 0) +- battery_hook_register(&dell_battery_hook); ++ acpi_battery_hook_register(&dell_battery_hook); + } + + static void dell_battery_exit(void) + { + if (battery_supported_modes != 0) +- battery_hook_unregister(&dell_battery_hook); ++ acpi_battery_hook_unregister(&dell_battery_hook); + } + + static int __init dell_init(void) +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -972,7 +972,7 @@ static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) + data->eppid_attr.attr.mode = 0444; + data->eppid_attr.show = eppid_show; + +- return devm_battery_hook_register(&data->wdev->dev, &data->hook); ++ return devm_acpi_battery_hook_register(&data->wdev->dev, &data->hook); + } + + static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method) +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -267,7 +267,7 @@ static int fujitsu_battery_charge_control_add(struct device *dev) + return -ENODEV; + + priv->charge_control_supported = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + } +@@ -277,7 +277,7 @@ static void fujitsu_battery_charge_control_remove(struct device *dev) + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + + if (priv->charge_control_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* Hardware access for LCD brightness control */ +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -512,7 +512,7 @@ static void huawei_wmi_battery_setup(struct device *dev) + return; + } + +- battery_hook_register(&huawei_wmi_battery_hook); ++ acpi_battery_hook_register(&huawei_wmi_battery_hook); + device_create_file(dev, &dev_attr_charge_control_thresholds); + } + +@@ -521,7 +521,7 @@ static void huawei_wmi_battery_exit(struct device *dev) + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + if (huawei->battery_available) { +- battery_hook_unregister(&huawei_wmi_battery_hook); ++ acpi_battery_hook_unregister(&huawei_wmi_battery_hook); + device_remove_file(dev, &dev_attr_charge_control_thresholds); + } + } +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2197,8 +2197,8 @@ static int ideapad_check_features(struct ideapad_private *priv) + priv->battery_hook.remove_battery = ideapad_battery_remove; + priv->battery_hook.name = "Ideapad Battery Extension"; + +- err = devm_battery_hook_register(&priv->platform_device->dev, +- &priv->battery_hook); ++ err = devm_acpi_battery_hook_register(&priv->platform_device->dev, ++ &priv->battery_hook); + if (err) + return err; + } +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -10021,13 +10021,13 @@ static int __init tpacpi_battery_init(struct ibm_init_struct *ibm) + battery_quirk_table, + ARRAY_SIZE(battery_quirk_table)); + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void tpacpi_battery_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + static struct ibm_struct battery_driver_data = { +diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c +--- a/drivers/platform/x86/lenovo/wmi-other.c ++++ b/drivers/platform/x86/lenovo/wmi-other.c +@@ -946,7 +946,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv) + priv->battery_hook.name = "Lenovo WMI Other Battery Extension"; + priv->bh_registered = true; + +- battery_hook_register(&priv->battery_hook); ++ acpi_battery_hook_register(&priv->battery_hook); + } + + /** +@@ -960,7 +960,7 @@ static void lwmi_om_psy_remove(struct lwmi_om_priv *priv) + if (!priv->bh_registered) + return; + +- battery_hook_unregister(&priv->battery_hook); ++ acpi_battery_hook_unregister(&priv->battery_hook); + priv->bh_registered = false; + } + +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -1053,7 +1053,7 @@ static int acpi_probe(struct platform_device *pdev) + if (ret) + goto out_platform_device; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +@@ -1068,7 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1428,13 +1428,13 @@ static int __init msi_ec_init(void) + if (result < 0) + return result; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void __exit msi_ec_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -956,7 +956,7 @@ static int oxp_platform_probe(struct platform_device *pdev) + return PTR_ERR(hwdev); + + if (oxp_psy_ext_supported()) { +- ret = devm_battery_hook_register(dev, &battery_hook); ++ ret = devm_acpi_battery_hook_register(dev, &battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -549,7 +549,8 @@ static int galaxybook_battery_threshold_init(struct samsung_galaxybook *galaxybo + galaxybook->battery_hook.remove_battery = galaxybook_battery_remove; + galaxybook->battery_hook.name = "Samsung Galaxy Book Battery Extension"; + +- return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook); ++ return devm_acpi_battery_hook_register(&galaxybook->platform->dev, ++ &galaxybook->battery_hook); + } + + /* +diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c +--- a/drivers/platform/x86/samsung-laptop.c ++++ b/drivers/platform/x86/samsung-laptop.c +@@ -1142,8 +1142,8 @@ static int __init samsung_battery_hook_init(struct samsung_laptop *samsung) + samsung->battery_hook.add_battery = samsung_battery_add; + samsung->battery_hook.remove_battery = samsung_battery_remove; + samsung->battery_hook.name = "Samsung Battery Extension"; +- retval = devm_battery_hook_register(&samsung->platform_device->dev, +- &samsung->battery_hook); ++ retval = devm_acpi_battery_hook_register(&samsung->platform_device->dev, ++ &samsung->battery_hook); + } + + return retval; +diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c +--- a/drivers/platform/x86/system76_acpi.c ++++ b/drivers/platform/x86/system76_acpi.c +@@ -288,12 +288,12 @@ static struct acpi_battery_hook system76_battery_hook = { + + static void system76_battery_init(void) + { +- battery_hook_register(&system76_battery_hook); ++ acpi_battery_hook_register(&system76_battery_hook); + } + + static void system76_battery_exit(void) + { +- battery_hook_unregister(&system76_battery_hook); ++ acpi_battery_hook_unregister(&system76_battery_hook); + } + + // Get the airplane mode LED brightness +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -3301,7 +3301,7 @@ static void toshiba_acpi_remove(struct platform_device *pdev) + } + + if (dev->battery_charge_mode_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + + if (toshiba_acpi) + toshiba_acpi = NULL; +@@ -3566,7 +3566,7 @@ static int toshiba_acpi_probe(struct platform_device *pdev) + * set, this must be done after toshiba_acpi is assigned. + */ + if (dev->battery_charge_mode_supported) +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -2165,7 +2165,7 @@ static int uniwill_battery_init(struct uniwill_data *data) + data->hook.add_battery = uniwill_add_battery; + data->hook.remove_battery = uniwill_remove_battery; + +- return devm_battery_hook_register(data->dev, &data->hook); ++ return devm_acpi_battery_hook_register(data->dev, &data->hook); + } + + static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy) +diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c +--- a/drivers/power/supply/cros_charge-control.c ++++ b/drivers/power/supply/cros_charge-control.c +@@ -315,7 +315,7 @@ static int cros_chctl_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + +- return devm_battery_hook_register(dev, &priv->battery_hook); ++ return devm_acpi_battery_hook_register(dev, &priv->battery_hook); + } + + static const struct platform_device_id cros_chctl_id[] = { +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,8 +18,9 @@ struct acpi_battery_hook { + struct list_head list; + }; + +-void battery_hook_register(struct acpi_battery_hook *hook); +-void battery_hook_unregister(struct acpi_battery_hook *hook); +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook); ++void acpi_battery_hook_register(struct acpi_battery_hook *hook); ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook); + + #endif diff --git a/pkgbuilds/linux-omarchy-bore/0803-acpi-battery-hook-api-acpi-prefix.patch.sig b/pkgbuilds/linux-omarchy-bore/0803-acpi-battery-hook-api-acpi-prefix.patch.sig new file mode 100644 index 0000000..5a6282a Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0803-acpi-battery-hook-api-acpi-prefix.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0804-acpi-battery-unified-hooks-acpi-sbs.patch b/pkgbuilds/linux-omarchy-bore/0804-acpi-battery-unified-hooks-acpi-sbs.patch new file mode 100644 index 0000000..a50095e --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0804-acpi-battery-unified-hooks-acpi-sbs.patch @@ -0,0 +1,472 @@ +diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig +--- a/drivers/acpi/Kconfig ++++ b/drivers/acpi/Kconfig +@@ -171,8 +171,12 @@ config ACPI_AC + To compile this driver as a module, choose M here: + the module will be called ac. + ++config ACPI_BATTERY_HOOKS ++ tristate ++ + config ACPI_BATTERY + tristate "Battery" ++ select ACPI_BATTERY_HOOKS + select POWER_SUPPLY + default y + help +@@ -445,6 +449,7 @@ config ACPI_HOTPLUG_IOAPIC + config ACPI_SBS + tristate "Smart Battery System" + depends on X86 && ACPI_EC ++ select ACPI_BATTERY_HOOKS + select POWER_SUPPLY + help + This driver supports the Smart Battery System, another +diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile +--- a/drivers/acpi/Makefile ++++ b/drivers/acpi/Makefile +@@ -97,6 +97,7 @@ obj-$(CONFIG_ACPI_NHLT) += nhlt.o + obj-$(CONFIG_ACPI_NUMA) += numa/ + obj-$(CONFIG_ACPI) += acpi_memhotplug.o + obj-$(CONFIG_ACPI_HOTPLUG_IOAPIC) += ioapic.o ++obj-$(CONFIG_ACPI_BATTERY_HOOKS) += battery_hooks.o + obj-$(CONFIG_ACPI_BATTERY) += battery.o + obj-$(CONFIG_ACPI_SBS) += sbshc.o + obj-$(CONFIG_ACPI_SBS) += sbs.o +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -48,6 +48,7 @@ MODULE_AUTHOR("Paul Diefenbaugh"); + MODULE_AUTHOR("Alexey Starikovskiy "); + MODULE_DESCRIPTION("ACPI Battery Driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static int battery_bix_broken_package; + static int battery_notification_delay_ms; +@@ -97,7 +98,7 @@ struct acpi_battery { + struct acpi_device *device; + struct device *phys_dev; + struct notifier_block pm_nb; +- struct list_head list; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long flags; + + struct mutex property_lock; /* Protects properties below. */ +@@ -723,164 +724,6 @@ static struct attribute *acpi_battery_attrs[] = { + }; + ATTRIBUTE_GROUPS(acpi_battery); + +-/* +- * The Battery Hooking API +- * +- * This API is used inside other drivers that need to expose +- * platform-specific behaviour within the generic driver in a +- * generic way. +- * +- */ +- +-static LIST_HEAD(acpi_battery_list); +-static LIST_HEAD(battery_hook_list); +-static DEFINE_MUTEX(hook_mutex); +- +-static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- /* +- * In order to remove a hook, we first need to +- * de-register all the batteries that are registered. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (!hook->remove_battery(battery->bat, hook)) +- power_supply_changed(battery->bat); +- } +- list_del_init(&hook->list); +- +- pr_info("hook unregistered: %s\n", hook->name); +-} +- +-void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) +-{ +- mutex_lock(&hook_mutex); +- /* +- * Ignore already unregistered battery hooks. This might happen +- * if a battery hook was previously unloaded due to an error when +- * adding a new battery. +- */ +- if (!list_empty(&hook->list)) +- acpi_battery_hook_unregister_unlocked(hook); +- +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); +- +-void acpi_battery_hook_register(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- mutex_lock(&hook_mutex); +- list_add(&hook->list, &battery_hook_list); +- /* +- * Now that the driver is registered, we need +- * to notify the hook that a battery is available +- * for each battery, so that the driver may add +- * its attributes. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (hook->add_battery(battery->bat, hook)) { +- /* +- * If a add-battery returns non-zero, +- * the registration of the hook has failed, +- * and we will not add it to the list of loaded +- * hooks. +- */ +- pr_err("hook failed to load: %s", hook->name); +- acpi_battery_hook_unregister_unlocked(hook); +- goto end; +- } +- +- power_supply_changed(battery->bat); +- } +- pr_info("new hook: %s\n", hook->name); +-end: +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_register); +- +-static void devm_acpi_battery_hook_unregister(void *data) +-{ +- struct acpi_battery_hook *hook = data; +- +- acpi_battery_hook_unregister(hook); +-} +- +-int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) +-{ +- acpi_battery_hook_register(hook); +- +- return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); +-} +-EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); +- +-/* +- * This function gets called right after the battery sysfs +- * attributes have been added, so that the drivers that +- * define custom sysfs attributes can add their own. +- */ +-static void battery_hook_add_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook_node, *tmp; +- +- mutex_lock(&hook_mutex); +- INIT_LIST_HEAD(&battery->list); +- list_add(&battery->list, &acpi_battery_list); +- /* +- * Since we added a new battery to the list, we need to +- * iterate over the hooks and call add_battery for each +- * hook that was registered. This usually happens +- * when a battery gets hotplugged or initialized +- * during the battery module initialization. +- */ +- list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { +- if (hook_node->add_battery(battery->bat, hook_node)) { +- /* +- * The notification of the hook has failed, to +- * prevent further errors we will unload the hook. +- */ +- pr_err("error in hook, unloading: %s", +- hook_node->name); +- acpi_battery_hook_unregister_unlocked(hook_node); +- } +- } +- mutex_unlock(&hook_mutex); +-} +- +-static void battery_hook_remove_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook; +- +- mutex_lock(&hook_mutex); +- /* +- * Before removing the hook, we need to remove all +- * custom attributes from the battery. +- */ +- list_for_each_entry(hook, &battery_hook_list, list) { +- hook->remove_battery(battery->bat, hook); +- } +- /* Then, just remove the battery from the list */ +- list_del(&battery->list); +- mutex_unlock(&hook_mutex); +-} +- +-static void __exit battery_hook_exit(void) +-{ +- struct acpi_battery_hook *hook; +- struct acpi_battery_hook *ptr; +- /* +- * At this point, the acpi_bus_unregister_driver() +- * has called remove for all batteries. We just +- * need to remove the hooks. +- */ +- list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- acpi_battery_hook_unregister(hook); +- } +- mutex_destroy(&hook_mutex); +-} +- + static int sysfs_add_battery(struct acpi_battery *battery) + { + struct power_supply_config psy_cfg = { +@@ -936,7 +779,7 @@ static int sysfs_add_battery(struct acpi_battery *battery) + battery->bat = NULL; + return result; + } +- battery_hook_add_battery(battery); ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); + return 0; + } + +@@ -945,7 +788,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery) + if (!battery->bat) + return; + +- battery_hook_remove_battery(battery); ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); + battery->bat = NULL; + } +@@ -1391,7 +1234,6 @@ static int __init acpi_battery_init(void) + static void __exit acpi_battery_exit(void) + { + platform_driver_unregister(&acpi_battery_driver); +- battery_hook_exit(); + } + + module_init(acpi_battery_init); +diff --git a/drivers/acpi/battery_hooks.c b/drivers/acpi/battery_hooks.c +new file mode 100644 +--- /dev/null ++++ b/drivers/acpi/battery_hooks.c +@@ -0,0 +1,159 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ACPI Battery Hooks ++ * ++ * Provides helpers for registering and unregistering battery hooks for ++ * drivers that use platform-specific extensions to ACPI-enumerated ++ * batteries (both ACPI Control Method batteries and Smart Battery System ++ * batteries). ++ */ ++ ++#define pr_fmt(fmt) "ACPI: battery: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static LIST_HEAD(acpi_battery_list); ++static LIST_HEAD(battery_hook_list); ++static DEFINE_MUTEX(hook_mutex); ++ ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ /* ++ * In order to remove a hook, we first need to ++ * de-register all the batteries that are registered. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (!hook->remove_battery(entry->battery, hook)) ++ power_supply_changed(entry->battery); ++ } ++ list_del_init(&hook->list); ++ ++ pr_info("hook unregistered: %s\n", hook->name); ++} ++ ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) ++{ ++ guard(mutex)(&hook_mutex); ++ ++ /* ++ * Ignore already unregistered battery hooks. This might happen ++ * if a battery hook was previously unloaded due to an error when ++ * adding a new battery. ++ */ ++ if (!list_empty(&hook->list)) ++ acpi_battery_hook_unregister_unlocked(hook); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); ++ ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&hook->list, &battery_hook_list); ++ /* ++ * Now that the driver is registered, we need ++ * to notify the hook that a battery is available ++ * for each battery, so that the driver may add ++ * its attributes. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (hook->add_battery(entry->battery, hook)) { ++ /* ++ * If a add-battery returns non-zero, ++ * the registration of the hook has failed, ++ * and we will not add it to the list of loaded ++ * hooks. ++ */ ++ pr_err("hook failed to load: %s\n", hook->name); ++ acpi_battery_hook_unregister_unlocked(hook); ++ return; ++ } ++ ++ power_supply_changed(entry->battery); ++ } ++ pr_info("new hook: %s\n", hook->name); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); ++ ++static void devm_acpi_battery_hook_unregister(void *data) ++{ ++ struct acpi_battery_hook *hook = data; ++ ++ acpi_battery_hook_unregister(hook); ++} ++ ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook) ++{ ++ acpi_battery_hook_register(hook); ++ ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); ++} ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); ++ ++/* ++ * This function gets called right after the battery sysfs ++ * attributes have been added, so that the drivers that ++ * define custom sysfs attributes can add their own. ++ */ ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery) ++{ ++ struct acpi_battery_hook *hook_node, *tmp; ++ ++ entry->battery = battery; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&entry->list_entry, &acpi_battery_list); ++ /* ++ * Since we added a new battery to the list, we need to ++ * iterate over the hooks and call add_battery for each ++ * hook that was registered. This usually happens ++ * when a battery gets hotplugged or initialized ++ * during the battery module initialization. ++ */ ++ list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { ++ if (hook_node->add_battery(entry->battery, hook_node)) { ++ /* ++ * The notification of the hook has failed, to ++ * prevent further errors we will unload the hook. ++ */ ++ pr_err("error in hook, unloading: %s\n", hook_node->name); ++ acpi_battery_hook_unregister_unlocked(hook_node); ++ } ++ } ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_add_hooks, "ACPI_BATTERY_HOOKS"); ++ ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry) ++{ ++ struct acpi_battery_hook *hook; ++ ++ guard(mutex)(&hook_mutex); ++ /* ++ * Before removing the hook, we need to remove all ++ * custom attributes from the battery. ++ */ ++ list_for_each_entry(hook, &battery_hook_list, list) ++ hook->remove_battery(entry->battery, hook); ++ ++ /* Then, just remove the battery from the list */ ++ list_del(&entry->list_entry); ++ entry->battery = NULL; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_remove_hooks, "ACPI_BATTERY_HOOKS"); ++ ++MODULE_DESCRIPTION("ACPI battery hooks"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c +--- a/drivers/acpi/sbs.c ++++ b/drivers/acpi/sbs.c +@@ -36,6 +36,7 @@ + MODULE_AUTHOR("Alexey Starikovskiy "); + MODULE_DESCRIPTION("Smart Battery System ACPI interface driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static unsigned int cache_time = 1000; + module_param(cache_time, uint, 0644); +@@ -54,6 +55,7 @@ struct acpi_battery { + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct acpi_sbs *sbs; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long update_time; + char name[8]; + char manufacturer_name[ACPI_SBS_BLOCK_MAX]; +@@ -555,6 +557,8 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id) + goto end; + } + ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); ++ + end: + pr_info("%s [%s]: Battery Slot [%s] (battery %s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), +@@ -566,8 +570,10 @@ static void acpi_battery_remove(struct acpi_sbs *sbs, int id) + { + struct acpi_battery *battery = &sbs->battery[id]; + +- if (battery->bat) ++ if (battery->bat) { ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); ++ } + } + + static int acpi_charger_add(struct acpi_sbs *sbs) +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,9 +18,18 @@ struct acpi_battery_hook { + struct list_head list; + }; + ++struct acpi_battery_hooks_list_entry { ++ struct list_head list_entry; ++ struct power_supply *battery; ++}; ++ + void acpi_battery_hook_register(struct acpi_battery_hook *hook); + void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); + int devm_acpi_battery_hook_register(struct device *dev, + struct acpi_battery_hook *hook); + ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery); ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-bore/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig b/pkgbuilds/linux-omarchy-bore/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig new file mode 100644 index 0000000..7b62083 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0805-hwmon-applesmc-charge-control-end-threshold.patch b/pkgbuilds/linux-omarchy-bore/0805-hwmon-applesmc-charge-control-end-threshold.patch new file mode 100644 index 0000000..9b45c66 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0805-hwmon-applesmc-charge-control-end-threshold.patch @@ -0,0 +1,209 @@ +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -380,6 +380,7 @@ config SENSORS_FAM15H_POWER + config SENSORS_APPLESMC + tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" + depends on INPUT && X86 ++ depends on POWER_SUPPLY || POWER_SUPPLY=n + select NEW_LEDS + select LEDS_CLASS + help +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,8 @@ + #include + #include + #include ++#include ++#include + #include + + /* data port used by Apple SMC */ +@@ -76,6 +78,20 @@ + + #define TEMP_SENSOR_TYPE "sp78" + ++/* ++ * BCLM caps charging at a percentage. BFCL only sets when the charging LED ++ * switches from orange to green. ++ */ ++#define BATTERY_CHARGE_LIMIT_KEY "BCLM" /* r/w ui8 */ ++#define BATTERY_CHARGE_LIMIT_LED_KEY "BFCL" /* r/w ui8 */ ++ ++/* ++ * Points kept between BCLM and BFCL so the LED turns green before charging ++ * stops. Measured on a MacBookAir6,2, where a margin of 1 only just ties; ++ * 3 leaves headroom for other models. ++ */ ++#define APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN 3 ++ + /* List of keys used to read/write fan speeds */ + static const char *const fan_speed_fmt[] = { + "F%dAc", /* actual speed */ +@@ -131,6 +147,8 @@ static struct applesmc_registers { + int num_light_sensors; /* number of light sensors */ + bool has_accelerometer; /* has motion sensor */ + bool has_key_backlight; /* has keyboard backlight */ ++ bool has_battery_charge_limit; /* has BCLM battery charge limit */ ++ bool has_battery_charge_limit_led; /* has BFCL MagSafe LED charge limit */ + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ +@@ -633,6 +651,12 @@ static int applesmc_init_smcreg_try(void) + if (ret) + return ret; + ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_KEY, &s->has_battery_charge_limit); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_LED_KEY, &s->has_battery_charge_limit_led); + if (ret) + return ret; + +@@ -724,6 +748,120 @@ static struct platform_driver applesmc_driver = { + }, + }; + ++static DEFINE_MUTEX(applesmc_bat_lock); ++ ++static int applesmc_bat_get_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ u8 limit; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ scoped_guard(mutex, &applesmc_bat_lock) ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ val->intval = limit; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_set_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ const union power_supply_propval *val) ++{ ++ u8 limit, led_limit, readback; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ /* 20 is known to work and 10 is not accepted; the floor is unknown. */ ++ if (val->intval < 20 || val->intval > 100) ++ return -EINVAL; ++ ++ limit = val->intval; ++ /* At 100% charging is never cut short, so no margin is needed. */ ++ led_limit = limit < 100 ? ++ limit - APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN : limit; ++ ++ scoped_guard(mutex, &applesmc_bat_lock) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ ++ /* ++ * The SMC silently ignores values it does not accept and ++ * still reports success, so read the limit back. ++ */ ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &readback, 1); ++ if (ret) ++ return ret; ++ if (readback != limit) ++ return -EINVAL; ++ ++ if (smcreg.has_battery_charge_limit_led) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_LED_KEY, ++ &led_limit, 1); ++ /* BCLM is already applied, so this is not an error. */ ++ if (ret) ++ pr_warn("failed to update MagSafe LED threshold (BFCL): %d\n", ++ ret); ++ } ++ } ++ ++ power_supply_changed(psy); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_property_is_writeable(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp) ++{ ++ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; ++} ++ ++static const enum power_supply_property applesmc_bat_props[] = { ++ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, ++}; ++ ++static const struct power_supply_ext applesmc_bat_ext = { ++ .name = "applesmc-charge-control", ++ .properties = applesmc_bat_props, ++ .num_properties = ARRAY_SIZE(applesmc_bat_props), ++ .get_property = applesmc_bat_get_property, ++ .set_property = applesmc_bat_set_property, ++ .property_is_writeable = applesmc_bat_property_is_writeable, ++}; ++ ++static int applesmc_bat_add(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ return power_supply_register_extension(battery, &applesmc_bat_ext, &pdev->dev, NULL); ++} ++ ++static int applesmc_bat_remove(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ power_supply_unregister_extension(battery, &applesmc_bat_ext); ++ return 0; ++} ++ ++static struct acpi_battery_hook applesmc_bat_hook = { ++ .name = "Apple SMC Battery Charge Control", ++ .add_battery = applesmc_bat_add, ++ .remove_battery = applesmc_bat_remove, ++}; ++ + /* + * applesmc_calibrate - Set our "resting" values. Callers must + * hold applesmc_lock. +@@ -1563,6 +1701,10 @@ static int __init applesmc_init(void) + goto out_light_ledclass; + } + ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_register(&applesmc_bat_hook); ++ + return 0; + + out_light_ledclass: +@@ -1589,6 +1731,10 @@ static int __init applesmc_init(void) + + static void __exit applesmc_exit(void) + { ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_unregister(&applesmc_bat_hook); ++ + hwmon_device_unregister(hwmon_dev); + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); diff --git a/pkgbuilds/linux-omarchy-bore/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig b/pkgbuilds/linux-omarchy-bore/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig new file mode 100644 index 0000000..73eabc0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0860-module-add-module-kallsyms-on-each-addr.patch b/pkgbuilds/linux-omarchy-bore/0860-module-add-module-kallsyms-on-each-addr.patch new file mode 100644 index 0000000..ebd6e0d --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0860-module-add-module-kallsyms-on-each-addr.patch @@ -0,0 +1,95 @@ +diff --git a/include/linux/module.h b/include/linux/module.h +--- a/include/linux/module.h ++++ b/include/linux/module.h +@@ -975,6 +975,10 @@ unsigned long module_kallsyms_lookup_name(const char *name); + + unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name); + ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data); ++ + #else /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + static inline int module_kallsyms_on_each_symbol(const char *modname, +@@ -1018,6 +1022,12 @@ static inline unsigned long find_kallsyms_symbol_value(struct module *mod, + return 0; + } + ++static inline void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++} ++ + #endif /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + /* Define __free(module_put) macro for struct module *. */ +diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c +--- a/kernel/module/kallsyms.c ++++ b/kernel/module/kallsyms.c +@@ -246,6 +246,18 @@ static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms, unsigned + return kallsyms->strtab + kallsyms->symtab[symnum].st_name; + } + ++/* ++ * Whether find_kallsyms_symbol() may resolve an address to symbol @symnum. ++ * Unnamed symbols are ignored: they're uninformative and inserted at a whim. ++ */ ++static bool is_lookup_symbol(struct mod_kallsyms *kallsyms, unsigned int symnum) ++{ ++ const char *name = kallsyms_symbol_name(kallsyms, symnum); ++ ++ return kallsyms->symtab[symnum].st_shndx != SHN_UNDEF && ++ *name != '\0' && !is_mapping_symbol(name); ++} ++ + /* + * Given a module and address, find the corresponding symbol and return its name + * while providing its size and offset if needed. +@@ -286,15 +298,7 @@ static const char *find_kallsyms_symbol(struct module *mod, + const Elf_Sym *sym = &kallsyms->symtab[i]; + unsigned long thisval = kallsyms_symbol_value(sym); + +- if (sym->st_shndx == SHN_UNDEF) +- continue; +- +- /* +- * We ignore unnamed symbols: they're uninformative +- * and inserted at a whim. +- */ +- if (*kallsyms_symbol_name(kallsyms, i) == '\0' || +- is_mapping_symbol(kallsyms_symbol_name(kallsyms, i))) ++ if (!is_lookup_symbol(kallsyms, i)) + continue; + + if (thisval <= addr && thisval > bestval) { +@@ -458,6 +462,28 @@ unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name) + return __find_kallsyms_symbol_value(mod, name); + } + ++/* ++ * Call @fn with the address of each symbol of @mod that find_kallsyms_symbol() ++ * may resolve an address to. Unlike module_kallsyms_on_each_symbol(), this ++ * also works while @mod is still being loaded. ++ */ ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++ struct mod_kallsyms *kallsyms; ++ unsigned int i; ++ ++ guard(rcu)(); ++ kallsyms = rcu_dereference(mod->kallsyms); ++ ++ /* ELF starts real symbols at 1. */ ++ for (i = 1; i < kallsyms->num_symtab; i++) { ++ if (is_lookup_symbol(kallsyms, i)) ++ fn(data, kallsyms_symbol_value(&kallsyms->symtab[i])); ++ } ++} ++ + int module_kallsyms_on_each_symbol(const char *modname, + int (*fn)(void *, const char *, unsigned long), + void *data) diff --git a/pkgbuilds/linux-omarchy-bore/0860-module-add-module-kallsyms-on-each-addr.patch.sig b/pkgbuilds/linux-omarchy-bore/0860-module-add-module-kallsyms-on-each-addr.patch.sig new file mode 100644 index 0000000..f0c502a Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0860-module-add-module-kallsyms-on-each-addr.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0861-ftrace-drop-weak-function-locations-of-modules.patch b/pkgbuilds/linux-omarchy-bore/0861-ftrace-drop-weak-function-locations-of-modules.patch new file mode 100644 index 0000000..82b00bb --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/0861-ftrace-drop-weak-function-locations-of-modules.patch @@ -0,0 +1,113 @@ +diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c +--- a/kernel/trace/ftrace.c ++++ b/kernel/trace/ftrace.c +@@ -4445,11 +4445,6 @@ static int print_rec(struct seq_file *m, unsigned long ip) + return ret == NULL ? -1 : 0; + } + #else +-static inline int test_for_valid_rec(struct dyn_ftrace *rec) +-{ +- return 1; +-} +- + static inline int print_rec(struct seq_file *m, unsigned long ip) + { + seq_printf(m, "%ps", (void *)ip); +@@ -7611,6 +7606,73 @@ static void test_is_sorted(unsigned long *start, unsigned long count) + } + #endif + ++#ifdef FTRACE_MCOUNT_MAX_OFFSET ++struct ftrace_mod_locs { ++ unsigned long *start; ++ unsigned long count; ++ unsigned long *valid; ++}; ++ ++/* Mark the locations that lie at most FTRACE_MCOUNT_MAX_OFFSET after @addr. */ ++static void ftrace_mark_valid_locs(void *data, unsigned long addr) ++{ ++ struct ftrace_mod_locs *locs = data; ++ unsigned long lo = 0, hi = locs->count, mid, ip; ++ ++ /* ++ * ftrace_call_adjust() moves a location forward by at most ++ * FTRACE_MCOUNT_MAX_OFFSET, so start looking that far before @addr. ++ */ ++ while (lo < hi) { ++ mid = lo + (hi - lo) / 2; ++ if (locs->start[mid] + FTRACE_MCOUNT_MAX_OFFSET < addr) ++ lo = mid + 1; ++ else ++ hi = mid; ++ } ++ ++ for (; lo < locs->count; lo++) { ++ if (locs->start[lo] > addr + FTRACE_MCOUNT_MAX_OFFSET) ++ break; ++ ip = ftrace_call_adjust(locs->start[lo]); ++ if (ip >= addr && ip - addr <= FTRACE_MCOUNT_MAX_OFFSET) ++ __set_bit(lo, locs->valid); ++ } ++} ++ ++/* ++ * A weak function overridden within its module keeps its mcount location but ++ * has no symbol. Zero such locations before they become records, as sorttable ++ * does for vmlinux: keep only those with a symbol at most ++ * FTRACE_MCOUNT_MAX_OFFSET before them. ++ */ ++static int ftrace_zero_weak_locs(struct module *mod, unsigned long *start, ++ unsigned long count) ++{ ++ struct ftrace_mod_locs locs = { .start = start, .count = count }; ++ unsigned long i; ++ ++ locs.valid = bitmap_zalloc(count, GFP_KERNEL); ++ if (!locs.valid) ++ return -ENOMEM; ++ ++ module_kallsyms_on_each_addr(mod, ftrace_mark_valid_locs, &locs); ++ ++ for_each_clear_bit(i, locs.valid, count) ++ start[i] = 0; ++ ++ bitmap_free(locs.valid); ++ return 0; ++} ++#else ++static inline int ftrace_zero_weak_locs(struct module *mod, ++ unsigned long *start, ++ unsigned long count) ++{ ++ return 0; ++} ++#endif ++ + static int ftrace_process_locs(struct module *mod, + unsigned long *start, + unsigned long *end) +@@ -7644,6 +7706,9 @@ static int ftrace_process_locs(struct module *mod, + test_is_sorted(start, count); + } + ++ if (mod && ftrace_zero_weak_locs(mod, start, count)) ++ return -ENOMEM; ++ + start_pg = ftrace_allocate_pages(count, &pages); + if (!start_pg) + return -ENOMEM; +@@ -8049,13 +8114,6 @@ void ftrace_module_enable(struct module *mod) + + cond_resched(); + +- /* Weak functions should still be ignored */ +- if (!test_for_valid_rec(rec)) { +- /* Clear all other flags. Should not be enabled anyway */ +- rec->flags = FTRACE_FL_DISABLED; +- continue; +- } +- + cnt = 0; + + /* diff --git a/pkgbuilds/linux-omarchy-bore/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig b/pkgbuilds/linux-omarchy-bore/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig new file mode 100644 index 0000000..d2d6943 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/0861-ftrace-drop-weak-function-locations-of-modules.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 deleted file mode 100644 index f340536..0000000 --- a/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch +++ /dev/null @@ -1,132 +0,0 @@ -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 deleted file mode 100644 index bbe1ce3..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/8201-xe-shrinker-return-freed-page-count.patch.sig and /dev/null 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 deleted file mode 100644 index 29ccecf..0000000 --- a/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch +++ /dev/null @@ -1,149 +0,0 @@ -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 deleted file mode 100644 index 0707c41..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig and /dev/null 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 deleted file mode 100644 index 6af456f..0000000 --- a/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index ddd9329..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/8203-xe-shrinker-release-through-the-put-helper.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch index 0a31aa0..353ca7c 100644 --- a/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch +++ b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch @@ -100,7 +100,7 @@ diff --git c/mm/internal.h i/mm/internal.h 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, +@@ -476,7 +476,7 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl, #ifdef CONFIG_MEMCG static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, @@ -109,7 +109,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c { 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, +@@ -538,6 +538,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, .gfp_mask = gfp_mask, .nid = nid, .memcg = memcg, @@ -117,7 +117,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c }; 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, +@@ -597,7 +598,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, } #else /* !CONFIG_MEMCG */ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, @@ -127,7 +127,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c { return 0; } -@@ -607,6 +609,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +@@ -609,6 +611,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, * @nid: node whose slab caches to target * @memcg: memory cgroup whose slab caches to target * @priority: the reclaim priority @@ -135,7 +135,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c * * Call the shrink functions to age shrinkable caches. * -@@ -622,7 +625,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +@@ -624,7 +627,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, * Returns the number of reclaimed slab objects. */ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, @@ -144,7 +144,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c { 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, +@@ -637,7 +640,8 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, * oom. */ if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) @@ -154,7 +154,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c /* * lockless algorithm of global shrink. -@@ -664,6 +668,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +@@ -666,6 +670,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, .gfp_mask = gfp_mask, .nid = nid, .memcg = memcg, diff --git a/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch.sig b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch.sig index 1b113b3..d4744f8 100644 Binary files a/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.patch.sig and b/pkgbuilds/linux-omarchy-bore/8204-mm-opportunistic-compaction.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 index 92b39ef..65dd70b 100644 --- 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 @@ -1,6 +1,6 @@ -diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h ---- c/include/linux/mmzone.h -+++ i/include/linux/mmzone.h +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h @@ -1498,6 +1498,32 @@ enum kswapd_opportunistic_compaction_type { KSWAPD_OPPORTUNISTIC_COMPACTION, }; @@ -56,10 +56,10 @@ diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h 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) +diff --git a/mm/mm_init.c b/mm/mm_init.c +--- a/mm/mm_init.c ++++ b/mm/mm_init.c +@@ -1558,7 +1558,8 @@ int __ref free_area_init_core_hotplug(struct pglist_data *pgdat) * when it starts in the near future. */ pgdat->nr_zones = 0; @@ -69,9 +69,9 @@ diff --git c/mm/mm_init.c i/mm/mm_init.c 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 +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c @@ -7413,6 +7413,49 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; } 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 index de65251..684949b 100644 Binary files a/pkgbuilds/linux-omarchy-bore/8206-mm-carry-order-and-hint-in-one-word.patch.sig 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/8208-mm-thp-deferred-split-uses-hint.patch b/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch deleted file mode 100644 index 8de9983..0000000 --- a/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index 88c17d4..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/8208-mm-thp-deferred-split-uses-hint.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/8211-xe-shrinker-skip-active-vm.patch b/pkgbuilds/linux-omarchy-bore/8211-xe-shrinker-skip-active-vm.patch new file mode 100644 index 0000000..76470c1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8211-xe-shrinker-skip-active-vm.patch @@ -0,0 +1,116 @@ +diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c +--- a/drivers/gpu/drm/xe/xe_bo.c ++++ b/drivers/gpu/drm/xe/xe_bo.c +@@ -28,6 +28,7 @@ + #include "xe_ggtt.h" + #include "xe_map.h" + #include "xe_migrate.h" ++#include "xe_module.h" + #include "xe_pat.h" + #include "xe_pm.h" + #include "xe_preempt_fence.h" +@@ -1244,6 +1245,26 @@ xe_bo_eviction_valuable(struct ttm_buffer_object *bo, const struct ttm_place *pl + return true; + } + ++static bool xe_bo_bound_to_active_vm(struct xe_bo *bo) ++{ ++ u64 window = (u64)READ_ONCE(xe_modparam.shrink_active_ms) * NSEC_PER_MSEC; ++ u64 now = ktime_get_ns(); ++ struct drm_gpuvm_bo *vm_bo; ++ ++ if (!window) ++ return false; ++ ++ drm_gem_for_each_gpuvm_bo(vm_bo, &bo->ttm.base) { ++ struct xe_vm *vm = gpuvm_to_vm(vm_bo->vm); ++ u64 last = READ_ONCE(vm->last_exec_ns); ++ ++ if (!xe_vm_in_fault_mode(vm) && last && now - last < window) ++ return true; ++ } ++ ++ return false; ++} ++ + /** + * xe_bo_shrink() - Try to shrink an xe bo. + * @ctx: The struct ttm_operation_ctx used for shrinking. +@@ -1295,6 +1316,10 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo, + goto out_unref; + } + ++ if (xe_bo_bound_to_active_vm(xe_bo) && ++ try_wait_for_completion(&xe->pm_block)) ++ goto out_unref; ++ + /* System CCS needs gpu copy when moving PL_TT -> PL_SYSTEM */ + needs_rpm = (!IS_DGFX(xe) && bo->resource->mem_type != XE_PL_SYSTEM && + xe_bo_needs_ccs_pages(xe_bo)); +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -22,5 +22,6 @@ + #define XE_DEFAULT_WEDGED_MODE XE_WEDGED_MODE_UPON_CRITICAL_ERROR + #define XE_DEFAULT_WEDGED_MODE_STR "upon-critical-error" + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 ++#define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 + + #endif +diff --git a/drivers/gpu/drm/xe/xe_exec.c b/drivers/gpu/drm/xe/xe_exec.c +--- a/drivers/gpu/drm/xe/xe_exec.c ++++ b/drivers/gpu/drm/xe/xe_exec.c +@@ -344,6 +344,7 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file) + if (!xe_vm_in_lr_mode(vm)) + xe_exec_queue_last_fence_set(q, vm, &job->drm.s_fence->finished); + xe_sched_job_push(job); ++ WRITE_ONCE(vm->last_exec_ns, ktime_get_ns()); + xe_vm_reactivate_rebind(vm); + + if (!err && !xe_vm_in_lr_mode(vm)) { +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -30,6 +30,7 @@ struct xe_modparam xe_modparam = { + #endif + .wedged_mode = XE_DEFAULT_WEDGED_MODE, + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, ++ .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, + /* the rest are 0 by default */ + }; + +@@ -37,6 +38,11 @@ module_param_named(svm_notifier_size, xe_modparam.svm_notifier_size, uint, 0600) + MODULE_PARM_DESC(svm_notifier_size, "Set the svm notifier size in MiB, must be power of 2 " + "[default=" __stringify(XE_DEFAULT_SVM_NOTIFIER_SIZE) "]"); + ++module_param_named(shrink_active_ms, xe_modparam.shrink_active_ms, uint, 0600); ++MODULE_PARM_DESC(shrink_active_ms, "Do not back up a buffer object bound to a VM that submitted work " ++ "within this many ms, 0=disabled " ++ "[default=" __stringify(XE_DEFAULT_SHRINK_ACTIVE_MS) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -25,6 +25,7 @@ struct xe_modparam { + #endif + unsigned int wedged_mode; + u32 svm_notifier_size; ++ unsigned int shrink_active_ms; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_vm_types.h b/drivers/gpu/drm/xe/xe_vm_types.h +--- a/drivers/gpu/drm/xe/xe_vm_types.h ++++ b/drivers/gpu/drm/xe/xe_vm_types.h +@@ -245,6 +245,8 @@ struct xe_vm { + /** @lru_bulk_move: Bulk LRU move list for this VM's BOs */ + struct ttm_lru_bulk_move lru_bulk_move; + ++ u64 last_exec_ns; ++ + u64 size; + + struct xe_pt *pt_root[XE_MAX_TILES_PER_DEVICE]; diff --git a/pkgbuilds/linux-omarchy-bore/8211-xe-shrinker-skip-active-vm.patch.sig b/pkgbuilds/linux-omarchy-bore/8211-xe-shrinker-skip-active-vm.patch.sig new file mode 100644 index 0000000..0a4ed41 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8211-xe-shrinker-skip-active-vm.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8212-xe-shrinker-backup-in-kswapd-only.patch b/pkgbuilds/linux-omarchy-bore/8212-xe-shrinker-backup-in-kswapd-only.patch new file mode 100644 index 0000000..f77d2f2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8212-xe-shrinker-backup-in-kswapd-only.patch @@ -0,0 +1,95 @@ +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -23,5 +23,6 @@ + #define XE_DEFAULT_WEDGED_MODE_STR "upon-critical-error" + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 + #define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 ++#define XE_DEFAULT_SHRINK_DIRECT_BACKUP false + + #endif +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -31,6 +31,7 @@ struct xe_modparam xe_modparam = { + .wedged_mode = XE_DEFAULT_WEDGED_MODE, + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, + .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, ++ .shrink_direct_backup = XE_DEFAULT_SHRINK_DIRECT_BACKUP, + /* the rest are 0 by default */ + }; + +@@ -43,6 +44,10 @@ MODULE_PARM_DESC(shrink_active_ms, "Do not back up a buffer object bound to a VM + "within this many ms, 0=disabled " + "[default=" __stringify(XE_DEFAULT_SHRINK_ACTIVE_MS) "]"); + ++module_param_named(shrink_direct_backup, xe_modparam.shrink_direct_backup, bool, 0600); ++MODULE_PARM_DESC(shrink_direct_backup, "Back up buffer objects in direct reclaim too, not only in kswapd " ++ "[default=" __stringify(XE_DEFAULT_SHRINK_DIRECT_BACKUP) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -26,6 +26,7 @@ struct xe_modparam { + unsigned int wedged_mode; + u32 svm_notifier_size; + unsigned int shrink_active_ms; ++ bool shrink_direct_backup; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c +--- a/drivers/gpu/drm/xe/xe_shrinker.c ++++ b/drivers/gpu/drm/xe/xe_shrinker.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + + #include + #include +@@ -11,6 +12,7 @@ + #include + + #include "xe_bo.h" ++#include "xe_module.h" + #include "xe_pm.h" + #include "xe_shrinker.h" + +@@ -170,9 +172,12 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + +-static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, ++ const struct shrink_control *sc) + { +- return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction; ++ return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction && ++ (current_is_kswapd() || READ_ONCE(xe_modparam.shrink_direct_backup) || ++ !try_wait_for_completion(&shrinker->xe->pm_block)); + } + + static unsigned long +@@ -188,7 +193,7 @@ xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + * wants, and it needs __GFP_FS. + */ + read_lock(&shrinker->lock); +- if (xe_shrinker_can_backup(sc)) ++ if (xe_shrinker_can_backup(shrinker, sc)) + num_pages = min_t(unsigned long, + ttm_backup_bytes_avail() >> PAGE_SHIFT, + shrinker->shrinkable_pages); +@@ -241,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + }; + bool runtime_pm; + bool purgeable; +- bool can_backup = xe_shrinker_can_backup(sc); ++ bool can_backup = xe_shrinker_can_backup(shrinker, sc); + + nr_to_scan = sc->nr_to_scan; + diff --git a/pkgbuilds/linux-omarchy-bore/8212-xe-shrinker-backup-in-kswapd-only.patch.sig b/pkgbuilds/linux-omarchy-bore/8212-xe-shrinker-backup-in-kswapd-only.patch.sig new file mode 100644 index 0000000..30e13a0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8212-xe-shrinker-backup-in-kswapd-only.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/8213-xe-hibernation-writeback-gated.patch b/pkgbuilds/linux-omarchy-bore/8213-xe-hibernation-writeback-gated.patch new file mode 100644 index 0000000..b639c28 --- /dev/null +++ b/pkgbuilds/linux-omarchy-bore/8213-xe-hibernation-writeback-gated.patch @@ -0,0 +1,112 @@ +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -24,5 +24,6 @@ + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 + #define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 + #define XE_DEFAULT_SHRINK_DIRECT_BACKUP false ++#define XE_DEFAULT_HIBERNATE_WRITEBACK false + + #endif +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -32,6 +32,7 @@ struct xe_modparam xe_modparam = { + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, + .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, + .shrink_direct_backup = XE_DEFAULT_SHRINK_DIRECT_BACKUP, ++ .hibernate_writeback = XE_DEFAULT_HIBERNATE_WRITEBACK, + /* the rest are 0 by default */ + }; + +@@ -48,6 +49,10 @@ module_param_named(shrink_direct_backup, xe_modparam.shrink_direct_backup, bool, + MODULE_PARM_DESC(shrink_direct_backup, "Back up buffer objects in direct reclaim too, not only in kswapd " + "[default=" __stringify(XE_DEFAULT_SHRINK_DIRECT_BACKUP) "]"); + ++module_param_named(hibernate_writeback, xe_modparam.hibernate_writeback, bool, 0600); ++MODULE_PARM_DESC(hibernate_writeback, "Write buffer objects to swap before hibernation, " ++ "integrated GPUs only [default=" __stringify(XE_DEFAULT_HIBERNATE_WRITEBACK) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -27,6 +27,7 @@ struct xe_modparam { + u32 svm_notifier_size; + unsigned int shrink_active_ms; + bool shrink_direct_backup; ++ bool hibernate_writeback; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_pm.c b/drivers/gpu/drm/xe/xe_pm.c +--- a/drivers/gpu/drm/xe/xe_pm.c ++++ b/drivers/gpu/drm/xe/xe_pm.c +@@ -26,6 +26,7 @@ + #include "xe_pcode.h" + #include "xe_printk.h" + #include "xe_pxp.h" ++#include "xe_shrinker.h" + #include "xe_sriov_vf_ccs.h" + #include "xe_sysctrl.h" + #include "xe_trace.h" +@@ -450,6 +451,9 @@ static int xe_pm_notifier_callback(struct notifier_block *nb, + if (err) + drm_dbg(&xe->drm, "Notifier evict user failed (%d)\n", err); + ++ if (action == PM_HIBERNATION_PREPARE) ++ xe_shrinker_backup_for_hibernation(xe->mem.shrinker); ++ + err = xe_bo_notifier_prepare_all_pinned(xe); + if (err) + drm_dbg(&xe->drm, "Notifier prepare pin failed (%d)\n", err); +diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c +--- a/drivers/gpu/drm/xe/xe_shrinker.c ++++ b/drivers/gpu/drm/xe/xe_shrinker.c +@@ -172,6 +172,34 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker) ++{ ++ struct ttm_operation_ctx ctx = { ++ .interruptible = false, ++ .no_wait_gpu = false, ++ }; ++ struct xe_bo_shrink_flags flags = { ++ .purge = false, ++ .writeback = true, ++ }; ++ unsigned long to_scan, scanned = 0, freed = 0; ++ ktime_t start = ktime_get(); ++ int ret; ++ ++ if (!READ_ONCE(xe_modparam.hibernate_writeback) || IS_DGFX(shrinker->xe) || ++ !ttm_backup_bytes_avail()) ++ return; ++ ++ read_lock(&shrinker->lock); ++ to_scan = shrinker->shrinkable_pages; ++ read_unlock(&shrinker->lock); ++ ++ ret = __xe_shrinker_walk(shrinker, &ctx, flags, to_scan, &scanned, &freed); ++ drm_info(&shrinker->xe->drm, ++ "Hibernation writeback: %lu of %lu pages scanned, %lu freed in %lld ms (%d)\n", ++ scanned, to_scan, freed, ktime_ms_delta(ktime_get(), start), ret); ++} ++ + static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, + const struct shrink_control *sc) + { +diff --git a/drivers/gpu/drm/xe/xe_shrinker.h b/drivers/gpu/drm/xe/xe_shrinker.h +--- a/drivers/gpu/drm/xe/xe_shrinker.h ++++ b/drivers/gpu/drm/xe/xe_shrinker.h +@@ -13,4 +13,6 @@ void xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long p + + int xe_shrinker_create(struct xe_device *xe); + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-bore/8213-xe-hibernation-writeback-gated.patch.sig b/pkgbuilds/linux-omarchy-bore/8213-xe-hibernation-writeback-gated.patch.sig new file mode 100644 index 0000000..0a27dd0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-bore/8213-xe-hibernation-writeback-gated.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 deleted file mode 100644 index ff35b72..0000000 --- a/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 7b95eee..0000000 Binary files a/pkgbuilds/linux-omarchy-bore/9999-bpftool-strip-wformat-bootstrap.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy-bore/PKGBUILD b/pkgbuilds/linux-omarchy-bore/PKGBUILD index d9c5b41..488143b 100644 --- a/pkgbuilds/linux-omarchy-bore/PKGBUILD +++ b/pkgbuilds/linux-omarchy-bore/PKGBUILD @@ -2,8 +2,8 @@ # Adopted from the Arch Linux linux package. pkgbase=linux-omarchy-bore -pkgver=7.2.5 -pkgrel=6 +pkgver=7.2.8 +pkgrel=5 pkgdesc='Omarchy Linux (BORE CPU scheduler, ADIOS I/O scheduler)' url='https://omarchy.org' arch=( @@ -36,14 +36,7 @@ options=( !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 +_srcname=linux-7.2.8 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} @@ -54,95 +47,150 @@ source=( 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} + 0210-pm-updates.patch{,.sig} + 0211-pm-fixes-1.patch{,.sig} + 0212-pm-fixes-2.patch{,.sig} + 0213-pm-fixes-3.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} + 0281-mm-no-deferred-kernel-page-table-free-at-boot.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} + 0301-btrfs-fixes-1.patch{,.sig} 0302-btrfs-zstd-decompress-direct-to-page.patch{,.sig} + 0303-btrfs-fixes-2.patch{,.sig} + 0304-btrfs-fixes-3.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} + 0320-vfs-open-consume-path-reference.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} + 0401-psr2-early-transport-panels.patch{,.sig} + 0402-drm-i915-quirk-panel-replay-exit-on-flush.patch{,.sig} + 0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch{,.sig} + 0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch{,.sig} + 0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch{,.sig} + 0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.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} + 0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch{,.sig} + 0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch{,.sig} + 0444-drm-i915-vrr-dc-balance-off-by-default.patch{,.sig} 0450-amd-hdmi-vrr-allm.patch{,.sig} 0451-amd-vtem-tmds-links.patch{,.sig} 0452-amd-hdmi-frl-default.patch{,.sig} + 0453-amd-vtem-null-sink.patch{,.sig} + 0454-amd-hdmi-frl-status-polling.patch{,.sig} + 0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch{,.sig} + 0456-amd-dml-frl-compressed-cap-check.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} + 0476-amd-display-ycbcr420-only-modes.patch{,.sig} + 0477-amd-display-irq-atomic-rmw.patch{,.sig} + 0478-amd-freesync-vcp-code-check-hdmi-pcon.patch{,.sig} + 0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.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} + 0511-asoc-updates.patch{,.sig} + 0512-sound-fixes-1.patch{,.sig} + 0513-asoc-fixes-1.patch{,.sig} + 0514-sound-fixes-2.patch{,.sig} + 0515-xps13-sof-quirk.patch{,.sig} + 0516-rt766-stream-config-type.patch{,.sig} + 0517-hda-realtek-rog-strix-g733zw-speakers.patch{,.sig} + 0518-asoc-amd-yc-acer-aspire-a314-23p.patch{,.sig} + 0522-sound-fixes-3.patch{,.sig} + 0523-asoc-fixes-2.patch{,.sig} + 0524-sound-fixes-4.patch{,.sig} + 0525-asoc-fixes-3.patch{,.sig} + 0526-sof-amd-acp7x-suspend-unknown-revision.patch{,.sig} + 0527-sound-fixes-5.patch{,.sig} + 0528-asoc-fixes-4.patch{,.sig} 0541-cvs-nova-lake-acpi-id.patch{,.sig} 0542-media-cvs-wake-irq-without-claiming-gpio.patch{,.sig} - 0560-input.patch{,.sig} + 0543-media-laptop-cameras.patch{,.sig} + 0544-ipu-bridge-lenovo-x9-upside-down.patch{,.sig} + 0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch{,.sig} + 0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch{,.sig} + 0547-int3472-imx471-vana-power-enable.patch{,.sig} + 0548-media-cvs-sensor-node-in-bound-callback.patch{,.sig} + 0549-media-imx471-analog-gain-max-960.patch{,.sig} + 0560-input-updates.patch{,.sig} + 0561-input-fixes.patch{,.sig} + 0562-hid-fixes.patch{,.sig} + 0563-input-fixes-2.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} + 0601-thunderbolt-updates.patch{,.sig} + 0602-thunderbolt-fixes.patch{,.sig} + 0603-usb-updates.patch{,.sig} + 0604-xhci-fixes.patch{,.sig} + 0605-thunderbolt-pcie-rescan-after-tunnel.patch{,.sig} + 0606-thunderbolt-fixes-2.patch{,.sig} + 0607-usb-fixes.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} + 0663-btintel-pcie-serialize-reset-type.patch{,.sig} + 0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch{,.sig} + 0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch{,.sig} + 0666-btintel-pcie-verify-serialize-dstate.patch{,.sig} + 0667-btintel-pcie-gp0-cause-w1c-write.patch{,.sig} + 0668-btintel-pcie-dstate-races.patch{,.sig} + 0669-btintel-version-tlv-parse-errors.patch{,.sig} + 0670-bluetooth-fixes.patch{,.sig} 0700-pci-target-speed-quirk.patch{,.sig} - 0750-applesmc-cache-race.patch{,.sig} - 0751-applesmc-key-backlight-workqueue-leak.patch{,.sig} + 0701-pci-amd-enhanced-atomics.patch{,.sig} + 0702-ahci-jmb585-32bit-dma-revert.patch{,.sig} + 0703-pci-vmd-scan-bios-enumerated-devices.patch{,.sig} + 0704-pci-vmd-meteor-lake-mtl016-flush.patch{,.sig} + 0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch{,.sig} + 0711-iommu-amd-perfopt-support.patch{,.sig} + 0712-drm-amdgpu-perfopt-in-identity-domain.patch{,.sig} + 0740-hwmon-updates.patch{,.sig} + 0741-hwmon-fixes-1.patch{,.sig} + 0742-hwmon-fixes-2.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} + 0802-platform-fixes.patch{,.sig} + 0803-acpi-battery-hook-api-acpi-prefix.patch{,.sig} + 0804-acpi-battery-unified-hooks-acpi-sbs.patch{,.sig} + 0805-hwmon-applesmc-charge-control-end-threshold.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} + 0860-module-add-module-kallsyms-on-each-addr.patch{,.sig} + 0861-ftrace-drop-weak-function-locations-of-modules.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} + 8211-xe-shrinker-skip-active-vm.patch{,.sig} + 8212-xe-shrinker-backup-in-kswapd-only.patch{,.sig} + 8213-xe-hibernation-writeback-gated.patch{,.sig} ) source_x86_64=(config.x86_64) validpgpkeys=( @@ -150,41 +198,37 @@ validpgpkeys=( 647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman 12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7 # Krzysztof Wilczynski ) -b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97ab6c656ea8e5b29f2c4a8fe4cc143a626f4cca6b972a8105991e4c6905' +b2sums=('5326dde778eb945f282740ef0d8765b46fbd198f1209cdfd7b91e5dac1312fc01f8bf0b2bc67b16a76de736edd8ea21b38b6c91e696ec0c4b39746cd21a15214' 'SKIP' - '6d92fb81077232b8cd1ae500b3aabc71434792e750ea6126dc095e50d4278d3e42b3cfdc3c8bc693ea14b109f6adfa3a1ed095187fc5501962fbe8f049f867bc' + '60eac409f1ba50fe319f6b3aa5b0af81bc7ce5628ca41c1af346a6d5ef0fc9c577527742d1f70ece7e07ea410fcd112b32d64f0a05af5b532f27b62e5e7d837a' 'SKIP' 'cc2a66a097b5567e80c59b7d6c492fef5e667fc71a0390908712c69512c4136dbaf3d150b62d04eff960c796f7a94e6d0915d3b92cb336318b49f5f516a3bba6' 'SKIP' - 'a32edb39b4ee9c0378239f4998f477fe5371e6931c188d660874b360ca6d3bdcfff71cf088bed071365627d87458eca0be625e154ca3d1e8150e5e0bdf64175c' + '9bdc9b93b30431d1b788f8a44cbf7e9030b8ff2b4e0cef9ceb7b8405b497eab1526567f840b0fc643e25626312322fcb008fcfd914363d42e3d3bc3eea6ed6ab' 'SKIP' - 'f6c00ac2400580dffe3605d2693809396e18185f0c59b21b86f1f22a1f8d6529c51e8cc2474bc40f74a96631f415da51521a8b35d5b096e9b9d164c2755fb091' + 'c5a06c5632ba1eeb592f19b7f1581acfb7adf603b098d1d966359a1bb266a330a5707b64aa1f0e0346003e0b3008f46d8af669b621ccacd9f219838fb5e122bc' 'SKIP' '4651980c9d988ed73dc91fd440467de0a1de21efa3efae0fc006524c88af40173182ce6826c212e8b7faaaae1aa512fc755272ec4663c185533e65c936780db3' 'SKIP' - '57f88a64a8ec20794c008c9de70b3f01680ce9023925df8e0f2c7be69f2600e10276d54bd264f5b8b258e04d38cf483cbf83bb79df466f17fe4b6b33fec52a76' + 'c974858b6e01c8db72700a472722d88820efb8b784cb2facb80c5babc8e93f00621b954ceafa22033b9a8cd61a981afcff4a2f759cc4f66ece013a71ce1572d7' 'SKIP' - '050d38240747325e90bb86bb7c47a14573dcb297ee323b3d84f74df9ddf3691cdf32ac9e293bd237c9b09221065b002e297745f4b898a937b9347dacb2de7109' + 'a55d203cef25122f29ae651a8011f15bb3210bb959b459c521dd7d18d3d65a79594196ba3eafffc223114eb640cb41e8c36310600ff451b3cd7d6b852ba6307a' 'SKIP' - 'd21fa7cb609188720365a8d95a98c3286f2f32a772309e66913c6b16bcf871ed28ea1ff01fc15ecbb47a02def1a557512126e39ac0daa956a1ba89878acf5de5' + '28179167d761963a2a988e1aaf2f2aa0724aed94ad6f79232701f2e13d5c5e0a4a8396e2b8c5a56bdd91a5a325f62ad22a14370ab1f717baaeb913abbee2d6eb' 'SKIP' - 'cd744c8f861d2cfdfdc98ea6875419a298dcb5d478444b8e551a52fe7ce5279e2d977f8610783d79144be4927a8bf41b2f80113e6f7aa3ea02f54b9984446017' + 'fc6deeaa32b42416a5f5c92ee507aaead8d088ea9a5795848c96ce58c3d745d1b1f101916ded9d7163b52d3d705e35b5de9a1a8e910fd4cb100a37f53444b6b2' 'SKIP' - '3434a5bbcd2bd8b4e2013400bf4d771ad5c8295ec74f67ff7acaf917954a01d25931679a71ddb8995fd7703b0b9904db4e6fe5f384d3705cf4d7b80dbf5dba55' - 'SKIP' - 'c8b8bb1ec676378d862f64b23445f170f2e995f3d89f4ffc727c377128ace87417e50d58c39e62d62795924ab4c62f3defad64139e3d3b472b75f6431484e2dd' - 'SKIP' - 'c9c1be8f72cef610592b1815a81a34706b54f2ef4a188acbd31d98243129e2939fe0c2b679f428a67eac70b069c8a662c3629c598f89430930f5b8bb602aada8' + '775d7f30c8eaa56989793050ed0e856fae3cfbc698681004f5b12945d90802c46fcf09a400fd2ee1af983ac2987d4dfc771e4542be69ec38352538afc09d07ee' 'SKIP' '4f58fc77bc13fe9492fe4662e0a4b879a8a1616e271c2095959dccdffed9b0a866a4eb9dc3ac74fad9eed11c362e43a7bfa56d8ea6f571572a63ff4550c5dc23' 'SKIP' - 'd1007ee452cdf92967ec7055f1f39a3007a69842040e12f5063a1d2f24fd6d1eeaff2357abf96d0e330541a4781f93fecbcb116a4c73371913582eb72b8f9466' + '921a4b6ca25fb1a10588d7a3df65277cf6acf1115e618741486d230bd8eee16daef3f712518f98c8a49222aed468e706015eb238ba289769a464f5f33a21b21e' 'SKIP' - '0d3309140e496ad552d1a15d4fbf04b5ad03877bc73ae689f57554e91b7458578f2c88b6bbd37dd9fdf822a8d48c2cd6f961ec735ae5887f96fa51d908d203ee' + '486987a57ff24ae8c58953c01db6c8e4f4eca4093a8484b28b9a647790a6757a381631e13d64605bb009613985b686a0f98ef58b2e2f9c9ddc5b4d0de1c62de1' 'SKIP' - '13beae6f8d23a1f075410f50c09bc94f24f505571af1feb3cee58f732c0d649a9ce5939bc11642c4f3c8dfbd967939b2a0a205220abf06f10d559c4c43a946a3' + 'e2d21e10be9239260aaf41ec5584885555d3d5150a817fdc2b04001444ae07cebefd140b20e841c0988843749e850ad52f93b98d005d5b158e158454c2020691' 'SKIP' - '2f24f88d8f960148046c36222fdaaadfed2ca88c60240fa1d111430898192ca45aeaa93a3afa24ad02b11c4dc8c3904a92f17c0284396f2f43352ad934a35056' + 'cd6d3232202e8e6a44e80783becc1b527336027420e308f354b32b6de1f83de210aac78ee0c639ba23030b4a0811cf7728bcaeed08e9dbe11caf3edff380ed6c' 'SKIP' 'e59c4da548b2ea4fd6144886e3e2e765b3e73972121e9e73dbc05c066834b2b35e6ef4c17617e77efc90260566bf7c0910d2138d8d5c1c3b6094eeac9de64cfe' 'SKIP' @@ -194,7 +238,9 @@ b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97a 'SKIP' '31abdd15ae3f9709d6912c90319a0b62d8380e1ae1169575c0e8e419f8ce264a7cf7c72558a85719cd5a1cae7341007adb3e3936268999ebd5215c2ed288f22c' 'SKIP' - '4efb9ab5258aee01b07bc322b160d2f6e3571ff82c4f3e317cab152a919c67522629d56fb79d517da034373cd6924bc3a20c98073197c93590feb1a34e902358' + 'f95af54bcfaf17e80d9c0f60243d946aff2bbb796ae47c92a97a353f598483f03ca87fccefbaa47639b1018b82e7879dae4b8759e8fd9206b33d219560d682a7' + 'SKIP' + 'ff9a3e5df8457880b25caef9de90a7eb513ad8de99296ed503268749a32d8fa79da11f598a5fd80d28231abdd65a247e2aa92ce7c8ccc9f497fa483eee93aa82' 'SKIP' '55951c59286df43f069302b0b1036424812f80b45c5ea13219fce1bfc3e831731432c216288d722e44493c736cbf89a3e44801b5a4f1f242c17b5a89b236a5ef' 'SKIP' @@ -206,65 +252,99 @@ b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97a 'SKIP' 'ad2cfdc3d8de2193110325395e11015577f26ef1081307bb236ed73fbd109822dd44ea026a57c2e17233e1fb7982e78aa986b71c043f42887e6c5d7f51b84ea9' 'SKIP' - '19c3dd32bbe3ed7caec86e90a6af39b07eb99d5a36515cf061bb6979e5a403ca98a0e00e584a9f746330d663e5fc0868ba7fc98ab2df264bc863bdf22ef3dd5e' + '92cadbb0f7c288987838534f523f85a67d07db19ce27fd6c8717f73c02f7ec7b7b47276fea8b2a86593178dbc4f44e774727050f2b46baafdb7296028fb24a0f' 'SKIP' - 'ed2ee4849b715d80ba103fe090669062fc08c6864c64e8f8291f7f21092f0184ca5528bdd3a9318f077ef00cbe8cf28bacd1e7a9cb9f9ae1047becd930a0c42e' - 'SKIP' - 'f09e80ed25fdda9b2c79bd5ea3701985596d478b0b4e6948a1ea8fb789ef944e71640ae200442afcc23a9f0e07b34d83365e223b620baf776bbd864977e633a0' + '0b281a5780efd064a6c98a08a41eadb1286a2aa5a7f1bab33d3adaa82713f4676bd3acf07556f3617ab0649f95fa5ce3a0418da1b6ae48cb580f0d7d5d9752b3' 'SKIP' 'a51e5f2a5e37c350fb4d715ece644c565a508970c3aeb1d28cb6cd03a0c7316a1935e028b64f20a03818926c611b50ae0ffd77d04c676d604d303beed0f96e1f' 'SKIP' - '25dcd2c4a4287f26c01419b7d6430629d2827b6efff473361c68d6ec44e94e6d2bb8ab8215dbae5da25781a668f571cfc28b60c5693eb4ed629bf80fe2f68ccf' + '71e8a5d91d285519367ba73241d01adc3b61a2b7b21f8061ecc3749fbf9d87f5ca414809343fd37530bfd5226f919cc3d87dc3b4034cc5b8be9c720f8b4676b2' 'SKIP' - '8d6eeb2330a3ca7c522cef77bf7336fac9444a0884d5d101c933abd3a31a3711529a31f9a181d4795148ecbcc24425750587314ce28e44c9ab2fd2de8365e853' + '64ca5b3c45b579c51c04f1177c8e6eb25db4c76f17f7b253c5ed959c9688bafaa97da701257435b34338432e88071f04a8d4049ba66895dd3ba804173cb08b28' + 'SKIP' + '4dfbcdb23d7117b75202944547f7ce956dd8fa7405bc5ca4856135e2becc21d950eb6e1aaec082da4290ac6cffa402686c2c9137d4dc23952796ce3334b95ff7' + 'SKIP' + '51a7a4f87d53e4b3d25041a0536b238d4550fcfdef0e7de5aa75c26a7a9217f6f3e260670154b9fa6674470f72e5e09c76571e6224d8be4eb5dd4383fef64a55' 'SKIP' 'fd487cb02212ffdae5d8beb7d895819377686085b95658dcf597a534b017adcb65dfdf06e0276e7322a46dc28118803b05c246c22e7d3c361f1778ed9e12a3c6' 'SKIP' - 'e18e5086c531a37837f6e7df72ce10a38289b02de4cea376772d45cccb5aabc628fe4f3f9607f646903a7cf59b02692703e6b26800604cf958136987df0b4d5d' + '2c58614d716943cc1883dc513c2206e82f49574eca79f3e7dde21f8dd0f02f18611b2b2a444745df820c30c887d8263bdf9786a4a90b7979efee8d86633cbb88' + 'SKIP' + '218d01002d5487e2cb29f479f9ef9a2beb8226d2adccd1a9f932dc162a8502003db19b1bf74395b0239a2a15888131bcdc969b737cf56358dac6ab14d1b4501d' 'SKIP' '1b340a502dcf5a1680caf5320b3aa72aa4317e4cf1d877c1f6425e281a5668d5f092417e1f282371c0a211344f5c187fb3bdaf3f97c7e122951edf7e4c08ca58' 'SKIP' '1124c5c3fd5104c886c841ead1ae9afb42683388b640b1a86523dad9919c6bd59a1f78b07ee5a213e0dca77c636a29676af0592d4ed1735ea35f282f6f23e46f' 'SKIP' - '15855e9c28cb0abaab7ab606904dfc84d426ccab44edae59f0e1f092202b80525929f8d394d04ca1513d1d5dfc2cfc108a66a9c1cb42b9966c6417562ef20689' - 'SKIP' '505bf01a9d6b0926b557b673bb686d1a4ff73ee70539991e52272fbf58aad3ea25cf5ffc5c2d094a2d5e78b18a1a134f2ab530f595073a2d3eba9ff2c1418e1e' 'SKIP' - 'b33b46c37f12f7650a67c3731b87ff84add1ac8623cc96b52223a1ef9946bcaa990ed7c7036674e228335f57ec9ed421ea4d6c1dd72d19b516215d9ac2c7c3ee' - 'SKIP' 'b4bf4c4c0672bad91d7f8d309e741104918dd8ee8688a00d600379eba5828d1b8157953377c73d0b58050b4ff888c43013ede4e9c959be8d9c028a6143bf58ed' 'SKIP' + 'a5ae2f6c29ba850292fcd1cd39d7af6041169ad9d73151d8fcc07ec2e1da473f3ac801eda892082b615d7f19c2a3ac17bceeb1d100dc1e2ff4b30287bbd8fc8b' + 'SKIP' + '3b8e01822f9f97842c5f7d7211df876d46d8477321546bd5b8cb74484550a2c520ffe351ce0ebb7df5659d2745187081883c091574c855ae7361315d153bc557' + 'SKIP' + '90e189032c6a04ca809607a5c9a8e798ff2f3dfa7d82bb510eb608da961196bb4b53f35907bfa9ce4006fe182102900bfa20f4566b146aaddaace4fc19adc305' + 'SKIP' + 'b9f60ea91694fb859543cf895ac9d654edd0aad92a3bce6ba9cfcfc450f413e49bc8cdb08adc6fba1a46ef319c10cdc501e3ffe6b37de222b11cd1c6cf138db9' + 'SKIP' + 'f80573494f330b31af70f1a079100082e00e3c7e185bbc51928d09d2ad3ccabb3134c72a0edcef994d6cc517015190d88c41301cf73c73419c594977f86a2906' + 'SKIP' 'e403bdc300284c4f0f4302e501b8ccfe5e9af04e73a6070eb6615322f5f40ed6edad53af2ed278741f61a4e3811b92faf1af02f113dbcc2a19bde335a55930d7' 'SKIP' '5de29d0eb5264e24ab9228bea2ea4b549b3fe37ea67c045df9f83064f11c2f557b9641e772d28db59f8d258418c77435b66ff471c3695043f0f8275d5e7b1685' 'SKIP' - '3c2cd960103ad97ce35679f906b3c9be79a9744959c5dfd0ea338d8075b698790934694b2f71fde026b164c5246cba71ffe3cd5dfc1464da338136fbbaea2f1c' + '25a4ec87d1c789cab55e8ee6357276eb2e87b5e70eba2cd978b2c898e7b79de3fbc8cf5997fcd660998223b6173c98e440c853ad7c3e116f52a48d38622dfb46' 'SKIP' '2b33b11e48d8dd8d6743f26170fb99eae38541e3665e2448d91cedf729cca7c0278bfba1078ecbe88201eac4407ec5bd0292a97dbab9cfc67e42646712befc1f' 'SKIP' - '71934436987ecf6914e5c559445c2b7ee92a31c467552342094c93a59d32563aa9930e710556cc1e271eeb352705a9df9b3702482f87d045d132233cee4ab48b' + '5ef0d9561a570941752fd1976a2929eb25fb4dc16ff7fad09ce71e0d6881924f1ba8839574f7f877bd8089d116d689e33a54c14e2f177fe797dc86b305a74c0f' 'SKIP' - '9fb1318a3a181644c67b2adcac0cb04d1b88dd0e632751967239d4e8d4698f193795d00194b0f68505964c84572b67168dcbed02ad5bb7abd41e406a62111489' + '0415549ab1bf3ef0f2af4e62130b66e80d31dd3a1d1acda1cfcc2e4c926f25cf9449153736216a564cbd0710c0f0f1294607bf27c26790bad16e0f8956127e32' + 'SKIP' + 'a0a17aa48cdbf60db7a6d3677fae8a3388757b0f1de71043d8eb93028710f5a26aa57eb2011199b84e9689f7e4457843b74a5731c7dc14e4d1fca44b957bf33e' + 'SKIP' + 'ff1f07b3ad829d444b92c93fa63ab2c08277fa512483806d119a4cc75efbbcfc197dc982d9a146b7e9b94a9ca5bac95eed54735f607d7715cd52444ff0039e30' + 'SKIP' + 'a5670a422ae3d084f20800a5ca2c5615efaef4cdc58e466ceb25a8c56597e592cbf3eea7f4b8b28f1e7c79e9ecc7ff29c284d7be41de26fc6291df020fdd3d33' 'SKIP' '353b7e89a7044b7426db730d5b900eb347907aa0c20e307d2317925d6021a969a343649ede7a32e698dad60dd4472eb197c9578fb0e36fc354a60266fd680cd7' 'SKIP' - '803223224f22aca25dfe34d73e2bd49438f0cfa83b71baa545fa6fe93ee1a631b6b62c7324dc1c0d2d2ded94c36185a83af39b4f4a3362f554a1943ec2e2ec3e' + 'd70e2f90fd0630f53b9680a1a935294d0a9e86e52ef9a712905a7799827ced2ff5acfa27a51980cf6f8157d60d9831cc0d62c623f98cf577810a4cab02b334fe' 'SKIP' - 'b0a7ab192066c472f3ebc39bc9afc9fef0528ff7c98bf67c5b9cb679cc6d93eab3d25e91d8d74c9c52f72803949a3ade7940e677b9a3114605b966df94da93bb' + '88a071e8e20be14db31253152064a26961b74c329793b7fd0c81b629973bd39040e9a66189e3dfda31ecc189f82e65781b310603f498e13456b8090662f80b0a' + 'SKIP' + '0cdfeb8a469295f0d7afc27800f5109f5c88c4afda93056d4c4fc9601988334c32d287e1490966ae21bbe80c5e4f8b8da2cdb6a019ec72682521972840247b8c' + 'SKIP' + 'acc6611ef0605b18b3326e2170f32c3daad02b6472b4fd8fd2a8ba4f99a35b319f2d67b6f1f017e9beb6e8cdb8edc081e97382f3cbe7999d8ad57bd5e929a9bb' + 'SKIP' + '29f44ba6256ca05c8f40f0f1b0e813a5b3928f23e37a23cef9e7e148832f41d8ab4ec43b398ef21a06cc8588e4feee24fbf1f3ec4428ef1b22b1d43a776022c8' + 'SKIP' + 'f3437588231ac856c45eee210b6d1e4f31019480665ae31f62e0fcc4b9931cea4bdcd2bb95345cb9dea26dba43931ccc73e9c9ed209ab3281a1618378658ec0d' 'SKIP' '824089f9f45e2ff9e788d874bb9294b9208b4486fd22c56cb59d4d88cb996ff5a81ac22f8ff136ddffa1e40330f02e53ab759ffea7e4535c11d4f8195069966b' 'SKIP' - '696309582c56fad530a0bf2be12bb744dedac11ed69642b14eade0a26baf1ce52b36074b89b963b40c9c6fbc4857cdc71b2a6c6d64afcbb2df716bbb57579986' - 'SKIP' - '14212fca051d3e9a85f75616ff7f95bbd119ef8e5400dafd4447f70db2c4e9c03d971ec4fdfb4db2319d2613062d74f54dfd5d0a100ab0fa27ac6c05e16bd063' + '22e8b6835a86ad485e5b23ba73a4e1515242b06f42063c99d0dda1f9248ce222e003acd43a517dced198a28bbd1812ce5faf66fa589717734558783fa385bb13' 'SKIP' '7228e249d25a99f5106040b8ddb5705979b3a5af21ca60d0d43a9ecaf6eaccef6bde8ea69c896cffbf2b5963ec4f68987e69b184a8885c7f4c98d2b05fd59fbb' 'SKIP' - 'a54ac1c6d43fe88dee08884ed2e5bad0a0b6d11f34a46dc9801e675860afb7641b57384248a144a621e66a73541d73da11f71671116f1cc7f21534526411439a' + '4d5c5e2fbb5d8fe50a899bcfc629645d1bc9ef995c76611ecb38d25070552487d53ea59cb48bb3922d9d9965cc1f7d37ceea4f2ec789ed1bf37d69b831dea6bf' 'SKIP' - '2e486cabad0d45aeb760d031c7946e9887c710b29dbe9c16e5f4bae6a230031dba4d817f1ee25a0bbca849d1c71b2f84052ffb73c4a6c4350a6ea5ee83c5a40e' + '47015b9baebb1d6dd2a68321980c1c97da097825966f480c8569bcc9df5bef0257c51d5f7398960f28016258eaf1fff25bf142a64a01b829e882b4c1dda10612' 'SKIP' - '23e2a5def526fa886d1f1b05607bfe6b1ecbb2d6009d023145adff9eadc3135f9078faca6638966b586e81e4ab9d48d23b5758504d5a043d5f46e7a701d185fa' + '4f3df2be1b52573ef4299934896dd5b4a5b9ebfa1f69c92f125edee1aa2db2f99bd2e6cf67734e1c953e9413bc6936067e8ac619cad66a5fe8dbcfcfc7906b83' + 'SKIP' + 'ee01ba98e614695154ba62789874843e48ce2a4715b3e6ca0304da0526f705672bb29253e0c52dc8f8fd3148c072067232ae6f4e86315dfc27b1563073cb92eb' + 'SKIP' + '0963ee814b6f64903d67757c5c55a93ec1f5b16374af16aeff2e7e494254d19031d4e1422428f4f7a8167592b7750a1db87476871eeb5cba851b377e59911149' + 'SKIP' + 'dcb350ef2987b801e481b5cf32cdfbe47018939f6b43e45b6dcff87eb75cc1a48f25e679caa823f3fe6391048f216ed9d95ae79b9cac237041857458f56e7f7a' + 'SKIP' + '0e4156949673d16caa6ee1405bf33d432ce2632e433d0abea2f4e9bc5b413500573ca06c10b8dfb477d2a6c7b4201f66ebe81ffb05302e3771dd2f0c515fc5da' + 'SKIP' + '9576c977d2c95cc474e84ac8dd7683a71df38d1d6e1ad80065cfa78db8185c7a52bb408af9881a71767a1c7e3f011424a180d0cba574b14ea53f65a567784181' + 'SKIP' + '4834e1cc74f3902e8e88eb28f9fb1f2bc3cdcda6745083092e7fc39c131bb0b1a8b06d74e6112e5d4c97d14e8b56302096691005474e6e5d2720da02463246a9' 'SKIP' '5d82f32f7c04b084530722b9848254aac289c2a754a10e16821083a871ec14065dd9ee83e72b8e9e8ba2915f3fbaf17b0874df724002eb08a5f4f84d1b6aecd8' 'SKIP' @@ -274,112 +354,186 @@ b2sums=('48551bee71cd02815136fb8abe7da4464c2e17c89ef35cb0c0530c8b969fe12127ca97a 'SKIP' 'eb79554ef0d014b94bc72d4d559c2cc5419df96486acb3b6a09d5e8e9dc48db626ce0f90338c67fe873b2cc3c45c3f308c8080532b60f74a07c9e39e01d34197' 'SKIP' - 'f657143cdd9c937d78315f4617392e60f7c46f5cec882e060f740f77a27dd4c045878cb66e568a6d9ae74e9c706ffb3052817db6f327c87b85ff2f46eea678e2' + '89482f70ea83fde5a88554f54c866d11c98baab1cbcb7fa6fe0079187821736b7ad9896246cb3976ec9d471fc0ebcec6dab32b95b4928704e23e3f4ff935f221' 'SKIP' - '0acbd87a09a62af860d14f342d61c82c29ec1711038d4abf4ae473df187fbda6aa49386d88928c5dc4c3379e097478b1d40d9ebe9360e64d02e59161164b2a3f' + '31202d94636ee8a62214952d0d54eb12b8e1e264edd20c374678dd442d5fc9f8b3ffa2e94cdb6d27c58004a660823d62ffe11aa81decb5af166cc6912486b9cc' + 'SKIP' + 'c0551a31a3c63b70966c9f87eb852cbbf43a13f649bb14b6fd2e8ca23f7fb311ba55d94ddb589a78a432eb225cd2336e553d399787071cee9933f2078fe8b5ad' + 'SKIP' + 'e6f6460dd43c5a7e4d943e6545479438286712237c9feec357317b1431aa778684132b734c5c113f1e57180de366411542c29f62507095e7db62e2d9aa6ad9a6' + 'SKIP' + '58899f7c2bb14ee442c6cfedb9a354f729c03cefd325cfe71da23f33d5a548f6083d5ad922231108fb58ee00f7ef2a2d45ce58c5767079e3ae3efaff306924d8' + 'SKIP' + 'acfcf80889e146bde867b12a7c1b4a7b88a71c09603d58974b647d8066cc281b300b7a43c0e1820f22fafb5f7056522091b9c6c0072ae6a4835e603578c5f95f' + 'SKIP' + 'c446acd1c5e498f0972febf76bf6198b4ee72c6e3e4ef285862564f2467d7ad7ad74eee88191fe1fd887a90a19ad61a3945ed9ee22a2ff6902fc5a806788b151' + 'SKIP' + '8bf8f181ffbd249b0af3a8fcc8463d84daa3c4aeb567595065c311f29b2dec2344fdd240d30314eae2c3d4a4daac53dad34e8452df11e386ce0a028267b18c0d' 'SKIP' 'bb06066e90132f0172333d46be54ca816eb7d3230a193a6cae765d8edc50df4a6ba9fb7c0647cd6befc8c3bbbe1d96c285ee202812477d7295c1130ea8cdd6f4' 'SKIP' - 'f1a94986b4b3f310fecf981e3619f9787a0c8cb0b4fb3f39e5fe85ee3400e7e50278d41bf5468f65e5bd890358d2aabe28d3e11ee838ccbda2ac7af16be568d9' + '788488e828ac47a55ab78ad43251f7a2e5ad15bd5b5468bb31d4ee31e5de01a79b5680d5b048cb123eb3b7bde75e6b3d1e366e745009ed4129c8a6585d92b6b1' + 'SKIP' + '3bdf369bc73fad9bcf5b866043ff007365302318f42de7107fee47a7e8404c02c9c8fb5762d07ef10aebd2eb815c5483fc4960b7936e30ea5aa969a55f392474' + 'SKIP' + 'bdc66e50a9ef697e3baeee768f0f2dc56485afeb8a4e9f9ffd45f183d9614c33e0d0e72a8babca87963d1d9d701148eb41990730f2215a1c40e0965cc7f05e78' + 'SKIP' + 'a22e13b9bf0eac5d4190fd42bfde72ad0830cdbde8c0d309b2dfe9b0151e4c85f9f14f595ba3f36af479173ba1274f7700293c3d61dd913f711196a2dae8d6dd' + 'SKIP' + 'ebf13eb83afc051e3b9c84bfff7b326d73de6bac03f5b4d5996485d06a9cc87aca795b5c029646f804d62314a97e4a2b5da0b5f72f71556f33b62fd26529797e' + 'SKIP' + '8c6b5b96a3992c7cfbe50384fc0a4504e6d522c6918739f822b5bab29066eaeee7d1cc525b41a9b5b354ce020b1837e88d9bb940f3d2ba55a034aa9a845d489e' + 'SKIP' + 'aa24ab789db9badbd38db52f1e01fbc9d5abc09d812e2426ff281c869bbc0923cd903842d0c10ed8cf0e1a4695d3508b8ee4650f6a3eac3655247830aebb2c0b' + 'SKIP' + '1cf95a50e985ac37abd0faf86f4138e510cfb33af0ec172a2ca63f9252e6e4cba2f6c43a0babb11a96973bef3b7bf309a0f22ede26dc437673fcc77afef7fc31' + 'SKIP' + 'd5ab07d8a1db9191fc3b0d1f44c0237e93d662fb8bf04dd55385d8cded9589f66c8212a4d37c651c505fcfcb4a26da072c0be71d97fa0774b4724ba925a7af8e' + 'SKIP' + '7da8c7cd405efee8b37aff9916215b2d7052850f734b693656d2b66bae485661484efadc7207fa00dca5aa02d5e1bbaec2df9abc3e583f280097a939fc32096b' + 'SKIP' + '36fa6403c6519e064972afa0905cbff55f88fb89cad7582a89079f657537458da3d93deae474e33e018928d03090e1a373c6246b180d79f43366e16ccee533f7' 'SKIP' '44dcaad2ab326ebee62d3e63207a161dcb1b63bdb21412b5624ca7875bb6e99997528683a633d7dd526dc5ae9408e73bd066e2a1e5b34ba5c829162df2265346' 'SKIP' - '162a2c2104d5f036657ace6eef2ac2d533c0934376fab2521ff1d3fccd704e3d08fa4f6162e9100a844db2aeb57fa3ce214b8058bd227f38b058ab7606ee7816' + '69c1d61ffec58e2d3058d9e9e5fda57a089397d5ad451224f75f1cc2816c7f3fbd650b31bfb5911157fd364f77cb6f720e0825ae3deed8fe9f03576b8f7f1b31' 'SKIP' - 'd50d5a69087a5ba6c416a6391232764c8281a8094645175fdb2cfc1bb011e33335ae4e96d79364681bea61edcb58ba9acb491e08c301d0667481759d0ee1e183' + 'e3d60abe9fbfa046409598588dd23a9b16439474595136526e3169ff47982e011ede263d403211f5f5692ac43cf88af5bbf0d11b8e7e959478305c67687c525d' 'SKIP' - '539b1d0e764f0ace3e572ac49f20a790183155ddc0bcd3a8d040f44c9891dd36b1a0accb8c0288267afbd33e6ceadf6fc3a5e3db40491c695e0c1d3dd64af40c' + '1fb895cb1deb1b227515087e12fb0f7fab70b0db25a1a535fa3c294a5455af4d987d02c7c8ff24aed41359055d1383ea90bf9709999382610ed156dbc4069086' 'SKIP' - 'f5cae6eabf2066d5deef737a15967d5d11340de34b181f3b3d841023507fc58af2b68926eb04bb342e1adb857165400f862f2b35ffec4204a107b3b22a1571e7' + 'a2fb510d537b9afe8719334e5578a79a8ce7b021ff76c59830c749dbd963c38b7dfb60a61422b88f76886ccffb5ad5cb456b307a14ed68a5c86981eefd4fe3d0' + 'SKIP' + '5ecc37fe83f8dad00a82aa40a0c75d7518d6458b2f22d77820a28a34d772f1255cdb2befa5e525344d5deabac8f5dd092434a9ee2a9c27ccca5c5c231790c017' + 'SKIP' + 'bf138fd2ba2618ae9ff64838706c2212b478513e04b54ca3bdd8bf97292a8a310d73e0cd96533be62f139b645d7ac5de22a4c3bc1fd9f67e807bb862e965ea08' + 'SKIP' + '825397852f1c25b18c37d25bc53a7706af1eedd97cb9cf85ac590d1044e2254d06c4639bd54cac06ad42534399081b4ca17b0b10605c152ed41a22674c0ae42d' + 'SKIP' + '42a4c3debfa847f46c26db0d3e7cb1a540d3d15deae359b582eb2c71514c2cdb36c28c8c57b22dc32d0cee4d00ad7519da01c66127446692c81ea7a9178d722e' 'SKIP' 'ab28a1f4f1756b8451840566d8100cb3d0b498b13d006593c72829b9783e94900bae78ab47c576d156e1d08c56602274b3f7d5bd885de17336623b23c3e4f255' 'SKIP' '8481f23c33a84d8efb798f4b83f78011f74cd5a3dec428b177d7e600922836c1eec61c4238724038221d8ca2e2cdf20462c5911085da3461aa315f7dd19d96d8' 'SKIP' - '19ebb39f3e53ce77dc3522d15c88270c5ca3fdd76fa8bd417852d08a1d85b30bc6eed3e11c95392b694dfc59bb8b43e41d43df4954f00593c8fbe50bd9e199cc' - 'SKIP' - '4dc42e9bfb5ee0f952bfd7befc9d333044aec11fc3a3852d4636f2e38ab139eb92c497eb3e30921b6ee8ae7d6e5834dc334ad25419bac2c875a9fd7eb7945f9b' + 'eca2778279d88085b6bf7af742773ad533906f98b146ba2526a9e00136e154913f233be728105765b0d2fc2774068f670a4ab1b49d8a7486d20e04d116f71c0b' 'SKIP' 'fea498b5c5ada6d7f57caf652b68f687081aae8552d2d04cd9e96764efd1b896208671f4f501210bb557710cca3ceb0c02ac2b5da6d4c04c210e87ec2d715384' 'SKIP' + '39650b6ac7f62174d4b24e5267f9c6042c22452f16c649e032570eacf428e83ef8e10959fe54adf8573ac1c84d6082eb713ae5f173165af7d1f687e5d26fe6bc' + 'SKIP' + '3acc8aeadbac6ea2dd5a4fba4c10165dc1e6a4a55b838bff184fa46e827df67ea4e9a8535e17ed8cc80cc5338252306a76049af5956dc767bd591729b2aceb4c' + 'SKIP' + 'd442bf561a4b5dd1058c45a8181157feb268a84da94ec7f260dc6d10f7e0ddb71f1097bef945509f3d212431975ad3197f778bcc1c93ce1829ed2070bcf6bf9c' + 'SKIP' + 'c966ed5918d71acb91e988f3776d84db0b072f42e3e3f05cc178fa92a0d902fa3e34beb790c963042b52bc500d6ab9a2802b84a8411ca02cc1e28d361603bb2d' + 'SKIP' + '60fde203ea68744ea9bb8b81a18aedc8a4de8a985dc65bfd6b2f6b43210f87c2875e53eb61f4498c9b236892abde92da32755b69ee5bdb23f079079f22b529ac' + 'SKIP' + '56420f41da60382143b4df2d1fd58384180ca3a179b9f1b5082b2e4b74976ed13a29725c622e61c698631d923384082b43c9f703ce417d09e99556e4600f981f' + 'SKIP' + '5d880f3cdb32ed6fb7ce26ffbd1774fda56641d9304b5372806b6e559dab31dcac233cd6765a0b64bb6afe99fd9602402fb5e3b8c791cd1a9eaf9af28ca01bee' + 'SKIP' + 'a6098df3b653702e2689ec25934839c8a986b0e2f113b216ff4f86c8c5d530584f61d33bf4a027d1b304deb989406a3b342f13e11a66cc91ec69457c5d2e6f14' + 'SKIP' '4a4af88fc8a8c37ae6d20b35d19e60a17672569d5489be120edbcc62ab3ba4bbc43b78cd0799b9fd73c84b089364320bcbad6fcb7128c2cb06d15ed984d6fc8c' 'SKIP' - '0959ca78ff31299adf1091f4aa1b32cd2e9066aba5e44f0658c91bcb93fe8714f7b671ffa2cd498ecbb6c6e643ac6571bfb193034af206599122fd7c2a3c23a8' + '411fd61157830b68da00e19be0d0db7ed61de97fd99b46a31c3958c5f66885265521e9e099c4f3404975db9de9b178cb698dbbe1399d81bf13ecff79b9ebd8b8' 'SKIP' - '03ca740048e48c5b6948f972f7a826240cf51c9b5c8645d1b1c50366e78cbf45f5b45b7b47f87f231b50aab46ef2f8fd78bc53899ba1c319a19f9f61c7cecb90' + '49adc1fc4c68da5c39d96746007d19107ae2d293518cfb36f3742aeba7723db31660fb1f7b3579e8fa88bb53206dbd1e718934f9b37e87925c6c0b06759e77fd' + 'SKIP' + 'f8a67f01fca5614f0eb3bfef59658913fb9b88b043f57c8bfedf04ad79912ff04d5d356dce8ff453ca001c151ab1bc017dcd7daffd5024e2bc20b087d08ab8ce' + 'SKIP' + '561a635f88fb3729937e1632d736a8981002cffd57cf16edad538381de8bb6a583f48b05e4d2bf1c8b6c03aece2495ac2850c1d66c4b540af2b88e1a9a0d9c2b' + 'SKIP' + 'da96dbbe3b6b4e189cf7bdce14647bba07bbc5599ce28ed385a1c34d237ee7ae3ac595d2552444615f2b15c2b7eda42f40743d5bdebd443e3ec31e5743f59056' + 'SKIP' + 'cb2494c930b2c2c4cd6755494339c5f95ace87940b2b2e51928d98ea63262e4bf7a4245f629b8bb06e7592f45a0c379e5583c8695563011009fdb07f965d7c1a' + 'SKIP' + '6083db5b92aa46bf54144b4d2fab368916974ef01334fbe934bded3818c5dfb4fc8d5d0e896bf1baddc29aac1c559f80dabc727082a757301117843def8089c8' + 'SKIP' + 'c25bff6e5be62aca887ae0781899e37a39e1aec90f0eb934ec5394abf560f4d02ad1fb3973a45d00058b46555bbc8d9febfcc8026e210f53a8864a91b3b80a9d' + 'SKIP' + 'e70e0077249bf008e069041a0239c6bb63075044cd0df3218649ff92a6a2d43467026886f2a6a70c70552268f6936764459d7cc0817a103b6a0f74c37c52c72d' + 'SKIP' + 'f1f853a0ef095a53080629a00598b7b1e0023141fb1890622ffd8efc4688afdeb87be4f1a096b76868840d45c10b3c88bb33f44677ff70378d63ec81eb3110e3' 'SKIP' '1b63569b589e994d1869e2f8eaf5d4ee225fe68862ac5c848e46544b827ca5fddb4bf060f5f39001608fd9bb284eec005f5f9b095307d3a008c9ea8957c2fad1' 'SKIP' - '073514dabc88f0206911eb27eb36157e425849221056438340b22ee7bfa6997b58ce7dc4470b09d7ee96f6374f117d91cc19d9032401b2355d1c0aba6fc75771' + 'f6430036af77a31a9ddd16200bd3c2c7b1b7e46475c7917dee91c835766be046a31736123ba8429ca2f0f8d3b93ef60522873bbec45d50b954cbb8319e6f25ba' 'SKIP' 'd7dffb68fd22db7ef41d1ac193dd297fb6150f034f257a44e6cb0a4848dc4a94e328c2e26d5296a72993e2db75d09e3ac46456d337a5bae767fb266082d3cc66' 'SKIP' + '4e4792acaf0455f41cb1541d20d37e7a39104aa2dfb505e9be29dc3e7fecf328557780982b32cd8f67f77594542f5b8104766a4e8b5ce2270c9c601728363b3f' + 'SKIP' + '00417f024d2cc0b0fad28dec7f11c3054f5da7bf8c353d06fd1086be1926eacbe07ba924380157f270aac910d771f909f34beed0f60106f6ae60c96f58423b4a' + 'SKIP' + '2c254a2df00e79f03b51d13846f232073e9276e022c7bf46bfe810cc98456d481535152afa9d915e0726e7701594453f9363b6190b8e616db203d176a08fd31f' + 'SKIP' + 'cbfa20431371f3332f28013bfa21beef21a5a6f616e09fa91e9adb42899cb2e1880e27cf4b90bba980bce26710af3ac3c1e49054df4e48051bcc4612732d3e9e' + 'SKIP' 'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835' 'SKIP' '803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67' 'SKIP' '4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d' 'SKIP' - '2b007e58c0541c6ae71ac07cc8c0762343a50e1f3eb52db5631ce725c4f074f41c639fbf6d8695daa78ee1d6a8cb9a0c31d0a9d9ea2a3a10c05f7f6421dda184' + '335c0c1164e9a2b9a9a252ddf7418ca0e6493f2f0fddcf3f01ae20666b6cb6713b8bf7a2a165d37485d2107b6c00f42b201974154c41c15c4456615b3bb0d78f' 'SKIP' - 'd3b2c0e4fd9f26a0eded375522478f8e9403232799d233eed4668920f8a9942ec6bafbc928f14459e4736f63a9a66d3ae2e9f70dc810145acb5fc522b13ecbd9' + '01a28159b9c42e6b52147d1b061e1a2cb40ee381f8b6f7e2db3f94adc160365bc0dee602713cf42745fd2275b822b273df25d739d7b77f03273922edc746e50f' 'SKIP' - 'c7b0be7a05a304e9993fe8ebccefa44944bf9d27dad9f7b3cc7111221bf50b2db71ca51cafc89dc03b237aef3aba7114543c8475cc393d13e61726e68898d3af' - 'SKIP' - '4008c7443bc221a142a939deac86918bef8f09ff1ca99a899586b13120edb8007450daf0b9d143770a9f04d616fe96f527b6aac160be45e5cfa66c0da898039c' + '42703ec988c2b1865fbe4eef67840f4370958e3e874d9f8a03a46c43619bed7656c56cae0ef1d8cca5340648b47ba7ddf813d25d9db3307f10146c45b8e34ade' 'SKIP' '029d9e899dd9e7bdbcacee34ee9b468e2e2a8f726e38d014891c8353f6afcc52a58c7d9289fb0487cce4d90c8d51f578c27b1dbcdab42de7308f50b7baf27877' 'SKIP' - '9ede433f4e01a5a313a133a8029d64250596582b93a33e434b8e2ee5bc53ba7f91947e1be2aaf57776028cc89e7ca62cc90067ee64650b3e503acad858777a8a' + '343b48e982195e3f07aa95dac09d5e09d5c02f823b91cc4f0775fa9529e9a985923a5af48067a1f071b99613acfa25b18e3b083e094f3cd97f6758a495c91c3e' 'SKIP' '9ac15b3542629c5333cc7d7f53b4f2995d807a31331b9e04d3da051be0ddde7e8c3cbc79837ffb49a4d364db1680cdc6d763465fa56bd4d26b16b765985babe1' 'SKIP' - '25fc5f03732a584c864ee2ad20a74e16777998b0b34f880bbe9d646cf1944bac1f276bbd62515475337eaa81c0f398d79888aca6dc18bcd06ebb7cee9e4a1d8e' - 'SKIP' '70e35154f9385156a11280294cc254cb314fd89653daedb46546a6bd64c550e3fc7e7a8df3e653c516687f0317bcc710fce82b4d649472333c501ed85fcd6cf6' 'SKIP' '576fd826f28d945782840865f3e5b7c87d7f6f91f08e473ff0d06a37caa2f89cdd9cb4a7aafef5551363aab9c60e887ee794d5b6292874c12baf88c23ea5668e' 'SKIP' - 'b69cf36cc5633e507866e67f59557646dda7d6ea436b145f3f3356c4b12e1300c94977a7d1214835f2476c94a30dd672076241b920d96d744fbc199539df506f' + '01125c624a4971686d02a23918e3da697645a322e5a7638159636d9a16c82f815efdad1d8e22c507518dc0b92b6842277637cb2d0940f4eaab40c0faa77d8da6' + 'SKIP' + '322e4e255a2b900ad224ff376b00199c40e26fc5407e15dd36c1ca4e51c7ca0431397a69696959d1c1910b02a4bdac8efbaa20f6f6f6dd5424b711c149ea367b' + 'SKIP' + 'ae93d961d0eef41ed39c473fe9478b62fab13ba395333b3c11b34b7787904a3d2684520ec76dbe2603dd96a146a68bcbe98da1b7e4cc1e659246127384bce22c' 'SKIP') -b2sums_x86_64=('f69a306213a769710e3429817239746406e115af29f34f37514119eb49f973c86953379b68d3df89700bef7f86e3ec43eb91d5f5821f2a8eae7f28dba589d265') +b2sums_x86_64=('a1961cda00700926c9618bd311b83bdbc1a392c1dde41d541de9f091b750121cc879560ffd1c398ab75078588e0677726eedb84529a13d66be934c85be5f7a73') # https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc -sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' +sha256sums=('12e8d5a973d1ad7c5a5c69882e4022b131ed715db7003fdcd760ddf8c3e51941' 'SKIP' - '95f3e9209629044028373af987dc0e270a5a15acb8c8e49c5f05057220c75fe2' + '777e4bbfe44531c6504fa646ee74e95e8ff11f264a3fa281b528e25960e741ad' 'SKIP' '6cf74fc3f28a751354187b6cf610c3e43cf9088b5f730b68dbb575828c3a7478' 'SKIP' - 'daf0aaebff3cf4679d0bb8137ace6c9a2de62f75aa4655a0a02ddf759f3c7f26' + '5de3a8f11e9217c9b7815f9348396e8a35f68c2d440bb4fc0746a3e1401e7ca5' 'SKIP' - 'a9171e731d08a454a50174889af8936fab962b33c37c67169b0cdf21b0b80821' + '58f21f8c48dbbb1295d488815aae50003155ec9ea5633ffbca4f0e17b18e8803' 'SKIP' '413e9994d35bc722cfe45752ab60a1bd8454443170d80987e6a5be6b7b745940' 'SKIP' - '01b9087c0cebf17d0ffbbbdd6025c9937fcdfa00c8672d310786ca3c3ce6daf9' + '19cf60d05f912155ce54ad6af70b933ab63a8277785efa03695851089d87ac29' 'SKIP' - '5c776365d391a15c23afb3969183449852f795f42355629ac563abf7ae5e4735' + 'd7c247f4e9f84e05b482c0294ef9053587cc50e46e29962cabcb27f97aa2277d' 'SKIP' - '3a6aaf3b29eb5b4018c5703626311cd1c7a61bd7100789633efc6a79a9b3247b' + '064d5c5429b491e5b62e29cddc7b31a7cf29a4b66597428ce042a017deb2d2c4' 'SKIP' - 'b28d9b548407f0da4280f4f9a6c0e92db6f3a32a35c7668471b92096eb7b093c' + '574b3e1152b16c27b281c56564044c38fc80a82a591acb41e99fbb23a658b03b' 'SKIP' - '9492d7db11fd327ff06a52cad945bf8ff6dfcabec72c435bf02b4432ee67475e' - 'SKIP' - '66af19c43c068144699183a17b67bcf4df03578937c4cffc5d8b24e2e2828f7b' - 'SKIP' - '267506a02ac723ea37adcc7c851051edbd1ed731c6aaea4c9e590aadc207855c' + '04350da18867666ef240e3a1d2d69281ad9990412154408564e2399bbc96628f' 'SKIP' '07baad05112a47cb0aea81191502c87e496e13b4f23a6ec36ea65bb4219c35a8' 'SKIP' - '720720067a21e8c57d2619f4c0b46846ac060ec6db97088634d62ab8628c70a9' + 'df870910371645a95140a3f92424db43c4e1a92bd493393e598d61c6266f1c7b' 'SKIP' - '3d1299d19aa9fbc96d25f67154009813e05e92340340953ac8355649cff837a4' + '3d5550935087a7d82b527227e8f08ed98c2ffdb830b526c09e30b93137b3e607' 'SKIP' - 'dac26fb42f5df71b30c46b3c5894c6016af5a283c57b45173386767ca53b1bf8' + '2351136c78ad2dfe68e34bf9a70d377724257f6239e68bf8f501637b607af6b2' 'SKIP' - '66eb2c49dbf708cc781d411d0e44ed613480f0ef66d522272929d9cb7bb21e93' + '6c93475fc28d211ad7c15c691ea882deedeea8c0defe153cdde0a2a74488c7a0' 'SKIP' '718b40e15350049e03c0ed281b9257fca5432b8dec2e0e145e8b26507ee8892d' 'SKIP' @@ -389,7 +543,9 @@ sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' 'SKIP' '7c25d0a53a115bc1f072ff0cfdd9aa88858e754ae8b15026a2582656e8bd9c43' 'SKIP' - '79799f12cdf42de1cf6c5d483896b439d77bc2e582e10c7215f5375a76612a72' + '336d72ab830bb33ef2a23bda1869e1fc071af64b24779281e197073ab70c851a' + 'SKIP' + '829300e576f418e64101a20d87f30f7c90798cd6e179bfb72768a83d99fa711e' 'SKIP' '5e3b455024fab2855b590091c9d14b0d12d5363f3d4bf025b31b9aa88b65eba5' 'SKIP' @@ -401,65 +557,99 @@ sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' 'SKIP' 'e1226c836a9c2fb6daff109e56ba21c13fd198e109cbb181de920af04fe153e6' 'SKIP' - '8d60f7dd26ef904419aa966427a9af2f2c9a079bb092ea0b58274ab905f6047a' + '2cdd81f5520b935e1d99629fbd6e1cc9d1351b301396316f37946b95084f1a26' 'SKIP' - '258597006eb96e1fb185ec9303ceb48148eedce95608b57900383f6df59d2989' - 'SKIP' - 'f935409f9aeba3314adfad7db1b3d895557a07029a80d10969804e49c0e270ca' + 'e65c2645b0d01301a91874183faed44a390b7a402d2dffd45171d210034dcc93' 'SKIP' 'd8e64b9da3beab7c33832c019da50d0977b39dc32ec1f649a7c3bc93a2fdb9a5' 'SKIP' - '1780f66b157de6d249301331e31b7505f6e6fa26686fa4c618623567e4b758ae' + '5e2515879d58eba10c861f73318f4c4b478b8dd592680eb58961aa4065d4c3df' 'SKIP' - '2501c98aaea6a53eeaa18d696f60b677e710d0f2d89f7525e47ba806de1878b3' + '874efac3eb32970f3e06275adc072a384b89ff69e2e5b7452d6146a6e89049b0' + 'SKIP' + '43aa4a11d9d48f5094b4c0e807d1d31598e861a50695bdfa52f19819b5ff5285' + 'SKIP' + '2b49e4c98c5d704bee0a0e2b38051d59177161bca7b0188e6c0feb555a95fbf1' 'SKIP' 'e6b770d37e80509ed7ec5d8b59b4d2e5d6dba54ba659d7392de3b567f1eb6e54' 'SKIP' - 'b8596b5b5b546fdc309e10326b0e2ccea5a0538d4919630ed2845cded78580aa' + 'a9b3ef8aa79488d1cd813507e040e0762d4c8c5845b7f4ecc29c6cea1ade8056' + 'SKIP' + '264d49d74eb4006c8eded08e39409045032920d76df035aebe8a1d0939d4b7b7' 'SKIP' '5cc215215fed4247c2d6b9732deff586331a07d54c3b00538a81921093a3a1a1' 'SKIP' 'e303da14a3c8a15fd1cb45394138f03dd16e66d2ac0d1ebcd933977df937d771' 'SKIP' - '503eba8d7a80b978fccdaac798f57cbb6cd2b2b04fd04c46399daa004b6646dc' - 'SKIP' '1f0c958b64ba48b8dc8b5548e1ad1934b8d4903fc16fec15014759e3681ea275' 'SKIP' - '47994d576008a377de612ed77e6b2e7bc6b24ad3a30a6d157646d380ae323142' - 'SKIP' 'd9eb3717ba98e270b0a998f8dd90cbda072f63b6a6ea8f66763bde3ea64e591d' 'SKIP' + 'ce634014dc0131a44c3c95a8e9cb0a9de433f59d73fd99d3e3297adde65fef71' + 'SKIP' + 'd069e594d170adb016c5000d9cb7eac12c3c267a6b60c27afde7f046dba0509b' + 'SKIP' + '78feb2e2fabccaf954fdae5b776a22cec4a3f4135fbd24f79596a5274fc0129b' + 'SKIP' + 'f864c7daeaac0bad482bbf3853583d8a6cbbf2bf264562e397fc26355ffd5504' + 'SKIP' + 'c3a709af1589bd0c467565dc66ec4c01bed9c2b94314dde30c839084e594557b' + 'SKIP' '1e2fddb2e0c5d184a2e8c774886cf9be2f2f292e4f05832b50e5b49c48a7bacc' 'SKIP' 'b9b8771b6a8bd7128f4344ad189e32c155abf72abd3ff646dcf5c78b08eb94c4' 'SKIP' - 'f62837e8ee51070f8139f718e0e80a4ce0c0dea9ce2328c10c63403aca5b3d2e' + '34991cf6cec6c22bc1a55a76bc5f72b405165a2f14ef977a0446df7c18bdfbb4' 'SKIP' 'cae59282e3d1afd69f4f5dc00a180c712ef464c98b8fd7ccc9b03de944dc79b9' 'SKIP' - '5a74c8b2ba11e5876eeeb5ba530ae97e384b48943fa7a31e13396ecb82c5a8ce' + '3b06b5285923c251b0ebaf69911875f796cf7f1d7580ec33bb218509fea313a7' 'SKIP' - '8b954ea37a2190022064c82688bc8cb1cf79a90598f078035be7d4041003cda9' + '15609bef72571868fec6b8971f53b74441e5e93fbdd3c677ede963d0719cbc40' + 'SKIP' + '1231a6e23557936b811ce5dc184c23a7fbd268a20d582b6820c67125df8f07c0' + 'SKIP' + 'ec12a77671afb85f8297c86fc97f8314b4f00a1630acb199af12e3550a9d4ec8' + 'SKIP' + '1b1660064a3dadf2bb3006b140d1c7016a1ad051f27e7a4d6d9f3f40d491fe9c' 'SKIP' '40ac31789399a5964c4fd820d4cdc7214add91109bbcf4a614619e5d8b367441' 'SKIP' - '707460d08c15f451d0a0e567c6e35522f3e1409bbe3bfbcf7160f33af06391b7' + 'ab3b968d8bc2073e8c53478aa5d5afbadc5329a22b1486a014e5d0d84bfcf752' 'SKIP' - 'eebbe50deab379d09b5149ef30769ad6dfa11109696934ee66b108ec406da942' + '2a2a81e647aefda80192d230cd1dbae2aeb611f66b7d6c83694b9f9f8e2befdb' + 'SKIP' + '2772f0733267d12f36ae2a339c9aeaf0a133a20381a6b73e0c26f2ef92996ff3' + 'SKIP' + '3998c774884fdfc94be3da3c4c3641bd4b5aeb1878d8ef13bf0e27cd9f2e9994' + 'SKIP' + '0d5afb79681a948a65943f557d143c15126e2497e71c5ff50ac13ed8619a67e5' + 'SKIP' + 'e95f8fb6d116b30c2b86d9fb51747a9f9b4e5f980c1a3b627a38940db9d0e521' 'SKIP' 'dab92dbc6eac02771c72801d94e6a53aabcf629a9cd79e85e0ce2ba44a65b06b' 'SKIP' - '30e2fc90c950869ab2fe1535b609f35dc1d46d6b6816de4afcbf687dbc224f0a' - 'SKIP' - '1fdf8c4d8ee07c02883b827a11a84c1e5f70570545b8a4a232f6b59dd02049fa' + 'f55bc6366f8b9ffef744b289788a19e8604239e84c1af8b929a6922cf07d223d' 'SKIP' '0057dff07a3ce9c7b7085edd1f912958e01ce8f7400151d55dfb74c0c6a084e2' 'SKIP' - '2762f72620781a672d9a21bcc591b862b04a756b271cd1009a7390eb1b77635e' + '9c4c16b9593449ce556a4d6325e5e2205cb1698f9000a967ba7df118395b1ef0' 'SKIP' - '541325476efee83232ab8a6c62c14cf3c3c957c49cf053ed2869f7780f86d032' + '00afdc277c53dff0898ac86fd44ead020e007373134d7c7c333285a9f1dbf786' 'SKIP' - '8a8eb0934424ab6312f26e03a06be5d0401360ee60a20a87b11f7c3c6590103c' + 'bed39a5e6bbb7cdac3f56a883a4684b2a63f3e8fe2f8cdcdd0b89b374b8d2a21' + 'SKIP' + '75d7fc371ba9b00a78af2e4b0fdb7cf79bf25a6620c983662d6b2e90cf5ce8c0' + 'SKIP' + '0fe32924142868879ac9417c67b0233b6ef8ddb0163183352479f5163bf7b5df' + 'SKIP' + '7444186ad4f6d8e2b52a3729064f649b035af6eae4f4ad46b9768c006419fdbc' + 'SKIP' + '94750a925a726ad6631d0486e7f3cb3796b3941715b188e790fa135d0aa5734d' + 'SKIP' + '143193a175084d65035bcfdcc81e9dfef2e794dd56d637556db2c73b117f708f' + 'SKIP' + 'f18821fa9527c7ee07a49b9c1c983a3584d5058f9734b520b5d51cb485c93f17' 'SKIP' 'f7ff13ea46359217d696c6ccd49bc3d74c441ac5893d5151d95138017b0d10aa' 'SKIP' @@ -469,73 +659,151 @@ sha256sums=('55ddf0df8325d9dad96fcff7bd93977d22e3f50af06527572af59b77c7632b78' 'SKIP' '7245c6e2cb5c0b0ddf800894f087770cbf8ccc6f8b3f53b9a21c704e9dfa42c2' 'SKIP' - '135140bce40644079be7743b51f8618fc829a8868ac53137050f76c45eca4061' + 'd49629fd003900f13418c05d8c2fc37c8dea2fd8a15debb5b863008c9c46021f' 'SKIP' - 'e6ec816ae309445c6c11e3433c04d0c9100e16c0e23268226d13c19e7be49edb' + '411fe33ab7021dcf73b02cdc80a64094a511333c52dd754e54d382fe1145de08' + 'SKIP' + '5104ee45fc11994b1cb44130811435c84fe4db532a2fa9a722c7b03dbbdc8bf1' + 'SKIP' + 'f7617e3c4248e015f6f174e7c8659299d831035d0fb3c89acd1a858a202f91b9' + 'SKIP' + 'e7dd17ccd4b417a1813c21285c738a3fdede1326d8a1445086ea1af294c3710b' + 'SKIP' + '058aa32a18723035b18a770e1bd7f4b4f101bf5618bf0114e1874a398de42e06' + 'SKIP' + '833c5ede23f4a014f428c64b7da550cdcd7ecc939dfeb45dce172b18417888a5' + 'SKIP' + '21a9196887fe8453218e4805e171b19958005f38e5d21886c58a55f8d9b86795' 'SKIP' '29c61ec984bd342113b3c4310a3d5fddfd6d7db83847cfb26b84cc730385320a' 'SKIP' - '53425998005a4115bdf6001b9ffbe62c66bb2db205994b2e13005a3417ea6cbb' + '4b5abcc24f557e83e4d385c8304c781d6ab78fc0a3935db97e01e7d27fc617ea' + 'SKIP' + '0dc5f0f91640715ee1744fb8cfe680de6ac1370f4df8e17d31016744f31d3638' + 'SKIP' + 'cd068c7e199f458356886eb321f759a91894bca374c71ccac242407d8b22a1f5' + 'SKIP' + '618369a173be9a5a2f91fb6044931d897bbf4911586bd94a10b43a25838872a2' + 'SKIP' + 'bd3535f78183d56b133986b814ec31a8c1aa933d71b3a611937e6377ee04af10' + 'SKIP' + 'bb1e2ab62b109e434bd2c2635b5912602b8a8cc0ba24743b7bb5f691dcde2353' + 'SKIP' + '55ddb7c072f3933eef42ac48f3b32f90a5298f5cf90b4ba5b2128a197250494a' + 'SKIP' + '62e446b250e96fb65a3068bac424e1e5fe4482ad6d7ac473fa94cb8ac047c0ab' + 'SKIP' + '8b55dbedf730b0dc675793418c21b909aed5db4311248bd277d3184839d261f0' + 'SKIP' + '5a09e7b8e10fdc61839ada49e9a33a64b240fa54847c1c0e0044518297196f0b' + 'SKIP' + 'b857f4c98ff5998adab1765a06ad307f9334a8d7d9644a3de556f2b0e9cb2b4b' 'SKIP' 'c28fca9ba015e96599627acf42e52737a6a846bdcbfd3d4af4147eda3109e12d' 'SKIP' - '5ce97dadb9eb1a8b0da41a8446ee74e01a5a7e7ab439bbea7a711318f04ae24f' + 'd15cdfc9b86c40a119a4f21f4fbaec72efebc67677a7386df46a61816d4c6e02' 'SKIP' - '44f1c7bd18ea7f8772ce41e24dddd5f314e416ec8dfac6afe1ce8680c1bb4903' + '1b070eef78ccffb3763b464f5a1b0636936f4c33b0be9b44ab116c5dcbadbebe' 'SKIP' - '0bb8ae5b6f23e13c60953eb5bbc8939e8f6c8faf2b216a20203af3b6f0217134' + 'eb0923de01175707151930d28b1a1a912d24bbc171248fa9f45f57a2125d077a' 'SKIP' - '907c92ef681154d10959a5f0cbe2a636688036990ba42e059af00cc2311b4be4' + 'c076ed3752bcc2033e435043668fe007eb1ea1760597416fbb5d90bed4947bec' + 'SKIP' + '0fa9fdc6d6c85435d7b3cb38b51823f458ab273c527fcbe21b945dbd89686304' + 'SKIP' + 'ef66f6c32ac98a311b7437265b21a8c70746f1ea4dfbe28aff655e778d09c28b' + 'SKIP' + 'b45303b559ab8990ecb4e0281e5d529625311e795d6c3a9a52c2c27498141e18' + 'SKIP' + '1728e516153acca3c343f5184c9894fe6f1a1fbc95abe1594f544ef601db39ed' 'SKIP' '534e68a989d36823f14a10299803f294eef39391619b4c329d78c20539d9fcd5' 'SKIP' '516e6ec51ca0330989128a4eb2a8f6785e5d45d930899cbbbf37b8147ddfd854' 'SKIP' - 'bda3d6842f15998ce3f3e33d0e6e420df9c9c2abcba13716af478720c5933753' - 'SKIP' - '3e7e7dba1ca88930c2c3f0d5411ddcee8c2e12fca59d859e2383dccaf9a08022' + '757ccc291d53030b89088802615ecf727df6f18ab2515cc0075008ebbd564e36' 'SKIP' '3a271bc6b0219152047fb2b78d9e695eb791b796ea6d8233096912c77d62a5c0' 'SKIP' + 'fd6bf3ed3d16c788330630fdd15604836ec2a0b6a7db2b04216f756da9117bd2' + 'SKIP' + '4d598b9e00edf7c17d915eec7c8d369fc0ea40badb7f4576cb3dedd92a685d4b' + 'SKIP' + '98ff0d6196470c582b3e3841ef35bf82a63b57ebaef952885bd1905bcb8e627b' + 'SKIP' + 'de2682af1db7f5ae8cd74da4f9020bfb7d2167f030eb15f0afb7bd46ffe49e84' + 'SKIP' + '73dc22b7f97a6cc5a5f35d6b30b9b7b12be2566160a024769550290a0e7e4029' + 'SKIP' + 'f06834dfaa9e976a5778fa4fd07117a85bbff3b788c91e61d8cbc628e1b8bfb3' + 'SKIP' + '11884198740c5ac572cbe87086b1744ce19f0e1f856775e6edccce843ab097a3' + 'SKIP' + 'e6d848172228e98dde3a7ec679e7ea6bb0a42cca0b0105c58c8f9d6d44e33943' + 'SKIP' '4e80f2d05591175d4ed0a1a144509e97260e5593472aae586c07956215d99162' 'SKIP' - '8ebce8f38e38bd66879d040028c2448e5bc7dcde9df0ccee2b4221015591b162' + '1b442ad6f28c684068844ce93616690b84651e35870802b02937351bea078b4a' 'SKIP' - '0b0e05b615ae6eb825bf7d7fa568f551b0dff6c2cee62c9285c1b0d63224ea1c' + '9be4f91e6a4be87791d700f44c228fe599468f06a4f627b79e75718ac020db4d' + 'SKIP' + 'ed635ed3b63cdb61614b8805aa5a9515d5b538522165be625dca89bdb7d6e801' + 'SKIP' + 'e28c29b9f359db85a60bfd49d2f28ba51f4d2272eb79b7d3e19ba8b3d5acdac9' + 'SKIP' + '3175c56fd2935848ce99fe10be1bc348231909f8479c3d3af911c5c7ab7f381b' + 'SKIP' + '30a12a616afa8fe95b969e90b42aeb240962b9fab8b8bd357ac00706abdd68c7' + 'SKIP' + 'eb0ebb8c55332dab8931c667ec84cd32c91c657d21af4504327ae05643108e1d' + 'SKIP' + '945c90d99f9a9fa47d61e471f76d17c0229ffa0fa8cda22de990a2ccc4194bb2' + 'SKIP' + '70ff41b2341836c27c5545a7948258f137d991c2879cbeb29f7af740c11d20e6' + 'SKIP' + 'b4efd997d3f09cbf258fd0f78aca44e4c96694dc4efc5c8c9a677ea67ebf5a54' 'SKIP' '2e1e2a0eb039ce61c1612bf6c7ab5227b99df053bcd5592da7758715dd333fda' 'SKIP' - '17de1f76da3db42dc3c7829a22953cd2c6de916999d809e3fb0bbb741753ec8f' + '6800dd82115ebe71d80dde0bb368fbd8563caa6c29127ffde0d6b5df81b502b0' 'SKIP' 'd90cc6570a47c8754e4c5d45834e0434c0a1fcabda9b8933b1fba84e3dabc083' 'SKIP' + 'a008c052ca657efcd4e27b8f7b819adbc9606e261aac37d41a40cf67c094cff9' + 'SKIP' + '57169ef893dff957cbf33f7c0dc22f2fdfcfcb3befcd62ad59e2456484423bac' + 'SKIP' + '01ecc1adbe715bb31153a2350ca46e1cb8a6e3fdc6899b2debeff015af2412ed' + 'SKIP' + 'a4331d3968fc5002534063b42bd219bddfe4a7d6b08e4cb30ed60f4366ca3c41' + 'SKIP' '028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8' 'SKIP' 'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311' 'SKIP' '44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a' 'SKIP' - '229b28cee6f2b8afb5888eed453ce7f82de033900383bd91957339007c9e6cbf' + '6f01b5357af7deed3f4bccea6678b4d9e8afc83e598978f42f244241667f975d' 'SKIP' - 'c5d61db05b19fde06102b0b3dbd76e1989c8f07f3b7d175176bd2d73f1a71e5c' + '0da7247035a359e2491933f9effff43ea7b29748f21a0e3080bef0018c2488e3' 'SKIP' - '5988d7a37b4b71a64ed09fea872f08515a3c7029d33fd053a6a1822bb811ddb5' - 'SKIP' - '1ec4fb93700b0696ba2c2a371f218f13b32dcc745523c7ee26a5c3cd7af253fc' + 'f81f6b5769e47716ca318ecf76ab1c0fafa0e165fa858a64251359937bffcf04' 'SKIP' 'c67fee35a42866dd3c9056ff099fc504cdad9530c107d959331b5572b21af818' 'SKIP' - '36cb306751c57de3e61c7a4cd18a7f131eef0dc1de15a4e28751d1e5b08bace8' + 'fdfe62019e6ef5dfba2fabecc6ee5b58400f41ef000883fed135ecaca881b40c' 'SKIP' '07683e522d6dea3d3ce658ee80bf4317f6c3caf248259da960bc4610f4d1e807' 'SKIP' - '66a0933d2d4f2edc8999b6fdf0fe10b5b607646fd6f1a1d534bf26270543f8f1' - 'SKIP' 'c5279468d93e562f509a6b599a2b7ca7ee0032d3fe35314c5769fc6c9a9234ab' 'SKIP' 'c0111614ec44014cbf621eedf5a4ef302a855423bc1633ee692392950e971d0e' 'SKIP' - '1cbe1ddd3b37cd0c7e4bad4af08e681eda2f7a61698004322bb87f320aa95dbb' + 'c2f916fa04d91edf926abf8fed086302fe538ba62d92450af0ddb6d3a33942f1' + 'SKIP' + 'd17774053ab07de92e18abdb0ff4d00352a02b6a97c48e28d8c875954c8eec36' + 'SKIP' + '774f337663b215c5aa8877c06ffce95be44403fe77516c1be9ef9789e2d049e5' 'SKIP') export KBUILD_BUILD_HOST=omarchy @@ -577,7 +845,8 @@ build() { cd $_srcname MAKEFLAGS="-j$(nproc) $MAKEFLAGS" make all - make -C tools/bpf/bpftool vmlinux.h feature-clang-bpf-co-re=1 + env -u CFLAGS -u CXXFLAGS -u LDFLAGS \ + make -C tools/bpf/bpftool vmlinux.h feature-clang-bpf-co-re=1 } _package() { diff --git a/pkgbuilds/linux-omarchy-bore/config.x86_64 b/pkgbuilds/linux-omarchy-bore/config.x86_64 index 27b8b44..f3006f9 100644 --- a/pkgbuilds/linux-omarchy-bore/config.x86_64 +++ b/pkgbuilds/linux-omarchy-bore/config.x86_64 @@ -670,6 +670,7 @@ CONFIG_ACPI_REV_OVERRIDE_POSSIBLE=y CONFIG_ACPI_EC=y CONFIG_ACPI_EC_DEBUGFS=m CONFIG_ACPI_AC=y +CONFIG_ACPI_BATTERY_HOOKS=y CONFIG_ACPI_BATTERY=y CONFIG_ACPI_BUTTON=y CONFIG_ACPI_VIDEO=m @@ -998,9 +999,9 @@ CONFIG_SOFTIRQ_ON_OWN_STACK=y CONFIG_ARCH_HAS_ELF_RANDOMIZE=y CONFIG_HAVE_ARCH_MMAP_RND_BITS=y CONFIG_HAVE_EXIT_THREAD=y -CONFIG_ARCH_MMAP_RND_BITS=32 +CONFIG_ARCH_MMAP_RND_BITS=28 CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS=y -CONFIG_ARCH_MMAP_RND_COMPAT_BITS=16 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS=8 CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES=y CONFIG_HAVE_PAGE_SIZE_4KB=y CONFIG_PAGE_SIZE_4KB=y @@ -2561,7 +2562,7 @@ CONFIG_EFI_CAPSULE_LOADER=m # CONFIG_EFI_TEST is not set CONFIG_EFI_DEV_PATH_PARSER=y CONFIG_APPLE_PROPERTIES=y -CONFIG_RESET_ATTACK_MITIGATION=y +# CONFIG_RESET_ATTACK_MITIGATION is not set CONFIG_EFI_RCI2_TABLE=y # CONFIG_EFI_DISABLE_PCI_DMA is not set CONFIG_EFI_EARLYCON=y @@ -4672,6 +4673,7 @@ CONFIG_INPUT_IMS_PCU=m CONFIG_INPUT_IQS269A=m CONFIG_INPUT_IQS626A=m CONFIG_INPUT_IQS7222=m +CONFIG_INPUT_ISA1200_HAPTIC=m CONFIG_INPUT_CMA3000=m CONFIG_INPUT_CMA3000_I2C=m CONFIG_INPUT_XEN_KBDDEV_FRONTEND=m @@ -5488,6 +5490,7 @@ CONFIG_SENSORS_ASUS_ROG_RYUJIN=m CONFIG_SENSORS_AXI_FAN_CONTROL=m CONFIG_SENSORS_K8TEMP=m CONFIG_SENSORS_K10TEMP=m +CONFIG_SENSORS_KB9002=m CONFIG_SENSORS_KBATT=m CONFIG_SENSORS_KFAN=m CONFIG_SENSORS_FAM15H_POWER=m @@ -5679,7 +5682,10 @@ CONFIG_SENSORS_MP9941=m CONFIG_SENSORS_MP9945=m CONFIG_SENSORS_MPQ7932_REGULATOR=y CONFIG_SENSORS_MPQ7932=m +CONFIG_SENSORS_MPQ82D00=m CONFIG_SENSORS_MPQ8785=m +CONFIG_SENSORS_MPQ8646=m +# CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE is not set CONFIG_SENSORS_PIM4328=m CONFIG_SENSORS_PLI1209BC=m CONFIG_SENSORS_PLI1209BC_REGULATOR=y @@ -5687,6 +5693,8 @@ CONFIG_SENSORS_PM6764TR=m CONFIG_SENSORS_PXE1610=m CONFIG_SENSORS_Q54SJ108A2=m CONFIG_SENSORS_STEF48H28=m +CONFIG_SENSORS_SQ24860=m +CONFIG_SENSORS_SQ24860_REGULATOR=y CONFIG_SENSORS_STPDDC60=m CONFIG_SENSORS_TDA38640=m CONFIG_SENSORS_TDA38640_REGULATOR=y @@ -5697,10 +5705,12 @@ CONFIG_SENSORS_TPS53679=m CONFIG_SENSORS_TPS546D24=m CONFIG_SENSORS_UCD9000=m CONFIG_SENSORS_UCD9200=m +CONFIG_SENSORS_VT7505=m CONFIG_SENSORS_XDP710=m CONFIG_SENSORS_XDP720=m CONFIG_SENSORS_XDPE152=m CONFIG_SENSORS_XDPE1A2G7B=m +CONFIG_SENSORS_XDPE1A2G7B_REGULATOR=y CONFIG_SENSORS_XDPE122=m CONFIG_SENSORS_XDPE122_REGULATOR=y CONFIG_SENSORS_ZL6100=m @@ -6772,6 +6782,7 @@ CONFIG_VIDEO_IMX290=m CONFIG_VIDEO_IMX296=m CONFIG_VIDEO_IMX319=m CONFIG_VIDEO_IMX355=m +CONFIG_VIDEO_IMX471=m CONFIG_VIDEO_MAX9271_LIB=m CONFIG_VIDEO_MT9M001=m CONFIG_VIDEO_MT9M111=m @@ -10200,7 +10211,7 @@ CONFIG_AMD_IOMMU=y CONFIG_DMAR_TABLE=y CONFIG_INTEL_IOMMU=y CONFIG_INTEL_IOMMU_SVM=y -CONFIG_INTEL_IOMMU_DEFAULT_ON=y +# CONFIG_INTEL_IOMMU_DEFAULT_ON is not set CONFIG_INTEL_IOMMU_FLOPPY_WA=y CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON=y CONFIG_INTEL_IOMMU_PERF_EVENTS=y @@ -11847,7 +11858,10 @@ CONFIG_INTEGRITY_MACHINE_KEYRING=y # CONFIG_INTEGRITY_CA_MACHINE_KEYRING is not set CONFIG_LOAD_UEFI_KEYS=y CONFIG_INTEGRITY_AUDIT=y -# CONFIG_IMA is not set +CONFIG_IMA=y +CONFIG_IMA_DEFAULT_HASH_SHA256=y +CONFIG_IMA_WRITE_POLICY=y +CONFIG_IMA_READ_POLICY=y # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set # CONFIG_EVM is not set # CONFIG_DEFAULT_SECURITY_SELINUX is not set @@ -12277,7 +12291,7 @@ CONFIG_NEED_DMA_MAP_STATE=y CONFIG_ARCH_DMA_ADDR_T_64BIT=y CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y CONFIG_SWIOTLB=y -CONFIG_SWIOTLB_DYNAMIC=y +# CONFIG_SWIOTLB_DYNAMIC is not set CONFIG_DMA_NEED_SYNC=y CONFIG_DMA_COHERENT_POOL=y CONFIG_DMA_CMA=y diff --git a/pkgbuilds/linux-omarchy-eevdf/.omarchy/package.json b/pkgbuilds/linux-omarchy-eevdf/.omarchy/package.json new file mode 100644 index 0000000..c3ed389 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "skip_build": true +} diff --git a/pkgbuilds/linux-omarchy/0010-archlinux-base.patch b/pkgbuilds/linux-omarchy-eevdf/0010-archlinux-base.patch similarity index 83% rename from pkgbuilds/linux-omarchy/0010-archlinux-base.patch rename to pkgbuilds/linux-omarchy-eevdf/0010-archlinux-base.patch index 55e9379..497422b 100644 --- a/pkgbuilds/linux-omarchy/0010-archlinux-base.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0010-archlinux-base.patch @@ -1,5 +1,4 @@ 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 @@ @@ -15,7 +14,7 @@ index f0e2e131a9a5..7b611da9a27a 100644 /* * Minimum number of threads to boot the kernel */ -@@ -2093,6 +2099,11 @@ __latent_entropy struct task_struct *copy_process( +@@ -2092,6 +2098,11 @@ __latent_entropy struct task_struct *copy_process( return ERR_PTR(-EPERM); } @@ -27,7 +26,7 @@ index f0e2e131a9a5..7b611da9a27a 100644 /* * 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) +@@ -3165,6 +3176,10 @@ static int check_unshare_flags(unsigned long unshare_flags) if (!current_is_single_threaded()) return -EINVAL; } @@ -38,7 +37,7 @@ index f0e2e131a9a5..7b611da9a27a 100644 return 0; } -@@ -3398,6 +3413,15 @@ static const struct ctl_table fork_sysctl_table[] = { +@@ -3400,6 +3415,15 @@ static const struct ctl_table fork_sysctl_table[] = { .mode = 0644, .proc_handler = sysctl_max_threads, }, diff --git a/pkgbuilds/linux-omarchy-eevdf/0010-archlinux-base.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0010-archlinux-base.patch.sig new file mode 100644 index 0000000..a828924 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0010-archlinux-base.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch b/pkgbuilds/linux-omarchy-eevdf/0011-kbuild-optimize-for-performance-o3.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch rename to pkgbuilds/linux-omarchy-eevdf/0011-kbuild-optimize-for-performance-o3.patch diff --git a/pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0011-kbuild-optimize-for-performance-o3.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0011-kbuild-optimize-for-performance-o3.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0011-kbuild-optimize-for-performance-o3.patch.sig diff --git a/pkgbuilds/linux-omarchy/0120-tlbpull.patch b/pkgbuilds/linux-omarchy-eevdf/0120-tlbpull.patch similarity index 67% rename from pkgbuilds/linux-omarchy/0120-tlbpull.patch rename to pkgbuilds/linux-omarchy-eevdf/0120-tlbpull.patch index 712b9b7..14e2d46 100644 --- a/pkgbuilds/linux-omarchy/0120-tlbpull.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0120-tlbpull.patch @@ -22,19 +22,6 @@ diff --git a/arch/x86/include/asm/pgtable_types.h b/arch/x86/include/asm/pgtable 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 @@ -168,35 +155,7 @@ diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c 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]; +@@ -88,9 +88,8 @@ static unsigned long direct_pages_count[PG_LEVEL_NUM]; void update_page_count(int level, unsigned long pages) { /* Protect against CPA */ @@ -207,16 +166,37 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c } static void split_page_count(int level) -@@ -418,6 +417,8 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa) +@@ -420,7 +419,7 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) int collapsed = 0; int i; +- spin_lock(&cpa_lock); + guard(spinlock)(&cpa_lock); -+ + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { for (i = 0; i < cpa->numpages; i++) - 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) +@@ -435,10 +434,8 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + collapsed += collapse_large_pages(addr, &pgtables); + } + +- if (!collapsed) { +- spin_unlock(&cpa_lock); ++ if (!collapsed) + return; +- } + + flush_tlb_all(); + +@@ -454,8 +451,6 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + else + pagetable_free(ptdesc); + } +- +- spin_unlock(&cpa_lock); + } + + static void cpa_collapse_large_pages(struct cpa_data *cpa) +@@ -916,24 +911,23 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) { /* change init_mm */ set_pte_atomic(kpte, pte); @@ -246,7 +226,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c } 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, +@@ -1103,16 +1097,11 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address, static int should_split_large_page(pte_t *kpte, unsigned long address, struct cpa_data *cpa) { @@ -265,7 +245,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c } 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, +@@ -1162,16 +1151,14 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, bool nx, rw; pte_t *tmp; @@ -284,7 +264,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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, +@@ -1204,7 +1191,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, break; default: @@ -292,7 +272,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c return 1; } -@@ -1225,7 +1217,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, +@@ -1252,7 +1238,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, * just split large page entry. */ flush_tlb_all(); @@ -300,21 +280,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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, +@@ -1327,11 +1312,11 @@ static int collapse_pmd_page(pmd_t *pmd, unsigned long addr, list_add(&page_ptdesc(pmd_page(old_pmd))->pt_list, pgtables); if (IS_ENABLED(CONFIG_X86_32)) { @@ -329,7 +295,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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, +@@ -1405,7 +1390,7 @@ static int collapse_pud_page(pud_t *pud, unsigned long addr, */ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) { @@ -338,7 +304,7 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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) +@@ -1413,26 +1398,24 @@ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) addr &= PMD_MASK; @@ -370,153 +336,6 @@ diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c 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 diff --git a/pkgbuilds/linux-omarchy-eevdf/0120-tlbpull.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0120-tlbpull.patch.sig new file mode 100644 index 0000000..3f44a63 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0120-tlbpull.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0121-smp-preempt.patch b/pkgbuilds/linux-omarchy-eevdf/0121-smp-preempt.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0121-smp-preempt.patch rename to pkgbuilds/linux-omarchy-eevdf/0121-smp-preempt.patch diff --git a/pkgbuilds/linux-omarchy/0121-smp-preempt.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0121-smp-preempt.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0121-smp-preempt.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0121-smp-preempt.patch.sig diff --git a/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch b/pkgbuilds/linux-omarchy-eevdf/0130-sched-detach-tasks.patch similarity index 91% rename from pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch rename to pkgbuilds/linux-omarchy-eevdf/0130-sched-detach-tasks.patch index 296b430..245f6e1 100644 --- a/pkgbuilds/linux-omarchy/0130-sched-detach-tasks.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0130-sched-detach-tasks.patch @@ -1,7 +1,7 @@ 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, +@@ -13350,12 +13350,15 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, * still unbalanced. ld_moved simply stays zero, so it is * correctly treated as an imbalance. */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0130-sched-detach-tasks.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0130-sched-detach-tasks.patch.sig new file mode 100644 index 0000000..b0ccd26 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0130-sched-detach-tasks.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy-eevdf/0140-sched-always-inline.patch similarity index 97% rename from pkgbuilds/linux-omarchy/0140-sched-always-inline.patch rename to pkgbuilds/linux-omarchy-eevdf/0140-sched-always-inline.patch index ee0da9b..cfb6c27 100644 --- a/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0140-sched-always-inline.patch @@ -212,7 +212,7 @@ diff --git a/include/linux/vtime.h b/include/linux/vtime.h 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) +@@ -5098,7 +5098,7 @@ static inline void prepare_task(struct task_struct *next) WRITE_ONCE(next->on_cpu, 1); } @@ -221,7 +221,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c { /* * 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) +@@ -5142,7 +5142,7 @@ static void zap_balance_callbacks(struct rq *rq) rq->balance_callback = found ? &balance_push_callback : NULL; } @@ -230,7 +230,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c { void (*func)(struct rq *rq); struct balance_callback *next; -@@ -5170,7 +5170,7 @@ struct balance_callback balance_push_callback = { +@@ -5177,7 +5177,7 @@ struct balance_callback balance_push_callback = { .func = balance_push, }; @@ -239,7 +239,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c __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 +@@ -5251,7 +5251,7 @@ prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf __acquire(__rq_lockp(this_rq())); } @@ -248,7 +248,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c __releases(__rq_lockp(rq)) { /* -@@ -5278,7 +5278,7 @@ static inline void kmap_local_sched_out(void) +@@ -5285,7 +5285,7 @@ static inline void kmap_local_sched_out(void) #endif } @@ -257,7 +257,7 @@ diff --git a/kernel/sched/core.c b/kernel/sched/core.c { #ifdef CONFIG_KMAP_LOCAL if (unlikely(current->kmap_ctrl.idx)) -@@ -5332,7 +5332,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, +@@ -5339,7 +5339,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, * past. 'prev == current' is still correct but we need to recalculate this_rq * because prev may have moved to another CPU. */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0140-sched-always-inline.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0140-sched-always-inline.patch.sig new file mode 100644 index 0000000..ee137b3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0140-sched-always-inline.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0141-sched-urgent-fixes.patch similarity index 67% rename from pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch rename to pkgbuilds/linux-omarchy-eevdf/0141-sched-urgent-fixes.patch index 9bc941f..f26a59b 100644 --- a/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0141-sched-urgent-fixes.patch @@ -1,21 +1,7 @@ -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, +@@ -10575,17 +10575,40 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu, return mig_llc; } @@ -58,7 +44,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c mm = p->mm; if (!mm) return mig_unrestricted; -@@ -10632,6 +10655,14 @@ alb_break_llc(struct lb_env *env) +@@ -10642,6 +10665,14 @@ alb_break_llc(struct lb_env *env) unsigned long util = 0; struct task_struct *cur; @@ -73,7 +59,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c if (env->src_rq->nr_running <= 1) return true; -@@ -10639,7 +10670,8 @@ alb_break_llc(struct lb_env *env) +@@ -10649,7 +10680,8 @@ alb_break_llc(struct lb_env *env) if (cur && cur->sched_class == &fair_sched_class) util = task_util(cur); @@ -83,7 +69,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c util, false) == mig_forbid) return true; } -@@ -10679,8 +10711,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) +@@ -10689,8 +10721,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) return true; @@ -93,7 +79,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c return false; return true; -@@ -11743,6 +11774,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, +@@ -11753,6 +11784,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, if (env->sd->flags & SD_SHARE_LLC) return false; @@ -109,17 +95,3 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c /* * 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-eevdf/0141-sched-urgent-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0141-sched-urgent-fixes.patch.sig new file mode 100644 index 0000000..0af85fe Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0141-sched-urgent-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch b/pkgbuilds/linux-omarchy-eevdf/0143-sched-hybrid-cluster-balancing.patch similarity index 68% rename from pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch rename to pkgbuilds/linux-omarchy-eevdf/0143-sched-hybrid-cluster-balancing.patch index 9c10fcd..137ec28 100644 --- a/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0143-sched-hybrid-cluster-balancing.patch @@ -14,22 +14,22 @@ diff --git a/include/linux/sched/sd_flags.h b/include/linux/sched/sd_flags.h 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, +@@ -11912,12 +11912,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; + if (balancing_at_rd) + *sg_overloaded = 1; + + /* + * Only account misfit load if @dst_cpu can @@ -44,43 +44,7 @@ diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c } } 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, +@@ -13030,9 +13043,24 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env, * average load. */ if (env->sd->flags & SD_ASYM_CPUCAPACITY && diff --git a/pkgbuilds/linux-omarchy-eevdf/0143-sched-hybrid-cluster-balancing.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0143-sched-hybrid-cluster-balancing.patch.sig new file mode 100644 index 0000000..f125b5b Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0143-sched-hybrid-cluster-balancing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch b/pkgbuilds/linux-omarchy-eevdf/0144-sched-nohz-idle-core.patch similarity index 97% rename from pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch rename to pkgbuilds/linux-omarchy-eevdf/0144-sched-nohz-idle-core.patch index 1fe001c..5af4cbd 100644 --- a/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0144-sched-nohz-idle-core.patch @@ -1,7 +1,7 @@ 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) +@@ -13934,29 +13934,62 @@ static inline int on_null_domain(struct rq *rq) */ static inline int find_new_ilb(void) { diff --git a/pkgbuilds/linux-omarchy-eevdf/0144-sched-nohz-idle-core.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0144-sched-nohz-idle-core.patch.sig new file mode 100644 index 0000000..9ac0c5f Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0144-sched-nohz-idle-core.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch b/pkgbuilds/linux-omarchy-eevdf/0145-sched-eevdf-tunables.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch rename to pkgbuilds/linux-omarchy-eevdf/0145-sched-eevdf-tunables.patch diff --git a/pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0145-sched-eevdf-tunables.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0145-sched-eevdf-tunables.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0145-sched-eevdf-tunables.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0210-pm-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0210-pm-updates.patch new file mode 100644 index 0000000..5110d06 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0210-pm-updates.patch @@ -0,0 +1,2365 @@ +diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c +--- a/drivers/acpi/acpi_processor.c ++++ b/drivers/acpi/acpi_processor.c +@@ -994,3 +994,357 @@ end: + } + EXPORT_SYMBOL_NS_GPL(acpi_processor_evaluate_cst, "ACPI_PROCESSOR_IDLE"); + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ ++ ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++struct acpi_lpi_states_array { ++ unsigned int size; ++ unsigned int composite_states_size; ++ struct acpi_lpi_state *entries; ++ struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; ++}; ++ ++static int obj_get_integer(union acpi_object *obj, u32 *value) ++{ ++ if (obj->type != ACPI_TYPE_INTEGER) ++ return -EINVAL; ++ ++ *value = obj->integer.value; ++ return 0; ++} ++ ++#define lpi_state_debug(handle, message, state_idx) \ ++ acpi_handle_debug(handle, message " for _LPI state %u\n", state_idx) ++ ++static void process_lpi_state_package(union acpi_object *lpi_pkg, ++ struct acpi_lpi_state *lpi_state, ++ acpi_handle handle, ++ unsigned int state_idx, bool strict) ++{ ++ union acpi_object *lpi_pkg_elem, *obj; ++ ++ if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) ++ return; ++ ++ lpi_pkg_elem = lpi_pkg->package.elements; ++ ++ /* Get the entry method first and skip the state if that fails. */ ++ obj = &lpi_pkg_elem[6]; ++ if (obj->type == ACPI_TYPE_BUFFER) { ++ struct acpi_power_register *reg; ++ ++ if (obj->buffer.length < sizeof(*reg)) { ++ lpi_state_debug(handle, "Invalid register data", state_idx); ++ return; ++ } ++ ++ reg = (struct acpi_power_register *)obj->buffer.pointer; ++ if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { ++ lpi_state_debug(handle, "Unsupported entry method", state_idx); ++ return; ++ } ++ ++ lpi_state->entry_method = ACPI_CSTATE_FFH; ++ lpi_state->address = reg->address; ++ } else if (obj->type == ACPI_TYPE_INTEGER) { ++ lpi_state->entry_method = ACPI_CSTATE_INTEGER; ++ lpi_state->address = obj->integer.value; ++ } else { ++ lpi_state_debug(handle, "Invalid entry method", state_idx); ++ return; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No min. residency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); ++ lpi_state->min_residency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No wake latency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); ++ lpi_state->wake_latency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) ++ lpi_state->flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) ++ lpi_state->arch_flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) ++ lpi_state->res_cnt_freq = 1; ++ ++ if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) ++ lpi_state->enable_parent_state = 0; ++ ++ /* Skip elements [7-8] i.e. Residency/Usage counters. */ ++ ++ /* ++ * Avoid out-of-bounds access if the size of the package is less than ++ * expected. ++ */ ++ if (lpi_pkg->package.count < 10) ++ return; ++ ++ obj = &lpi_pkg_elem[9]; ++ if (obj->type == ACPI_TYPE_STRING) ++ strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); ++} ++ ++static int acpi_processor_evaluate_lpi(acpi_handle handle, ++ struct acpi_lpi_states_array *info, ++ bool strict) ++{ ++ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object *lpi_data, *lpi_pkg; ++ unsigned int lpi_pkg_count, state_idx; ++ struct acpi_lpi_state *lpi_state; ++ acpi_status status; ++ int ret = 0; ++ ++ status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); ++ if (ACPI_FAILURE(status)) { ++ acpi_handle_debug(handle, "No _LPI, giving up\n"); ++ return -ENODEV; ++ } ++ ++ lpi_data = buffer.pointer; ++ ++ /* There must be at least 4 elements = 3 elements + 1 package */ ++ if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || ++ lpi_data->package.count < 4) { ++ acpi_handle_debug(handle, "Not enough elements in _LPI\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_pkg_count = lpi_data->package.elements[2].integer.value; ++ ++ /* Validate number of power states. */ ++ if (!lpi_pkg_count || lpi_pkg_count != lpi_data->package.count - 3) { ++ acpi_handle_debug(handle, "Invalid _LPI state count\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_state = kzalloc_objs(*lpi_state, lpi_pkg_count); ++ if (!lpi_state) { ++ ret = -ENOMEM; ++ goto end; ++ } ++ ++ info->size = lpi_pkg_count; ++ info->entries = lpi_state; ++ ++ /* _LPI State packages start at index 3. */ ++ lpi_pkg = &lpi_data->package.elements[3]; ++ ++ for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { ++ lpi_state->index = state_idx; ++ process_lpi_state_package(lpi_pkg++, lpi_state++, handle, ++ state_idx, strict); ++ } ++ ++ acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); ++end: ++ kfree(buffer.pointer); ++ return ret; ++} ++ ++/** ++ * combine_lpi_states - combine local and parent LPI states to form a composite LPI state ++ * ++ * @local: local LPI state ++ * @parent: parent LPI state ++ * @result: composite LPI state ++ */ ++static bool combine_lpi_states(struct acpi_lpi_state *local, ++ struct acpi_lpi_state *parent, ++ struct acpi_lpi_state *result) ++{ ++ if (parent->entry_method == ACPI_CSTATE_INTEGER) { ++ if (!parent->address) /* 0 means autopromotable */ ++ return false; ++ result->address = local->address + parent->address; ++ } else { ++ result->address = parent->address; ++ } ++ ++ result->min_residency = max(local->min_residency, parent->min_residency); ++ result->wake_latency = local->wake_latency + parent->wake_latency; ++ result->enable_parent_state = parent->enable_parent_state; ++ result->entry_method = local->entry_method; ++ ++ result->flags = parent->flags; ++ result->arch_flags = parent->arch_flags; ++ result->index = parent->index; ++ ++ scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); ++ return true; ++} ++ ++#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) ++ ++static void stash_composite_state(struct acpi_lpi_states_array *curr_level, ++ struct acpi_lpi_state *t) ++{ ++ curr_level->composite_states[curr_level->composite_states_size++] = t; ++} ++ ++static bool too_many_states(acpi_handle handle, unsigned int state_count) ++{ ++ if (state_count < ACPI_PROCESSOR_MAX_POWER) ++ return false; ++ ++ acpi_handle_info(handle, "No space for more _LPI states than %d\n", ++ ACPI_PROCESSOR_MAX_POWER); ++ return true; ++} ++ ++static unsigned int flatten_lpi_states(acpi_handle handle, ++ struct acpi_lpi_state *lpi_states, ++ unsigned int state_count, ++ struct acpi_lpi_states_array *curr, ++ struct acpi_lpi_states_array *prev) ++{ ++ struct acpi_lpi_state *parent_lpi = curr->entries; ++ unsigned int j; ++ ++ /* ++ * Combine each of the "raw" _LPI states from the current (processor ++ * container) level with all of the composite _LPI states from the ++ * previous (processor or processor container) level. ++ */ ++ for (j = 0; j < curr->size; j++, parent_lpi++) { ++ struct acpi_lpi_state *flpi; ++ int i; ++ ++ if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) ++ continue; ++ ++ if (too_many_states(handle, state_count)) ++ break; ++ ++ flpi = &lpi_states[state_count]; ++ ++ for (i = 0; i < prev->composite_states_size; i++) { ++ struct acpi_lpi_state *local_lpi = prev->composite_states[i]; ++ ++ if (parent_lpi->index > local_lpi->enable_parent_state) ++ continue; ++ ++ if (!combine_lpi_states(local_lpi, parent_lpi, flpi)) ++ continue; ++ ++ stash_composite_state(curr, flpi); ++ state_count++; ++ flpi++; ++ ++ if (state_count >= ACPI_PROCESSOR_MAX_POWER) ++ break; ++ } ++ } ++ ++ return state_count; ++} ++ ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ struct acpi_lpi_states_array info[2], *prev, *curr; ++ acpi_handle handle = pr_handle; ++ unsigned int state_count = 0; ++ unsigned int i; ++ int ret; ++ ++ if (!osc_pc_lpi_support_confirmed) ++ return -EOPNOTSUPP; ++ ++ curr = &info[0]; ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ return ret; ++ ++ /* Copy all of the usable first-level states to power.lpi_states[]. */ ++ for (i = 0; i < curr->size; i++) { ++ struct acpi_lpi_state *lpi = &curr->entries[i]; ++ struct acpi_lpi_state *flpi; ++ ++ /* ++ * Skip states that are not enabled or have an inadequate entry ++ * method for this level. ++ */ ++ if (!(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) || ++ lpi->entry_method == ACPI_CSTATE_INTEGER) ++ continue; ++ ++ if (too_many_states(pr_handle, state_count)) ++ break; ++ ++ flpi = &pr_power->lpi_states[state_count++]; ++ memcpy(flpi, lpi, sizeof(*lpi)); ++ stash_composite_state(curr, flpi); ++ } ++ ++ kfree(curr->entries); ++ ++ /* ++ * If there are no _LPI states at the first level, there are no _LPI ++ * states at all. ++ */ ++ if (!state_count) ++ return -ENODATA; ++ ++ prev = curr; ++ curr = &info[1]; ++ ++ for (;;) { ++ struct acpi_lpi_states_array *tmp; ++ struct acpi_device *d; ++ ++ if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) ++ break; ++ ++ d = acpi_fetch_acpi_dev(handle); ++ if (!d) ++ break; ++ ++ if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) ++ break; ++ ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ break; ++ ++ /* flatten all the LPI states in this level of hierarchy */ ++ state_count = flatten_lpi_states(pr_handle, pr_power->lpi_states, ++ state_count, curr, prev); ++ ++ kfree(curr->entries); ++ ++ tmp = prev, prev = curr, curr = tmp; ++ } ++ ++ /* reset the index after flattening */ ++ for (i = 0; i < state_count; i++) ++ pr_power->lpi_states[i].index = i; ++ ++ pr_power->count = state_count; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_processor_extract_lpi_info, "ACPI_PROCESSOR_IDLE"); ++#endif /* CONFIG_ACPI_PROCESSOR_IDLE */ +diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c +--- a/drivers/acpi/processor_idle.c ++++ b/drivers/acpi/processor_idle.c +@@ -853,237 +853,6 @@ static int acpi_processor_setup_cstates(struct acpi_processor *pr) + + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ + +-struct acpi_lpi_states_array { +- unsigned int size; +- unsigned int composite_states_size; +- struct acpi_lpi_state *entries; +- struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; +-}; +- +-static int obj_get_integer(union acpi_object *obj, u32 *value) +-{ +- if (obj->type != ACPI_TYPE_INTEGER) +- return -EINVAL; +- +- *value = obj->integer.value; +- return 0; +-} +- +-static int acpi_processor_evaluate_lpi(acpi_handle handle, +- struct acpi_lpi_states_array *info) +-{ +- acpi_status status; +- int ret = 0; +- int pkg_count, state_idx = 1, loop; +- struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; +- union acpi_object *lpi_data; +- struct acpi_lpi_state *lpi_state; +- +- status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); +- if (ACPI_FAILURE(status)) { +- acpi_handle_debug(handle, "No _LPI, giving up\n"); +- return -ENODEV; +- } +- +- lpi_data = buffer.pointer; +- +- /* There must be at least 4 elements = 3 elements + 1 package */ +- if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || +- lpi_data->package.count < 4) { +- pr_debug("not enough elements in _LPI\n"); +- ret = -ENODATA; +- goto end; +- } +- +- pkg_count = lpi_data->package.elements[2].integer.value; +- +- /* Validate number of power states. */ +- if (pkg_count < 1 || pkg_count != lpi_data->package.count - 3) { +- pr_debug("count given by _LPI is not valid\n"); +- ret = -ENODATA; +- goto end; +- } +- +- lpi_state = kzalloc_objs(*lpi_state, pkg_count); +- if (!lpi_state) { +- ret = -ENOMEM; +- goto end; +- } +- +- info->size = pkg_count; +- info->entries = lpi_state; +- +- /* LPI States start at index 3 */ +- for (loop = 3; state_idx <= pkg_count; loop++, state_idx++, lpi_state++) { +- union acpi_object *element, *pkg_elem, *obj; +- +- element = &lpi_data->package.elements[loop]; +- if (element->type != ACPI_TYPE_PACKAGE || element->package.count < 7) +- continue; +- +- pkg_elem = element->package.elements; +- +- obj = pkg_elem + 6; +- if (obj->type == ACPI_TYPE_BUFFER) { +- struct acpi_power_register *reg; +- +- if (obj->buffer.length < sizeof(*reg)) { +- acpi_handle_debug(handle, +- "Invalid register data for _LPI state %d\n", +- state_idx); +- continue; +- } +- +- reg = (struct acpi_power_register *)obj->buffer.pointer; +- if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_IO && +- reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) +- continue; +- +- lpi_state->address = reg->address; +- lpi_state->entry_method = +- reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE ? +- ACPI_CSTATE_FFH : ACPI_CSTATE_SYSTEMIO; +- } else if (obj->type == ACPI_TYPE_INTEGER) { +- lpi_state->entry_method = ACPI_CSTATE_INTEGER; +- lpi_state->address = obj->integer.value; +- } else { +- pr_debug("Entry method of state-%d is invalid, disable it.\n", +- state_idx); +- continue; +- } +- +- lpi_state->index = state_idx; +- if (obj_get_integer(pkg_elem + 0, &lpi_state->min_residency)) { +- pr_debug("No min. residency found, assuming 10 us\n"); +- lpi_state->min_residency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 1, &lpi_state->wake_latency)) { +- pr_debug("No wakeup residency found, assuming 10 us\n"); +- lpi_state->wake_latency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 2, &lpi_state->flags)) +- lpi_state->flags = 0; +- +- if (obj_get_integer(pkg_elem + 3, &lpi_state->arch_flags)) +- lpi_state->arch_flags = 0; +- +- if (obj_get_integer(pkg_elem + 4, &lpi_state->res_cnt_freq)) +- lpi_state->res_cnt_freq = 1; +- +- if (obj_get_integer(pkg_elem + 5, &lpi_state->enable_parent_state)) +- lpi_state->enable_parent_state = 0; +- +- /* Skip elements [7-8] i.e. Residency/Usage counters. */ +- +- /* +- * Avoid out-of-bounds access if the size of the package is less +- * than expected. +- */ +- if (element->package.count < 10) +- continue; +- +- obj = pkg_elem + 9; +- if (obj->type == ACPI_TYPE_STRING) +- strscpy(lpi_state->desc, obj->string.pointer, +- ACPI_CX_DESC_LEN); +- } +- +- acpi_handle_debug(handle, "Found %d power states\n", state_idx); +-end: +- kfree(buffer.pointer); +- return ret; +-} +- +-/** +- * combine_lpi_states - combine local and parent LPI states to form a composite LPI state +- * +- * @local: local LPI state +- * @parent: parent LPI state +- * @result: composite LPI state +- */ +-static bool combine_lpi_states(struct acpi_lpi_state *local, +- struct acpi_lpi_state *parent, +- struct acpi_lpi_state *result) +-{ +- if (parent->entry_method == ACPI_CSTATE_INTEGER) { +- if (!parent->address) /* 0 means autopromotable */ +- return false; +- result->address = local->address + parent->address; +- } else { +- result->address = parent->address; +- } +- +- result->min_residency = max(local->min_residency, parent->min_residency); +- result->wake_latency = local->wake_latency + parent->wake_latency; +- result->enable_parent_state = parent->enable_parent_state; +- result->entry_method = local->entry_method; +- +- result->flags = parent->flags; +- result->arch_flags = parent->arch_flags; +- result->index = parent->index; +- +- scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); +- return true; +-} +- +-#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) +- +-static void stash_composite_state(struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_state *t) +-{ +- curr_level->composite_states[curr_level->composite_states_size++] = t; +-} +- +-static unsigned int flatten_lpi_states(struct acpi_processor *pr, +- unsigned int flat_state_cnt, +- struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_states_array *prev_level) +-{ +- int i, j, state_count = curr_level->size; +- struct acpi_lpi_state *p, *t = curr_level->entries; +- +- curr_level->composite_states_size = 0; +- for (j = 0; j < state_count; j++, t++) { +- struct acpi_lpi_state *flpi; +- +- if (!(t->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) +- continue; +- +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) { +- pr_warn("Limiting number of LPI states to max (%d)\n", +- ACPI_PROCESSOR_MAX_POWER); +- pr_warn("Please increase ACPI_PROCESSOR_MAX_POWER if needed.\n"); +- break; +- } +- +- flpi = &pr->power.lpi_states[flat_state_cnt]; +- +- if (!prev_level) { /* leaf/processor node */ +- memcpy(flpi, t, sizeof(*t)); +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- continue; +- } +- +- for (i = 0; i < prev_level->composite_states_size; i++) { +- p = prev_level->composite_states[i]; +- if (t->index <= p->enable_parent_state && +- combine_lpi_states(p, t, flpi)) { +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- flpi++; +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) +- break; +- } +- } +- } +- +- kfree(curr_level->entries); +- return flat_state_cnt; +-} +- + int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + { + return -EOPNOTSUPP; +@@ -1091,64 +860,16 @@ int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + + static int acpi_processor_get_lpi_info(struct acpi_processor *pr) + { +- int ret, i; +- acpi_status status; +- acpi_handle handle = pr->handle, pr_ahandle; +- struct acpi_device *d = NULL; +- struct acpi_lpi_states_array info[2], *tmp, *prev, *curr; +- unsigned int state_count; ++ int ret; + + /* make sure our architecture has support */ + ret = acpi_processor_ffh_lpi_probe(pr->id); + if (ret == -EOPNOTSUPP) + return ret; + +- if (!osc_pc_lpi_support_confirmed) +- return -EOPNOTSUPP; +- +- if (!acpi_has_method(handle, "_LPI")) +- return -EINVAL; +- +- prev = &info[0]; +- curr = &info[1]; +- handle = pr->handle; +- ret = acpi_processor_evaluate_lpi(handle, prev); ++ ret = acpi_processor_extract_lpi_info(pr->handle, &pr->power, false); + if (ret) + return ret; +- state_count = flatten_lpi_states(pr, 0, prev, NULL); +- +- status = acpi_get_parent(handle, &pr_ahandle); +- while (ACPI_SUCCESS(status)) { +- d = acpi_fetch_acpi_dev(pr_ahandle); +- if (!d) +- break; +- +- handle = pr_ahandle; +- +- if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) +- break; +- +- /* can be optional ? */ +- if (!acpi_has_method(handle, "_LPI")) +- break; +- +- ret = acpi_processor_evaluate_lpi(handle, curr); +- if (ret) +- break; +- +- /* flatten all the LPI states in this level of hierarchy */ +- state_count = flatten_lpi_states(pr, state_count, curr, prev); +- +- tmp = prev, prev = curr, curr = tmp; +- +- status = acpi_get_parent(handle, &pr_ahandle); +- } +- +- /* reset the index after flattening */ +- for (i = 0; i < state_count; i++) +- pr->power.lpi_states[i].index = i; +- +- pr->power.count = state_count; + + /* Tell driver that _LPI is supported. */ + pr->flags.has_lpi = 1; +diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c +--- a/drivers/base/power/main.c ++++ b/drivers/base/power/main.c +@@ -41,6 +41,9 @@ + #include "../base.h" + #include "power.h" + ++#undef MODULE_PARAM_PREFIX ++#define MODULE_PARAM_PREFIX "pm." ++ + typedef int (*pm_callback_t)(struct device *); + + /* +@@ -532,6 +535,11 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644); + MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace, + "Backtrace all CPUs on DPM watchdog timeout"); + ++static bool __read_mostly dpm_watchdog_enabled = ++ IS_ENABLED(CONFIG_DPM_WATCHDOG_ENABLED); ++module_param(dpm_watchdog_enabled, bool, 0644); ++MODULE_PARM_DESC(dpm_watchdog_enabled, "Enable DPM watchdog"); ++ + static unsigned int __read_mostly dpm_watchdog_timeout = CONFIG_DPM_WATCHDOG_TIMEOUT; + static unsigned int __read_mostly dpm_watchdog_warning_timeout = + CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT; +@@ -627,6 +635,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + wd->dev = dev; + wd->tsk = current; + wd->fatal = dpm_watchdog_timeout == dpm_watchdog_warning_timeout; +@@ -645,6 +656,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + timer_delete_sync(timer); + timer_destroy_on_stack(timer); + } +diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c +--- a/drivers/base/power/runtime.c ++++ b/drivers/base/power/runtime.c +@@ -469,9 +469,6 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) + if (retval == -EACCES) + retval = -EAGAIN; + +- if (retval != -EAGAIN && retval != -EBUSY) +- dev->power.runtime_error = retval; +- + return retval; + } + +@@ -751,6 +748,9 @@ static int rpm_suspend(struct device *dev, int rpmflags) + dev->power.deferred_resume = false; + wake_up_all(&dev->power.wait_queue); + ++ if (retval != -EAGAIN && retval != -EBUSY) ++ dev->power.runtime_error = retval; ++ + /* + * On transient errors, if the callback routine failed an autosuspend, + * and if the last_busy time has been updated so that there is a new +diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +--- a/drivers/cpufreq/amd-pstate-ut.c ++++ b/drivers/cpufreq/amd-pstate-ut.c +@@ -59,6 +59,7 @@ static int amd_pstate_ut_check_freq(u32 index); + static int amd_pstate_ut_epp(u32 index); + static int amd_pstate_ut_check_driver(u32 index); + static int amd_pstate_ut_check_freq_attrs(u32 index); ++static int amd_pstate_ut_check_floor_freq(u32 index); + + static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid }, +@@ -68,6 +69,7 @@ static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_epp", amd_pstate_ut_epp }, + {"amd_pstate_ut_check_driver", amd_pstate_ut_check_driver }, + {"amd_pstate_ut_check_freq_attrs", amd_pstate_ut_check_freq_attrs }, ++ {"amd_pstate_ut_check_floor_freq", amd_pstate_ut_check_floor_freq }, + }; + + static bool test_in_list(const char *list, const char *name) +@@ -275,6 +277,7 @@ static int amd_pstate_set_mode(enum amd_pstate_mode mode) + static int amd_pstate_ut_epp(u32 index) + { + static const char * const epp_strings[] = { ++ "dynamic", + "power", + "balance_power", + "balance_performance", +@@ -282,10 +285,10 @@ static int amd_pstate_ut_epp(u32 index) + }; + char *buf __free(cleanup_page) = NULL; + struct cpufreq_policy *policy = NULL; ++ unsigned long orig_dynamic_epp = 0; + enum amd_pstate_mode orig_mode; + struct amd_cpudata *cpudata; + unsigned long orig_policy; +- bool orig_dynamic_epp; + int ret, cpu = 0; + u16 epp; + int i; +@@ -294,9 +297,11 @@ static int amd_pstate_ut_epp(u32 index) + if (!policy) + return -ENODEV; + +- cpudata = policy->driver_data; + orig_mode = amd_pstate_get_status(); +- orig_dynamic_epp = cpudata->dynamic_epp; ++ if (policy->driver_data) { ++ cpudata = policy->driver_data; ++ orig_dynamic_epp = cpudata->dynamic_epp; ++ } + + /* Drop reference before potential driver change. */ + cpufreq_cpu_put(policy); +@@ -321,16 +326,6 @@ static int amd_pstate_ut_epp(u32 index) + orig_policy = cpudata->policy; + cpudata->policy = CPUFREQ_POLICY_POWERSAVE; + +- /* +- * Disable dynamic EPP before running test. If "orig_dynamic_epp" is +- * true, the driver will do a redundant switch at the end and there +- * is no need for enabling it again at the end of the test. +- */ +- if (cpudata->dynamic_epp) { +- pr_debug("Dynamic EPP is enabled, disabling it\n"); +- amd_pstate_clear_dynamic_epp(policy); +- } +- + for (epp = 0; epp <= U8_MAX; epp++) { + u8 val; + +@@ -367,6 +362,11 @@ static int amd_pstate_ut_epp(u32 index) + if (ret < 0) + goto out; + strreplace(buf, '\n', '\0'); ++ /* ++ * "dynamic" mode reports the EPP as "dynamic(profile:X)" ++ * Trim at "(" and just compare tie the epp string. ++ */ ++ strreplace(buf, '(', '\0'); + + if (strcmp(buf, epp_strings[i])) { + pr_err("String EPP value mismatch: %s != %s\n", buf, epp_strings[i]); +@@ -380,18 +380,23 @@ static int amd_pstate_ut_epp(u32 index) + out: + if (policy) { + cpudata->policy = orig_policy; ++ /* ++ * If the driver had enabled dynamic_epp to begin with, ++ * restore it here before dropping policy reference. ++ */ ++ if (orig_dynamic_epp) { ++ int ret2; ++ ++ ret2 = store_energy_performance_preference(policy, ++ epp_strings[0], ++ strlen(epp_strings[0])); ++ if (!ret && (ret2 < 0)) ++ ret = ret2; ++ } + up_write(&policy->rwsem); + cpufreq_cpu_put(policy); + } + +- if (orig_dynamic_epp) { +- int ret2; +- +- ret2 = amd_pstate_set_mode(AMD_PSTATE_DISABLE); +- if (!ret && ret2) +- ret = ret2; +- } +- + if (orig_mode != amd_pstate_get_status()) { + int ret2; + +@@ -557,6 +562,80 @@ out: + return ret; + } + ++static int amd_pstate_ut_check_floor_freq(u32 index) ++{ ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL; ++ char *buf __free(cleanup_page) = NULL; ++ unsigned int orig_floor_freq; ++ unsigned int floor_freq; ++ int ret, cpu = 0; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO)) ++ return -EOPNOTSUPP; ++ ++ policy = cpufreq_cpu_get(cpu); ++ if (!policy) ++ return -ENODEV; ++ ++ buf = (char *)__get_free_page(GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ guard(rwsem_write)(&policy->rwsem); ++ ++ if (!policy->driver_data) ++ return -ENODEV; ++ ++ /* Retrieve original floor frequency */ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ ret = kstrtou32(buf, 0, &orig_floor_freq); ++ if (ret) ++ return ret; ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", policy->cpuinfo.min_freq); ++ ++ /* Set floor frequency to cpuinfo.min_freq */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to set floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ strreplace(buf, '\n', '\0'); ++ ret = kstrtou32(buf, 0, &floor_freq); ++ if (ret) ++ return ret; ++ ++ /* Confirm sysfs reflects the change correctly. */ ++ if (floor_freq != policy->cpuinfo.min_freq) { ++ pr_err("Floor frequency value mismatch: %u != %u\n", ++ floor_freq, policy->cpuinfo.min_freq); ++ return -EINVAL; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", orig_floor_freq); ++ ++ /* Restore the original value. */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to restore floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ return 0; ++} ++ + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); +@@ -575,10 +654,16 @@ static int __init amd_pstate_ut_init(void) + + ret = amd_pstate_ut_cases[i].func(i); + +- if (ret) ++ if (ret) { ++ /* Platform does not support the feature being tested. */ ++ if (ret == -EOPNOTSUPP) { ++ pr_err("%-4d %-20s\t skipped!\n", i+1, amd_pstate_ut_cases[i].name); ++ continue; ++ } + pr_err("%-4d %-20s\t fail: %d!\n", i+1, amd_pstate_ut_cases[i].name, ret); +- else ++ } else { + pr_info("%-4d %-20s\t success!\n", i+1, amd_pstate_ut_cases[i].name); ++ } + } + + return 0; +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -75,7 +75,7 @@ const char *amd_pstate_get_mode_string(enum amd_pstate_mode mode) + mode = AMD_PSTATE_UNDEFINED; + return amd_pstate_mode_string[mode]; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_mode_string); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_mode_string); + + struct quirk_entry { + u32 nominal_freq; +@@ -87,7 +87,6 @@ static struct cpufreq_driver amd_pstate_driver; + static struct cpufreq_driver amd_pstate_epp_driver; + static int cppc_state = AMD_PSTATE_UNDEFINED; + static bool amd_pstate_prefcore = true; +-static bool dynamic_epp; + static struct quirk_entry *quirks; + + /* +@@ -106,6 +105,7 @@ static struct quirk_entry *quirks; + * 3 balance_power + * 4 power + * 5 custom (for raw EPP values) ++ * 6 dynamic (platform profile driven selection) + */ + enum energy_perf_value_index { + EPP_INDEX_DEFAULT = 0, +@@ -114,6 +114,7 @@ enum energy_perf_value_index { + EPP_INDEX_BALANCE_POWERSAVE, + EPP_INDEX_POWERSAVE, + EPP_INDEX_CUSTOM, ++ EPP_INDEX_DYNAMIC, + EPP_INDEX_MAX, + }; + +@@ -124,6 +125,7 @@ static const char * const energy_perf_strings[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = "balance_power", + [EPP_INDEX_POWERSAVE] = "power", + [EPP_INDEX_CUSTOM] = "custom", ++ [EPP_INDEX_DYNAMIC] = "dynamic", + }; + static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); + +@@ -134,7 +136,7 @@ static unsigned int epp_values[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_BALANCE_POWERSAVE, + [EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_POWERSAVE, + }; +-static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 1); ++static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 2); + + typedef int (*cppc_mode_transition_fn)(int); + +@@ -462,7 +464,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + { + union perf_cached perf = READ_ONCE(cpudata->perf); + u64 cap1, numerator, cppc_req; +- u8 min_perf; + + int ret = rdmsrq_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, + &cap1); +@@ -478,16 +479,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + return ret; + + WRITE_ONCE(cpudata->cppc_req_cached, cppc_req); +- min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); +- +- /* +- * Clear out the min_perf part to check if the rest of the MSR is 0, if yes, this is an +- * indication that the min_perf value is the one specified through the BIOS option +- */ +- cppc_req &= ~(AMD_CPPC_MIN_PERF_MASK); +- +- if (!cppc_req) +- perf.bios_min_perf = min_perf; + + perf.highest_perf = numerator; + perf.max_limit_perf = numerator; +@@ -495,6 +486,7 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + perf.nominal_perf = FIELD_GET(AMD_CPPC_NOMINAL_PERF_MASK, cap1); + perf.lowest_nonlinear_perf = FIELD_GET(AMD_CPPC_LOWNONLIN_PERF_MASK, cap1); + perf.lowest_perf = FIELD_GET(AMD_CPPC_LOWEST_PERF_MASK, cap1); ++ perf.bios_min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, FIELD_GET(AMD_CPPC_HIGHEST_PERF_MASK, cap1)); + WRITE_ONCE(cpudata->floor_perf_cnt, FIELD_GET(AMD_CPPC_FLOOR_PERF_CNT_MASK, cap1)); +@@ -789,6 +781,7 @@ static unsigned int amd_pstate_fast_switch(struct cpufreq_policy *policy, + static void amd_pstate_adjust_perf(struct cpufreq_policy *policy, + unsigned long _min_perf, + unsigned long target_perf, ++ unsigned long _max_perf, + unsigned long capacity) + { + u8 max_perf, min_perf, des_perf, cap_perf; +@@ -1044,6 +1037,13 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) + return -EINVAL; + } + ++ if (perf.bios_min_perf) { ++ u32 bios_min_freq = perf_to_freq(perf, cpudata->nominal_freq, perf.bios_min_perf); ++ ++ pr_debug("Found Requested CPU Min Frequency of %uKHz on CPU%d\n", ++ bios_min_freq, cpudata->cpu); ++ } ++ + return 0; + } + +@@ -1192,6 +1192,24 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + return NOTIFY_OK; + } + ++static int amd_pstate_get_epp_from_platform_profile(struct cpufreq_policy *policy, ++ enum platform_profile_option profile) ++{ ++ switch (profile) { ++ case PLATFORM_PROFILE_PERFORMANCE: ++ return AMD_CPPC_EPP_PERFORMANCE; ++ case PLATFORM_PROFILE_BALANCED: ++ return amd_pstate_get_balanced_epp(policy); ++ case PLATFORM_PROFILE_LOW_POWER: ++ return AMD_CPPC_EPP_POWERSAVE; ++ default: ++ break; ++ } ++ ++ pr_err("Unknown Platform Profile %d\n", profile); ++ return -EOPNOTSUPP; ++} ++ + static int amd_pstate_profile_probe(void *drvdata, unsigned long *choices) + { + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); +@@ -1217,31 +1235,19 @@ static int amd_pstate_profile_set(struct device *dev, + struct amd_cpudata *cpudata = dev_get_drvdata(dev); + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; ++ u8 epp; + + if (!policy) + return -ENODEV; + +- switch (profile) { +- case PLATFORM_PROFILE_LOW_POWER: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_BALANCED: +- ret = amd_pstate_set_epp(policy, +- amd_pstate_get_balanced_epp(policy)); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_PERFORMANCE: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_PERFORMANCE); +- if (ret) +- return ret; +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; ++ ret = amd_pstate_set_epp(policy, epp); ++ if (ret) ++ return ret; + + cpudata->current_profile = profile; + +@@ -1267,28 +1273,20 @@ void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy) + kfree(cpudata->profile_name); + cpudata->dynamic_epp = false; + } +-EXPORT_SYMBOL_GPL(amd_pstate_clear_dynamic_epp); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_clear_dynamic_epp); + + static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; ++ u64 prev = READ_ONCE(cpudata->cppc_req_cached); + int ret; + u8 epp; + +- switch (cpudata->current_profile) { +- case PLATFORM_PROFILE_PERFORMANCE: +- epp = AMD_CPPC_EPP_PERFORMANCE; +- break; +- case PLATFORM_PROFILE_LOW_POWER: +- epp = AMD_CPPC_EPP_POWERSAVE; +- break; +- case PLATFORM_PROFILE_BALANCED: +- epp = amd_pstate_get_balanced_epp(policy); +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", cpudata->current_profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, cpudata->current_profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1321,6 +1319,9 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + cleanup: + amd_pstate_clear_dynamic_epp(policy); + ++ epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, prev); ++ /* Restore previous EPP if toggling Dynamic EPP failed. */ ++ amd_pstate_set_epp(policy, epp); + return ret; + } + +@@ -1391,7 +1392,7 @@ static ssize_t show_amd_pstate_hw_prefcore(struct cpufreq_policy *policy, + static ssize_t show_energy_performance_available_preferences( + struct cpufreq_policy *policy, char *buf) + { +- int offset = 0, i; ++ int i, offset = 0; + struct amd_cpudata *cpudata = policy->driver_data; + + if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) +@@ -1414,11 +1415,6 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + bool raw_epp = false; + u8 epp; + +- if (cpudata->dynamic_epp) { +- pr_debug("EPP cannot be set when dynamic EPP is enabled\n"); +- return -EBUSY; +- } +- + /* + * if the value matches a number, use that, otherwise see if + * matches an index in the energy_perf_strings array +@@ -1429,6 +1425,25 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + ret = sysfs_match_string(energy_perf_strings, buf); + if (ret < 0 || ret == EPP_INDEX_CUSTOM) + return -EINVAL; ++ ++ if (ret == EPP_INDEX_DYNAMIC) { ++ if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) ++ return -EBUSY; ++ /* ++ * Dynamic EPP was already enabled for this CPU. ++ * Nothing to do. ++ */ ++ if (cpudata->dynamic_epp) ++ return count; ++ ++ cpudata->current_profile = PLATFORM_PROFILE_BALANCED; ++ ret = amd_pstate_set_dynamic_epp(policy); ++ if (ret) ++ return ret; ++ ++ return count; ++ } ++ + if (ret) + epp = epp_values[ret]; + else +@@ -1440,6 +1455,13 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + return -EBUSY; + } + ++ /* ++ * Dynamic EPP was enabled previously! ++ * Switch back to the static EPP mode. ++ */ ++ if (cpudata->dynamic_epp) ++ amd_pstate_clear_dynamic_epp(policy); ++ + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1448,7 +1470,7 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + + return count; + } +-EXPORT_SYMBOL_GPL(store_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_energy_performance_preference); + + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf) + { +@@ -1457,7 +1479,7 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + + epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached); + +- if (cpudata->raw_epp) ++ if (!cpudata->dynamic_epp && cpudata->raw_epp) + return sysfs_emit(buf, "%u\n", epp); + + switch (epp) { +@@ -1477,12 +1499,14 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + return -EINVAL; + } + ++ if (cpudata->dynamic_epp) ++ return sysfs_emit(buf, "dynamic(profile:%s)\n", energy_perf_strings[preference]); ++ + return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]); + } +-EXPORT_SYMBOL_GPL(show_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_energy_performance_preference); + +-static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, +- const char *buf, size_t count) ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count) + { + struct amd_cpudata *cpudata = policy->driver_data; + union perf_cached perf = READ_ONCE(cpudata->perf); +@@ -1505,13 +1529,15 @@ static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, + + return ret ?: count; + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_amd_pstate_floor_freq); + +-static ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) + { + struct amd_cpudata *cpudata = policy->driver_data; + + return sysfs_emit(buf, "%u\n", cpudata->floor_freq); + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_amd_pstate_floor_freq); + + static ssize_t show_amd_pstate_floor_count(struct cpufreq_policy *policy, char *buf) + { +@@ -1579,7 +1605,7 @@ struct freq_attr **amd_pstate_get_current_attrs(void) + return NULL; + return current_pstate_driver->attr; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_current_attrs); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_current_attrs); + + static struct freq_attr **get_freq_attrs(void) + { +@@ -1764,7 +1790,7 @@ int amd_pstate_get_status(void) + { + return cppc_state; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status); + + int amd_pstate_update_status(const char *buf, size_t size) + { +@@ -1784,7 +1810,7 @@ int amd_pstate_update_status(const char *buf, size_t size) + + return 0; + } +-EXPORT_SYMBOL_GPL(amd_pstate_update_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_update_status); + + static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -1812,50 +1838,12 @@ static ssize_t prefcore_show(struct device *dev, + return sysfs_emit(buf, "%s\n", str_enabled_disabled(amd_pstate_prefcore)); + } + +-static ssize_t dynamic_epp_show(struct device *dev, +- struct device_attribute *attr, char *buf) +-{ +- return sysfs_emit(buf, "%s\n", str_enabled_disabled(dynamic_epp)); +-} +- +-static ssize_t dynamic_epp_store(struct device *a, struct device_attribute *b, +- const char *buf, size_t count) +-{ +- bool enabled; +- int ret; +- +- ret = kstrtobool(buf, &enabled); +- if (ret) +- return ret; +- +- guard(mutex)(&amd_pstate_driver_lock); +- +- if (cppc_state != AMD_PSTATE_ACTIVE) { +- pr_debug("dynamic_epp can only be toggled in active mode\n"); +- return -EINVAL; +- } +- +- /* Nothing to do */ +- if (dynamic_epp == enabled) +- return count; +- +- /* reinitialize with desired dynamic EPP value */ +- dynamic_epp = enabled; +- ret = amd_pstate_change_driver_mode(cppc_state); +- if (ret) +- dynamic_epp = false; +- +- return ret ? ret : count; +-} +- + static DEVICE_ATTR_RW(status); + static DEVICE_ATTR_RO(prefcore); +-static DEVICE_ATTR_RW(dynamic_epp); + + static struct attribute *pstate_global_attributes[] = { + &dev_attr_status.attr, + &dev_attr_prefcore.attr, +- &dev_attr_dynamic_epp.attr, + NULL + }; + +@@ -1938,12 +1926,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + +- /* Fetch the firmware programmed default EPP value */ ++ /* Cache the firmware programmed EPP */ + default_epp = amd_pstate_get_epp(cpudata); + if (default_epp < 0) { + ret = default_epp; + goto free_cpudata1; + } ++ FIELD_MODIFY(AMD_CPPC_EPP_PERF_MASK, &cpudata->cppc_req_cached, default_epp); + + /* + * Set the policy to provide a valid fallback value in case +@@ -1961,10 +1950,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + cpudata->current_profile = PLATFORM_PROFILE_BALANCED; + } + +- if (dynamic_epp) +- ret = amd_pstate_set_dynamic_epp(policy); +- else +- ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); ++ ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); + if (ret) + goto free_cpudata1; + +@@ -2036,6 +2022,18 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy) + if (!policy->cpuinfo.max_freq) + return -ENODEV; + ++ /* Must be a switch between PERFORMANCE and POWERSAVE */ ++ if (cpudata->policy != policy->policy) { ++ /* ++ * Disable dynamic_epp when switching ++ * out of CPUFREQ_POLICY_POWERSAVE. ++ */ ++ if (cpudata->dynamic_epp) { ++ WARN_ON_ONCE(cpudata->policy != CPUFREQ_POLICY_POWERSAVE); ++ amd_pstate_clear_dynamic_epp(policy); ++ } ++ } ++ + cpudata->policy = policy->policy; + + ret = amd_pstate_epp_update_limit(policy, true); +@@ -2363,19 +2361,8 @@ static int __init amd_prefcore_param(char *str) + return 0; + } + +-static int __init amd_dynamic_epp_param(char *str) +-{ +- if (!strcmp(str, "disable")) +- dynamic_epp = false; +- if (!strcmp(str, "enable")) +- dynamic_epp = true; +- +- return 0; +-} +- + early_param("amd_pstate", amd_pstate_param); + early_param("amd_prefcore", amd_prefcore_param); +-early_param("amd_dynamic_epp", amd_dynamic_epp_param); + + MODULE_AUTHOR("Huang Rui "); + MODULE_DESCRIPTION("AMD Processor P-state Frequency Driver"); +diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h +--- a/drivers/cpufreq/amd-pstate.h ++++ b/drivers/cpufreq/amd-pstate.h +@@ -11,6 +11,13 @@ + #include + #include + ++#if IS_MODULE(CONFIG_X86_AMD_PSTATE_UT) ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) \ ++ EXPORT_SYMBOL_FOR_MODULES(symbol, "amd-pstate-ut") ++#else ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) ++#endif ++ + /********************************************************************* + * AMD P-state INTERFACE * + *********************************************************************/ +@@ -153,6 +160,8 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + const char *buf, size_t count); + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf); + void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy); ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count); ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf); + + struct freq_attr; + +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2225,14 +2225,17 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + * @policy: cpufreq policy object of the target CPU. + * @min_perf: Minimum (required) performance level (units of @capacity). + * @target_perf: Target (desired) performance level (units of @capacity). ++ * @max_perf: Maximum (allowed) performance level (units of @capacity). + * @capacity: Capacity of the target CPU. + * +- * Carry out a fast performance level switch of @cpu without sleeping. ++ * Carry out a fast performance level adjustment for the CPU represented by ++ * @policy without sleeping. + * + * The driver's ->adjust_perf() callback invoked by this function must be +- * suitable for being called from within RCU-sched read-side critical sections +- * and it is expected to select a suitable performance level equal to or above +- * @min_perf and preferably equal to or below @target_perf. ++ * suitable for calling from within RCU-sched read-side critical sections and ++ * it is expected to program the processor to select suitable performance ++ * levels between @min_perf and @max_perf inclusive and preferably close to ++ * @target_perf going forward for the CPU represented by @policy. + * + * This function must not be called if policy->fast_switch_enabled is unset. + * +@@ -2244,9 +2247,10 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { +- cpufreq_driver->adjust_perf(policy, min_perf, target_perf, capacity); ++ cpufreq_driver->adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /** +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -299,7 +299,6 @@ struct pstate_funcs { + static struct pstate_funcs pstate_funcs __read_mostly; + + static bool hwp_active __ro_after_init; +-static int hwp_mode_bdw __ro_after_init; + static bool per_cpu_limits __ro_after_init; + static bool hwp_forced __ro_after_init; + static bool hwp_boost __read_mostly; +@@ -587,21 +586,14 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) + + hwp_is_hybrid = true; + +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_pstate * scaling, +- perf_ctl_scaling); +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_pstate * scaling, +- perf_ctl_scaling); +- + freq = perf_ctl_max_phys * perf_ctl_scaling; + cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); + +- freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.min_freq = freq; + /* + * Cast the min P-state value retrieved via pstate_funcs.get_min() to + * the effective range of HWP performance levels. + */ +- cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); ++ cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); + } + + static bool turbo_is_disabled(void) +@@ -979,12 +971,10 @@ static int hybrid_get_cost(struct device *dev, unsigned long freq, + * capacity. Similarly, P-cores start to be populated when E-cores are + * utilized above 60% of the capacity. + */ +- if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_ATOM) { +- if (hybrid_has_l3(dev->id)) /* E-core */ +- *cost += 1; +- } else { /* P-core */ ++ if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_CORE) /* P-core */ + *cost += 2; +- } ++ else if (hybrid_has_l3(dev->id)) /* E-core */ ++ *cost += 1; + + return 0; + } +@@ -1185,6 +1175,22 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static void intel_pstate_update_freq_limits(struct cpudata *cpu) ++{ ++ int scaling = cpu->pstate.scaling; ++ unsigned int turbo_freq = cpu->pstate.turbo_pstate * scaling; ++ unsigned int max_freq = cpu->pstate.max_pstate * scaling; ++ int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; ++ ++ if (scaling != perf_ctl_scaling) { ++ turbo_freq = rounddown(turbo_freq, perf_ctl_scaling); ++ max_freq = rounddown(max_freq, perf_ctl_scaling); ++ } ++ ++ cpu->pstate.turbo_freq = turbo_freq; ++ cpu->pstate.max_freq = max_freq; ++} ++ + static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + { + u64 cap; +@@ -1197,20 +1203,8 @@ static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + + static void intel_pstate_get_hwp_cap(struct cpudata *cpu) + { +- int scaling = cpu->pstate.scaling; +- + __intel_pstate_get_hwp_cap(cpu); +- +- cpu->pstate.max_freq = cpu->pstate.max_pstate * scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * scaling; +- if (scaling != cpu->pstate.perf_ctl_scaling) { +- int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; +- +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_freq, +- perf_ctl_scaling); +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_freq, +- perf_ctl_scaling); +- } ++ intel_pstate_update_freq_limits(cpu); + } + + static void hybrid_update_capacity(struct cpudata *cpu) +@@ -2299,33 +2293,16 @@ static int hwp_get_cpu_scaling(int cpu) + return intel_pstate_cppc_get_scaling(cpu); + } + +-static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) +-{ +- trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); +- cpu->pstate.current_pstate = pstate; +- /* +- * Generally, there is no guarantee that this code will always run on +- * the CPU being updated, so force the register update to run on the +- * right CPU. +- */ +- wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, +- pstate_funcs.get_val(cpu, pstate)); +-} +- +-static void intel_pstate_set_min_pstate(struct cpudata *cpu) +-{ +- intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); +-} +- + static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + { + int perf_ctl_scaling = pstate_funcs.get_scaling(); + + cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); + cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); ++ cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; + cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; + +- if (hwp_active && !hwp_mode_bdw) { ++ if (hwp_active) { + __intel_pstate_get_hwp_cap(cpu); + + if (pstate_funcs.get_cpu_scaling) { +@@ -2345,11 +2322,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); + } + +- if (cpu->pstate.scaling == perf_ctl_scaling) { +- cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.max_freq = cpu->pstate.max_pstate * perf_ctl_scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * perf_ctl_scaling; +- } ++ intel_pstate_update_freq_limits(cpu); + + if (pstate_funcs.get_aperf_mperf_shift) + cpu->aperf_mperf_shift = pstate_funcs.get_aperf_mperf_shift(); +@@ -2882,8 +2855,22 @@ static void intel_pstate_update_perf_limits(struct cpudata *cpu, + cpu->min_perf_ratio); + } + ++static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) ++{ ++ trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); ++ cpu->pstate.current_pstate = pstate; ++ /* ++ * Generally, there is no guarantee that this code will always run on ++ * the CPU being updated, so force the register update to run on the ++ * right CPU. ++ */ ++ wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, ++ pstate_funcs.get_val(cpu, pstate)); ++} ++ + static int intel_pstate_set_policy(struct cpufreq_policy *policy) + { ++ unsigned int freq = policy->min; + struct cpudata *cpu; + + if (!policy->cpuinfo.max_freq) +@@ -2899,7 +2886,23 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + + intel_pstate_update_perf_limits(cpu, policy->min, policy->max); + +- if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ if (hwp_active) { ++ /* ++ * The active mode only requires an update util hook if HWP ++ * boost is used and the policy is not "performance". ++ */ ++ if (hwp_boost && cpu->policy != CPUFREQ_POLICY_PERFORMANCE) { ++ intel_pstate_set_update_util_hook(policy->cpu); ++ } else { ++ intel_pstate_clear_update_util_hook(policy->cpu); ++ if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ freq = cpu->max_perf_ratio * cpu->pstate.scaling; ++ if (cpu->pstate.scaling != cpu->pstate.perf_ctl_scaling) ++ freq = rounddown(freq, cpu->pstate.perf_ctl_scaling); ++ } ++ } ++ intel_pstate_hwp_set(policy->cpu); ++ } else if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + int pstate = max(cpu->pstate.min_pstate, cpu->max_perf_ratio); + + /* +@@ -2908,25 +2911,17 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + */ + intel_pstate_clear_update_util_hook(policy->cpu); + intel_pstate_set_pstate(cpu, pstate); ++ freq = pstate * cpu->pstate.scaling; + } else { + intel_pstate_set_update_util_hook(policy->cpu); + } +- +- if (hwp_active) { +- /* +- * When hwp_boost was active before and dynamically it +- * was turned off, in that case we need to clear the +- * update util hook. +- */ +- if (!hwp_boost) +- intel_pstate_clear_update_util_hook(policy->cpu); +- intel_pstate_hwp_set(policy->cpu); +- } + /* +- * policy->cur is never updated with the intel_pstate driver, but it +- * is used as a stale frequency value. So, keep it within limits. ++ * policy->cur is never updated in the intel_pstate driver, but it is ++ * used as a stale frequency value, so set it to reflect the actual ++ * requested P-state in the "performance" policy case and to the min ++ * otherwise. + */ +- policy->cur = policy->min; ++ policy->cur = freq; + + mutex_unlock(&intel_pstate_limits_lock); + +@@ -2969,6 +2964,11 @@ static int intel_pstate_verify_policy(struct cpufreq_policy_data *policy) + return 0; + } + ++static void intel_pstate_set_min_pstate(struct cpudata *cpu) ++{ ++ intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); ++} ++ + static int intel_cpufreq_cpu_offline(struct cpufreq_policy *policy) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3242,6 +3242,7 @@ static unsigned int intel_cpufreq_fast_switch(struct cpufreq_policy *policy, + static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3272,7 +3273,13 @@ static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + if (min_pstate > cpu->max_perf_ratio) + min_pstate = cpu->max_perf_ratio; + +- max_pstate = min(cap_pstate, cpu->max_perf_ratio); ++ max_pstate = cap_pstate; ++ if (max_perf < capacity) ++ max_pstate = DIV_ROUND_UP(cap_pstate * max_perf, capacity); ++ ++ if (max_pstate > cpu->max_perf_ratio) ++ max_pstate = cpu->max_perf_ratio; ++ + if (max_pstate < min_pstate) + max_pstate = min_pstate; + +@@ -3679,14 +3686,14 @@ static inline bool intel_pstate_has_acpi_ppc(void) { return false; } + static inline void intel_pstate_request_control_from_smm(void) {} + #endif /* CONFIG_ACPI */ + +-#define INTEL_PSTATE_HWP_BROADWELL 0x01 ++#define INTEL_PSTATE_HWP_NOT_HYBRID 0x01 + + #define X86_MATCH_HWP(vfm, hwp_mode) \ + X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_HWP, hwp_mode) + + static const struct x86_cpu_id hwp_support_ids[] __initconst = { +- X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_BROADWELL), +- X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_BROADWELL), ++ X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_NOT_HYBRID), ++ X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_NOT_HYBRID), + X86_MATCH_HWP(INTEL_ANY, 0), + {} + }; +@@ -3811,7 +3818,6 @@ static int __init intel_pstate_init(void) + + if (!no_hwp) { + hwp_active = true; +- hwp_mode_bdw = id->driver_data; + intel_pstate.attr = hwp_cpufreq_attrs; + intel_cpufreq.attr = hwp_cpufreq_attrs; + intel_cpufreq.flags |= CPUFREQ_NEED_UPDATE_LIMITS; +@@ -3819,7 +3825,8 @@ static int __init intel_pstate_init(void) + if (!default_driver) + default_driver = &intel_pstate; + +- pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; ++ if (!id->driver_data) ++ pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; + + goto hwp_cpu_matched; + } +diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c +--- a/drivers/idle/intel_idle.c ++++ b/drivers/idle/intel_idle.c +@@ -1780,6 +1780,7 @@ module_param_named(no_native, no_native, bool, 0444); + MODULE_PARM_DESC(no_native, "Ignore cpu specific (native) idle states in lieu of ACPI idle states"); + + static struct acpi_processor_power acpi_state_table __initdata; ++static bool acpi_lpi_available __initdata; + + /** + * intel_idle_cst_usable - Check if the _CST information can be used. +@@ -1804,18 +1805,37 @@ static bool __init intel_idle_cst_usable(void) + return true; + } + +-static bool __init intel_idle_acpi_cst_extract(void) ++static bool __init intel_idle_acpi_extract_lpi_cstates(void) + { + unsigned int cpu; + +- if (no_acpi) { +- pr_debug("Not allowed to use ACPI _CST\n"); +- return false; ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); ++ if (!pr) ++ continue; ++ ++ if (acpi_processor_extract_lpi_info(pr->handle, ++ &acpi_state_table, true)) ++ continue; ++ ++ acpi_lpi_available = true; ++ return true; + } + +- for_each_possible_cpu(cpu) { +- struct acpi_processor *pr = per_cpu(processors, cpu); ++ pr_debug("No ACPI _LPI idle states\n"); ++ return false; ++} + ++static bool __init intel_idle_acpi_extract_cst_cstates(void) ++{ ++ unsigned int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); + if (!pr) + continue; + +@@ -1827,18 +1847,96 @@ static bool __init intel_idle_acpi_cst_extract(void) + if (!intel_idle_cst_usable()) + continue; + +- if (!acpi_processor_claim_cst_control()) +- break; +- + return true; + } + +- acpi_state_table.count = 0; + pr_debug("ACPI _CST not found or not usable\n"); + return false; + } + +-static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) ++static bool __init intel_idle_acpi_extract_cstates(void) ++{ ++ if (intel_idle_acpi_extract_lpi_cstates()) ++ return true; ++ ++ if (intel_idle_acpi_extract_cst_cstates()) ++ return true; ++ ++ return false; ++} ++ ++static bool __init intel_idle_acpi_probe(void) ++{ ++ if (no_acpi) { ++ pr_debug("Not allowed to use ACPI for C-states extraction\n"); ++ return false; ++ } ++ ++ if (intel_idle_acpi_extract_cstates() && ++ acpi_processor_claim_cst_control()) ++ return true; ++ ++ acpi_state_table.count = 0; ++ return false; ++} ++ ++static void __init intel_idle_complete_state_init(struct cpuidle_state *state) ++{ ++ if (intel_idle_state_needs_timer_stop(state)) ++ state->flags |= CPUIDLE_FLAG_TIMER_STOP; ++ ++ state->enter = intel_idle; ++ state->enter_dead = intel_idle_enter_dead; ++ state->enter_s2idle = intel_idle_s2idle; ++} ++ ++static void __init intel_idle_init_cstates_acpi_lpi(struct cpuidle_driver *drv) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ struct acpi_lpi_state *lpi_state; ++ struct cpuidle_state *state; ++ ++ if (intel_idle_max_cstate_reached(index)) ++ break; ++ ++ lpi_state = &acpi_state_table.lpi_states[index]; ++ ++ state = &drv->states[drv->state_count++]; ++ ++ scnprintf(state->name, CPUIDLE_NAME_LEN, "C%d_LPI", index + 1); ++ strscpy(state->desc, lpi_state->desc, CPUIDLE_DESC_LEN); ++ state->exit_latency = lpi_state->wake_latency; ++ state->target_residency = lpi_state->min_residency; ++ state->flags = MWAIT2flg(lpi_state->address); ++ /* ++ * Assume that entering any of the idle states extracted from ++ * _LPI except for the first two will cause the TLB to be ++ * flushed and let the core call leave_mm() for them upfront ++ * to avoid unnecessary wakeups due to TLB shootdowns. ++ */ ++ if (index > 1) ++ state->flags |= CPUIDLE_FLAG_TLB_FLUSHED; ++ ++ if (disabled_states_mask & BIT(index + 1)) ++ state->flags |= CPUIDLE_FLAG_OFF; ++ ++ intel_idle_complete_state_init(state); ++ ++ pr_info("%s: MWAIT hint 0x%x\n", state->name, flg2MWAIT(state->flags)); ++ } ++ ++ /* ++ * Assume the first idle state in the table to be C1 and if any deeper ++ * idle states are exposed while X86_FEATURE_NONSTOP_TSC is unset, mark ++ * the TSC as unstable. ++ */ ++ if (index > 1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) ++ mark_tsc_unstable("TSC halts in idle"); ++} ++ ++static void __init intel_idle_init_cstates_acpi_cst(struct cpuidle_driver *drv) + { + int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +@@ -1880,49 +1978,76 @@ static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + if (disabled_states_mask & BIT(cstate)) + state->flags |= CPUIDLE_FLAG_OFF; + +- if (intel_idle_state_needs_timer_stop(state)) +- state->flags |= CPUIDLE_FLAG_TIMER_STOP; +- + if (cx->type > ACPI_STATE_C1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) + mark_tsc_unstable("TSC halts in idle"); + +- state->enter = intel_idle; +- state->enter_dead = intel_idle_enter_dead; +- state->enter_s2idle = intel_idle_s2idle; ++ intel_idle_complete_state_init(state); + } + } + +-static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + { +- int cstate, limit; ++ if (acpi_lpi_available) ++ intel_idle_init_cstates_acpi_lpi(drv); ++ else ++ intel_idle_init_cstates_acpi_cst(drv); ++} + +- /* +- * If there are no _CST C-states, do not disable any C-states by +- * default. +- */ +- if (!acpi_state_table.count) +- return false; ++static bool __init intel_idle_acpi_hint_match(unsigned int flags, u32 acpi_hint, ++ u32 table_hint) ++{ ++ if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { ++ acpi_hint &= ~MWAIT_SUBSTATE_MASK; ++ table_hint &= ~MWAIT_SUBSTATE_MASK; ++ } ++ return acpi_hint == table_hint; ++} ++ ++static bool __init intel_idle_off_by_default_lpi(unsigned int flags, u32 mwait_hint) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ u32 acpi_hint = acpi_state_table.lpi_states[index].address; ++ ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) ++ return false; ++ } ++ return true; ++} ++ ++static bool __init intel_idle_off_by_default_cst(unsigned int flags, u32 mwait_hint) ++{ ++ int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +- limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + /* + * If limit > 0, intel_idle_cst_usable() has returned 'true', so all of + * the interesting states are ACPI_CSTATE_FFH. + */ + for (cstate = 1; cstate < limit; cstate++) { + u32 acpi_hint = acpi_state_table.states[cstate].address; +- u32 table_hint = mwait_hint; + +- if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { +- acpi_hint &= ~MWAIT_SUBSTATE_MASK; +- table_hint &= ~MWAIT_SUBSTATE_MASK; +- } +- +- if (acpi_hint == table_hint) ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) + return false; + } + return true; + } + ++static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++{ ++ /* ++ * If there is no C-states information in the ACPI tables, do not ++ * disable any C-states by default. ++ */ ++ if (!acpi_state_table.count) ++ return false; ++ ++ if (acpi_lpi_available) ++ return intel_idle_off_by_default_lpi(flags, mwait_hint); ++ ++ return intel_idle_off_by_default_cst(flags, mwait_hint); ++} ++ + static inline bool ignore_native(void) + { + return no_native && !no_acpi; +@@ -1930,7 +2055,7 @@ static inline bool ignore_native(void) + #else /* !CONFIG_ACPI_PROCESSOR_CSTATE */ + #define force_use_acpi (false) + +-static inline bool intel_idle_acpi_cst_extract(void) { return false; } ++static inline bool intel_idle_acpi_probe(void) { return false; } + static inline void intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) { } + static inline bool intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) + { +@@ -2745,7 +2870,7 @@ static int __init intel_idle_init(void) + if (icpu) { + if (icpu->state_table) + cpuidle_state_table = icpu->state_table; +- else if (!intel_idle_acpi_cst_extract()) ++ else if (!intel_idle_acpi_probe()) + return -ENODEV; + + auto_demotion_disable_flags = icpu->auto_demotion_disable_flags; +@@ -2754,8 +2879,8 @@ static int __init intel_idle_init(void) + if (icpu->c1_demotion_supported) + c1_demotion_supported = true; + if (icpu->use_acpi || force_use_acpi) +- intel_idle_acpi_cst_extract(); +- } else if (!intel_idle_acpi_cst_extract()) { ++ intel_idle_acpi_probe(); ++ } else if (!intel_idle_acpi_probe()) { + return -ENODEV; + } + +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -1581,8 +1581,6 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { +- int ret; +- + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1591,39 +1589,35 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clks) + return opp_table; + +- /* Find clk for the device */ +- opp_table->clk = clk_get(dev, NULL); ++ /* ++ * There are few platforms which don't want the OPP core to manage ++ * device's clock settings. In such cases neither the platform ++ * provides the clks explicitly to us, nor the DT contains a valid ++ * clk entry. The OPP nodes in DT may still contain "opp-hz" property ++ * though, which we need to parse and allow the platform to find an ++ * OPP based on freq later on. ++ * ++ * This is a simple solution to take care of such corner cases, i.e. ++ * make the clk_count 1, which lets us allocate space for frequency ++ * in opp->rates and also parse the entries in DT. Use ++ * clk_get_optional() instead of clk_get() so opp_table->clk stays ++ * NULL for such devices, instead of holding an ERR_PTR(-ENOENT) that ++ * consumers must remember to special-case. ++ */ ++ opp_table->clk = clk_get_optional(dev, NULL); + +- ret = PTR_ERR_OR_ZERO(opp_table->clk); +- if (!ret) { ++ if (IS_ERR(opp_table->clk)) { ++ dev_pm_opp_put_opp_table(opp_table); ++ dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); ++ return ERR_CAST(opp_table->clk); ++ } ++ ++ if (opp_table->clk) + opp_table->config_clks = _opp_config_clk_single; +- opp_table->clk_count = 1; +- return opp_table; +- } + +- if (ret == -ENOENT) { +- /* +- * There are few platforms which don't want the OPP core to +- * manage device's clock settings. In such cases neither the +- * platform provides the clks explicitly to us, nor the DT +- * contains a valid clk entry. The OPP nodes in DT may still +- * contain "opp-hz" property though, which we need to parse and +- * allow the platform to find an OPP based on freq later on. +- * +- * This is a simple solution to take care of such corner cases, +- * i.e. make the clk_count 1, which lets us allocate space for +- * frequency in opp->rates and also parse the entries in DT. +- */ +- opp_table->clk_count = 1; ++ opp_table->clk_count = 1; + +- dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); +- return opp_table; +- } +- +- dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, ret, "Couldn't find clock\n"); +- +- return ERR_PTR(ret); ++ return opp_table; + } + + /* +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -315,6 +315,19 @@ static inline int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, + } + #endif + ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict); ++#else ++static inline int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ return -ENODEV; ++} ++#endif ++ + #ifdef CONFIG_ACPI_HOTPLUG_CPU + /* Arch dependent functions for cpu hotplug support */ + int acpi_map_cpu(acpi_handle handle, phys_cpuid_t physid, u32 acpi_id, +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -379,6 +379,7 @@ struct cpufreq_driver { + void (*adjust_perf)(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + + /* +@@ -624,6 +625,7 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + bool cpufreq_driver_has_adjust_perf(void); + int cpufreq_driver_target(struct cpufreq_policy *policy, +diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig +--- a/kernel/power/Kconfig ++++ b/kernel/power/Kconfig +@@ -268,6 +268,16 @@ config DPM_WATCHDOG + captured in pstore device for inspection in subsequent + boot session. + ++config DPM_WATCHDOG_ENABLED ++ bool "Enable DPM watchdog by default" ++ depends on DPM_WATCHDOG ++ default y ++ help ++ If you say Y here, the DPM watchdog will be enabled by default. ++ If you say N, it will be compiled in but disabled. It can be ++ enabled at boot time via the "pm.dpm_watchdog_enabled" kernel ++ parameter or at runtime via sysfs. ++ + config DPM_WATCHDOG_TIMEOUT + int "Watchdog timeout to panic in seconds" + range 1 120 +diff --git a/kernel/power/swap.c b/kernel/power/swap.c +--- a/kernel/power/swap.c ++++ b/kernel/power/swap.c +@@ -430,19 +430,22 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + + if (!handle->cur) + return -EINVAL; +- offset = alloc_swapdev_block(root_swap); +- error = write_page(buf, offset, hb); +- if (error) +- return error; +- handle->cur->entries[handle->k++] = offset; ++ ++ /* ++ * If the current map page is full, allocate and link next one first. ++ * Delaying this until here avoids writing an empty swap map page when ++ * the image size is an exact MAP_PAGE_ENTRIES multiple. ++ */ + if (handle->k >= MAP_PAGE_ENTRIES) { + offset = alloc_swapdev_block(root_swap); + if (!offset) + return -ENOSPC; ++ + handle->cur->next_swap = offset; + error = write_page(handle->cur, handle->cur_swap, hb); + if (error) +- goto out; ++ return error; ++ + clear_page(handle->cur); + handle->cur_swap = offset; + handle->k = 0; +@@ -450,7 +453,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + if (hb && low_free_pages() <= handle->reqd_free_pages) { + error = hib_wait_io(hb); + if (error) +- goto out; ++ return error; + /* + * Recalculate the number of required free pages, to + * make sure we never take more than half. +@@ -458,14 +461,21 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + handle->reqd_free_pages = reqd_free_pages(); + } + } +- out: +- return error; ++ ++ offset = alloc_swapdev_block(root_swap); ++ error = write_page(buf, offset, hb); ++ if (error) ++ return error; ++ handle->cur->entries[handle->k++] = offset; ++ return 0; + } + + static int flush_swap_writer(struct swap_map_handle *handle) + { +- if (handle->cur && handle->cur_swap) ++ if (handle->cur && handle->cur_swap && handle->k) + return write_page(handle->cur, handle->cur_swap, NULL); ++ else if (handle->cur && handle->cur_swap) ++ return 0; + else + return -EINVAL; + } +@@ -739,7 +749,7 @@ static int save_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +@@ -1243,7 +1253,7 @@ static int load_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c +--- a/kernel/sched/cpufreq_schedutil.c ++++ b/kernel/sched/cpufreq_schedutil.c +@@ -50,6 +50,7 @@ struct sugov_cpu { + + unsigned long util; + unsigned long bw_min; ++ unsigned long bw_max; + + /* The field below is for single-CPU policies only: */ + #ifdef CONFIG_NO_HZ_COMMON +@@ -243,6 +244,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) + util = effective_cpu_util(sg_cpu->cpu, util, &min, &max); + util = max(util, boost); + sg_cpu->bw_min = min; ++ sg_cpu->bw_max = max; + sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); + } + +@@ -495,7 +497,7 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time, + sg_cpu->util = prev_util; + + cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min, +- sg_cpu->util, max_cap); ++ sg_cpu->util, sg_cpu->bw_max, max_cap); + + sg_policy->need_freq_update = false; + sg_policy->last_freq_update_time = time; +@@ -601,7 +603,7 @@ static ssize_t rate_limit_us_show(struct gov_attr_set *attr_set, char *buf) + { + struct sugov_tunables *tunables = to_sugov_tunables(attr_set); + +- return sprintf(buf, "%u\n", tunables->rate_limit_us); ++ return sysfs_emit(buf, "%u\n", tunables->rate_limit_us); + } + + static ssize_t +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -797,7 +797,13 @@ pub trait Driver { + } + + /// Driver's `adjust_perf` callback. +- fn adjust_perf(_policy: &mut Policy, _min_perf: usize, _target_perf: usize, _capacity: usize) { ++ fn adjust_perf( ++ _policy: &mut Policy, ++ _min_perf: usize, ++ _target_perf: usize, ++ _max_perf: usize, ++ _capacity: usize, ++ ) { + build_error!(VTABLE_DEFAULT_ERROR) + } + +@@ -1270,12 +1276,13 @@ impl Registration { + ptr: *mut bindings::cpufreq_policy, + min_perf: c_ulong, + target_perf: c_ulong, ++ max_perf: c_ulong, + capacity: c_ulong, + ) { + // SAFETY: The `ptr` is guaranteed to be valid by the contract with the C code for the + // lifetime of `policy`. + let policy = unsafe { Policy::from_raw_mut(ptr) }; +- T::adjust_perf(policy, min_perf, target_perf, capacity); ++ T::adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /// Driver's `get_intermediate` callback. diff --git a/pkgbuilds/linux-omarchy-eevdf/0210-pm-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0210-pm-updates.patch.sig new file mode 100644 index 0000000..8ffea46 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0210-pm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0211-pm-fixes-1.patch b/pkgbuilds/linux-omarchy-eevdf/0211-pm-fixes-1.patch new file mode 100644 index 0000000..ed2d0f5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0211-pm-fixes-1.patch @@ -0,0 +1,104 @@ +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -453,8 +453,8 @@ int dev_pm_opp_get_opp_count(struct device *dev) + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_dbg(dev, "%s: OPP table not found (%ld)\n", +- __func__, PTR_ERR(opp_table)); ++ dev_dbg(dev, "%s: OPP table not found (%pe)\n", ++ __func__, opp_table); + return PTR_ERR(opp_table); + } + +@@ -611,8 +611,8 @@ _find_key(struct device *dev, unsigned long *key, int index, bool available, + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -722,8 +722,8 @@ struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev, + struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -1036,8 +1036,8 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, + + /* Regulator not available for device */ + if (IS_ERR(reg)) { +- dev_dbg(dev, "%s: regulator not available: %ld\n", __func__, +- PTR_ERR(reg)); ++ dev_dbg(dev, "%s: regulator not available: %pe\n", __func__, ++ reg); + return 0; + } + +@@ -1448,8 +1448,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) + temp_freq = freq; + opp = _find_freq_ceil(opp_table, &temp_freq); + if (IS_ERR(opp)) { +- dev_err(dev, "%s: failed to find OPP for freq %lu (%ld)\n", +- __func__, freq, PTR_ERR(opp)); ++ dev_err(dev, "%s: failed to find OPP for freq %lu (%pe)\n", ++ __func__, freq, opp); + return PTR_ERR(opp); + } + +@@ -1581,6 +1581,8 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { ++ int ret; ++ + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1607,9 +1609,9 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clk = clk_get_optional(dev, NULL); + + if (IS_ERR(opp_table->clk)) { ++ ret = dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); + dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); +- return ERR_CAST(opp_table->clk); ++ return ERR_PTR(ret); + } + + if (opp_table->clk) +@@ -2869,8 +2871,8 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, + struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; + + if (IS_ERR(opp_table)) { +- dev_warn(dev, "%s: Device OPP not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_warn(dev, "%s: Device OPP not found (%pe)\n", __func__, ++ opp_table); + return PTR_ERR(opp_table); + } + +diff --git a/drivers/opp/of.c b/drivers/opp/of.c +--- a/drivers/opp/of.c ++++ b/drivers/opp/of.c +@@ -1345,8 +1345,8 @@ int of_get_required_opp_performance_state(struct device_node *np, int index) + _find_table_of_opp_np(required_np); + + if (IS_ERR(opp_table)) { +- pr_err("%s: Failed to find required OPP table %pOF: %ld\n", +- __func__, np, PTR_ERR(opp_table)); ++ pr_err("%s: Failed to find required OPP table %pOF: %pe\n", ++ __func__, np, opp_table); + return PTR_ERR(opp_table); + } + diff --git a/pkgbuilds/linux-omarchy-eevdf/0211-pm-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0211-pm-fixes-1.patch.sig new file mode 100644 index 0000000..b6284eb Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0211-pm-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0212-pm-fixes-2.patch b/pkgbuilds/linux-omarchy-eevdf/0212-pm-fixes-2.patch new file mode 100644 index 0000000..0e9c073 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0212-pm-fixes-2.patch @@ -0,0 +1,54 @@ +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -408,9 +408,18 @@ int hibernation_snapshot(int platform_mode) + if (error) + goto Close; + ++ error = dpm_prepare(PMSG_FREEZE); ++ if (error) ++ goto Complete; ++ ++ /* Preallocate image memory before freezing kernel threads and shutting down devices. */ ++ error = hibernate_preallocate_memory(); ++ if (error) ++ goto Complete; ++ + error = freeze_kernel_threads(); + if (error) +- goto Close; ++ goto Cleanup; + + if (hibernation_test(TEST_FREEZER)) { + +@@ -422,15 +431,6 @@ int hibernation_snapshot(int platform_mode) + goto Thaw; + } + +- error = dpm_prepare(PMSG_FREEZE); +- if (error) +- goto Complete; +- +- /* Preallocate image memory before shutting down devices. */ +- error = hibernate_preallocate_memory(); +- if (error) +- goto Complete; +- + console_suspend_all(); + pm_restrict_gfp_mask(); + +@@ -464,10 +464,12 @@ int hibernation_snapshot(int platform_mode) + platform_end(platform_mode); + return error; + +- Complete: +- dpm_complete(PMSG_RECOVER); + Thaw: + thaw_kernel_threads(); ++ Cleanup: ++ swsusp_free(); ++ Complete: ++ dpm_complete(PMSG_RECOVER); + goto Close; + } + diff --git a/pkgbuilds/linux-omarchy-eevdf/0212-pm-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0212-pm-fixes-2.patch.sig new file mode 100644 index 0000000..17b44d0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0212-pm-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0213-pm-fixes-3.patch b/pkgbuilds/linux-omarchy-eevdf/0213-pm-fixes-3.patch new file mode 100644 index 0000000..e8c1523 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0213-pm-fixes-3.patch @@ -0,0 +1,61 @@ +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2590,8 +2590,8 @@ static void cpufreq_update_pressure(struct cpufreq_policy *policy) + + cpu = cpumask_first(policy->related_cpus); + max_freq = arch_scale_freq_ref(cpu); +- if (!max_freq) +- max_freq = policy->cpuinfo.max_freq; ++ if (!max_freq && cpufreq_driver->scale_freq_ref) ++ max_freq = cpufreq_driver->scale_freq_ref(policy); + + capped_freq = policy->max; + +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -1175,6 +1175,14 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static unsigned int intel_pstate_scale_freq_ref(struct cpufreq_policy *policy) ++{ ++ if (READ_ONCE(all_cpu_data[policy->cpu]->capacity_perf)) ++ return policy->cpuinfo.max_freq; ++ ++ return 0; ++} ++ + static void intel_pstate_update_freq_limits(struct cpudata *cpu) + { + int scaling = cpu->pstate.scaling; +@@ -3092,6 +3100,7 @@ static struct cpufreq_driver intel_pstate = { + .offline = intel_pstate_cpu_offline, + .online = intel_pstate_cpu_online, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_pstate", + }; + +@@ -3415,6 +3424,7 @@ static struct cpufreq_driver intel_cpufreq = { + .suspend = intel_cpufreq_suspend, + .resume = intel_pstate_resume, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_cpufreq", + }; + +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -420,6 +420,9 @@ struct cpufreq_driver { + /* Will be called after the driver is fully initialized */ + void (*ready)(struct cpufreq_policy *policy); + ++ /* Return the capacity reference frequency for policy. */ ++ unsigned int (*scale_freq_ref)(struct cpufreq_policy *policy); ++ + struct freq_attr **attr; + + /* platform specific boost support code */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0213-pm-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0213-pm-fixes-3.patch.sig new file mode 100644 index 0000000..8400915 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0213-pm-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch b/pkgbuilds/linux-omarchy-eevdf/0220-x86-amd-zen5-tlb-sizes.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch rename to pkgbuilds/linux-omarchy-eevdf/0220-x86-amd-zen5-tlb-sizes.patch diff --git a/pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0220-x86-amd-zen5-tlb-sizes.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0220-x86-amd-zen5-tlb-sizes.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0220-x86-amd-zen5-tlb-sizes.patch.sig diff --git a/pkgbuilds/linux-omarchy/0250-zsmalloc.patch b/pkgbuilds/linux-omarchy-eevdf/0250-zsmalloc.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0250-zsmalloc.patch rename to pkgbuilds/linux-omarchy-eevdf/0250-zsmalloc.patch diff --git a/pkgbuilds/linux-omarchy/0250-zsmalloc.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0250-zsmalloc.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0250-zsmalloc.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0250-zsmalloc.patch.sig diff --git a/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch b/pkgbuilds/linux-omarchy-eevdf/0260-mglru-exec-protect.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch rename to pkgbuilds/linux-omarchy-eevdf/0260-mglru-exec-protect.patch diff --git a/pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0260-mglru-exec-protect.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0260-mglru-exec-protect.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0260-mglru-exec-protect.patch.sig diff --git a/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch b/pkgbuilds/linux-omarchy-eevdf/0270-ksm-rmap-walk.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch rename to pkgbuilds/linux-omarchy-eevdf/0270-ksm-rmap-walk.patch diff --git a/pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0270-ksm-rmap-walk.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0270-ksm-rmap-walk.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0270-ksm-rmap-walk.patch.sig diff --git a/pkgbuilds/linux-omarchy/0280-mm-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0280-mm-updates.patch similarity index 91% rename from pkgbuilds/linux-omarchy/0280-mm-updates.patch rename to pkgbuilds/linux-omarchy-eevdf/0280-mm-updates.patch index 46a53c5..e0bf148 100644 --- a/pkgbuilds/linux-omarchy/0280-mm-updates.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0280-mm-updates.patch @@ -278,25 +278,7 @@ diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c 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); +@@ -952,6 +952,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item); bool memcg_stat_item_valid(int idx); bool memcg_vm_event_item_valid(enum vm_event_item idx); unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx); @@ -305,7 +287,7 @@ diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h 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, +@@ -1404,6 +1406,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec, return node_page_state(lruvec_pgdat(lruvec), idx); } @@ -576,66 +558,6 @@ diff --git a/lib/maple_tree.c b/lib/maple_tree.c 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 @@ -898,24 +820,7 @@ diff --git a/mm/swap.c b/mm/swap.c 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 +@@ -1173,8 +1106,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid)); @@ -1129,20 +1034,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c 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, +@@ -4819,7 +4877,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, struct reclaim_stat stat; struct lru_gen_mm_walk *walk; int scanned, reclaimed; @@ -1152,7 +1044,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c 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, +@@ -4831,6 +4890,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness, &list, &isolated, &type, &type_scanned); @@ -1160,7 +1052,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c /* 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, +@@ -4843,6 +4903,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, retry: reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg); sc->nr_reclaimed += reclaimed; @@ -1168,7 +1060,7 @@ diff --git a/mm/vmscan.c b/mm/vmscan.c /* 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, +@@ -4892,6 +4953,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, goto retry; } diff --git a/pkgbuilds/linux-omarchy-eevdf/0280-mm-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0280-mm-updates.patch.sig new file mode 100644 index 0000000..8a29396 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0280-mm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch b/pkgbuilds/linux-omarchy-eevdf/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch new file mode 100644 index 0000000..e8e9809 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch @@ -0,0 +1,35 @@ +diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c +--- a/mm/pgtable-generic.c ++++ b/mm/pgtable-generic.c +@@ -438,12 +438,30 @@ static void kernel_pgtable_work_func(struct work_struct *work) + __pagetable_free(pt); + } + ++static void schedule_kernel_pgtable_free(void) ++{ ++ schedule_work(&kernel_pgtable_work.work); ++} ++ + void pagetable_free_kernel(struct ptdesc *pt) + { + spin_lock(&kernel_pgtable_work.lock); + list_add(&pt->pt_list, &kernel_pgtable_work.list); + spin_unlock(&kernel_pgtable_work.lock); + +- schedule_work(&kernel_pgtable_work.work); ++ /* ++ * The workqueue may not exist yet while the system is booting. ++ * kernel_pgtable_drain_early() schedules the work once it does. ++ */ ++ if (system_state != SYSTEM_BOOTING) ++ schedule_kernel_pgtable_free(); + } ++ ++static int __init kernel_pgtable_drain_early(void) ++{ ++ /* Free the kernel page tables queued while booting. */ ++ schedule_kernel_pgtable_free(); ++ return 0; ++} ++core_initcall(kernel_pgtable_drain_early); + #endif diff --git a/pkgbuilds/linux-omarchy-eevdf/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig new file mode 100644 index 0000000..cf29026 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch b/pkgbuilds/linux-omarchy-eevdf/0290-zstd-bmi2-fallback-aliases.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch rename to pkgbuilds/linux-omarchy-eevdf/0290-zstd-bmi2-fallback-aliases.patch diff --git a/pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0290-zstd-bmi2-fallback-aliases.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0290-zstd-bmi2-fallback-aliases.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0290-zstd-bmi2-fallback-aliases.patch.sig diff --git a/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch b/pkgbuilds/linux-omarchy-eevdf/0291-zstd-bmi2-cpu-feature-dispatch.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch rename to pkgbuilds/linux-omarchy-eevdf/0291-zstd-bmi2-cpu-feature-dispatch.patch diff --git a/pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig diff --git a/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch b/pkgbuilds/linux-omarchy-eevdf/0292-crypto-zstd-defer-cstream-init.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch rename to pkgbuilds/linux-omarchy-eevdf/0292-crypto-zstd-defer-cstream-init.patch diff --git a/pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0292-crypto-zstd-defer-cstream-init.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0292-crypto-zstd-defer-cstream-init.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0292-crypto-zstd-defer-cstream-init.patch.sig diff --git a/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch b/pkgbuilds/linux-omarchy-eevdf/0293-crypto-zstd-defer-dstream-init.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch rename to pkgbuilds/linux-omarchy-eevdf/0293-crypto-zstd-defer-dstream-init.patch diff --git a/pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0293-crypto-zstd-defer-dstream-init.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0293-crypto-zstd-defer-dstream-init.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0293-crypto-zstd-defer-dstream-init.patch.sig diff --git a/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch b/pkgbuilds/linux-omarchy-eevdf/0295-af-alg-restrict.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch rename to pkgbuilds/linux-omarchy-eevdf/0295-af-alg-restrict.patch diff --git a/pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0295-af-alg-restrict.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0295-af-alg-restrict.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0295-af-alg-restrict.patch.sig diff --git a/pkgbuilds/linux-omarchy/0300-btrfs.patch b/pkgbuilds/linux-omarchy-eevdf/0300-btrfs.patch similarity index 78% rename from pkgbuilds/linux-omarchy/0300-btrfs.patch rename to pkgbuilds/linux-omarchy-eevdf/0300-btrfs.patch index 0cb9f8d..837f7bc 100644 --- a/pkgbuilds/linux-omarchy/0300-btrfs.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0300-btrfs.patch @@ -1,50 +1,7 @@ -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) +@@ -2630,10 +2630,9 @@ static int fill_dummy_bgs(struct btrfs_fs_info *fs_info) /* Fill dummy cache as FULL */ bg->length = map->chunk_len; @@ -56,7 +13,7 @@ diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c 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, +@@ -3922,7 +3921,7 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, old_val += num_bytes; cache->used = old_val; cache->reserved -= num_bytes; @@ -311,43 +268,7 @@ diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c 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, +@@ -1169,7 +1169,7 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, struct defrag_target_range *entry; struct defrag_target_range *tmp; LIST_HEAD(target_list); @@ -356,69 +277,45 @@ diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c 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); +@@ -1196,10 +1196,8 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + * range or the extent lock. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) { +- kfree(folios); + if (ret < 0) -+ return ret; -+ + 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, +@@ -1234,16 +1232,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, if (ret < 0) goto unlock_extent; - list_for_each_entry(entry, &target_list, list) { -- 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) { + defrag_one_locked_target(inode, entry, folios, nr_pages, &cached_state); + if (entry->start > last_defrag_end) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, + entry->start - last_defrag_end, true); + last_defrag_end = entry->start + entry->len; - } - - list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_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, +@@ -1255,7 +1249,6 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, folio_unlock(folios[i]); folio_put(folios[i]); } - kfree(folios); -+ btrfs_delalloc_release_extents(inode, len); -+ if (last_defrag_end < start + len) -+ btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, -+ 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, + btrfs_delalloc_release_extents(inode, len); + if (last_defrag_end < start + len) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, +@@ -1326,10 +1319,8 @@ static int defrag_one_cluster(struct btrfs_inode *inode, inode->root->fs_info->sectorsize_bits; } out: @@ -744,29 +641,7 @@ diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c 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 = { +@@ -591,12 +591,13 @@ static const struct address_space_operations btree_aops = { struct extent_buffer *btrfs_find_create_tree_block( struct btrfs_fs_info *fs_info, @@ -781,7 +656,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c } /* -@@ -608,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( +@@ -609,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, struct btrfs_tree_parent_check *check) { @@ -796,7 +671,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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, +@@ -667,10 +669,7 @@ static struct btrfs_root *btrfs_alloc_root(struct btrfs_fs_info *fs_info, init_waitqueue_head(&root->log_commit_wait[1]); INIT_LIST_HEAD(&root->log_ctxs[0]); INIT_LIST_HEAD(&root->log_ctxs[1]); @@ -807,7 +682,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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, +@@ -2053,7 +2052,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, if (IS_ERR(log_tree_root->node)) { ret = PTR_ERR(log_tree_root->node); log_tree_root->node = NULL; @@ -816,7 +691,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c btrfs_put_root(log_tree_root); return ret; } -@@ -2062,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, +@@ -2063,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, btrfs_put_root(log_tree_root); if (unlikely(ret)) { ASSERT(BTRFS_FS_ERROR(fs_info) != 0); @@ -825,7 +700,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } -@@ -2303,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) +@@ -2304,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) return 0; out: @@ -836,7 +711,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } -@@ -2395,8 +2395,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, +@@ -2400,8 +2399,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, int btrfs_validate_super(const struct btrfs_fs_info *fs_info, const struct btrfs_super_block *sb, int mirror_num) { @@ -847,7 +722,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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, +@@ -2443,24 +2442,24 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info, */ if (unlikely(!is_power_of_2(sectorsize) || sectorsize < BTRFS_MIN_BLOCKSIZE || sectorsize > BTRFS_MAX_METADATA_BLOCKSIZE)) { @@ -876,7 +751,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c le32_to_cpu(sb->__unused_leafsize), nodesize); ret = -EINVAL; } -@@ -2905,7 +2905,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) +@@ -2910,7 +2909,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) fs_info->nodesize = 4096; fs_info->sectorsize = 4096; fs_info->sectorsize_bits = ilog2(4096); @@ -884,7 +759,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c /* 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) +@@ -2984,8 +2982,8 @@ static int btrfs_uuid_rescan_kthread(void *data) ret = btrfs_uuid_tree_iterate(fs_info); if (ret < 0) { if (ret != -EINTR) @@ -895,7 +770,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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) +@@ -3088,7 +3086,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_rebuild_free_space_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -904,7 +779,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } } -@@ -3094,7 +3093,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3099,7 +3097,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_delete_free_space_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -913,7 +788,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } } -@@ -3105,7 +3104,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3110,7 +3108,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) */ ret = btrfs_delete_orphan_free_space_entries(fs_info); if (ret < 0) { @@ -923,7 +798,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } /* -@@ -3139,7 +3139,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3144,7 +3143,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_recover_relocation(fs_info); mutex_unlock(&fs_info->cleaner_mutex); if (ret < 0) { @@ -932,7 +807,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } -@@ -3149,7 +3149,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3154,7 +3153,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_create_free_space_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -941,7 +816,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c return ret; } } -@@ -3177,7 +3177,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) +@@ -3182,7 +3181,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) ret = btrfs_create_uuid_tree(fs_info); if (ret) { btrfs_warn(fs_info, @@ -950,46 +825,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3372,7 +3371,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device { u32 sectorsize; u32 nodesize; @@ -997,7 +833,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3481,7 +3479,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device /* Set up fs_info before parsing mount options */ nodesize = btrfs_super_nodesize(disk_super); sectorsize = btrfs_super_sectorsize(disk_super); @@ -1005,7 +841,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3500,7 +3497,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device else fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; @@ -1013,7 +849,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3566,7 +3562,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_read_sys_array(fs_info); mutex_unlock(&fs_info->chunk_mutex); if (ret) { @@ -1022,7 +858,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_sb_buffer; } -@@ -3568,7 +3577,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3585,7 +3581,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_read_chunk_tree(fs_info); if (ret) { @@ -1031,7 +867,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_tree_roots; } -@@ -3598,7 +3607,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3615,7 +3611,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_get_dev_zone_info_all_devices(fs_info); if (ret) { btrfs_err(fs_info, @@ -1040,7 +876,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_block_groups; } -@@ -3621,72 +3630,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3638,72 +3634,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device ret = btrfs_verify_dev_extents(fs_info); if (ret) { btrfs_err(fs_info, @@ -1129,7 +965,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c goto fail_sysfs; } -@@ -3742,7 +3752,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device +@@ -3759,7 +3756,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device fs_info->fs_root = btrfs_get_fs_root(fs_info, BTRFS_FS_TREE_OBJECTID, true); if (IS_ERR(fs_info->fs_root)) { ret = PTR_ERR(fs_info->fs_root); @@ -1138,7 +974,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c 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 +@@ -3780,7 +3777,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device btrfs_info(fs_info, "checking UUID tree"); ret = btrfs_check_uuid_tree(fs_info); if (ret) { @@ -1147,7 +983,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c close_ctree(fs_info); return ret; } -@@ -3879,8 +3889,8 @@ static int write_dev_supers(struct btrfs_device *device, +@@ -3896,8 +3893,8 @@ static int write_dev_supers(struct btrfs_device *device, continue; } else if (ret < 0) { btrfs_err(device->fs_info, @@ -1158,7 +994,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c atomic_inc(&device->sb_write_errors); continue; } -@@ -3898,8 +3908,8 @@ static int write_dev_supers(struct btrfs_device *device, +@@ -3915,8 +3912,8 @@ static int write_dev_supers(struct btrfs_device *device, GFP_NOFS); if (IS_ERR(folio)) { btrfs_err(device->fs_info, @@ -1169,7 +1005,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c atomic_inc(&device->sb_write_errors); continue; } -@@ -4381,6 +4391,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) +@@ -4398,6 +4395,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) */ flush_workqueue(fs_info->fixup_workers); @@ -1191,7 +1027,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c /* * 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) +@@ -4528,7 +4540,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) if (!btrfs_is_shutdown(fs_info)) { ret = btrfs_commit_super(fs_info); if (ret) @@ -1200,7 +1036,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c } } -@@ -4550,6 +4575,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) +@@ -4567,6 +4579,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) free_root_pointers(fs_info, true); btrfs_free_fs_roots(fs_info); @@ -1214,7 +1050,7 @@ diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c /* * 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) +@@ -5015,6 +5034,7 @@ static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info) btrfs_assert_delayed_root_empty(fs_info); btrfs_destroy_all_delalloc_inodes(fs_info); btrfs_drop_all_logs(fs_info); @@ -1461,198 +1297,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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) +@@ -2976,13 +2994,25 @@ void btrfs_readahead(struct readahead_control *rac) struct extent_map *em_cached = NULL; struct fsverity_info *vi = NULL; @@ -1681,123 +1326,8 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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; +@@ -3298,12 +3328,9 @@ void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + trans->inhibited_ebs_hand = 0; } -static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, @@ -1811,7 +1341,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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 +@@ -3316,7 +3343,15 @@ static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info refcount_set(&eb->refs, 1); ASSERT(eb->len <= BTRFS_MAX_METADATA_BLOCKSIZE); @@ -1827,7 +1357,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c return eb; } -@@ -3502,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) +@@ -3613,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) * The caller needs to free the existing folios and retry using the same order. */ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, @@ -1836,7 +1366,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c struct extent_buffer **found_eb_ret) { -@@ -3524,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, +@@ -3635,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, if (!ret) goto finish; @@ -1844,7 +1374,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -3643,7 +3679,27 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, /* For now, we should only have single-page folios for btree inode. */ ASSERT(folio_nr_pages(existing_folio) == 1); @@ -1872,7 +1402,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -3674,8 +3730,10 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, eb->folio_size = folio_size(eb->folios[i]); eb->folio_shift = folio_shift(eb->folios[i]); /* Should not fail, as we have preallocated the memory. */ @@ -1884,7 +1414,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c /* * 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, +@@ -3690,13 +3748,104 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, return 0; } @@ -1990,7 +1520,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -3720,9 +3869,14 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, if (eb) return eb; @@ -2008,7 +1538,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c /* * 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, +@@ -3733,66 +3887,18 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, btrfs_set_buffer_lockdep_class(lockdep_owner, eb, level); @@ -2079,7 +1609,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c attached++; /* -@@ -3758,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, +@@ -3869,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, out: WARN_ON(!refcount_dec_and_test(&eb->refs)); @@ -2090,71 +1620,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c /* * 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) +@@ -4030,6 +4140,32 @@ void free_extent_buffer_stale(struct extent_buffer *eb) release_extent_buffer(eb); } @@ -2187,7 +1653,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c 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, +@@ -4054,26 +4190,42 @@ void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, return; } @@ -2246,29 +1712,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c } 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, +@@ -4923,6 +5075,8 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, .level = level, .transid = gen }; @@ -2277,7 +1721,7 @@ diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c struct extent_buffer *eb; int ret; -@@ -4797,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, +@@ -4931,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, check.has_first_key = true; } @@ -2347,197 +1791,10 @@ diff --git a/fs/btrfs/fiemap.c b/fs/btrfs/fiemap.c 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) +@@ -1573,8 +1573,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) else btrfs_inode_lock(inode, BTRFS_ILOCK_MMAP); @@ -2546,7 +1803,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c /* * 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) +@@ -1657,8 +1655,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) if (ret) goto out_release_extents; @@ -2555,7 +1812,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c 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) +@@ -2663,8 +2659,8 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len) lockstart = round_up(offset, fs_info->sectorsize); lockend = round_down(offset + len, fs_info->sectorsize) - 1; @@ -2566,7 +1823,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c /* * Only do this if we are in the same block and we aren't doing the * entire block. -@@ -2888,7 +2862,7 @@ enum { +@@ -2868,7 +2864,7 @@ enum { static int btrfs_zero_range_check_range_boundary(struct btrfs_inode *inode, u64 offset) { @@ -2575,7 +1832,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c struct extent_map *em; int ret; -@@ -2918,7 +2892,7 @@ static int btrfs_zero_range(struct inode *inode, +@@ -2898,7 +2894,7 @@ static int btrfs_zero_range(struct inode *inode, struct extent_changeset *data_reserved = NULL; int ret; u64 alloc_hint = 0; @@ -2584,7 +1841,7 @@ diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c 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, +@@ -2947,8 +2943,8 @@ static int btrfs_zero_range(struct inode *inode, } btrfs_free_extent_map(em); @@ -2735,7 +1992,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c return ret; } -@@ -3050,7 +3051,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, +@@ -3065,7 +3066,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, u64 qgroup_reserved) { struct btrfs_root *root = inode->root; @@ -2744,7 +2001,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c 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) +@@ -3926,7 +3927,7 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) out: if (ret) @@ -2753,7 +2010,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c return ret; } -@@ -4210,8 +4211,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path +@@ -4229,8 +4230,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path ret = btrfs_load_inode_props(inode, path); if (ret) btrfs_err(fs_info, @@ -2764,7 +2021,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } /* -@@ -6825,8 +6826,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, +@@ -6844,8 +6845,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, } if (ret) { btrfs_err(fs_info, @@ -2775,7 +2032,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } /* -@@ -10205,6 +10206,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) +@@ -10224,6 +10225,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; struct btrfs_swapfile_pin *sp; struct rb_node *node, *next; @@ -2784,7 +2041,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c 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) +@@ -10233,15 +10236,24 @@ static void btrfs_free_swapfile_pins(struct inode *inode) if (sp->inode == inode) { rb_erase(&sp->node, &fs_info->swapfile_pins); if (sp->is_block_group) { @@ -2811,7 +2068,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } struct btrfs_swap_info { -@@ -10300,8 +10312,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, +@@ -10319,8 +10331,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, struct btrfs_backref_share_check_ctx *backref_ctx = NULL; struct btrfs_path *path = NULL; int ret = 0; @@ -2822,7 +2079,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c /* * 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, +@@ -10570,6 +10584,9 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, ret = 0; else goto out; @@ -2832,7 +2089,7 @@ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c } if (bsi.block_len && -@@ -10598,6 +10615,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, +@@ -10617,6 +10634,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, if (ret) return ret; @@ -3215,43 +2472,6 @@ diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c } } -@@ -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 @@ -3655,87 +2875,7 @@ diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c 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, +@@ -4151,10 +4152,10 @@ static int copy_remapped_data(struct btrfs_fs_info *fs_info, u64 old_addr, u64 new_addr, u64 length) { int ret; @@ -3748,17 +2888,6 @@ diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c 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 @@ -4127,7 +3256,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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) +@@ -1249,20 +1231,9 @@ static void scrub_stripe_read_repair_worker(struct work_struct *work) static void scrub_read_endio(struct btrfs_bio *bbio) { struct scrub_stripe *stripe = bbio->private; @@ -4149,7 +3278,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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) +@@ -1274,7 +1245,7 @@ static void scrub_write_endio(struct btrfs_bio *bbio) { struct scrub_stripe *stripe = bbio->private; struct btrfs_fs_info *fs_info = stripe->bg->fs_info; @@ -4158,7 +3287,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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 +@@ -1353,7 +1324,7 @@ static void scrub_write_sectors(struct scrub_ctx *sctx, struct scrub_stripe *str bbio = NULL; } if (!bbio) @@ -4167,7 +3296,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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) +@@ -1848,7 +1819,7 @@ static void scrub_submit_extent_sector_read(struct scrub_stripe *stripe) continue; } @@ -4176,7 +3305,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c logical, scrub_read_endio, stripe); } -@@ -1869,7 +1840,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, +@@ -1873,7 +1844,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, { struct btrfs_fs_info *fs_info = sctx->fs_info; struct btrfs_bio *bbio; @@ -4184,7 +3313,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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, +@@ -1886,7 +1856,7 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, return; } @@ -4193,7 +3322,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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, +@@ -2142,7 +2112,7 @@ static int scrub_raid56_cached_parity(struct scrub_ctx *sctx, for (int i = 0; i < data_stripes; i++) { struct scrub_stripe *stripe = &sctx->raid56_data_stripes[i]; @@ -4202,7 +3331,7 @@ diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c 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, +@@ -3095,14 +3065,6 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, /* At mount time we have ensured nodesize is in the range of [4K, 64K]. */ ASSERT(fs_info->nodesize <= BTRFS_STRIPE_LEN); @@ -4436,16 +3565,7 @@ diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c 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) +@@ -1528,12 +1528,8 @@ static noinline int commit_fs_roots(struct btrfs_trans_handle *trans) ASSERT(atomic_read(&root->log_writers) == 0, "atomic_read(&root->log_writers)=%d", atomic_read(&root->log_writers)); @@ -4460,7 +3580,7 @@ diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c 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, +@@ -1651,7 +1647,7 @@ static int qgroup_account_snapshot(struct btrfs_trans_handle *trans, ret = btrfs_write_and_wait_transaction(trans); if (unlikely(ret)) { btrfs_err(fs_info, @@ -4469,16 +3589,16 @@ diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c return ret; } -@@ -2588,7 +2582,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) +@@ -2597,7 +2593,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) ret = btrfs_write_and_wait_transaction(trans); if (unlikely(ret)) { - btrfs_err(fs_info, "error while writing out transaction: %d", ret); + btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); - mutex_unlock(&fs_info->tree_log_mutex); - goto scrub_continue; - } -@@ -2749,8 +2743,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, + /* + * Abort before releasing tree_log_mutex, so a log sync waiting + * on it sees the fs error and skips writing super_for_commit +@@ -2764,8 +2760,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, WRITE_ONCE(trans->transaction->aborted, error); trace_btrfs_transaction_abort(trans); if (first_hit) { @@ -4489,67 +3609,6 @@ diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c 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 @@ -4811,7 +3870,7 @@ diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c 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, +@@ -652,7 +652,7 @@ static int btrfs_end_enable_verity(struct file *filp, const void *desc, rollback_ret = rollback_verity(inode); if (rollback_ret) btrfs_err(inode->root->fs_info, @@ -4820,46 +3879,6 @@ diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c 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 @@ -4914,7 +3933,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c const char *old_name; /* -@@ -4588,7 +4553,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) +@@ -4592,7 +4557,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) if (ret == -ENOSPC) { enospc_errors++; } else if (ret == -ETXTBSY) { @@ -4923,7 +3942,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c "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) +@@ -6053,6 +6018,19 @@ struct btrfs_chunk_map *btrfs_alloc_chunk_map(int num_stripes, gfp_t gfp) return map; } @@ -4943,7 +3962,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c 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, +@@ -6071,11 +6049,10 @@ static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, map->start = start; map->chunk_len = ctl->chunk_size; map->stripe_size = ctl->stripe_size; @@ -4957,7 +3976,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c 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, +@@ -6254,7 +6231,7 @@ int btrfs_chunk_alloc_add_chunk_item(struct btrfs_trans_handle *trans, btrfs_set_stack_chunk_length(chunk, bg->length); btrfs_set_stack_chunk_owner(chunk, BTRFS_EXTENT_TREE_OBJECTID); btrfs_set_stack_chunk_stripe_len(chunk, BTRFS_STRIPE_LEN); @@ -4966,7 +3985,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c 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, +@@ -7636,9 +7613,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, map->start = logical; map->chunk_len = length; map->num_stripes = num_stripes; @@ -4977,7 +3996,7 @@ diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c /* * 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, +@@ -7649,6 +7624,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, */ map->sub_stripes = btrfs_raid_array[index].sub_stripes; map->verified_stripes = 0; diff --git a/pkgbuilds/linux-omarchy-eevdf/0300-btrfs.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0300-btrfs.patch.sig new file mode 100644 index 0000000..9d469a9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0300-btrfs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0301-btrfs-fixes-1.patch b/pkgbuilds/linux-omarchy-eevdf/0301-btrfs-fixes-1.patch new file mode 100644 index 0000000..8cc1ec5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0301-btrfs-fixes-1.patch @@ -0,0 +1,92 @@ +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 +@@ -627,7 +627,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/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags &= ~BTRFS_INODE_COMPRESS; + inode_flags |= BTRFS_INODE_NOCOMPRESS; + } else if (fsflags & FS_COMPR_FL) { ++ enum btrfs_compression_type comp_type; + + if (IS_SWAPFILE(&inode->vfs_inode)) + return -ETXTBSY; +@@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags |= BTRFS_INODE_COMPRESS; + inode_flags &= ~BTRFS_INODE_NOCOMPRESS; + +- comp = btrfs_compress_type2str(fs_info->compress_type); +- if (!comp || comp[0] == 0) +- comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB); ++ /* ++ * Keep the algorithm recorded in the compression property, ++ * otherwise changing an unrelated attribute would reset it to ++ * the mount default, since FS_IOC_SETFLAGS callers write back ++ * the whole flag set they got from FS_IOC_GETFLAGS and that ++ * includes FS_COMPR_FL for any inode carrying the property. ++ * ++ * Inodes with the compress flag set but no property keep using ++ * the mount default, so they behave as before. ++ */ ++ if (inode->prop_compress) ++ comp_type = inode->prop_compress; ++ else if (fs_info->compress_type) ++ comp_type = fs_info->compress_type; ++ else ++ comp_type = BTRFS_COMPRESS_ZLIB; ++ comp = btrfs_compress_type2str(comp_type); + } else { + inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS); + } +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -6417,6 +6417,13 @@ static int process_extent(struct send_ctx *sctx, + + if (S_ISLNK(sctx->cur_inode_mode)) + return 0; ++ if (unlikely(!S_ISREG(sctx->cur_inode_mode))) { ++ btrfs_crit(sctx->send_root->fs_info, ++ "send: extent for non-regular inode %llu root %llu mode 0%llo", ++ key->objectid, btrfs_root_id(sctx->send_root), ++ sctx->cur_inode_mode & S_IFMT); ++ return -EUCLEAN; ++ } + + if (sctx->parent_root && !sctx->cur_inode_new) { + ret = is_extent_unchanged(sctx, path, key); +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2710,6 +2710,7 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + { + struct btrfs_block_group *block_group; + u64 min_alloc_bytes; ++ int ret = 0; + + if (!btrfs_is_zoned(fs_info)) + return 0; +@@ -2729,11 +2730,11 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + block_group->start + block_group->zone_capacity) + goto out; + +- do_zone_finish(block_group, true); ++ ret = do_zone_finish(block_group, true); + + out: + btrfs_put_block_group(block_group); +- return 0; ++ return ret; + } + + static void btrfs_zone_finish_endio_workfn(struct work_struct *work) diff --git a/pkgbuilds/linux-omarchy-eevdf/0301-btrfs-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0301-btrfs-fixes-1.patch.sig new file mode 100644 index 0000000..f05ee3b Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0301-btrfs-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch b/pkgbuilds/linux-omarchy-eevdf/0302-btrfs-zstd-decompress-direct-to-page.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch rename to pkgbuilds/linux-omarchy-eevdf/0302-btrfs-zstd-decompress-direct-to-page.patch diff --git a/pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0302-btrfs-zstd-decompress-direct-to-page.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0302-btrfs-zstd-decompress-direct-to-page.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0302-btrfs-zstd-decompress-direct-to-page.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0303-btrfs-fixes-2.patch b/pkgbuilds/linux-omarchy-eevdf/0303-btrfs-fixes-2.patch new file mode 100644 index 0000000..42654e7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0303-btrfs-fixes-2.patch @@ -0,0 +1,97 @@ +diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c +--- a/fs/btrfs/block-group.c ++++ b/fs/btrfs/block-group.c +@@ -3074,6 +3074,18 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + ++ /* ++ * Ensure the corresponding space_info object is created and ++ * assigned to our block group. We want our bg to be added to the rbtree ++ * with its ->space_info set. ++ * ++ * On a zoned filesystem btrfs_add_new_free_space() ends up in ++ * __btrfs_add_free_space_zoned(), which dereferences ++ * block_group->space_info, so it has to be set beforehand. ++ */ ++ cache->space_info = space_info; ++ ASSERT(cache->space_info); ++ + ret = btrfs_add_new_free_space(cache, chunk_offset, chunk_offset + size, NULL); + btrfs_free_excluded_extents(cache); + if (ret) { +@@ -3081,14 +3093,6 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + +- /* +- * Ensure the corresponding space_info object is created and +- * assigned to our block group. We want our bg to be added to the rbtree +- * with its ->space_info set. +- */ +- cache->space_info = space_info; +- ASSERT(cache->space_info); +- + ret = btrfs_add_block_group_cache(cache); + if (ret) { + btrfs_remove_free_space_cache(cache); +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -1909,6 +1909,16 @@ static int check_inode_ref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ if (unlikely(btrfs_is_fstree(btrfs_header_owner(leaf)) && ++ (key->offset < BTRFS_FIRST_FREE_OBJECTID || ++ key->offset > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid offset for ref key, have %llu expect [%llu, %lld]", ++ key->offset, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + ptr = btrfs_item_ptr_offset(leaf, slot); + end = ptr + btrfs_item_size(leaf, slot); + while (ptr < end) { +@@ -1952,12 +1962,14 @@ static int check_inode_extref(struct extent_buffer *leaf, + { + unsigned long ptr = btrfs_item_ptr_offset(leaf, slot); + unsigned long end = ptr + btrfs_item_size(leaf, slot); ++ const bool is_fstree = btrfs_is_fstree(btrfs_header_owner(leaf)); + + if (unlikely(!check_prev_ino(leaf, key, slot, prev_key))) + return -EUCLEAN; + + while (ptr < end) { + struct btrfs_inode_extref *extref = (struct btrfs_inode_extref *)ptr; ++ u64 parent; + u16 namelen; + + if (unlikely(ptr + sizeof(*extref) > end)) { +@@ -1967,7 +1979,24 @@ static int check_inode_extref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ parent = btrfs_inode_extref_parent(leaf, extref); ++ if (unlikely(is_fstree && (parent < BTRFS_FIRST_FREE_OBJECTID || ++ parent > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid parent for extref key, have %llu expect [%llu, %lld]", ++ parent, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + namelen = btrfs_inode_extref_name_len(leaf, extref); ++ if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) { ++ inode_ref_err(leaf, slot, ++ "invalid inode extref name length, has %u expect [1, %u]", ++ namelen, BTRFS_NAME_LEN); ++ return -EUCLEAN; ++ } ++ + if (unlikely(ptr + sizeof(*extref) + namelen > end)) { + inode_ref_err(leaf, slot, + "inode extref overflow, ptr %lu end %lu namelen %u", diff --git a/pkgbuilds/linux-omarchy-eevdf/0303-btrfs-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0303-btrfs-fixes-2.patch.sig new file mode 100644 index 0000000..c75c8f3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0303-btrfs-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0304-btrfs-fixes-3.patch b/pkgbuilds/linux-omarchy-eevdf/0304-btrfs-fixes-3.patch new file mode 100644 index 0000000..7d1ab48 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0304-btrfs-fixes-3.patch @@ -0,0 +1,77 @@ +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -2306,7 +2306,7 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key + + if (unlikely(btrfs_item_size(leaf, slot) != 0)) { + generic_err(leaf, slot, +- "invalid item size for free space info, has %u expect 0", ++ "invalid item size for free space extent, has %u expect 0", + btrfs_item_size(leaf, slot)); + return -EUCLEAN; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,6 +740,36 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + ++static bool should_rename_device(const struct btrfs_device *dev) ++{ ++ bool ret; ++ const char *old_name; ++ ++ rcu_read_lock(); ++ old_name = rcu_dereference(dev->name); ++ /* ++ * For systems booted without an initramfs, the rootfs has the device ++ * name "/dev/root". ++ * ++ * Although using btrfs without an initramfs is not recommended (if a ++ * new device is added to the rootfs, the system can no longer boot, as ++ * there is no way to register all devices), there is still a minority ++ * of users doing this. ++ * ++ * And after the system is up, a later device scan on the real block ++ * device file will never get this device's name updated, as the ++ * device->devt is still the same. ++ * ++ * Here we add one and only one exception for "/dev/root", to allow the ++ * device name to be updated even if the new path points to the same ++ * block device. ++ */ ++ ret = (strcmp(old_name, "/dev/root") == 0); ++ rcu_read_unlock(); ++ ++ return ret; ++} ++ + /* + * Add new device to list of registered devices + * +@@ -860,7 +890,8 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || device->devt != path_devt) { ++ } else if (!device->name || device->devt != path_devt || ++ should_rename_device(device)) { + const char *old_name; + + /* +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2688,6 +2688,11 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) + + switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { + case 0: /* single */ ++ case BTRFS_BLOCK_GROUP_RAID0: ++ case BTRFS_BLOCK_GROUP_RAID1: ++ case BTRFS_BLOCK_GROUP_RAID1C3: ++ case BTRFS_BLOCK_GROUP_RAID1C4: ++ case BTRFS_BLOCK_GROUP_RAID10: + ret = (atomic_read(&zinfo->active_zones_left) >= (1 + reserved)); + break; + case BTRFS_BLOCK_GROUP_DUP: diff --git a/pkgbuilds/linux-omarchy-eevdf/0304-btrfs-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0304-btrfs-fixes-3.patch.sig new file mode 100644 index 0000000..d471f2e Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0304-btrfs-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch b/pkgbuilds/linux-omarchy-eevdf/0310-fuse-eof-zeroing.patch similarity index 91% rename from pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch rename to pkgbuilds/linux-omarchy-eevdf/0310-fuse-eof-zeroing.patch index b34a909..38a6427 100644 --- a/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0310-fuse-eof-zeroing.patch @@ -1,7 +1,7 @@ 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, +@@ -2289,6 +2289,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) { @@ -28,7 +28,7 @@ diff --git a/fs/fuse/file.c b/fs/fuse/file.c 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, +@@ -2909,6 +2913,11 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, /* we could have extended the file */ if (!(mode & FALLOC_FL_KEEP_SIZE)) { diff --git a/pkgbuilds/linux-omarchy-eevdf/0310-fuse-eof-zeroing.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0310-fuse-eof-zeroing.patch.sig new file mode 100644 index 0000000..e4796a2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0310-fuse-eof-zeroing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0311-fuse-perf.patch b/pkgbuilds/linux-omarchy-eevdf/0311-fuse-perf.patch similarity index 86% rename from pkgbuilds/linux-omarchy/0311-fuse-perf.patch rename to pkgbuilds/linux-omarchy-eevdf/0311-fuse-perf.patch index 5162077..ca3c02a 100644 --- a/pkgbuilds/linux-omarchy/0311-fuse-perf.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0311-fuse-perf.patch @@ -1,7 +1,7 @@ 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); +@@ -213,10 +213,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash); /* * A new request is available, wake fiq->waitq */ @@ -17,7 +17,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c kill_fasync(&fiq->fasync, SIGIO, POLL_IN); spin_unlock(&fiq->lock); } -@@ -236,7 +239,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, +@@ -233,7 +236,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, if (fiq->connected) { fiq->forget_list_tail->next = forget; fiq->forget_list_tail = forget; @@ -26,7 +26,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c } else { kfree(forget); spin_unlock(&fiq->lock); -@@ -258,7 +261,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) +@@ -255,7 +258,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) list_del_init(&req->intr_entry); spin_unlock(&fiq->lock); } else { @@ -35,7 +35,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c } } else { spin_unlock(&fiq->lock); -@@ -288,11 +291,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); +@@ -285,11 +288,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req) { @@ -50,7 +50,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c } else { spin_unlock(&fiq->lock); req->out.h.error = -ENOTCONN; -@@ -755,6 +760,11 @@ static void __fuse_request_send(struct fuse_req *req) +@@ -752,6 +757,11 @@ static void __fuse_request_send(struct fuse_req *req) /* acquire extra reference, since request is still needed after fuse_request_end() */ __fuse_get_request(req); @@ -62,7 +62,7 @@ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c fuse_send_one(fiq, req); request_wait_answer(req); -@@ -1809,7 +1819,7 @@ void fuse_chan_resend(struct fuse_chan *fch) +@@ -1806,7 +1816,7 @@ void fuse_chan_resend(struct fuse_chan *fch) } /* iq and pq requests are both oldest to newest */ list_splice(&to_queue, &fiq->pending); @@ -94,7 +94,7 @@ diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h 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) +@@ -1420,6 +1420,7 @@ static void process_init_reply(struct fuse_args *args, int error) fm->sb->s_bdi->ra_pages = min(fm->sb->s_bdi->ra_pages, ra_pages); diff --git a/pkgbuilds/linux-omarchy-eevdf/0311-fuse-perf.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0311-fuse-perf.patch.sig new file mode 100644 index 0000000..9bbc4c6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0311-fuse-perf.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch b/pkgbuilds/linux-omarchy-eevdf/0312-fuse-writethrough-uptodate.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch rename to pkgbuilds/linux-omarchy-eevdf/0312-fuse-writethrough-uptodate.patch diff --git a/pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0312-fuse-writethrough-uptodate.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0312-fuse-writethrough-uptodate.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0312-fuse-writethrough-uptodate.patch.sig diff --git a/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch b/pkgbuilds/linux-omarchy-eevdf/0313-fuse-background-wakeup.patch similarity index 85% rename from pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch rename to pkgbuilds/linux-omarchy-eevdf/0313-fuse-background-wakeup.patch index 77e750a..27fbcdc 100644 --- a/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0313-fuse-background-wakeup.patch @@ -1,7 +1,7 @@ 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) +@@ -406,7 +406,8 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val) fch->max_background = val; fch->blocked = fch->num_background >= fch->max_background; if (!fch->blocked) diff --git a/pkgbuilds/linux-omarchy-eevdf/0313-fuse-background-wakeup.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0313-fuse-background-wakeup.patch.sig new file mode 100644 index 0000000..cfd1cd7 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0313-fuse-background-wakeup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0320-vfs-open-consume-path-reference.patch b/pkgbuilds/linux-omarchy-eevdf/0320-vfs-open-consume-path-reference.patch new file mode 100644 index 0000000..eba1599 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0320-vfs-open-consume-path-reference.patch @@ -0,0 +1,119 @@ +diff --git a/fs/internal.h b/fs/internal.h +--- a/fs/internal.h ++++ b/fs/internal.h +@@ -204,6 +204,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, + int flag); + int chown_common(const struct path *path, uid_t user, gid_t group); + extern int vfs_open(const struct path *, struct file *); ++int vfs_open_consume(struct path *, struct file *); + + /* + * inode.c +diff --git a/fs/namei.c b/fs/namei.c +--- a/fs/namei.c ++++ b/fs/namei.c +@@ -4652,6 +4652,7 @@ static const char *open_last_lookups(struct nameidata *nd, + static int do_open(struct nameidata *nd, + struct file *file, const struct open_flags *op) + { ++ struct vfsmount *mnt; + struct mnt_idmap *idmap; + int open_flag = op->open_flag; + bool do_truncate; +@@ -4693,11 +4694,17 @@ static int do_open(struct nameidata *nd, + error = mnt_want_write(nd->path.mnt); + if (error) + return error; ++ /* ++ * A dedicated reference is needed because after the call to ++ * vfs_open_consume() we no longer own the reference in nd->path.mnt ++ * while we need to undo write acess below. ++ */ ++ mnt = mntget(nd->path.mnt); + do_truncate = true; + } + error = may_open(idmap, &nd->path, acc_mode, open_flag); + if (!error && !(file->f_mode & FMODE_OPENED)) +- error = vfs_open(&nd->path, file); ++ error = vfs_open_consume(&nd->path, file); + if (!error) + error = security_file_post_open(file, op->acc_mode); + if (!error && do_truncate) +@@ -4706,8 +4713,10 @@ static int do_open(struct nameidata *nd, + WARN_ON(1); + error = -EINVAL; + } +- if (do_truncate) +- mnt_drop_write(nd->path.mnt); ++ if (do_truncate) { ++ mnt_drop_write(mnt); ++ mntput(mnt); ++ } + return error; + } + +diff --git a/fs/open.c b/fs/open.c +--- a/fs/open.c ++++ b/fs/open.c +@@ -882,6 +882,11 @@ static inline int file_get_write_access(struct file *f) + return error; + } + ++/* ++ * Populate struct file ++ * ++ * NOTE: it assumes f_path is populated and consumes the caller's reference. ++ */ + static int do_dentry_open(struct file *f, + int (*open)(struct inode *, struct file *)) + { +@@ -889,7 +894,6 @@ static int do_dentry_open(struct file *f, + struct inode *inode = f->f_path.dentry->d_inode; + int error; + +- path_get(&f->f_path); + f->f_inode = inode; + f->f_mapping = inode->i_mapping; + f->f_wb_err = filemap_sample_wb_err(f->f_mapping); +@@ -1006,6 +1010,7 @@ int finish_open(struct file *file, struct dentry *dentry, + BUG_ON(file->f_mode & FMODE_OPENED); /* once it's opened, it's opened */ + + file->__f_path.dentry = dentry; ++ path_get(&file->f_path); + return do_dentry_open(file, open); + } + EXPORT_SYMBOL(finish_open); +@@ -1049,6 +1054,7 @@ int vfs_open(const struct path *path, struct file *file) + int ret; + + file->__f_path = *path; ++ path_get(&file->f_path); + ret = do_dentry_open(file, NULL); + if (!ret) { + /* +@@ -1061,6 +1067,25 @@ int vfs_open(const struct path *path, struct file *file) + return ret; + } + ++/** ++ * vfs_open_consume - open the file at the given path and consume the reference ++ * @path: path to open ++ * @file: newly allocated file with f_flag initialized ++ */ ++int vfs_open_consume(struct path *path, struct file *file) ++{ ++ int ret; ++ ++ file->__f_path = *path; ++ path->mnt = NULL; ++ path->dentry = NULL; ++ ret = do_dentry_open(file, NULL); ++ if (!ret) { ++ fsnotify_open(file); ++ } ++ return ret; ++} ++ + struct file *dentry_open(const struct path *path, int flags, + const struct cred *cred) + { diff --git a/pkgbuilds/linux-omarchy-eevdf/0320-vfs-open-consume-path-reference.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0320-vfs-open-consume-path-reference.patch.sig new file mode 100644 index 0000000..c0764ef Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0320-vfs-open-consume-path-reference.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch b/pkgbuilds/linux-omarchy-eevdf/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch rename to pkgbuilds/linux-omarchy-eevdf/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch diff --git a/pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig diff --git a/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch b/pkgbuilds/linux-omarchy-eevdf/0360-gpu-mem-cgroup.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch rename to pkgbuilds/linux-omarchy-eevdf/0360-gpu-mem-cgroup.patch diff --git a/pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0360-gpu-mem-cgroup.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0360-gpu-mem-cgroup.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0360-gpu-mem-cgroup.patch.sig diff --git a/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch b/pkgbuilds/linux-omarchy-eevdf/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch rename to pkgbuilds/linux-omarchy-eevdf/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch diff --git a/pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig diff --git a/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch b/pkgbuilds/linux-omarchy-eevdf/0401-psr2-early-transport-panels.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch rename to pkgbuilds/linux-omarchy-eevdf/0401-psr2-early-transport-panels.patch diff --git a/pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0401-psr2-early-transport-panels.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0402-psr2-early-transport-panels.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0401-psr2-early-transport-panels.patch.sig diff --git a/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch b/pkgbuilds/linux-omarchy-eevdf/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch similarity index 64% rename from pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch rename to pkgbuilds/linux-omarchy-eevdf/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch index 28a6330..27e9b7d 100644 --- a/pkgbuilds/linux-omarchy-bore/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch @@ -1,5 +1,4 @@ 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 { @@ -11,10 +10,9 @@ index c21e0c0ef0b1..e62788328bfd 100644 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, +@@ -2481,6 +2481,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); @@ -22,7 +20,7 @@ index beaa1d62613d..644ab9b1bee9 100644 } /** -@@ -2506,6 +2507,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) +@@ -2512,6 +2513,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) cancel_work_sync(&psr->work); cancel_delayed_work_sync(&psr->dc3co_work); @@ -30,48 +28,48 @@ index beaa1d62613d..644ab9b1bee9 100644 } /** -@@ -3539,6 +3541,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) +@@ -3545,6 +3547,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); } +#define PANEL_REPLAY_REENABLE_DELAY_MS 50 + -+static bool panel_replay_alpm_cursor_lag_workaround_enabled(struct intel_dp *intel_dp) ++static bool panel_replay_exit_on_flush_enabled(struct intel_dp *intel_dp) +{ + return intel_dp_is_edp(intel_dp) && + intel_dp->psr.panel_replay_enabled && + intel_dp->psr.sel_update_enabled && + intel_has_dpcd_quirk(intel_dp, -+ QUIRK_PANEL_REPLAY_ALPM_CURSOR_LAG); ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); +} + 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) +@@ -3563,6 +3576,11 @@ static void intel_psr_work(struct work_struct *work) if (intel_dp->psr.pause_counter) goto unlock; -+ /* The dedicated delayed work owns re-entry while the workaround is armed. */ -+ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ /* The dedicated delayed work owns re-entry while the quirk is armed. */ ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && + delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) + goto unlock; + /* * We have to make sure PSR is ready for re-enable * otherwise it keeps disabled until next full enable/disable cycle. -@@ -3566,6 +3584,10 @@ static void intel_psr_work(struct work_struct *work) +@@ -3572,6 +3590,10 @@ static void intel_psr_work(struct work_struct *work) if (!__psr_wait_for_idle_locked(intel_dp)) goto unlock; -+ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) && ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && + delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) + goto unlock; + /* * The delayed work can race with an invalidate hence we need to * recheck. Since psr_flush first clears this and then reschedules we -@@ -3579,6 +3601,33 @@ static void intel_psr_work(struct work_struct *work) +@@ -3585,6 +3607,33 @@ static void intel_psr_work(struct work_struct *work) mutex_unlock(&intel_dp->psr.lock); } @@ -84,7 +82,7 @@ index beaa1d62613d..644ab9b1bee9 100644 + mutex_lock(&intel_dp->psr.lock); + + if (!intel_dp->psr.enabled || -+ !panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp) || ++ !panel_replay_exit_on_flush_enabled(intel_dp) || + intel_dp->psr.pause_counter || + READ_ONCE(intel_dp->psr.irq_aux_error)) + goto unlock; @@ -105,25 +103,25 @@ index beaa1d62613d..644ab9b1bee9 100644 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, +@@ -3656,8 +3705,11 @@ void intel_psr_invalidate(struct intel_display *display, INTEL_FRONTBUFFER_ALL_MASK(intel_dp->psr.pipe); intel_dp->psr.busy_frontbuffer_bits |= pipe_frontbuffer_bits; - if (pipe_frontbuffer_bits) + if (pipe_frontbuffer_bits) { -+ if (panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) ++ if (panel_replay_exit_on_flush_enabled(intel_dp)) + cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); _psr_invalidate_handle(intel_dp); + } mutex_unlock(&intel_dp->psr.lock); } -@@ -3767,6 +3819,15 @@ void intel_psr_flush(struct intel_display *display, +@@ -3773,6 +3825,15 @@ void intel_psr_flush(struct intel_display *display, if (intel_dp->psr.pause_counter) goto unlock; + if (pipe_frontbuffer_bits && -+ panel_replay_alpm_cursor_lag_workaround_enabled(intel_dp)) { ++ panel_replay_exit_on_flush_enabled(intel_dp)) { + intel_psr_exit(intel_dp); + mod_delayed_work(display->wq.unordered, + &intel_dp->psr.panel_replay_reenable_work, @@ -134,7 +132,7 @@ index beaa1d62613d..644ab9b1bee9 100644 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) +@@ -3836,6 +3897,8 @@ void intel_psr_init(struct intel_dp *intel_dp) INIT_WORK(&intel_dp->psr.work, intel_psr_work); INIT_DELAYED_WORK(&intel_dp->psr.dc3co_work, tgl_dc3co_disable_work); @@ -144,53 +142,31 @@ index beaa1d62613d..644ab9b1bee9 100644 } 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"); +@@ -94,6 +94,14 @@ static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) + drm_info(display->drm, "Applying disable Panel Replay quirk\n"); } ++static void quirk_panel_replay_exit_on_flush(struct intel_dp *intel_dp) ++{ ++ struct intel_display *display = to_intel_display(intel_dp); ++ ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); ++ drm_info(display->drm, "Applying Panel Replay exit on flush quirk\n"); ++} ++ static void quirk_disable_psr2(struct intel_display *display) -@@ -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, - }, - }; - + { + intel_set_quirk(display, QUIRK_DISABLE_PSR2); diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h -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, ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH, }; void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy-eevdf/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..196c3ee Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-eevdf/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..24c203e --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -284,7 +284,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0db9, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + /* Dell XPS 16 DA16260 */ + { diff --git a/pkgbuilds/linux-omarchy-eevdf/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..0393b90 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-eevdf/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..8eb9b65 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -292,7 +292,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0dba, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + }; + diff --git a/pkgbuilds/linux-omarchy-eevdf/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..095745c Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch b/pkgbuilds/linux-omarchy-eevdf/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch new file mode 100644 index 0000000..1da7f71 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -267,6 +267,9 @@ static struct intel_quirk intel_quirks[] = { + + /* Xiaomi Book Pro 14 2026 */ + { 0xb081, 0x1d72, 0x2424, quirk_disable_psr2 }, ++ ++ /* Dell XPS 13 DX13260 */ ++ { DEVICE_ID_ANY, 0x1028, 0x0e54, quirk_disable_psr2 }, + }; + + static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { +@@ -294,6 +297,14 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_panel_replay_exit_on_flush, + }, ++ /* Dell XPS 13 DX13260 */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e54, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .hook = quirk_disable_edp_panel_replay, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-eevdf/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig new file mode 100644 index 0000000..16a1fb9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-eevdf/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..94d0d55 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch @@ -0,0 +1,19 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -305,6 +305,15 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_disable_edp_panel_replay, + }, ++ /* Dell XPS 13 DX13260 (Wildcat Lake), sink "Bamboo" */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e53, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .sink_device_id = SINK_DEVICE_ID(0x42, 0x61, 0x6d, 0x62, 0x6f, 0x6f), ++ .hook = quirk_panel_replay_exit_on_flush, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-eevdf/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..36a38a4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch b/pkgbuilds/linux-omarchy-eevdf/0411-drm-xe-display-no-stolen-framebuffers.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch rename to pkgbuilds/linux-omarchy-eevdf/0411-drm-xe-display-no-stolen-framebuffers.patch diff --git a/pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0411-drm-xe-display-no-stolen-framebuffers.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0411-drm-xe-display-no-stolen-framebuffers.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0411-drm-xe-display-no-stolen-framebuffers.patch.sig diff --git a/pkgbuilds/linux-omarchy/0420-safe-window.patch b/pkgbuilds/linux-omarchy-eevdf/0420-safe-window.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0420-safe-window.patch rename to pkgbuilds/linux-omarchy-eevdf/0420-safe-window.patch diff --git a/pkgbuilds/linux-omarchy/0420-safe-window.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0420-safe-window.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0420-safe-window.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0420-safe-window.patch.sig diff --git a/pkgbuilds/linux-omarchy/0430-fbc.patch b/pkgbuilds/linux-omarchy-eevdf/0430-fbc.patch similarity index 97% rename from pkgbuilds/linux-omarchy/0430-fbc.patch rename to pkgbuilds/linux-omarchy-eevdf/0430-fbc.patch index 4548866..b64db93 100644 --- a/pkgbuilds/linux-omarchy/0430-fbc.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0430-fbc.patch @@ -55,7 +55,7 @@ diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/dis 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, +@@ -1932,10 +1932,10 @@ static int intel_plane_min_height(struct intel_plane *plane, return 1; } @@ -70,7 +70,7 @@ diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/dr { 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, +@@ -1943,10 +1943,10 @@ static int intel_plane_max_width(struct intel_plane *plane, return INT_MAX; } diff --git a/pkgbuilds/linux-omarchy-eevdf/0430-fbc.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0430-fbc.patch.sig new file mode 100644 index 0000000..41dee2a Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0430-fbc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch b/pkgbuilds/linux-omarchy-eevdf/0440-xe3-peak-bandwidth.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch rename to pkgbuilds/linux-omarchy-eevdf/0440-xe3-peak-bandwidth.patch diff --git a/pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0440-xe3-peak-bandwidth.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0440-xe3-peak-bandwidth.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0440-xe3-peak-bandwidth.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch b/pkgbuilds/linux-omarchy-eevdf/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch new file mode 100644 index 0000000..e2e1fac --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch @@ -0,0 +1,95 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c +--- a/drivers/gpu/drm/i915/display/intel_cursor.c ++++ b/drivers/gpu/drm/i915/display/intel_cursor.c +@@ -536,7 +536,8 @@ static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); +@@ -569,8 +570,10 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch) { ++ i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); + return; ++ } + + if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) { + if (crtc_state->enable_psr2_su_region_et) { +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1177,6 +1177,8 @@ struct intel_crtc_state { + bool has_sel_update; + bool enable_psr2_sel_fetch; + bool enable_psr2_su_region_et; ++ /* Drop the stale selective fetch enable bits as selective fetch is turned off */ ++ bool clear_psr2_sel_fetch; + bool req_psr2_sdp_prior_scanline; + bool has_panel_replay; + bool link_off_after_as_sdp_when_pr_active; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2941,6 +2941,8 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) + { + struct intel_display *display = to_intel_display(state); ++ const struct intel_crtc_state *old_crtc_state = ++ intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + struct intel_plane_state *new_plane_state, *old_plane_state; + struct intel_plane *plane; +@@ -2953,6 +2955,19 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + bool full_update = false, su_area_changed; + int i, ret; + ++ /* ++ * Selective fetch is not always usable, for instance it is dropped ++ * while pipe CRC is active. The planes keep their selective fetch ++ * enable bit set in hardware over that, and a plane disabled while ++ * selective fetch is off never gets the bit cleared. Once selective ++ * fetch comes back the hardware would resume fetching for a plane that ++ * is no longer enabled and keep its DDB range reserved, so have the ++ * plane update drop the bit for every plane of the pipe as selective ++ * fetch is turned off. ++ */ ++ crtc_state->clear_psr2_sel_fetch = old_crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->enable_psr2_sel_fetch; ++ + if (!crtc_state->enable_psr2_sel_fetch) + return 0; + +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -885,7 +885,8 @@ static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); +@@ -1634,10 +1635,8 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) +- return; +- +- if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) ++ if (crtc_state->enable_psr2_sel_fetch && ++ drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), + SEL_FETCH_PLANE_CTL_ENABLE); + else diff --git a/pkgbuilds/linux-omarchy-eevdf/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig new file mode 100644 index 0000000..fa39ab5 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch b/pkgbuilds/linux-omarchy-eevdf/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch new file mode 100644 index 0000000..820d377 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dsb.c b/drivers/gpu/drm/i915/display/intel_dsb.c +--- a/drivers/gpu/drm/i915/display/intel_dsb.c ++++ b/drivers/gpu/drm/i915/display/intel_dsb.c +@@ -902,9 +902,17 @@ void intel_dsb_wait_for_delayed_vblank(struct intel_atomic_state *state, + * the hardware itself guarantees that we're SCL lines + * away from the delayed vblank, and we won't be inside + * the vmin safe window so this extra wait does nothing. ++ * ++ * Experimentally, DSB may observe a slightly stale ++ * PIPEDSL value. When the actual scanline has just reached ++ * safe_window_start, WAIT_DSL_OUT may complete immediately ++ * due to the stale value. ++ * ++ * Shift the start back by one scanline to ensure the wait ++ * window is entered reliably. + */ + intel_dsb_wait_scanline_out(state, dsb, +- intel_vrr_safe_window_start(crtc_state), ++ intel_vrr_safe_window_start(crtc_state) - 1, + intel_vrr_vmin_safe_window_end(crtc_state)); + /* + * When the push is sent during vblank it will trigger diff --git a/pkgbuilds/linux-omarchy-eevdf/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig new file mode 100644 index 0000000..cd6b14b Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0444-drm-i915-vrr-dc-balance-off-by-default.patch b/pkgbuilds/linux-omarchy-eevdf/0444-drm-i915-vrr-dc-balance-off-by-default.patch new file mode 100644 index 0000000..9efda11 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0444-drm-i915-vrr-dc-balance-off-by-default.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_params.c b/drivers/gpu/drm/i915/display/intel_display_params.c +--- a/drivers/gpu/drm/i915/display/intel_display_params.c ++++ b/drivers/gpu/drm/i915/display/intel_display_params.c +@@ -120,6 +120,10 @@ intel_display_param_named_unsafe(enable_psr, int, 0400, + "(0=disabled, 1=enable up to PSR1, 2=enable up to PSR2) " + "Default: -1 (use per-chip default)"); + ++intel_display_param_named_unsafe(enable_dc_balance, bool, 0400, ++ "Enable VRR DC balance (0=disabled, 1=enabled). " ++ "Default: 0 (disabled)"); ++ + intel_display_param_named_unsafe(enable_panel_replay, int, 0400, + "Enable Panel Replay (0=disabled, 1=enabled). Default: -1 (use per-chip default)"); + +diff --git a/drivers/gpu/drm/i915/display/intel_display_params.h b/drivers/gpu/drm/i915/display/intel_display_params.h +--- a/drivers/gpu/drm/i915/display/intel_display_params.h ++++ b/drivers/gpu/drm/i915/display/intel_display_params.h +@@ -46,6 +46,7 @@ struct drm_printer; + param(bool, enable_dp_mst, true, 0600) \ + param(int, enable_fbc, -1, 0600) \ + param(int, enable_psr, -1, 0600) \ ++ param(bool, enable_dc_balance, false, 0600) \ + param(int, enable_panel_replay, -1, 0600) \ + param(bool, psr_safest_params, false, 0400) \ + param(bool, enable_psr2_sel_fetch, true, 0400) \ +diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c +--- a/drivers/gpu/drm/i915/display/intel_vrr.c ++++ b/drivers/gpu/drm/i915/display/intel_vrr.c +@@ -383,10 +383,12 @@ static bool intel_vrr_dc_balance_possible(const struct intel_crtc_state *crtc_st + static void + intel_vrr_dc_balance_compute_config(struct intel_crtc_state *crtc_state) + { ++ struct intel_display *display = to_intel_display(crtc_state); + int guardband_usec, adjustment_usec; + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + +- if (!intel_vrr_dc_balance_possible(crtc_state) || !crtc_state->vrr.enable) ++ if (!intel_vrr_dc_balance_possible(crtc_state) || ++ !crtc_state->vrr.enable || !display->params.enable_dc_balance) + return; + + crtc_state->vrr.dc_balance.vmax = crtc_state->vrr.vmax; diff --git a/pkgbuilds/linux-omarchy-eevdf/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig new file mode 100644 index 0000000..c8a67fa Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch b/pkgbuilds/linux-omarchy-eevdf/0450-amd-hdmi-vrr-allm.patch similarity index 97% rename from pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch rename to pkgbuilds/linux-omarchy-eevdf/0450-amd-hdmi-vrr-allm.patch index ebe9a36..02991bc 100644 --- a/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0450-amd-hdmi-vrr-allm.patch @@ -9,7 +9,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm #include "modules/inc/mod_power.h" #include "modules/power/power_helpers.h" -@@ -10068,6 +10069,9 @@ static void update_freesync_state_on_stream( +@@ -10088,6 +10089,9 @@ static void update_freesync_state_on_stream( &vrr_infopacket, pack_sdp_v1_3); @@ -19,7 +19,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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( +@@ -10099,6 +10103,36 @@ static void update_freesync_state_on_stream( new_stream->vrr_infopacket = vrr_infopacket; new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); @@ -56,7 +56,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -10665,9 +10699,12 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, } if (acrtc_state->stream) { @@ -70,7 +70,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } } -@@ -12049,6 +12086,14 @@ static void get_freesync_config_for_crtc( +@@ -12069,6 +12106,14 @@ static void get_freesync_config_for_crtc( } out: new_crtc_state->freesync_config = config; @@ -85,7 +85,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } static void reset_freesync_config_for_crtc( -@@ -14011,6 +14056,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, +@@ -14031,6 +14076,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) goto update; @@ -101,7 +101,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -14056,7 +14110,9 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; } @@ -112,7 +112,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -14072,6 +14128,59 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; } } @@ -172,7 +172,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } if (amdgpu_dm_connector->dc_link) -@@ -14093,6 +14202,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, +@@ -14113,6 +14222,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, if (dm_con_state) dm_con_state->freesync_capable = freesync_capable; diff --git a/pkgbuilds/linux-omarchy-eevdf/0450-amd-hdmi-vrr-allm.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0450-amd-hdmi-vrr-allm.patch.sig new file mode 100644 index 0000000..8e70633 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0450-amd-hdmi-vrr-allm.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch b/pkgbuilds/linux-omarchy-eevdf/0451-amd-vtem-tmds-links.patch similarity index 91% rename from pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch rename to pkgbuilds/linux-omarchy-eevdf/0451-amd-vtem-tmds-links.patch index f61c400..5bc46ad 100644 --- a/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0451-amd-vtem-tmds-links.patch @@ -1,7 +1,7 @@ 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( +@@ -10089,7 +10089,10 @@ static void update_freesync_state_on_stream( &vrr_infopacket, pack_sdp_v1_3); diff --git a/pkgbuilds/linux-omarchy-eevdf/0451-amd-vtem-tmds-links.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0451-amd-vtem-tmds-links.patch.sig new file mode 100644 index 0000000..29e7f44 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0451-amd-vtem-tmds-links.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch b/pkgbuilds/linux-omarchy-eevdf/0452-amd-hdmi-frl-default.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch rename to pkgbuilds/linux-omarchy-eevdf/0452-amd-hdmi-frl-default.patch diff --git a/pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0452-amd-hdmi-frl-default.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0452-amd-hdmi-frl-default.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0452-amd-hdmi-frl-default.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0453-amd-vtem-null-sink.patch b/pkgbuilds/linux-omarchy-eevdf/0453-amd-vtem-null-sink.patch new file mode 100644 index 0000000..0a3c3d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0453-amd-vtem-null-sink.patch @@ -0,0 +1,14 @@ +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 +@@ -10090,8 +10090,8 @@ static void update_freesync_state_on_stream( + pack_sdp_v1_3); + + /* 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 && ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_FRL || ++ (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A && + aconn && aconn->base.display_info.hdmi.vrr_cap.supported)) + mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); + diff --git a/pkgbuilds/linux-omarchy-eevdf/0453-amd-vtem-null-sink.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0453-amd-vtem-null-sink.patch.sig new file mode 100644 index 0000000..795a770 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0453-amd-vtem-null-sink.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0454-amd-hdmi-frl-status-polling.patch b/pkgbuilds/linux-omarchy-eevdf/0454-amd-hdmi-frl-status-polling.patch new file mode 100644 index 0000000..62ada93 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0454-amd-hdmi-frl-status-polling.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -2025,7 +2025,7 @@ static void hdmi_frl_status_polling_work(struct work_struct *work) + if (!dc_is_hdmi_signal(dc_link->connector_signal)) + continue; + +- if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) ++ if (dc_link->frl_link_settings.frl_link_rate == 0) + continue; + + link_update = dc_link_frl_poll_status_flag(dc_link); diff --git a/pkgbuilds/linux-omarchy-eevdf/0454-amd-hdmi-frl-status-polling.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0454-amd-hdmi-frl-status-polling.patch.sig new file mode 100644 index 0000000..786879a Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0454-amd-hdmi-frl-status-polling.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch b/pkgbuilds/linux-omarchy-eevdf/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch new file mode 100644 index 0000000..26db142 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +--- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c ++++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +@@ -882,7 +882,7 @@ static void verify_link_capability_non_destructive(struct dc_link *link) + else + link->verified_link_cap = dp_get_max_link_cap(link); + } else if (dc_is_hdmi_signal(link->local_sink->sink_signal)) { +- link->verified_link_cap = link->reported_link_cap; ++ link->frl_verified_link_cap = link->frl_reported_link_cap; + + if (is_hdmi_frl_in_use(link)) { + link->local_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; diff --git a/pkgbuilds/linux-omarchy-eevdf/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig new file mode 100644 index 0000000..f225581 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0456-amd-dml-frl-compressed-cap-check.patch b/pkgbuilds/linux-omarchy-eevdf/0456-amd-dml-frl-compressed-cap-check.patch new file mode 100644 index 0000000..4531604 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0456-amd-dml-frl-compressed-cap-check.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +--- a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c ++++ b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +@@ -630,17 +630,17 @@ enum frl_cap_chk_result dml1_frl_cap_chk(struct frl_cap_chk_params *params) + { + struct frl_cap_chk_intermediates inter; + +-#if defined (CONFIG_DRM_AMD_DC_FP) +- if (params->compressed) +- return dml1_frl_cap_chk_compressed(params, &inter); +-#endif +- + return dml1_frl_cap_chk_inter(params, &inter); + } + + enum frl_cap_chk_result dml1_frl_cap_chk_inter(struct frl_cap_chk_params *params, + struct frl_cap_chk_intermediates *inter) + { ++#if defined (CONFIG_DRM_AMD_DC_FP) ++ if (params->compressed) ++ return dml1_frl_cap_chk_compressed(params, inter); ++#endif ++ + return dml1_frl_cap_chk_uncompressed(params, inter); + } + diff --git a/pkgbuilds/linux-omarchy-eevdf/0456-amd-dml-frl-compressed-cap-check.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0456-amd-dml-frl-compressed-cap-check.patch.sig new file mode 100644 index 0000000..8297ca2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0456-amd-dml-frl-compressed-cap-check.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch b/pkgbuilds/linux-omarchy-eevdf/0460-vesa-displayid-dsc-bpp.patch similarity index 98% rename from pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch rename to pkgbuilds/linux-omarchy-eevdf/0460-vesa-displayid-dsc-bpp.patch index 1ba6ec7..7597723 100644 --- a/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0460-vesa-displayid-dsc-bpp.patch @@ -1,7 +1,7 @@ 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( +@@ -7116,6 +7116,11 @@ static void fill_stream_properties_from_drm_display_mode( stream->output_color_space = get_output_color_space(timing_out, connector_state); stream->content_type = get_output_content_type(connector_state); @@ -13,7 +13,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm } static void fill_audio_info(struct audio_info *audio_info, -@@ -7616,6 +7621,7 @@ create_stream_for_sink(struct drm_connector *connector, +@@ -7636,6 +7641,7 @@ create_stream_for_sink(struct drm_connector *connector, struct drm_display_mode mode; struct drm_display_mode saved_mode; struct drm_display_mode *freesync_mode = NULL; @@ -21,7 +21,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -7727,6 +7733,16 @@ create_stream_for_sink(struct drm_connector *connector, } } diff --git a/pkgbuilds/linux-omarchy-eevdf/0460-vesa-displayid-dsc-bpp.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0460-vesa-displayid-dsc-bpp.patch.sig new file mode 100644 index 0000000..922eab7 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/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-eevdf/0461-vesa-dsc-passthru-mode-match-fix.patch similarity index 89% rename from pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch rename to pkgbuilds/linux-omarchy-eevdf/0461-vesa-dsc-passthru-mode-match-fix.patch index e27b493..b826d02 100644 --- a/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0461-vesa-dsc-passthru-mode-match-fix.patch @@ -1,7 +1,7 @@ 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, +@@ -7642,6 +7642,7 @@ create_stream_for_sink(struct drm_connector *connector, struct drm_display_mode saved_mode; struct drm_display_mode *freesync_mode = NULL; struct drm_display_mode *dsc_passthru_mode = NULL; @@ -9,7 +9,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -7708,6 +7709,22 @@ create_stream_for_sink(struct drm_connector *connector, mode_refresh = drm_mode_vrefresh(&mode); @@ -32,7 +32,7 @@ diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm 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, +@@ -7733,16 +7750,6 @@ create_stream_for_sink(struct drm_connector *connector, } } diff --git a/pkgbuilds/linux-omarchy-eevdf/0461-vesa-dsc-passthru-mode-match-fix.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0461-vesa-dsc-passthru-mode-match-fix.patch.sig new file mode 100644 index 0000000..cb18d66 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/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-eevdf/0472-amdgpu-userq-post-reset-error.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch rename to pkgbuilds/linux-omarchy-eevdf/0472-amdgpu-userq-post-reset-error.patch diff --git a/pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0472-amdgpu-userq-post-reset-error.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0472-amdgpu-userq-post-reset-error.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0472-amdgpu-userq-post-reset-error.patch.sig diff --git a/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch b/pkgbuilds/linux-omarchy-eevdf/0474-amd-display-oled-vesa-backlight.patch similarity index 92% rename from pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch rename to pkgbuilds/linux-omarchy-eevdf/0474-amd-display-oled-vesa-backlight.patch index 590d8a0..035758b 100644 --- a/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0474-amd-display-oled-vesa-backlight.patch @@ -1,7 +1,7 @@ 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) +@@ -4176,8 +4176,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; diff --git a/pkgbuilds/linux-omarchy-eevdf/0474-amd-display-oled-vesa-backlight.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0474-amd-display-oled-vesa-backlight.patch.sig new file mode 100644 index 0000000..f64009c Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/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-eevdf/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch rename to pkgbuilds/linux-omarchy-eevdf/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch diff --git a/pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0476-amd-display-ycbcr420-only-modes.patch b/pkgbuilds/linux-omarchy-eevdf/0476-amd-display-ycbcr420-only-modes.patch new file mode 100644 index 0000000..8d6c8a4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0476-amd-display-ycbcr420-only-modes.patch @@ -0,0 +1,25 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7020,16 +7020,11 @@ static void fill_stream_properties_from_drm_display_mode( + timing_out->v_border_top = 0; + timing_out->v_border_bottom = 0; + /* TODO: un-hardcode */ +- if (drm_mode_is_420_only(info, mode_in) +- && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || +- stream->signal == SIGNAL_TYPE_HDMI_FRL) +- && aconnector +- && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) +- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; +- else if (drm_mode_is_420_also(info, mode_in) +- && aconnector +- && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 +- || aconnector->force_yuv420_output)) ++ if (drm_mode_is_420_only(info, mode_in) || ++ (aconnector && ++ (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 || ++ aconnector->force_yuv420_output) && ++ drm_mode_is_420_also(info, mode_in))) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + && aconnector diff --git a/pkgbuilds/linux-omarchy-eevdf/0476-amd-display-ycbcr420-only-modes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0476-amd-display-ycbcr420-only-modes.patch.sig new file mode 100644 index 0000000..2b5da94 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0476-amd-display-ycbcr420-only-modes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0477-amd-display-irq-atomic-rmw.patch b/pkgbuilds/linux-omarchy-eevdf/0477-amd-display-irq-atomic-rmw.patch new file mode 100644 index 0000000..1b0eb75 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0477-amd-display-irq-atomic-rmw.patch @@ -0,0 +1,303 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -3345,7 +3345,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + if (acrtc && state->stream_status[i].plane_count != 0 && + amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + if (rc) + drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", + enable ? "enable" : "disable"); +@@ -3369,7 +3369,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + /* During gpu-reset we disable and then enable vblank irq, so + * don't use amdgpu_irq_get/put() to avoid refcount change. + */ +- if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) ++ if (!amdgpu_dm_irq_set(adev, irq_source, enable)) + drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); + + } else if (acrtc && state->stream_status[i].plane_count != 0) { +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +@@ -550,6 +550,18 @@ struct amdgpu_display_manager { + struct common_irq_params + vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + ++ /** ++ * @irq_reg_lock: ++ * ++ * Serializes the read-modify-writes of the HW interrupt control ++ * registers. Several interrupt sources share one register - e.g. the ++ * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK ++ * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must ++ * not race with acking another. Held only across amdgpu_dm_irq_set() ++ * and amdgpu_dm_irq_ack(). ++ */ ++ spinlock_t irq_reg_lock; ++ + /** + * @dmub_trace_params: + * +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +@@ -31,6 +31,7 @@ + #include "amdgpu_dm_psr.h" + #include "amdgpu_dm_replay.h" + #include "amdgpu_dm_crtc.h" ++#include "amdgpu_dm_irq.h" + #include "amdgpu_dm_plane.h" + #include "amdgpu_dm_trace.h" + #include "amdgpu_dm_debugfs.h" +@@ -87,7 +88,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) + + irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; + +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + + DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", + acrtc->crtc_id, enable ? "en" : "dis", rc); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +@@ -1378,12 +1378,13 @@ void dm_helpers_free_gpu_mem( + + bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) + { ++ struct amdgpu_device *adev = ctx->driver_context; + enum dc_irq_source irq_source; + bool ret; + + irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + +- ret = dc_interrupt_set(ctx->dc, irq_source, enable); ++ ret = amdgpu_dm_irq_set(adev, irq_source, enable); + + DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", + enable ? "en" : "dis", ret); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +@@ -424,6 +424,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) + DRM_DEBUG_KMS("DM_IRQ\n"); + + spin_lock_init(&adev->dm.irq_handler_list_table_lock); ++ spin_lock_init(&adev->dm.irq_reg_lock); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + /* low context handler list init */ +@@ -496,7 +497,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, false); ++ amdgpu_dm_irq_set(adev, src, false); + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + +@@ -533,7 +534,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -558,7 +559,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -645,6 +646,21 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + } + ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ return dc_interrupt_set(adev->dm.dc, src, enable); ++} ++ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ dc_interrupt_ack(adev->dm.dc, src); ++} ++ + /** + * amdgpu_dm_irq_handler - Generic DM IRQ handler + * @adev: amdgpu base driver device containing the DM device +@@ -665,7 +681,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + entry->src_id, + entry->src_data[0]); + +- dc_interrupt_ack(adev->dm.dc, src); ++ amdgpu_dm_irq_ack(adev, src); + + /* Call high irq work immediately */ + amdgpu_dm_irq_immediate_work(adev, src); +@@ -703,7 +719,7 @@ static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, src, st); ++ amdgpu_dm_irq_set(adev, src, st); + return 0; + } + +@@ -737,7 +753,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, + if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) + dc_allow_idle_optimizations(dc, false); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -791,7 +807,7 @@ static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -817,7 +833,7 @@ static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -881,9 +897,7 @@ void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) + } + void amdgpu_dm_outbox_init(struct amdgpu_device *adev) + { +- dc_interrupt_set(adev->dm.dc, +- DC_IRQ_SOURCE_DMCUB_OUTBOX, +- true); ++ amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); + } + + /** +@@ -905,7 +919,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + + /* First, clear all hpd and hpdrx interrupts */ + for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { +- if (!dc_interrupt_set(adev->dm.dc, i, false)) ++ if (!amdgpu_dm_irq_set(adev, i, false)) + drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", + i); + } +@@ -934,7 +948,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * of dm. Note that only hpd interrupt types are registered with + * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on + * hpd_rx isn't available. DM currently controls hpd_rx +- * explicitly with dc_interrupt_set() ++ * explicitly with amdgpu_dm_irq_set() + */ + if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { + irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; +@@ -943,23 +957,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * and what bios reports as the # of connectors with hpd + * sources. Since the # of hpd source types registered + * with base driver == mode_info.num_hpd, we have to +- * fallback to dc_interrupt_set for the remaining types. ++ * fallback to amdgpu_dm_irq_set for the remaining types. + */ + if (irq_type < adev->mode_info.num_hpd) { + if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) + drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ true); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ true); + } + } + drm_connector_list_iter_end(&iter); +@@ -1003,16 +1015,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) + drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ false); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ false); + } + } + drm_connector_list_iter_end(&iter); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +@@ -81,6 +81,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, + enum dc_irq_source irq_source, + void *ih_index); + ++/** ++ * amdgpu_dm_irq_set - enable or disable a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to toggle ++ * @enable: true to enable the source, false to disable it ++ * ++ * DM-wide replacement for dc_interrupt_set(). As locking is DM's ++ * responsibility, this is a thin wrapper serializes the underlying ++ * read-modify-write against the other interrupt sources sharing HW control ++ * registers with @src, so DM must never call dc_interrupt_set() directly. ++ * ++ * Returns: true if the source was toggled. ++ */ ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable); ++ ++/** ++ * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to acknowledge ++ * ++ * DM-wide replacement for dc_interrupt_ack(), serialized the same way as ++ * amdgpu_dm_irq_set(). ++ */ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); ++ + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); + + void amdgpu_dm_outbox_init(struct amdgpu_device *adev); diff --git a/pkgbuilds/linux-omarchy-eevdf/0477-amd-display-irq-atomic-rmw.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0477-amd-display-irq-atomic-rmw.patch.sig new file mode 100644 index 0000000..c0f963e Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0477-amd-display-irq-atomic-rmw.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch b/pkgbuilds/linux-omarchy-eevdf/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch new file mode 100644 index 0000000..dcdb282 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -14232,6 +14232,12 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + } + } + ++ /* HDMI/PCON without an EDID FreeSync VCP code is not FreeSync-capable. */ ++ if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && ++ !sink->edid_caps.freesync_vcp_code) ++ freesync_capable = false; ++ + /* Handle MCCS */ + if (do_mccs) { + dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); diff --git a/pkgbuilds/linux-omarchy-eevdf/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig new file mode 100644 index 0000000..eed1a60 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch b/pkgbuilds/linux-omarchy-eevdf/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch new file mode 100644 index 0000000..13b9043 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch @@ -0,0 +1,40 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -10135,20 +10135,19 @@ static void update_freesync_state_on_stream( + */ + if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { +- struct dc_info_packet hf_vsif = {0}; ++ struct dc_info_packet vsp_infopacket = {0}; + bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; + bool allm = sink_allm && + (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || + vrr_params.state == VRR_STATE_ACTIVE_FIXED); + bool allm_changed; + +- if (allm) +- mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ mod_build_hf_vsif_infopacket(new_stream, &vsp_infopacket, allm, allm); + +- allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, +- sizeof(hf_vsif)) != 0; ++ allm_changed = memcmp(&new_stream->vsp_infopacket, &vsp_infopacket, ++ sizeof(vsp_infopacket)) != 0; + new_crtc_state->freesync_vrr_info_changed |= allm_changed; +- new_stream->hfvsif_infopacket = hf_vsif; ++ new_stream->vsp_infopacket = vsp_infopacket; + + if (allm_changed) + drm_dbg_driver(adev_to_drm(adev), +@@ -10727,8 +10726,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; +- bundle->stream_update.hfvsif_infopacket = +- &acrtc_state->stream->hfvsif_infopacket; ++ bundle->stream_update.vsp_infopacket = ++ &acrtc_state->stream->vsp_infopacket; + } + } + } diff --git a/pkgbuilds/linux-omarchy-eevdf/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig new file mode 100644 index 0000000..37a9ec9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0510-sound-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0510-sound-updates.patch new file mode 100644 index 0000000..1368f53 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0510-sound-updates.patch @@ -0,0 +1,12141 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -1757,6 +1757,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) + * by the drivers/platform/x86/serial-multi-instantiate.c driver, which + * knows which client device id to use for each resource. + */ ++ {"AWDZ8399", }, + {"BSG1160", }, + {"BSG2150", }, + {"CSC3551", }, +diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c +--- a/drivers/platform/x86/serial-multi-instantiate.c ++++ b/drivers/platform/x86/serial-multi-instantiate.c +@@ -307,6 +307,15 @@ static void smi_remove(struct platform_device *pdev) + smi_devs_unregister(smi); + } + ++static const struct smi_node aw88399_hda = { ++ .instances = { ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ {} ++ }, ++ .bus_type = SMI_AUTO_DETECT, ++}; ++ + static const struct smi_node bsg1160_data = { + .instances = { + { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, +@@ -405,6 +414,7 @@ static const struct smi_node tas2781_hda = { + * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). + */ + static const struct acpi_device_id smi_acpi_ids[] = { ++ { "AWDZ8399", (unsigned long)&aw88399_hda }, + { "BSG1160", (unsigned long)&bsg1160_data }, + { "BSG2150", (unsigned long)&bsg2150_data }, + { "CSC3551", (unsigned long)&cs35l41_hda }, +diff --git a/include/sound/ac97/codec.h b/include/sound/ac97/codec.h +--- a/include/sound/ac97/codec.h ++++ b/include/sound/ac97/codec.h +@@ -33,7 +33,7 @@ struct ac97_id { + }; + + /** +- * ac97_codec_device - a ac97 codec ++ * struct ac97_codec_device - a ac97 codec + * @dev: the core device + * @vendor_id: the vendor_id of the codec, as sensed on the AC-link + * @num: the codec number, 0 is primary, 1 is first slave, etc ... +@@ -53,7 +53,7 @@ struct ac97_codec_device { + }; + + /** +- * ac97_codec_driver - a ac97 codec driver ++ * struct ac97_codec_driver - a ac97 codec driver + * @driver: the device driver structure + * @probe: the function called when a ac97_codec_device is matched + * @remove: the function called when the device is unbound/removed +diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h +--- a/include/sound/asequencer.h ++++ b/include/sound/asequencer.h +@@ -11,9 +11,6 @@ + #include + #include + +-/* helper macro */ +-#define snd_seq_event_bounce_ext_data(ev) ((void*)((char *)(ev)->data.ext.ptr + sizeof(struct snd_seq_event_bounce))) +- + /* + * type check macros + */ +diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h +new file mode 100644 +--- /dev/null ++++ b/include/sound/aw88399.h +@@ -0,0 +1,621 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * linux/sound/aw88399.h -- Platform data for AW88399 ++ * ++ * Copyright (c) 2023 AWINIC Technology CO., LTD ++ * ++ * Author: Weidong Wang ++ */ ++ ++#ifndef __SOUND_AW88399_H ++#define __SOUND_AW88399_H ++ ++#include ++#include ++ ++/* registers list */ ++#define AW88399_ID_REG (0x00) ++#define AW88399_SYSST_REG (0x01) ++#define AW88399_SYSINT_REG (0x02) ++#define AW88399_SYSINTM_REG (0x03) ++#define AW88399_SYSCTRL_REG (0x04) ++#define AW88399_SYSCTRL2_REG (0x05) ++#define AW88399_I2SCTRL1_REG (0x06) ++#define AW88399_I2SCTRL2_REG (0x07) ++#define AW88399_I2SCTRL3_REG (0x08) ++#define AW88399_DACCFG1_REG (0x09) ++#define AW88399_DACCFG2_REG (0x0A) ++#define AW88399_DACCFG3_REG (0x0B) ++#define AW88399_DACCFG4_REG (0x0C) ++#define AW88399_DACCFG5_REG (0x0D) ++#define AW88399_DACCFG6_REG (0x0E) ++#define AW88399_DACCFG7_REG (0x0F) ++#define AW88399_MPDCFG1_REG (0x10) ++#define AW88399_MPDCFG2_REG (0x11) ++#define AW88399_MPDCFG3_REG (0x12) ++#define AW88399_MPDCFG4_REG (0x13) ++#define AW88399_PWMCTRL1_REG (0x14) ++#define AW88399_PWMCTRL2_REG (0x15) ++#define AW88399_PWMCTRL3_REG (0x16) ++#define AW88399_I2SCFG1_REG (0x17) ++#define AW88399_DBGCTRL_REG (0x18) ++#define AW88399_HAGCST_REG (0x20) ++#define AW88399_VBAT_REG (0x21) ++#define AW88399_TEMP_REG (0x22) ++#define AW88399_PVDD_REG (0x23) ++#define AW88399_ISNDAT_REG (0x24) ++#define AW88399_VSNDAT_REG (0x25) ++#define AW88399_I2SINT_REG (0x26) ++#define AW88399_I2SCAPCNT_REG (0x27) ++#define AW88399_ANASTA1_REG (0x28) ++#define AW88399_ANASTA2_REG (0x29) ++#define AW88399_ANASTA3_REG (0x2A) ++#define AW88399_TESTDET_REG (0x2B) ++#define AW88399_DSMCFG1_REG (0x30) ++#define AW88399_DSMCFG2_REG (0x31) ++#define AW88399_DSMCFG3_REG (0x32) ++#define AW88399_DSMCFG4_REG (0x33) ++#define AW88399_DSMCFG5_REG (0x34) ++#define AW88399_DSMCFG6_REG (0x35) ++#define AW88399_DSMCFG7_REG (0x36) ++#define AW88399_DSMCFG8_REG (0x37) ++#define AW88399_TESTIN_REG (0x38) ++#define AW88399_TESTOUT_REG (0x39) ++#define AW88399_MEMTEST_REG (0x3A) ++#define AW88399_VSNCTRL1_REG (0x3B) ++#define AW88399_ISNCTRL1_REG (0x3C) ++#define AW88399_ISNCTRL2_REG (0x3D) ++#define AW88399_DSPMADD_REG (0x40) ++#define AW88399_DSPMDAT_REG (0x41) ++#define AW88399_WDT_REG (0x42) ++#define AW88399_ACR1_REG (0x43) ++#define AW88399_ACR2_REG (0x44) ++#define AW88399_ASR1_REG (0x45) ++#define AW88399_ASR2_REG (0x46) ++#define AW88399_DSPCFG_REG (0x47) ++#define AW88399_ASR3_REG (0x48) ++#define AW88399_ASR4_REG (0x49) ++#define AW88399_DSPVCALB_REG (0x4A) ++#define AW88399_CRCCTRL_REG (0x4B) ++#define AW88399_DSPDBG1_REG (0x4C) ++#define AW88399_DSPDBG2_REG (0x4D) ++#define AW88399_DSPDBG3_REG (0x4E) ++#define AW88399_PLLCTRL1_REG (0x50) ++#define AW88399_PLLCTRL2_REG (0x51) ++#define AW88399_PLLCTRL3_REG (0x52) ++#define AW88399_CDACTRL1_REG (0x53) ++#define AW88399_CDACTRL2_REG (0x54) ++#define AW88399_CDACTRL3_REG (0x55) ++#define AW88399_SADCCTRL1_REG (0x56) ++#define AW88399_SADCCTRL2_REG (0x57) ++#define AW88399_BOPCTRL1_REG (0x58) ++#define AW88399_BOPCTRL2_REG (0x5A) ++#define AW88399_BOPCTRL3_REG (0x5B) ++#define AW88399_BOPCTRL4_REG (0x5C) ++#define AW88399_BOPCTRL5_REG (0x5D) ++#define AW88399_BOPCTRL6_REG (0x5E) ++#define AW88399_BOPCTRL7_REG (0x5F) ++#define AW88399_BSTCTRL1_REG (0x60) ++#define AW88399_BSTCTRL2_REG (0x61) ++#define AW88399_BSTCTRL3_REG (0x62) ++#define AW88399_BSTCTRL4_REG (0x63) ++#define AW88399_BSTCTRL5_REG (0x64) ++#define AW88399_BSTCTRL6_REG (0x65) ++#define AW88399_BSTCTRL7_REG (0x66) ++#define AW88399_BSTCTRL8_REG (0x67) ++#define AW88399_BSTCTRL9_REG (0x68) ++#define AW88399_BSTCTRL10_REG (0x69) ++#define AW88399_CPCTRL_REG (0x6A) ++#define AW88399_EFWH_REG (0x6C) ++#define AW88399_EFWM2_REG (0x6D) ++#define AW88399_EFWM1_REG (0x6E) ++#define AW88399_EFWL_REG (0x6F) ++#define AW88399_TESTCTRL1_REG (0x70) ++#define AW88399_TESTCTRL2_REG (0x71) ++#define AW88399_EFCTRL1_REG (0x72) ++#define AW88399_EFCTRL2_REG (0x73) ++#define AW88399_EFRH4_REG (0x74) ++#define AW88399_EFRH3_REG (0x75) ++#define AW88399_EFRH2_REG (0x76) ++#define AW88399_EFRH1_REG (0x77) ++#define AW88399_EFRL4_REG (0x78) ++#define AW88399_EFRL3_REG (0x79) ++#define AW88399_EFRL2_REG (0x7A) ++#define AW88399_EFRL1_REG (0x7B) ++#define AW88399_TM_REG (0x7C) ++#define AW88399_TM2_REG (0x7D) ++ ++#define AW88399_REG_MAX (0x7E) ++#define AW88399_MUTE_VOL (1023) ++ ++#define AW88399_DSP_CFG_ADDR (0x9B00) ++#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) ++#define AW88399_DSP_FW_ADDR (0x8980) ++#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) ++#define AW88399_DSP_ROM_CHECK_DATA (0x4638) ++ ++#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) ++#define AW88399_CALI_RE_HBITS_SHIFT (16) ++ ++#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) ++#define AW88399_CALI_RE_LBITS_SHIFT (0) ++ ++#define AW88399_I2STXEN_START_BIT (9) ++#define AW88399_I2STXEN_BITS_LEN (1) ++#define AW88399_I2STXEN_MASK \ ++ (~(((1<> (shift)) ++#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) ++#define AW88399_CRC_CHECK_PASS_VAL (0x4) ++ ++#define AW88399_CRC_CFG_BASE_ADDR (0xD80) ++#define AW88399_CRC_FW_BASE_ADDR (0x4C0) ++#define AW88399_ACF_FILE "aw88399_acf.bin" ++#define AW88399_DEV_SYSST_CHECK_MAX (10) ++#define AW88399_CHIP_ID 0x2183 ++ ++#define AW88399_START_RETRIES (5) ++#define AW88399_START_WORK_DELAY_MS (0) ++ ++enum { ++ AW_EF_AND_CHECK = 0, ++ AW_EF_OR_CHECK, ++}; ++ ++enum { ++ AW88399_DEV_VDSEL_DAC = 0, ++ AW88399_DEV_VDSEL_VSENSE = 32, ++}; ++ ++enum { ++ AW88399_DSP_CRC_NA = 0, ++ AW88399_DSP_CRC_OK = 1, ++}; ++ ++enum { ++ AW88399_DSP_FW_UPDATE_OFF = 0, ++ AW88399_DSP_FW_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_FORCE_UPDATE_OFF = 0, ++ AW88399_FORCE_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_1000_US = 1000, ++ AW88399_2000_US = 2000, ++ AW88399_3000_US = 3000, ++ AW88399_4000_US = 4000, ++}; ++ ++enum AW88399_DEV_STATUS { ++ AW88399_DEV_PW_OFF = 0, ++ AW88399_DEV_PW_ON, ++}; ++ ++enum AW88399_DEV_FW_STATUS { ++ AW88399_DEV_FW_FAILED = 0, ++ AW88399_DEV_FW_OK, ++}; ++ ++enum AW88399_DEV_MEMCLK { ++ AW88399_DEV_MEMCLK_OSC = 0, ++ AW88399_DEV_MEMCLK_PLL = 1, ++}; ++ ++enum AW88399_DEV_DSP_CFG { ++ AW88399_DEV_DSP_WORK = 0, ++ AW88399_DEV_DSP_BYPASS = 1, ++}; ++ ++enum { ++ AW88399_NOT_RCV_MODE = 0, ++ AW88399_RCV_MODE = 1, ++}; ++ ++enum { ++ AW88399_SYNC_START = 0, ++ AW88399_ASYNC_START, ++}; ++ ++struct aw_device; ++struct aw_container; ++struct aw_cali_desc; ++struct gpio_desc; ++struct i2c_client; ++struct regmap; ++ ++struct aw88399 { ++ struct aw_device *aw_pa; ++ struct mutex lock; ++ struct gpio_desc *reset_gpio; ++ struct delayed_work start_work; ++ struct regmap *regmap; ++ struct aw_container *aw_cfg; ++ ++ unsigned int check_val; ++ unsigned int crc_init_val; ++ unsigned int vcalb_init_val; ++ unsigned int dither_st; ++ bool bsts_unreliable; ++ bool fw_needs_reload; ++}; ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev); ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable); ++int aw_dev_get_dsp_status(struct aw_device *aw_dev); ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value); ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc); ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name); ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute); ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel); ++void aw88399_hw_reset(struct aw88399 *aw88399); ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); ++extern const struct regmap_config aw88399_remap_config; ++int aw88399_request_firmware_file(struct aw88399 *aw88399); ++void aw88399_start(struct aw88399 *aw88399, bool sync_start); ++void aw88399_startup_work(struct work_struct *work); ++int aw88399_stop(struct aw_device *aw_dev); ++ ++#endif /* __SOUND_AW88399_H */ +diff --git a/include/sound/control.h b/include/sound/control.h +--- a/include/sound/control.h ++++ b/include/sound/control.h +@@ -7,6 +7,7 @@ + * Copyright (c) by Jaroslav Kysela + */ + ++#include + #include + #include + #include +@@ -167,6 +168,8 @@ snd_ctl_find_id_mixer(struct snd_card *card, const char *name) + + int snd_ctl_create(struct snd_card *card); + ++extern struct rw_semaphore snd_ioctl_rwsem; ++ + int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn); + int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn); + #ifdef CONFIG_COMPAT +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -108,8 +108,8 @@ struct snd_card { + char longname[80]; /* name of this soundcard */ + char irq_descr[32]; /* Interrupt description */ + char mixername[80]; /* mixer name */ +- char components[128]; /* card components delimited with +- space */ ++ char *components; /* card components, space-delimited */ ++ unsigned int components_alloc_size; /* current allocation size of components */ + struct module *module; /* top-level module */ + + void *private_data; /* private data for soundcard */ +diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h +--- a/include/sound/hda_codec.h ++++ b/include/sound/hda_codec.h +@@ -256,6 +256,7 @@ struct hda_codec { + unsigned int link_down_at_suspend:1; /* link down at runtime suspend */ + unsigned int relaxed_resume:1; /* don't resume forcibly for jack */ + unsigned int forced_resume:1; /* forced resume for jack */ ++ unsigned int acomp_requested_resume:1; /* resume requested by acomp */ + unsigned int no_stream_clean_at_suspend:1; /* do not clean streams at suspend */ + unsigned int ctl_dev_id:1; /* old control element id build behaviour */ + unsigned int eld_jack_detect:1; /* Machine jack-detection by ELD */ +diff --git a/include/sound/hda_regmap.h b/include/sound/hda_regmap.h +--- a/include/sound/hda_regmap.h ++++ b/include/sound/hda_regmap.h +@@ -69,11 +69,14 @@ void snd_hdac_regmap_sync(struct hdac_device *codec); + + /** + * snd_hdac_regmap_write - Write a verb with caching ++ * @codec: HD-audio codec base device + * @nid: codec NID +- * @reg: verb to write ++ * @verb: verb to write + * @val: value to write + * + * For writing an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, +@@ -85,13 +88,16 @@ snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_update - Update a verb value with caching ++ * snd_hdac_regmap_update - Update a verb value with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to update + * @mask: bit mask to update + * @val: value to update + * + * For updating an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, +@@ -104,12 +110,15 @@ snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_read - Read a verb with caching ++ * snd_hdac_regmap_read - Read a verb with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to read + * @val: pointer to store the value + * + * For reading an amp value, use snd_hda_regmap_get_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, +@@ -125,12 +134,12 @@ snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -148,13 +157,14 @@ snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the AMP value with a bit mask. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -169,13 +179,12 @@ snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_get_amp_stereo - Read stereo AMP values + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read stereo AMP values. The lower byte is left, the upper byte is right. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +@@ -192,14 +201,15 @@ snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_update_amp_stereo - update the stereo AMP value + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the stereo AMP value with a bit mask. + * The lower byte is left, the upper byte is right. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +diff --git a/include/sound/pcm.h b/include/sound/pcm.h +--- a/include/sound/pcm.h ++++ b/include/sound/pcm.h +@@ -145,61 +145,60 @@ struct snd_pcm_ops { + #define SNDRV_PCM_RATE_8000_768000 (SNDRV_PCM_RATE_8000_384000|\ + SNDRV_PCM_RATE_705600|\ + SNDRV_PCM_RATE_768000) +-#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << (__force int)SNDRV_PCM_FORMAT_##fmt) +-#define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) +-#define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) +-#define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) +-#define SNDRV_PCM_FMTBIT_S16_BE _SNDRV_PCM_FMTBIT(S16_BE) +-#define SNDRV_PCM_FMTBIT_U16_LE _SNDRV_PCM_FMTBIT(U16_LE) +-#define SNDRV_PCM_FMTBIT_U16_BE _SNDRV_PCM_FMTBIT(U16_BE) +-#define SNDRV_PCM_FMTBIT_S24_LE _SNDRV_PCM_FMTBIT(S24_LE) +-#define SNDRV_PCM_FMTBIT_S24_BE _SNDRV_PCM_FMTBIT(S24_BE) +-#define SNDRV_PCM_FMTBIT_U24_LE _SNDRV_PCM_FMTBIT(U24_LE) +-#define SNDRV_PCM_FMTBIT_U24_BE _SNDRV_PCM_FMTBIT(U24_BE) ++#define SNDRV_PCM_FMTBIT_S8 (1ULL << SNDRV_PCM_FORMAT_S8) ++#define SNDRV_PCM_FMTBIT_U8 (1ULL << SNDRV_PCM_FORMAT_U8) ++#define SNDRV_PCM_FMTBIT_S16_LE (1ULL << SNDRV_PCM_FORMAT_S16_LE) ++#define SNDRV_PCM_FMTBIT_S16_BE (1ULL << SNDRV_PCM_FORMAT_S16_BE) ++#define SNDRV_PCM_FMTBIT_U16_LE (1ULL << SNDRV_PCM_FORMAT_U16_LE) ++#define SNDRV_PCM_FMTBIT_U16_BE (1ULL << SNDRV_PCM_FORMAT_U16_BE) ++#define SNDRV_PCM_FMTBIT_S24_LE (1ULL << SNDRV_PCM_FORMAT_S24_LE) ++#define SNDRV_PCM_FMTBIT_S24_BE (1ULL << SNDRV_PCM_FORMAT_S24_BE) ++#define SNDRV_PCM_FMTBIT_U24_LE (1ULL << SNDRV_PCM_FORMAT_U24_LE) ++#define SNDRV_PCM_FMTBIT_U24_BE (1ULL << SNDRV_PCM_FORMAT_U24_BE) + // For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + // available bit count in most significant bit. It's for the case of so-called 'left-justified' or + // `right-padding` sample which has less width than 32 bit. +-#define SNDRV_PCM_FMTBIT_S32_LE _SNDRV_PCM_FMTBIT(S32_LE) +-#define SNDRV_PCM_FMTBIT_S32_BE _SNDRV_PCM_FMTBIT(S32_BE) +-#define SNDRV_PCM_FMTBIT_U32_LE _SNDRV_PCM_FMTBIT(U32_LE) +-#define SNDRV_PCM_FMTBIT_U32_BE _SNDRV_PCM_FMTBIT(U32_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT_LE _SNDRV_PCM_FMTBIT(FLOAT_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT_BE _SNDRV_PCM_FMTBIT(FLOAT_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_LE _SNDRV_PCM_FMTBIT(FLOAT64_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_BE _SNDRV_PCM_FMTBIT(FLOAT64_BE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_LE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_BE) +-#define SNDRV_PCM_FMTBIT_MU_LAW _SNDRV_PCM_FMTBIT(MU_LAW) +-#define SNDRV_PCM_FMTBIT_A_LAW _SNDRV_PCM_FMTBIT(A_LAW) +-#define SNDRV_PCM_FMTBIT_IMA_ADPCM _SNDRV_PCM_FMTBIT(IMA_ADPCM) +-#define SNDRV_PCM_FMTBIT_MPEG _SNDRV_PCM_FMTBIT(MPEG) +-#define SNDRV_PCM_FMTBIT_GSM _SNDRV_PCM_FMTBIT(GSM) +-#define SNDRV_PCM_FMTBIT_S20_LE _SNDRV_PCM_FMTBIT(S20_LE) +-#define SNDRV_PCM_FMTBIT_U20_LE _SNDRV_PCM_FMTBIT(U20_LE) +-#define SNDRV_PCM_FMTBIT_S20_BE _SNDRV_PCM_FMTBIT(S20_BE) +-#define SNDRV_PCM_FMTBIT_U20_BE _SNDRV_PCM_FMTBIT(U20_BE) +-#define SNDRV_PCM_FMTBIT_SPECIAL _SNDRV_PCM_FMTBIT(SPECIAL) +-#define SNDRV_PCM_FMTBIT_S24_3LE _SNDRV_PCM_FMTBIT(S24_3LE) +-#define SNDRV_PCM_FMTBIT_U24_3LE _SNDRV_PCM_FMTBIT(U24_3LE) +-#define SNDRV_PCM_FMTBIT_S24_3BE _SNDRV_PCM_FMTBIT(S24_3BE) +-#define SNDRV_PCM_FMTBIT_U24_3BE _SNDRV_PCM_FMTBIT(U24_3BE) +-#define SNDRV_PCM_FMTBIT_S20_3LE _SNDRV_PCM_FMTBIT(S20_3LE) +-#define SNDRV_PCM_FMTBIT_U20_3LE _SNDRV_PCM_FMTBIT(U20_3LE) +-#define SNDRV_PCM_FMTBIT_S20_3BE _SNDRV_PCM_FMTBIT(S20_3BE) +-#define SNDRV_PCM_FMTBIT_U20_3BE _SNDRV_PCM_FMTBIT(U20_3BE) +-#define SNDRV_PCM_FMTBIT_S18_3LE _SNDRV_PCM_FMTBIT(S18_3LE) +-#define SNDRV_PCM_FMTBIT_U18_3LE _SNDRV_PCM_FMTBIT(U18_3LE) +-#define SNDRV_PCM_FMTBIT_S18_3BE _SNDRV_PCM_FMTBIT(S18_3BE) +-#define SNDRV_PCM_FMTBIT_U18_3BE _SNDRV_PCM_FMTBIT(U18_3BE) +-#define SNDRV_PCM_FMTBIT_G723_24 _SNDRV_PCM_FMTBIT(G723_24) +-#define SNDRV_PCM_FMTBIT_G723_24_1B _SNDRV_PCM_FMTBIT(G723_24_1B) +-#define SNDRV_PCM_FMTBIT_G723_40 _SNDRV_PCM_FMTBIT(G723_40) +-#define SNDRV_PCM_FMTBIT_G723_40_1B _SNDRV_PCM_FMTBIT(G723_40_1B) +-#define SNDRV_PCM_FMTBIT_DSD_U8 _SNDRV_PCM_FMTBIT(DSD_U8) +-#define SNDRV_PCM_FMTBIT_DSD_U16_LE _SNDRV_PCM_FMTBIT(DSD_U16_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_LE _SNDRV_PCM_FMTBIT(DSD_U32_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U16_BE _SNDRV_PCM_FMTBIT(DSD_U16_BE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_BE _SNDRV_PCM_FMTBIT(DSD_U32_BE) ++#define SNDRV_PCM_FMTBIT_S32_LE (1ULL << SNDRV_PCM_FORMAT_S32_LE) ++#define SNDRV_PCM_FMTBIT_S32_BE (1ULL << SNDRV_PCM_FORMAT_S32_BE) ++#define SNDRV_PCM_FMTBIT_U32_LE (1ULL << SNDRV_PCM_FORMAT_U32_LE) ++#define SNDRV_PCM_FMTBIT_U32_BE (1ULL << SNDRV_PCM_FORMAT_U32_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_BE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE) ++#define SNDRV_PCM_FMTBIT_MU_LAW (1ULL << SNDRV_PCM_FORMAT_MU_LAW) ++#define SNDRV_PCM_FMTBIT_A_LAW (1ULL << SNDRV_PCM_FORMAT_A_LAW) ++#define SNDRV_PCM_FMTBIT_IMA_ADPCM (1ULL << SNDRV_PCM_FORMAT_IMA_ADPCM) ++#define SNDRV_PCM_FMTBIT_MPEG (1ULL << SNDRV_PCM_FORMAT_MPEG) ++#define SNDRV_PCM_FMTBIT_GSM (1ULL << SNDRV_PCM_FORMAT_GSM) ++#define SNDRV_PCM_FMTBIT_S20_LE (1ULL << SNDRV_PCM_FORMAT_S20_LE) ++#define SNDRV_PCM_FMTBIT_U20_LE (1ULL << SNDRV_PCM_FORMAT_U20_LE) ++#define SNDRV_PCM_FMTBIT_S20_BE (1ULL << SNDRV_PCM_FORMAT_S20_BE) ++#define SNDRV_PCM_FMTBIT_U20_BE (1ULL << SNDRV_PCM_FORMAT_U20_BE) ++#define SNDRV_PCM_FMTBIT_SPECIAL (1ULL << SNDRV_PCM_FORMAT_SPECIAL) ++#define SNDRV_PCM_FMTBIT_S24_3LE (1ULL << SNDRV_PCM_FORMAT_S24_3LE) ++#define SNDRV_PCM_FMTBIT_U24_3LE (1ULL << SNDRV_PCM_FORMAT_U24_3LE) ++#define SNDRV_PCM_FMTBIT_S24_3BE (1ULL << SNDRV_PCM_FORMAT_S24_3BE) ++#define SNDRV_PCM_FMTBIT_U24_3BE (1ULL << SNDRV_PCM_FORMAT_U24_3BE) ++#define SNDRV_PCM_FMTBIT_S20_3LE (1ULL << SNDRV_PCM_FORMAT_S20_3LE) ++#define SNDRV_PCM_FMTBIT_U20_3LE (1ULL << SNDRV_PCM_FORMAT_U20_3LE) ++#define SNDRV_PCM_FMTBIT_S20_3BE (1ULL << SNDRV_PCM_FORMAT_S20_3BE) ++#define SNDRV_PCM_FMTBIT_U20_3BE (1ULL << SNDRV_PCM_FORMAT_U20_3BE) ++#define SNDRV_PCM_FMTBIT_S18_3LE (1ULL << SNDRV_PCM_FORMAT_S18_3LE) ++#define SNDRV_PCM_FMTBIT_U18_3LE (1ULL << SNDRV_PCM_FORMAT_U18_3LE) ++#define SNDRV_PCM_FMTBIT_S18_3BE (1ULL << SNDRV_PCM_FORMAT_S18_3BE) ++#define SNDRV_PCM_FMTBIT_U18_3BE (1ULL << SNDRV_PCM_FORMAT_U18_3BE) ++#define SNDRV_PCM_FMTBIT_G723_24 (1ULL << SNDRV_PCM_FORMAT_G723_24) ++#define SNDRV_PCM_FMTBIT_G723_24_1B (1ULL << SNDRV_PCM_FORMAT_G723_24_1B) ++#define SNDRV_PCM_FMTBIT_G723_40 (1ULL << SNDRV_PCM_FORMAT_G723_40) ++#define SNDRV_PCM_FMTBIT_G723_40_1B (1ULL << SNDRV_PCM_FORMAT_G723_40_1B) ++#define SNDRV_PCM_FMTBIT_DSD_U8 (1ULL << SNDRV_PCM_FORMAT_DSD_U8) ++#define SNDRV_PCM_FMTBIT_DSD_U16_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U16_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_BE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_BE) + + #ifdef SNDRV_LITTLE_ENDIAN + #define SNDRV_PCM_FMTBIT_S16 SNDRV_PCM_FMTBIT_S16_LE +@@ -228,11 +227,10 @@ struct snd_pcm_ops { + #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE + #endif + +-#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT((__force int)SNDRV_PCM_SUBFORMAT_##fmt) +-#define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 _SNDRV_PCM_SUBFMTBIT(MSBITS_24) ++#define SNDRV_PCM_SUBFMTBIT_STD (1U << SNDRV_PCM_SUBFORMAT_STD) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX (1U << SNDRV_PCM_SUBFORMAT_MSBITS_MAX) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_20) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_24) + + struct snd_pcm_file { + struct snd_pcm_substream *substream; +@@ -1515,7 +1513,7 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, + */ + static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + { +- return 1ULL << (__force int) pcm_format; ++ return 1ULL << pcm_format; + } + + /** +@@ -1523,9 +1521,7 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + * @f: the iterator variable in snd_pcm_format_t type + */ + #define pcm_for_each_format(f) \ +- for ((f) = SNDRV_PCM_FORMAT_FIRST; \ +- (__force int)(f) <= (__force int)SNDRV_PCM_FORMAT_LAST; \ +- (f) = (__force snd_pcm_format_t)((__force int)(f) + 1)) ++ for ((f) = SNDRV_PCM_FORMAT_FIRST; (f) <= SNDRV_PCM_FORMAT_LAST; (f)++) + + /* printk helpers */ + #define pcm_err(pcm, fmt, args...) \ +diff --git a/include/sound/pcm_params.h b/include/sound/pcm_params.h +--- a/include/sound/pcm_params.h ++++ b/include/sound/pcm_params.h +@@ -71,7 +71,7 @@ static inline void snd_mask_set(struct snd_mask *mask, unsigned int val) + static inline void snd_mask_set_format(struct snd_mask *mask, + snd_pcm_format_t format) + { +- snd_mask_set(mask, (__force unsigned int)format); ++ snd_mask_set(mask, format); + } + + static inline void snd_mask_reset(struct snd_mask *mask, unsigned int val) +@@ -132,7 +132,7 @@ static inline int snd_mask_test(const struct snd_mask *mask, unsigned int val) + static inline int snd_mask_test_format(const struct snd_mask *mask, + snd_pcm_format_t format) + { +- return snd_mask_test(mask, (__force unsigned int)format); ++ return snd_mask_test(mask, format); + } + + static inline int snd_mask_single(const struct snd_mask *mask) +@@ -302,8 +302,7 @@ static inline int snd_interval_eq(const struct snd_interval *i1, const struct sn + */ + static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_access_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_ACCESS)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_ACCESS)); + } + + /** +@@ -312,8 +311,7 @@ static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + */ + static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_format_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_FORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_FORMAT)); + } + + /** +@@ -323,8 +321,7 @@ static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + static inline snd_pcm_subformat_t + params_subformat(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_subformat_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_SUBFORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_SUBFORMAT)); + } + + /** +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -176,8 +176,9 @@ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream); + /* main midi functions */ + + int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info); +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); +@@ -191,6 +192,15 @@ long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, + long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, + const unsigned char *buf, long count); + ++/* non-nested version */ ++static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, ++ int subdevice, ++ int mode, ++ struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h +--- a/include/sound/seq_virmidi.h ++++ b/include/sound/seq_virmidi.h +@@ -46,7 +46,6 @@ struct snd_virmidi_dev { + int client; /* created/attached client */ + int port; /* created/attached port */ + unsigned int flags; /* SNDRV_VIRMIDI_* */ +- rwlock_t filelist_lock; + struct rw_semaphore filelist_sem; + struct list_head filelist; + }; +diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h +--- a/include/uapi/sound/asequencer.h ++++ b/include/uapi/sound/asequencer.h +@@ -308,16 +308,6 @@ struct snd_seq_ump_event { + }; + }; + +-/* +- * bounce event - stored as variable size data +- */ +-struct snd_seq_event_bounce { +- int err; +- struct snd_seq_event event; +- /* external data follows here. */ +-}; +- +- + /* system information */ + struct snd_seq_system_info { + int queues; /* maximum queues count */ +@@ -348,10 +338,10 @@ struct snd_seq_running_info { + + + /* client types */ +-typedef int __bitwise snd_seq_client_type_t; +-#define NO_CLIENT ((__force snd_seq_client_type_t) 0) +-#define USER_CLIENT ((__force snd_seq_client_type_t) 1) +-#define KERNEL_CLIENT ((__force snd_seq_client_type_t) 2) ++typedef int snd_seq_client_type_t; ++#define NO_CLIENT 0 ++#define USER_CLIENT 1 ++#define KERNEL_CLIENT 2 + + /* event filter flags */ + #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ +diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h +--- a/include/uapi/sound/asound.h ++++ b/include/uapi/sound/asound.h +@@ -169,72 +169,72 @@ enum { + SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, + }; + +-typedef int __bitwise snd_pcm_access_t; +-#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ +-#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ +-#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ ++typedef int snd_pcm_access_t; ++#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ ++#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ ++#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ + #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED + +-typedef int __bitwise snd_pcm_format_t; +-#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) +-#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) +-#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) +-#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) +-#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) +-#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) +-#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ ++typedef int snd_pcm_format_t; ++#define SNDRV_PCM_FORMAT_S8 0 ++#define SNDRV_PCM_FORMAT_U8 1 ++#define SNDRV_PCM_FORMAT_S16_LE 2 ++#define SNDRV_PCM_FORMAT_S16_BE 3 ++#define SNDRV_PCM_FORMAT_U16_LE 4 ++#define SNDRV_PCM_FORMAT_U16_BE 5 ++#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ + /* + * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + * available bit count in most significant bit. It's for the case of so-called 'left-justified' or + * `right-padding` sample which has less width than 32 bit. + */ +-#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) +-#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) +-#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) +-#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) +-#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ +-#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) +-#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) +-#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) +-#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) +-#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) +-#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S32_LE 10 ++#define SNDRV_PCM_FORMAT_S32_BE 11 ++#define SNDRV_PCM_FORMAT_U32_LE 12 ++#define SNDRV_PCM_FORMAT_U32_BE 13 ++#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ ++#define SNDRV_PCM_FORMAT_MU_LAW 20 ++#define SNDRV_PCM_FORMAT_A_LAW 21 ++#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 ++#define SNDRV_PCM_FORMAT_MPEG 23 ++#define SNDRV_PCM_FORMAT_GSM 24 ++#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ + /* gap in the numbering for a future standard linear format */ +-#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) +-#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ +-#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ +-#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ +-#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ ++#define SNDRV_PCM_FORMAT_SPECIAL 31 ++#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ ++#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ ++#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ ++#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ + #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE + #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 + +@@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; + #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE + #endif + +-typedef int __bitwise snd_pcm_subformat_t; +-#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) ++typedef int snd_pcm_subformat_t; ++#define SNDRV_PCM_SUBFORMAT_STD 0 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 + #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 + + #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ +@@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; + #define __SND_STRUCT_TIME64 + #endif + +-typedef int __bitwise snd_pcm_state_t; +-#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ +-#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ +-#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ +-#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ +-#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ +-#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ +-#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ +-#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ +-#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ ++typedef int snd_pcm_state_t; ++#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ ++#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ ++#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ ++#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ ++#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ ++#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ ++#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ ++#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ ++#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ + #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED + + enum { +@@ -1058,7 +1058,7 @@ struct snd_timer_tread { + * * + ****************************************************************************/ + +-#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) ++#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) + + struct snd_ctl_card_info { + int card; /* card number */ +@@ -1072,24 +1072,43 @@ struct snd_ctl_card_info { + unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ + }; + +-typedef int __bitwise snd_ctl_elem_type_t; +-#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ +-#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ +-#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ +-#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ +-#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ ++/* ++ * Card components can exceed the fixed 128 bytes in snd_ctl_card_info. ++ * Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS ++ * to retrieve the full string. ++ */ ++ ++/* Type values for struct snd_ctl_card_bytes::type */ ++enum { ++ SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */ ++}; ++ ++struct snd_ctl_card_bytes { ++ __u32 type; /* SND_CTL_CARD_BTYPE_* */ ++ __u32 data_allocated; /* size of @data buffer in bytes */ ++ __u32 data_len; /* in/out: actual data length in bytes */ ++ __u32 reserved; /* explicit pad */ ++ __u64 data; /* user buffer (pointer stored as __u64) */ ++}; ++ ++typedef int snd_ctl_elem_type_t; ++#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ ++#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ ++#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ ++#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ ++#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ + #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 + +-typedef int __bitwise snd_ctl_elem_iface_t; +-#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ +-#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ +-#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ +-#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ +-#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ +-#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ +-#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ ++typedef int snd_ctl_elem_iface_t; ++#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ ++#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ ++#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ ++#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ ++#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ ++#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ ++#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ + #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER + + #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) +@@ -1198,6 +1217,7 @@ struct snd_ctl_tlv { + + #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) + #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) ++#define SNDRV_CTL_IOCTL_CARD_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes) + #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) + #define SNDRV_CTL_IOCTL_ELEM_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info) + #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) +diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h +--- a/include/uapi/sound/firewire.h ++++ b/include/uapi/sound/firewire.h +@@ -79,11 +79,12 @@ struct snd_firewire_event_motu_register_dsp_change { + * + * @type: Fixed to SNDRV_FIREWIRE_EVENT_FF400_MESSAGE. + * @message_count: The number of messages. ++ * @messages: Array of @message_count messages + * @messages.message: The messages expressing hardware knob operation. + * @messages.tstamp: The isochronous cycle at which the request subaction of asynchronous +- * transaction was sent to deliver the message. It has 16 bit unsigned integer ++ * transaction was sent to deliver the message. It has 16-bit unsigned integer + * value. The higher 3 bits of value expresses the lower three bits of second +- * field in the format of CYCLE_TIME, up to 7. The rest 13 bits expresses cycle ++ * field in the format of CYCLE_TIME, up to 7. The remaining 13 bits express cycle + * field up to 7999. + * + * The structure expresses message transmitted by Fireface 400 when operating hardware knob. +@@ -191,8 +192,9 @@ struct snd_firewire_motu_register_dsp_meter { + #define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT + 2) + + /** +- * snd_firewire_motu_register_dsp_parameter - the container for parameters of DSP controlled +- * by register access. ++ * struct snd_firewire_motu_register_dsp_parameter - the container for parameters ++ * of DSP controlled by register access. ++ * @mixer: aggregate of @mixer.source and @mixer.output + * @mixer.source.gain: The gain of source to mixer. + * @mixer.source.pan: The L/R balance of source to mixer. + * @mixer.source.flag: The flag of source to mixer, including mute, solo. +@@ -200,21 +202,25 @@ struct snd_firewire_motu_register_dsp_meter { + * Audio Express. + * @mixer.source.paired_width: The width of paired source to mixer, only for 4 pre and + * Audio Express. ++ * @mixer.output: FIXME + * @mixer.output.paired_volume: The volume of paired output from mixer. + * @mixer.output.paired_flag: The flag of paired output from mixer. ++ * @output: output parameters + * @output.main_paired_volume: The volume of paired main output. + * @output.hp_paired_volume: The volume of paired hp output. + * @output.hp_paired_assignment: The source assigned to paired hp output. +- * @output.reserved: Padding for 32 bit alignment for future extension. ++ * @output.reserved: Padding for 32-bit alignment for future extension. ++ * @line_input: line input parameters + * @line_input.boost_flag: The flags of boost for line inputs, only for 828mk2 and Traveler. + * @line_input.nominal_level_flag: The flags of nominal level for line inputs, only for 828mk2 and + * Traveler. +- * @line_input.reserved: Padding for 32 bit alignment for future extension. ++ * @line_input.reserved: Padding for 32-bit alignment for future extension. ++ * @input: input parameters + * @input.gain_and_invert: The value including gain and invert for input, only for Ultralite, 4 pre + * and Audio Express. + * @input.flag: The flag of input; e.g. jack detection, phantom power, and pad, only for Ultralite, + * 4 pre and Audio express. +- * @reserved: Padding so that the size of structure is kept to 512 byte, but for future extension. ++ * @reserved: Padding so that the size of structure is kept to 512 bytes, but for future extension. + * + * The structure expresses the set of parameters for DSP controlled by register access. + */ +@@ -272,8 +278,8 @@ struct snd_firewire_motu_register_dsp_parameter { + * controlled by command + * @data: Signal level meters. The mapping between position and signal channel is model-dependent. + * +- * The structure expresses the part of DSP status for hardware meter. The 32 bit storage is +- * estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is ++ * The structure expresses the part of DSP status for hardware meter. The 32-bit storage is ++ * estimated to include IEEE 764 32-bit single precision floating point (binary32) value. It is + * expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0. + */ + struct snd_firewire_motu_command_dsp_meter { +diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c +--- a/sound/atmel/ac97c.c ++++ b/sound/atmel/ac97c.c +@@ -693,7 +693,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct snd_card *card; + struct atmel_ac97c *chip; +- struct resource *regs; ++ void __iomem *regs; + struct clk *pclk; + static const struct snd_ac97_bus_ops ops = { + .write = atmel_ac97c_write, +@@ -702,42 +702,34 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + int retval; + int irq; + +- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- if (!regs) { +- dev_dbg(&pdev->dev, "no memory resource\n"); +- return -ENXIO; +- } ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- dev_dbg(&pdev->dev, "could not get irq: %d\n", irq); ++ if (irq < 0) + return irq; +- } + +- pclk = clk_get(&pdev->dev, "ac97_clk"); ++ pclk = devm_clk_get_enabled(&pdev->dev, "ac97_clk"); + if (IS_ERR(pclk)) { + dev_dbg(&pdev->dev, "no peripheral clock\n"); + return PTR_ERR(pclk); + } +- retval = clk_prepare_enable(pclk); +- if (retval) +- goto err_prepare_enable; + +- retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, ++ retval = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct atmel_ac97c), &card); + if (retval) { + dev_dbg(&pdev->dev, "could not create sound card device\n"); +- goto err_snd_card_new; ++ return retval; + } + + chip = get_chip(card); + +- retval = request_irq(irq, atmel_ac97c_interrupt, 0, "AC97C", chip); +- if (retval) { +- dev_dbg(&pdev->dev, "unable to request irq %d\n", irq); +- goto err_request_irq; +- } ++ retval = devm_request_irq(&pdev->dev, irq, atmel_ac97c_interrupt, 0, "AC97C", chip); ++ if (retval) ++ return retval; ++ + chip->irq = irq; + + spin_lock_init(&chip->lock); +@@ -749,13 +741,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->card = card; + chip->pclk = pclk; + chip->pdev = pdev; +- chip->regs = ioremap(regs->start, resource_size(regs)); +- +- if (!chip->regs) { +- dev_dbg(&pdev->dev, "could not remap register memory\n"); +- retval = -ENOMEM; +- goto err_ioremap; +- } ++ chip->regs = regs; + + chip->reset_pin = devm_gpiod_get_index(dev, "ac97", 2, GPIOD_OUT_HIGH); + if (IS_ERR(chip->reset_pin)) +@@ -770,25 +756,25 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + retval = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (retval) { + dev_dbg(&pdev->dev, "could not register on ac97 bus\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_mixer_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 mixer\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_pcm_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 pcm device\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = snd_card_register(card); + if (retval) { + dev_dbg(&pdev->dev, "could not register sound card\n"); +- goto err_ac97_bus; ++ return retval; + } + + platform_set_drvdata(pdev, card); +@@ -797,18 +783,6 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->regs, irq); + + return 0; +- +-err_ac97_bus: +- iounmap(chip->regs); +-err_ioremap: +- free_irq(irq, chip); +-err_request_irq: +- snd_card_free(card); +-err_snd_card_new: +- clk_disable_unprepare(pclk); +-err_prepare_enable: +- clk_put(pclk); +- return retval; + } + + static int atmel_ac97c_suspend(struct device *pdev) +@@ -839,13 +813,6 @@ static void atmel_ac97c_remove(struct platform_device *pdev) + ac97c_writel(chip, CAMR, 0); + ac97c_writel(chip, COMR, 0); + ac97c_writel(chip, MR, 0); +- +- clk_disable_unprepare(chip->pclk); +- clk_put(chip->pclk); +- iounmap(chip->regs); +- free_irq(chip->irq, chip); +- +- snd_card_free(card); + } + + static struct platform_driver atmel_ac97c_driver = { +diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c +--- a/sound/core/compress_offload.c ++++ b/sound/core/compress_offload.c +@@ -1393,17 +1393,17 @@ static long snd_compr_ioctl_compat(struct file *file, unsigned int cmd, + #endif + + static const struct file_operations snd_compr_file_ops = { +- .owner = THIS_MODULE, +- .open = snd_compr_open, +- .release = snd_compr_free, +- .write = snd_compr_write, +- .read = snd_compr_read, +- .unlocked_ioctl = snd_compr_ioctl, ++ .owner = THIS_MODULE, ++ .open = snd_compr_open, ++ .release = snd_compr_free, ++ .write = snd_compr_write, ++ .read = snd_compr_read, ++ .unlocked_ioctl = snd_compr_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_compr_ioctl_compat, ++ .compat_ioctl = snd_compr_ioctl_compat, + #endif +- .mmap = snd_compr_mmap, +- .poll = snd_compr_poll, ++ .mmap = snd_compr_mmap, ++ .poll = snd_compr_poll, + }; + + static int snd_compress_dev_register(struct snd_device *device) +diff --git a/sound/core/control.c b/sound/core/control.c +--- a/sound/core/control.c ++++ b/sound/core/control.c +@@ -38,7 +38,7 @@ struct snd_kctl_ioctl { + snd_kctl_ioctl_func_t fioctl; + }; + +-static DECLARE_RWSEM(snd_ioctl_rwsem); ++DECLARE_RWSEM(snd_ioctl_rwsem); + static DECLARE_RWSEM(snd_ctl_layer_rwsem); + static LIST_HEAD(snd_control_ioctls); + #ifdef CONFIG_COMPAT +@@ -55,9 +55,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + struct snd_ctl_file *ctl; + int i, err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); + if (!card) { +@@ -65,12 +63,10 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + goto __error1; + } + err = snd_card_file_add(card, file); +- if (err < 0) { +- err = -ENODEV; ++ if (err < 0) + goto __error1; +- } + if (!try_module_get(card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); +@@ -876,23 +872,81 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, + { + struct snd_ctl_card_info *info __free(kfree) = + kzalloc(sizeof(*info), GFP_KERNEL); ++ ssize_t n; + + if (! info) + return -ENOMEM; ++ ++ static_assert(sizeof(info->components) >= 2); ++ + scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ + info->card = card->number; + strscpy(info->id, card->id, sizeof(info->id)); + strscpy(info->driver, card->driver, sizeof(info->driver)); + strscpy(info->name, card->shortname, sizeof(info->name)); + strscpy(info->longname, card->longname, sizeof(info->longname)); + strscpy(info->mixername, card->mixername, sizeof(info->mixername)); +- strscpy(info->components, card->components, sizeof(info->components)); ++ n = strscpy(info->components, components, sizeof(info->components)); ++ if (n < 0) // mark the truncation with '>' before NULL terminator ++ info->components[sizeof(info->components) - 2] = '>'; + } + if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) + return -EFAULT; + return 0; + } + ++static int snd_ctl_card_bytes(struct snd_card *card, ++ struct snd_ctl_card_bytes *info, ++ unsigned int __user *data_len_out) ++{ ++ unsigned int data_len; ++ ++ switch (info->type) { ++ case SND_CTL_CARD_BTYPE_COMPONENTS: ++ scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ ++ data_len = strlen(components) + 1; ++ ++ if (!info->data || info->data_allocated == 0) ++ break; ++ ++ if (info->data_allocated < data_len) ++ return -ENOMEM; ++ ++ if (copy_to_user(u64_to_user_ptr(info->data), components, data_len)) ++ return -EFAULT; ++ } ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (put_user(data_len, data_len_out)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int snd_ctl_card_bytes_user(struct snd_card *card, ++ struct snd_ctl_card_bytes __user *_info) ++{ ++ struct snd_ctl_card_bytes info; ++ ++ if (copy_from_user(&info, _info, sizeof(info))) ++ return -EFAULT; ++ ++ return snd_ctl_card_bytes(card, &info, &_info->data_len); ++} ++ + static int snd_ctl_elem_list(struct snd_card *card, + struct snd_ctl_elem_list *list) + { +@@ -1990,6 +2044,8 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); ++ case SNDRV_CTL_IOCTL_CARD_BYTES: ++ return snd_ctl_card_bytes_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: +@@ -2339,16 +2395,15 @@ EXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer); + * INIT PART + */ + +-static const struct file_operations snd_ctl_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_ctl_read, +- .open = snd_ctl_open, +- .release = snd_ctl_release, +- .poll = snd_ctl_poll, +- .unlocked_ioctl = snd_ctl_ioctl, +- .compat_ioctl = snd_ctl_ioctl_compat, +- .fasync = snd_ctl_fasync, ++static const struct file_operations snd_ctl_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_ctl_read, ++ .open = snd_ctl_open, ++ .release = snd_ctl_release, ++ .poll = snd_ctl_poll, ++ .unlocked_ioctl = snd_ctl_ioctl, ++ .compat_ioctl = snd_ctl_ioctl_compat, ++ .fasync = snd_ctl_fasync, + }; + + /* call lops under rwsems; called from snd_ctl_dev_*() below() */ +diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c +--- a/sound/core/control_compat.c ++++ b/sound/core/control_compat.c +@@ -226,8 +226,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, + if (type < 0) + return type; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -236,7 +236,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, + data->value.integer.value[i] = val; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (size < 0) { + dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; +@@ -259,8 +259,8 @@ static int copy_ctl_value_to_user(void __user *userdata, + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, size; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -269,7 +269,7 @@ static int copy_ctl_value_to_user(void __user *userdata, + return -EFAULT; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (copy_to_user(valuep, data->value.bytes.data, size)) + return -EFAULT; + } +@@ -417,7 +417,7 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, + break; + } + return snd_ctl_elem_add(file, data, replace); +-} ++} + + enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), +@@ -446,6 +446,7 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: ++ case SNDRV_CTL_IOCTL_CARD_BYTES: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: +diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c +--- a/sound/core/hwdep.c ++++ b/sound/core/hwdep.c +@@ -87,7 +87,7 @@ static int snd_hwdep_open(struct inode *inode, struct file * file) + + if (!try_module_get(hw->card->module)) { + snd_card_unref(hw->card); +- return -EFAULT; ++ return -ENODEV; + } + + init_waitqueue_entry(&wait, current); +@@ -323,18 +323,17 @@ static int snd_hwdep_control_ioctl(struct snd_card *card, + + */ + +-static const struct file_operations snd_hwdep_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_hwdep_llseek, +- .read = snd_hwdep_read, +- .write = snd_hwdep_write, +- .open = snd_hwdep_open, +- .release = snd_hwdep_release, +- .poll = snd_hwdep_poll, +- .unlocked_ioctl = snd_hwdep_ioctl, +- .compat_ioctl = snd_hwdep_ioctl_compat, +- .mmap = snd_hwdep_mmap, ++static const struct file_operations snd_hwdep_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_hwdep_llseek, ++ .read = snd_hwdep_read, ++ .write = snd_hwdep_write, ++ .open = snd_hwdep_open, ++ .release = snd_hwdep_release, ++ .poll = snd_hwdep_poll, ++ .unlocked_ioctl = snd_hwdep_ioctl, ++ .compat_ioctl = snd_hwdep_ioctl_compat, ++ .mmap = snd_hwdep_mmap, + }; + + static void snd_hwdep_free(struct snd_hwdep *hwdep) +diff --git a/sound/core/info.c b/sound/core/info.c +--- a/sound/core/info.c ++++ b/sound/core/info.c +@@ -78,7 +78,7 @@ static int alloc_info_private(struct snd_info_entry *entry, + if (!entry || !entry->p) + return -ENODEV; + if (!try_module_get(entry->module)) +- return -EFAULT; ++ return -ENODEV; + data = kzalloc_obj(*data); + if (!data) { + module_put(entry->module); +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -466,20 +466,19 @@ static int snd_disconnect_fasync(int fd, struct file *file, int on) + return -ENODEV; + } + +-static const struct file_operations snd_shutdown_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_disconnect_llseek, +- .read = snd_disconnect_read, +- .write = snd_disconnect_write, +- .release = snd_disconnect_release, +- .poll = snd_disconnect_poll, +- .unlocked_ioctl = snd_disconnect_ioctl, ++static const struct file_operations snd_shutdown_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_disconnect_llseek, ++ .read = snd_disconnect_read, ++ .write = snd_disconnect_write, ++ .release = snd_disconnect_release, ++ .poll = snd_disconnect_poll, ++ .unlocked_ioctl = snd_disconnect_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_disconnect_ioctl, ++ .compat_ioctl = snd_disconnect_ioctl, + #endif +- .mmap = snd_disconnect_mmap, +- .fasync = snd_disconnect_fasync ++ .mmap = snd_disconnect_mmap, ++ .fasync = snd_disconnect_fasync + }; + + /** +@@ -592,6 +591,9 @@ static int snd_card_do_free(struct snd_card *card) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + #endif + snd_device_free_all(card); ++ kfree(card->components); ++ card->components = NULL; ++ card->components_alloc_size = 0; + if (card->private_free) + card->private_free(card); + #ifdef CONFIG_SND_CTL_DEBUG +@@ -725,7 +727,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, + int len, loops; + bool is_default = false; + char *id; +- ++ + copy_valid_id_string(card, src, nid); + id = card->id; + +@@ -1034,21 +1036,44 @@ int __init snd_card_info_init(void) + * + * Return: Zero otherwise a negative error code. + */ +- ++ + int snd_component_add(struct snd_card *card, const char *component) + { + char *ptr; + int len = strlen(component); ++ unsigned int cur_len, need_len; + +- ptr = strstr(card->components, component); +- if (ptr != NULL) { +- if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ +- return 1; ++ guard(rwsem_write)(&snd_ioctl_rwsem); ++ ++ if (card->components) { ++ ptr = strstr(card->components, component); ++ if (ptr) { ++ if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ ++ return 1; ++ } ++ cur_len = strlen(card->components) + 1; ++ } else { ++ cur_len = 0; + } +- if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { ++ ++ need_len = cur_len + len + 1; ++ if (need_len > 512) { + snd_BUG(); + return -ENOMEM; + } ++ ++ if (need_len > card->components_alloc_size) { ++ unsigned int new_alloc = roundup(need_len, 32); ++ ++ ptr = krealloc(card->components, new_alloc, GFP_KERNEL); ++ if (!ptr) ++ return -ENOMEM; ++ if (!card->components) ++ ptr[0] = '\0'; ++ card->components = ptr; ++ card->components_alloc_size = new_alloc; ++ } ++ + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); +diff --git a/sound/core/jack.c b/sound/core/jack.c +--- a/sound/core/jack.c ++++ b/sound/core/jack.c +@@ -303,41 +303,41 @@ static ssize_t jack_type_read(struct file *file, + } + + static const struct file_operations jack_type_fops = { +- .open = simple_open, +- .read = jack_type_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_type_read, ++ .llseek = default_llseek, + }; + #endif + + static const struct file_operations sw_inject_enable_fops = { +- .open = simple_open, +- .read = sw_inject_enable_read, +- .write = sw_inject_enable_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = sw_inject_enable_read, ++ .write = sw_inject_enable_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jackin_inject_fops = { +- .open = simple_open, +- .write = jackin_inject_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .write = jackin_inject_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_id_fops = { +- .open = simple_open, +- .read = jack_kctl_id_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_id_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_mask_bits_fops = { +- .open = simple_open, +- .read = jack_kctl_mask_bits_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_mask_bits_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_status_fops = { +- .open = simple_open, +- .read = jack_kctl_status_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_status_read, ++ .llseek = default_llseek, + }; + + static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, +diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c +--- a/sound/core/oss/mixer_oss.c ++++ b/sound/core/oss/mixer_oss.c +@@ -30,9 +30,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + struct snd_mixer_oss_file *fmixer; + int err; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + card = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIXER); +@@ -60,7 +58,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + kfree(fmixer); + snd_card_file_remove(card, file); + snd_card_unref(card); +- return -EFAULT; ++ return -ENODEV; + } + snd_card_unref(card); + return 0; +@@ -407,13 +405,12 @@ static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, + * REGISTRATION PART + */ + +-static const struct file_operations snd_mixer_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .open = snd_mixer_oss_open, +- .release = snd_mixer_oss_release, +- .unlocked_ioctl = snd_mixer_oss_ioctl, +- .compat_ioctl = snd_mixer_oss_ioctl_compat, ++static const struct file_operations snd_mixer_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .open = snd_mixer_oss_open, ++ .release = snd_mixer_oss_release, ++ .unlocked_ioctl = snd_mixer_oss_ioctl, ++ .compat_ioctl = snd_mixer_oss_ioctl_compat, + }; + + /* +diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c +--- a/sound/core/oss/pcm_oss.c ++++ b/sound/core/oss/pcm_oss.c +@@ -879,11 +879,11 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&substream->mmap_count)) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { +@@ -909,7 +909,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + else + sformat = snd_pcm_plug_slave_format(format, sformat_mask); + +- if ((__force int)sformat < 0 || ++ if (sformat < 0 || + !snd_mask_test_format(sformat_mask, sformat)) { + pcm_for_each_format(sformat) { + if (snd_mask_test_format(sformat_mask, sformat) && +@@ -921,7 +921,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + goto failure; + } + format_found: +- err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); ++ err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0); + if (err < 0) + goto failure; + +@@ -930,9 +930,9 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, +- (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); ++ SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, +- (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); ++ snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, +@@ -1875,7 +1875,7 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) + format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(format_mask, fmt)) { +- int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); ++ int f = snd_pcm_oss_format_to(fmt); + if (f >= 0) + formats |= f; + } +@@ -2495,9 +2495,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + int nonblock; + wait_queue_entry_t wait; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + pcm = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM); +@@ -2509,7 +2507,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { +@@ -3129,17 +3127,16 @@ static inline void snd_pcm_oss_proc_done(struct snd_pcm *pcm) + * ENTRY functions + */ + +-static const struct file_operations snd_pcm_oss_f_reg = +-{ +- .owner = THIS_MODULE, +- .read = snd_pcm_oss_read, +- .write = snd_pcm_oss_write, +- .open = snd_pcm_oss_open, +- .release = snd_pcm_oss_release, +- .poll = snd_pcm_oss_poll, +- .unlocked_ioctl = snd_pcm_oss_ioctl, +- .compat_ioctl = snd_pcm_oss_ioctl_compat, +- .mmap = snd_pcm_oss_mmap, ++static const struct file_operations snd_pcm_oss_f_reg = { ++ .owner = THIS_MODULE, ++ .read = snd_pcm_oss_read, ++ .write = snd_pcm_oss_write, ++ .open = snd_pcm_oss_open, ++ .release = snd_pcm_oss_release, ++ .poll = snd_pcm_oss_poll, ++ .unlocked_ioctl = snd_pcm_oss_ioctl, ++ .compat_ioctl = snd_pcm_oss_ioctl_compat, ++ .mmap = snd_pcm_oss_mmap, + }; + + static void register_oss_dsp(struct snd_pcm *pcm, int index) +diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c +--- a/sound/core/oss/pcm_plugin.c ++++ b/sound/core/oss/pcm_plugin.c +@@ -272,13 +272,13 @@ static int snd_pcm_plug_formats(const struct snd_mask *mask, + SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); +- snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); ++ snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & lower_32_bits(linfmts)) + formats.bits[0] |= lower_32_bits(linfmts); + if (formats.bits[1] & upper_32_bits(linfmts)) + formats.bits[1] |= upper_32_bits(linfmts); +- return snd_mask_test(&formats, (__force int)format); ++ return snd_mask_test(&formats, format); + } + + static const snd_pcm_format_t preferred_formats[] = { +@@ -307,20 +307,20 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + { + int i; + +- if (snd_mask_test(format_mask, (__force int)format)) ++ if (snd_mask_test(format_mask, format)) + return format; + if (!snd_pcm_plug_formats(format_mask, format)) +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + if (snd_pcm_format_linear(format)) { + unsigned int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format) > 0; + int big = snd_pcm_format_big_endian(format) > 0; + unsigned int badness, best = -1; +- snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; ++ snd_pcm_format_t best_format = -1; + for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { + snd_pcm_format_t f = preferred_formats[i]; + unsigned int w; +- if (!snd_mask_test(format_mask, (__force int)f)) ++ if (!snd_mask_test(format_mask, f)) + continue; + w = snd_pcm_format_width(f); + if (w >= width) +@@ -334,21 +334,21 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + best = badness; + } + } +- if ((__force int)best_format >= 0) ++ if (best_format >= 0) + return best_format; + else +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } else { + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + snd_pcm_format_t format1 = preferred_formats[i]; +- if (snd_mask_test(format_mask, (__force int)format1)) ++ if (snd_mask_test(format_mask, format1)) + return format1; + } + fallthrough; + default: +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } + } + } +diff --git a/sound/core/pcm.c b/sound/core/pcm.c +--- a/sound/core/pcm.c ++++ b/sound/core/pcm.c +@@ -211,11 +211,9 @@ static const char * const snd_pcm_format_names[] = { + */ + const char *snd_pcm_format_name(snd_pcm_format_t format) + { +- unsigned int format_num = (__force unsigned int)format; +- +- if (format_num >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format_num]) ++ if (format >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format]) + return "Unknown"; +- return snd_pcm_format_names[format_num]; ++ return snd_pcm_format_names[format]; + } + EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +@@ -275,12 +273,12 @@ static const char *snd_pcm_stream_name(int stream) + + static const char *snd_pcm_access_name(snd_pcm_access_t access) + { +- return snd_pcm_access_names[(__force int)access]; ++ return snd_pcm_access_names[access]; + } + + static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) + { +- return snd_pcm_subformat_names[(__force int)subformat]; ++ return snd_pcm_subformat_names[subformat]; + } + + static const char *snd_pcm_tstamp_mode_name(int mode) +@@ -290,7 +288,7 @@ static const char *snd_pcm_tstamp_mode_name(int mode) + + static const char *snd_pcm_state_name(snd_pcm_state_t state) + { +- return snd_pcm_state_names[(__force int)state]; ++ return snd_pcm_state_names[state]; + } + + #if IS_ENABLED(CONFIG_SND_PCM_OSS) +diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c +--- a/sound/core/pcm_misc.c ++++ b/sound/core/pcm_misc.c +@@ -24,15 +24,12 @@ struct pcm_format_data { + unsigned char silence[8]; /* silence data to fill */ + }; + +-/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ +-#define INT __force int +- + static bool valid_format(snd_pcm_format_t format) + { +- return (INT)format >= 0 && (INT)format <= (INT)SNDRV_PCM_FORMAT_LAST; ++ return format >= 0 && format <= SNDRV_PCM_FORMAT_LAST; + } + +-static const struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { ++static const struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, +@@ -251,7 +248,7 @@ int snd_pcm_format_signed(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].signd; ++ val = pcm_formats[format].signd; + if (val < 0) + return -EINVAL; + return val; +@@ -300,7 +297,7 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].le; ++ val = pcm_formats[format].le; + if (val < 0) + return -EINVAL; + return val; +@@ -337,7 +334,7 @@ int snd_pcm_format_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].width; ++ val = pcm_formats[format].width; + if (!val) + return -EINVAL; + return val; +@@ -356,7 +353,7 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].phys; ++ val = pcm_formats[format].phys; + if (!val) + return -EINVAL; + return val; +@@ -390,9 +387,9 @@ const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) + { + if (!valid_format(format)) + return NULL; +- if (! pcm_formats[(INT)format].phys) ++ if (! pcm_formats[format].phys) + return NULL; +- return pcm_formats[(INT)format].silence; ++ return pcm_formats[format].silence; + } + EXPORT_SYMBOL(snd_pcm_format_silence_64); + +@@ -416,12 +413,12 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int + return -EINVAL; + if (samples == 0) + return 0; +- width = pcm_formats[(INT)format].phys; /* physical width */ ++ width = pcm_formats[format].phys; /* physical width */ + if (!width) + return -EINVAL; +- pat = pcm_formats[(INT)format].silence; ++ pat = pcm_formats[format].silence; + /* signed or 1 byte data */ +- if (pcm_formats[(INT)format].signd == 1 || width <= 8) { ++ if (pcm_formats[format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; +diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c +--- a/sound/core/pcm_native.c ++++ b/sound/core/pcm_native.c +@@ -257,7 +257,7 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, + } + + /* macro for simplified cast */ +-#define PARAM_MASK_BIT(b) (1U << (__force int)(b)) ++#define PARAM_MASK_BIT(b) (1U << (b)) + + static bool hw_support_mmap(struct snd_pcm_substream *substream) + { +@@ -489,7 +489,7 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + params->msbits = snd_interval_value(i); + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + params->msbits = snd_pcm_format_width(format); + } + } +@@ -497,13 +497,13 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + if (params->msbits) { + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + + if (snd_pcm_format_linear(format) && + snd_pcm_format_width(format) != params->msbits) { + m_rw = hw_param_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT); + snd_mask_reset(m_rw, +- (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + if (snd_mask_empty(m_rw)) + return -EINVAL; + } +@@ -1252,7 +1252,7 @@ static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) + runtime->trigger_master = NULL; + } + +-#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0 ++#define ACTION_ARG_IGNORE 0 + + struct action_ops { + int (*pre_action)(struct snd_pcm_substream *substream, +@@ -1637,7 +1637,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); + /* + * pause callbacks: pass boolean (to start pause or resume) as state argument + */ +-#define pause_pushed(state) (__force bool)(state) ++#define pause_pushed(state) (bool)(state) + + static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +@@ -1709,14 +1709,14 @@ static const struct action_ops snd_pcm_action_pause = { + static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push) + { + return snd_pcm_action(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream, + bool push) + { + return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + #ifdef CONFIG_PM +@@ -1984,7 +1984,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) + { + snd_pcm_state_t cur_state = snd_pcm_get_state(substream); +- int f_flags = (__force int)state; ++ int f_flags = state; + + if (cur_state == SNDRV_PCM_STATE_OPEN || + cur_state == SNDRV_PCM_STATE_DISCONNECTED) +@@ -2052,7 +2052,7 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, + + return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, + substream, +- (__force snd_pcm_state_t)f_flags); ++ (snd_pcm_state_t)f_flags); + } + + /* +@@ -2463,7 +2463,7 @@ static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) +- snd_mask_reset(&m, (__force unsigned)k); ++ snd_mask_reset(&m, k); + } + return snd_mask_refine(mask, &m); + } +@@ -2545,16 +2545,16 @@ static int snd_pcm_hw_rule_subformats(struct snd_pcm_hw_params *params, + + snd_mask_none(&m); + /* All PCMs support at least the default STD subformat. */ +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_STD); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_STD); + + pcm_for_each_format(f) { +- if (!snd_mask_test(fmask, (__force unsigned)f)) ++ if (!snd_mask_test(fmask, f)) + continue; + + if (f == SNDRV_PCM_FORMAT_S32_LE && *subformats) + m.bits[0] |= *subformats; + else if (snd_pcm_format_linear(f)) +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + } + + return snd_mask_refine(sfmask, &m); +@@ -2882,9 +2882,10 @@ static int snd_pcm_open_file(struct file *file, + static int snd_pcm_playback_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); +@@ -2896,9 +2897,10 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file) + static int snd_pcm_capture_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); +@@ -2920,7 +2922,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + init_waitqueue_entry(&wait, current); +@@ -3916,6 +3918,8 @@ static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf) + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; ++ if (runtime->state == SNDRV_PCM_STATE_DISCONNECTED) ++ return VM_FAULT_SIGBUS; + offset = vmf->pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) +@@ -4256,29 +4260,29 @@ static unsigned long snd_pcm_get_unmapped_area(struct file *file, + + const struct file_operations snd_pcm_f_ops[2] = { + { +- .owner = THIS_MODULE, +- .write = snd_pcm_write, +- .write_iter = snd_pcm_writev, +- .open = snd_pcm_playback_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .write = snd_pcm_write, ++ .write_iter = snd_pcm_writev, ++ .open = snd_pcm_playback_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + }, + { +- .owner = THIS_MODULE, +- .read = snd_pcm_read, +- .read_iter = snd_pcm_readv, +- .open = snd_pcm_capture_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .read = snd_pcm_read, ++ .read_iter = snd_pcm_readv, ++ .open = snd_pcm_capture_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + } + }; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -408,9 +408,10 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, + return 0; + } + +-/* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile) ++/* called from sound/core/seq/seq_midi.c and sound/core/ump.c */ ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth) + { + int err; + +@@ -419,13 +420,14 @@ int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, + if (!try_module_get(rmidi->card->module)) + return -ENXIO; + +- guard(mutex)(&rmidi->open_mutex); ++ mutex_lock_nested(&rmidi->open_mutex, depth); + err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); + if (err < 0) + module_put(rmidi->card->module); ++ mutex_unlock(&rmidi->open_mutex); + return err; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_open); ++EXPORT_SYMBOL(snd_rawmidi_kernel_open_nested); + + static int snd_rawmidi_open(struct inode *inode, struct file *file) + { +@@ -441,9 +443,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file) + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + if (maj == snd_major) { + rmidi = snd_lookup_minor_data(iminor(inode), +@@ -1784,14 +1784,14 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, + */ + + static const struct file_operations snd_rawmidi_f_ops = { +- .owner = THIS_MODULE, +- .read = snd_rawmidi_read, +- .write = snd_rawmidi_write, +- .open = snd_rawmidi_open, +- .release = snd_rawmidi_release, +- .poll = snd_rawmidi_poll, +- .unlocked_ioctl = snd_rawmidi_ioctl, +- .compat_ioctl = snd_rawmidi_ioctl_compat, ++ .owner = THIS_MODULE, ++ .read = snd_rawmidi_read, ++ .write = snd_rawmidi_write, ++ .open = snd_rawmidi_open, ++ .release = snd_rawmidi_release, ++ .poll = snd_rawmidi_poll, ++ .unlocked_ioctl = snd_rawmidi_ioctl, ++ .compat_ioctl = snd_rawmidi_ioctl_compat, + }; + + static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, +diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c +--- a/sound/core/seq/oss/seq_oss.c ++++ b/sound/core/seq/oss/seq_oss.c +@@ -206,17 +206,16 @@ odev_poll(struct file *file, poll_table * wait) + * registration of sequencer minor device + */ + +-static const struct file_operations seq_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = odev_read, +- .write = odev_write, +- .open = odev_open, +- .release = odev_release, +- .poll = odev_poll, +- .unlocked_ioctl = odev_ioctl, +- .compat_ioctl = odev_ioctl_compat, +- .llseek = noop_llseek, ++static const struct file_operations seq_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .read = odev_read, ++ .write = odev_write, ++ .open = odev_open, ++ .release = odev_release, ++ .poll = odev_poll, ++ .unlocked_ioctl = odev_ioctl, ++ .compat_ioctl = odev_ioctl_compat, ++ .llseek = noop_llseek, + }; + + static int __init +diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c +--- a/sound/core/seq/seq_clientmgr.c ++++ b/sound/core/seq/seq_clientmgr.c +@@ -59,7 +59,7 @@ static DEFINE_MUTEX(register_mutex); + * client table + */ + static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +-static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; ++static struct snd_seq_client __rcu *clienttab[SNDRV_SEQ_MAX_CLIENTS]; + static struct snd_seq_usage client_usage; + + /* +@@ -95,15 +95,23 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) + return snd_seq_total_cells(client->pool) > 0; + } + +-/* return pointer to client structure for specified id */ +-static struct snd_seq_client *clientptr(int clientid) ++/* return pointer to client structure for specified id; call under RCU read-lock */ ++static struct snd_seq_client *__clientptr(int clientid) + { + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } +- return clienttab[clientid]; ++ return rcu_dereference_check(clienttab[clientid], ++ lockdep_is_held(&clients_lock)); ++} ++ ++/* return pointer to client structure for specified id */ ++static struct snd_seq_client *clientptr(int clientid) ++{ ++ guard(rcu)(); ++ return __clientptr(clientid); + } + + static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) +@@ -115,8 +123,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + clientid); + return NULL; + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + if (clienttablock[clientid]) +@@ -150,8 +158,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + snd_seq_device_load_drivers(); + } + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + } +@@ -212,7 +220,6 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); +- rwlock_init(&client->ports_lock); + mutex_init(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + mutex_init(&client->ioctl_mutex); +@@ -224,14 +231,17 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { +- if (clienttab[c] || clienttablock[c]) ++ if (rcu_access_pointer(clienttab[c]) || clienttablock[c]) + continue; +- clienttab[client->number = c] = client; ++ client->number = c; ++ rcu_assign_pointer(clienttab[c], client); + return client; + } + } else { +- if (clienttab[client_index] == NULL && !clienttablock[client_index]) { +- clienttab[client->number = client_index] = client; ++ if (rcu_access_pointer(clienttab[client_index]) == NULL && ++ !clienttablock[client_index]) { ++ client->number = client_index; ++ rcu_assign_pointer(clienttab[client_index], client); + return client; + } + } +@@ -249,10 +259,16 @@ static int seq_free_client1(struct snd_seq_client *client) + return 0; + scoped_guard(spinlock_irq, &clients_lock) { + clienttablock[client->number] = 1; +- clienttab[client->number] = NULL; ++ rcu_assign_pointer(clienttab[client->number], NULL); + } + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); ++ /* the client has been unpublished from the table; wait for a grace ++ * period so that lockless readers (snd_seq_client_use_ptr()) that ++ * observed the old pointer can no longer take a new use_lock ++ * reference, then drain the outstanding references before freeing ++ */ ++ synchronize_rcu(); + snd_use_lock_sync(&client->use_lock); + if (client->pool) + snd_seq_pool_delete(&client->pool); +@@ -296,11 +312,8 @@ static int snd_seq_open(struct inode *inode, struct file *file) + int c, mode; /* client id */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + scoped_guard(mutex, ®ister_mutex) { + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); +@@ -517,9 +530,8 @@ static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event) + * Return the error event. + * + * If the receiver client is a user client, the original event is +- * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If +- * the original event is also variable length, the external data is +- * copied after the event record. ++ * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. The ++ * external data of a variable length event is not copied along. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. +@@ -724,10 +736,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + + /* lock list */ + if (atomic) +- read_lock(&grp->list_lock); ++ rcu_read_lock(); + else + down_read_nested(&grp->list_mutex, hop); +- list_for_each_entry(subs, &grp->list_head, src_list) { ++ hlist_for_each_entry_rcu(subs, &grp->list_head, src_list, ++ lockdep_is_held(&grp->list_mutex)) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; +@@ -748,7 +761,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + memcpy(event, &event_saved, saved_size); + } + if (atomic) +- read_unlock(&grp->list_lock); ++ rcu_read_unlock(); + else + up_read(&grp->list_mutex); + memcpy(event, &event_saved, saved_size); +@@ -1945,7 +1958,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + { + struct snd_seq_query_subs *subs = arg; + struct snd_seq_port_subs_info *group; +- struct list_head *p; ++ struct hlist_node *p; + int i; + + struct snd_seq_client *cptr __free(snd_seq_client) = +@@ -1972,15 +1985,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; +@@ -2536,19 +2549,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) + { +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int count = 0; + + guard(rwsem_read)(&group->list_mutex); +- if (list_empty(&group->list_head)) ++ if (hlist_empty(&group->list_head)) + return; + snd_iprintf(buffer, msg); +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (is_src) +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", +@@ -2670,16 +2683,15 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, + * REGISTRATION PART + */ + +-static const struct file_operations snd_seq_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_seq_read, +- .write = snd_seq_write, +- .open = snd_seq_open, +- .release = snd_seq_release, +- .poll = snd_seq_poll, +- .unlocked_ioctl = snd_seq_ioctl, +- .compat_ioctl = snd_seq_ioctl_compat, ++static const struct file_operations snd_seq_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_seq_read, ++ .write = snd_seq_write, ++ .open = snd_seq_open, ++ .release = snd_seq_release, ++ .poll = snd_seq_poll, ++ .unlocked_ioctl = snd_seq_ioctl, ++ .compat_ioctl = snd_seq_ioctl_compat, + }; + + static struct device *seq_dev; +diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h +--- a/sound/core/seq/seq_clientmgr.h ++++ b/sound/core/seq/seq_clientmgr.h +@@ -49,7 +49,6 @@ struct snd_seq_client { + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; +- rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ +diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c +--- a/sound/core/seq/seq_ports.c ++++ b/sound/core/seq/seq_ports.c +@@ -48,8 +48,8 @@ struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + + if (client == NULL) + return NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ +@@ -71,8 +71,8 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + + num = pinfo->addr.port; + found = NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ +@@ -98,10 +98,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + /* initialize snd_seq_port_subs_info */ + static void port_subs_info_init(struct snd_seq_port_subs_info *grp) + { +- INIT_LIST_HEAD(&grp->list_head); ++ INIT_HLIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; +- rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +@@ -154,7 +153,6 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + + num = max(port, 0); + guard(mutex)(&client->ports_mutex); +- guard(write_lock_irq)(&client->ports_lock); + struct list_head *insert_before = &client->ports_list_head; + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) +@@ -166,12 +164,13 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } +- /* insert the new port */ +- list_add_tail(&new_port->list, insert_before); +- client->num_ports++; ++ /* finish initializing the port before publishing it to RCU readers */ + new_port->addr.port = num; /* store the port number in the port */ + if (!new_port->name[0]) + sprintf(new_port->name, "port-%d", num); ++ /* insert the new port */ ++ list_add_tail_rcu(&new_port->list, insert_before); ++ client->num_ports++; + + return num; + } +@@ -202,12 +201,12 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack); + + static inline struct snd_seq_subscribers * +-get_subscriber(struct list_head *p, bool is_src) ++get_subscriber(struct hlist_node *p, bool is_src) + { + if (is_src) +- return list_entry(p, struct snd_seq_subscribers, src_list); ++ return hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- return list_entry(p, struct snd_seq_subscribers, dest_list); ++ return hlist_entry(p, struct snd_seq_subscribers, dest_list); + } + + /* +@@ -219,9 +218,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_port_subs_info *grp, + int is_src) + { +- struct list_head *p, *n; ++ struct hlist_node *p, *n; + +- list_for_each_safe(p, n, &grp->list_head) { ++ hlist_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + + subs = get_subscriber(p, is_src); +@@ -238,13 +237,13 @@ static void clear_subscriber_list(struct snd_seq_client *client, + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) +- kfree(subs); ++ kfree_rcu(subs, rcu); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); +- kfree(subs); ++ kfree_rcu(subs, rcu); + } + } + +@@ -254,7 +253,13 @@ static int port_delete(struct snd_seq_client *client, + { + /* set closing flag and wait for all port access are gone */ + port->closing = 1; +- snd_use_lock_sync(&port->use_lock); ++ /* the port has already been unlinked from the client's port list; ++ * wait for a grace period so that RCU readers still traversing the ++ * list can no longer take a new use_lock reference, then drain the ++ * outstanding references before freeing ++ */ ++ synchronize_rcu(); ++ snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); +@@ -277,11 +282,10 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + struct snd_seq_client_port *found = NULL, *p; + + scoped_guard(mutex, &client->ports_mutex) { +- guard(write_lock_irq)(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ +- list_del(&p->list); ++ list_del_rcu(&p->list); + client->num_ports--; + found = p; + break; +@@ -297,26 +301,16 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + /* delete the all ports belonging to the given client */ + int snd_seq_delete_all_ports(struct snd_seq_client *client) + { +- struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; +- +- /* move the port list to deleted_list, and +- * clear the port list in the client data. ++ ++ /* unlink and delete each port; port_delete() waits for an RCU grace ++ * period before draining the port, so concurrent lockless readers can ++ * no longer take a new use_lock reference on it + */ + guard(mutex)(&client->ports_mutex); +- scoped_guard(write_lock_irq, &client->ports_lock) { +- if (!list_empty(&client->ports_list_head)) { +- list_add(&deleted_list, &client->ports_list_head); +- list_del_init(&client->ports_list_head); +- } else { +- INIT_LIST_HEAD(&deleted_list); +- } +- client->num_ports = 0; +- } +- +- /* remove each port in deleted_list */ +- list_for_each_entry_safe(port, tmp, &deleted_list, list) { +- list_del(&port->list); ++ list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { ++ list_del_rcu(&port->list); ++ client->num_ports--; + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } +@@ -499,20 +493,20 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + bool is_src, bool exclusive, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + guard(rwsem_write)(&grp->list_mutex); + if (exclusive) { +- if (!list_empty(&grp->list_head)) ++ if (!hlist_empty(&grp->list_head)) + return -EBUSY; + } else { + if (grp->exclusive) + return -EBUSY; + /* check whether already exists */ +- list_for_each(p, &grp->list_head) { ++ hlist_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + return -EBUSY; +@@ -526,11 +520,10 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + } + + /* add to list */ +- guard(write_lock_irq)(&grp->list_lock); + if (is_src) +- list_add_tail(&subs->src_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->src_list, &grp->list_head); + else +- list_add_tail(&subs->dest_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + +@@ -544,17 +537,15 @@ static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *list; ++ struct hlist_node *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; +- scoped_guard(write_lock_irq, &grp->list_lock) { +- empty = list_empty(list); +- if (!empty) +- list_del_init(list); +- grp->exclusive = 0; +- } ++ empty = hlist_unhashed(list); ++ if (!empty) ++ hlist_del_init_rcu(list); ++ grp->exclusive = 0; + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +@@ -590,8 +581,8 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + + subs->info = *info; + atomic_set(&subs->ref_count, 0); +- INIT_LIST_HEAD(&subs->src_list); +- INIT_LIST_HEAD(&subs->dest_list); ++ INIT_HLIST_NODE(&subs->src_list); ++ INIT_HLIST_NODE(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + +@@ -612,7 +603,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: +- kfree(subs); ++ kfree_rcu(subs, rcu); + return err; + } + +@@ -633,7 +624,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + */ + scoped_guard(rwsem_write, &dest->list_mutex) { + /* look for the connection */ +- list_for_each_entry(subs, &dest->list_head, dest_list) { ++ hlist_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, +@@ -648,7 +639,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); +- kfree(subs); ++ kfree_rcu(subs, rcu); + return 0; + } + +@@ -662,7 +653,7 @@ int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + int err = -ENOENT; + + guard(rwsem_read)(&src_grp->list_mutex); +- list_for_each_entry(s, &src_grp->list_head, src_list) { ++ hlist_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; +diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h +--- a/sound/core/seq/seq_ports.h ++++ b/sound/core/seq/seq_ports.h +@@ -28,17 +28,17 @@ + + struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ +- struct list_head src_list; /* link of sources */ +- struct list_head dest_list; /* link of destinations */ ++ struct hlist_node src_list; /* link of sources */ ++ struct hlist_node dest_list; /* link of destinations */ + atomic_t ref_count; ++ struct rcu_head rcu; /* for deferred free */ + }; + + struct snd_seq_port_subs_info { +- struct list_head list_head; /* list of subscribed ports */ ++ struct hlist_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; +- rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); + }; +diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c +--- a/sound/core/seq/seq_ump_client.c ++++ b/sound/core/seq/seq_ump_client.c +@@ -37,8 +37,11 @@ struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ +- rwlock_t output_lock; /* protects out_rfile output access */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ ++ /* RCU-protected shadow of out_rfile.output for the delivery hot path; ++ * out_rfile itself is only touched by open/close under open_mutex ++ */ ++ struct snd_rawmidi_substream __rcu *out_substream; + struct seq_ump_input_buffer input; /* input parser context */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ +@@ -89,8 +92,8 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + unsigned char type; + int len; + +- guard(read_lock_irqsave)(&client->output_lock); +- substream = client->out_rfile.output; ++ guard(rcu)(); ++ substream = rcu_dereference(client->out_substream); + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) +@@ -108,19 +111,22 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + static int seq_ump_client_open(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + int err; + + guard(mutex)(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { ++ /* out_rfile is only accessed under open_mutex; the delivery ++ * path reads out_substream via RCU, so open into out_rfile ++ * directly and publish the substream afterwards ++ */ + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, +- &rfile); ++ &client->out_rfile); + if (err < 0) + return err; +- scoped_guard(write_lock_irqsave, &client->output_lock) +- client->out_rfile = rfile; ++ rcu_assign_pointer(client->out_substream, ++ client->out_rfile.output); + } + client->opened[dir]++; + return 0; +@@ -130,17 +136,18 @@ static int seq_ump_client_open(struct seq_ump_client *client, int dir) + static int seq_ump_client_close(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + + guard(mutex)(&ump->open_mutex); + if (!--client->opened[dir]) { +- if (dir == STR_OUT) { +- scoped_guard(write_lock_irqsave, &client->output_lock) { +- rfile = client->out_rfile; +- client->out_rfile = (struct snd_rawmidi_file){}; +- } +- if (rfile.rmidi) +- snd_rawmidi_kernel_release(&rfile); ++ if (dir == STR_OUT && client->out_rfile.rmidi) { ++ rcu_assign_pointer(client->out_substream, NULL); ++ /* wait for a grace period so that no reader in the ++ * delivery path is still writing to the substream ++ * before it is released ++ */ ++ synchronize_rcu(); ++ snd_rawmidi_kernel_release(&client->out_rfile); ++ client->out_rfile = (struct snd_rawmidi_file){}; + } + } + return 0; +@@ -480,7 +487,6 @@ static int snd_seq_ump_probe(struct snd_seq_device *dev) + + INIT_WORK(&client->group_notify_work, handle_group_notify); + client->ump = ump; +- rwlock_init(&client->output_lock); + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, +diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c +--- a/sound/core/seq/seq_virmidi.c ++++ b/sound/core/seq/seq_virmidi.c +@@ -78,10 +78,11 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + int len; + + if (atomic) +- read_lock(&rdev->filelist_lock); ++ rcu_read_lock(); + else + down_read(&rdev->filelist_sem); +- list_for_each_entry(vmidi, &rdev->filelist, list) { ++ list_for_each_entry_rcu(vmidi, &rdev->filelist, list, ++ lockdep_is_held(&rdev->filelist_sem)) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { +@@ -96,7 +97,7 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + } + } + if (atomic) +- read_unlock(&rdev->filelist_lock); ++ rcu_read_unlock(); + else + up_read(&rdev->filelist_sem); + +@@ -200,8 +201,7 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) + vmidi->port = rdev->port; + runtime->private_data = vmidi; + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_add_tail(&vmidi->list, &rdev->filelist); ++ list_add_tail_rcu(&vmidi->list, &rdev->filelist); + } + vmidi->rdev = rdev; + return 0; +@@ -243,9 +243,13 @@ static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) + struct snd_virmidi *vmidi = substream->runtime->private_data; + + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_del(&vmidi->list); ++ list_del_rcu(&vmidi->list); + } ++ /* wait for a grace period so that lockless readers in the atomic ++ * delivery path (snd_virmidi_dev_receive_event()) are no longer ++ * traversing this entry before its parser and memory are freed ++ */ ++ synchronize_rcu(); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); +@@ -508,7 +512,6 @@ int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmi + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); +- rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; +diff --git a/sound/core/sound.c b/sound/core/sound.c +--- a/sound/core/sound.c ++++ b/sound/core/sound.c +@@ -167,11 +167,10 @@ static int snd_open(struct inode *inode, struct file *file) + return err; + } + +-static const struct file_operations snd_fops = +-{ +- .owner = THIS_MODULE, +- .open = snd_open, +- .llseek = noop_llseek, ++static const struct file_operations snd_fops = { ++ .owner = THIS_MODULE, ++ .open = snd_open, ++ .llseek = noop_llseek, + }; + + #ifdef CONFIG_SND_DYNAMIC_MINORS +@@ -358,7 +357,7 @@ static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_bu + struct snd_minor *mptr; + + guard(mutex)(&sound_mutex); +- for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) { ++ for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + mptr = snd_minors[minor]; + if (!mptr) + continue; +@@ -395,15 +394,21 @@ int __init snd_minor_info_init(void) + + static int __init alsa_sound_init(void) + { ++ int err; ++ + snd_major = major; + snd_ecards_limit = cards_limit; +- if (register_chrdev(major, "alsa", &snd_fops)) { ++ ++ err = register_chrdev(major, "alsa", &snd_fops); ++ if (err < 0) { + pr_err("ALSA core: unable to register native major device number %d\n", major); +- return -EIO; ++ return err; + } +- if (snd_info_init() < 0) { ++ ++ err = snd_info_init(); ++ if (err < 0) { + unregister_chrdev(major, "alsa"); +- return -ENOMEM; ++ return err; + } + + #ifdef CONFIG_SND_DEBUG +diff --git a/sound/core/sound_kunit.c b/sound/core/sound_kunit.c +--- a/sound/core/sound_kunit.c ++++ b/sound/core/sound_kunit.c +@@ -17,8 +17,8 @@ + .name = #fmt, \ + } + +-#define WRONG_FORMAT_1 (__force snd_pcm_format_t)((__force int)SNDRV_PCM_FORMAT_LAST + 1) +-#define WRONG_FORMAT_2 (__force snd_pcm_format_t)-1 ++#define WRONG_FORMAT_1 (SNDRV_PCM_FORMAT_LAST + 1) ++#define WRONG_FORMAT_2 -1 + + #define VALID_NAME "ValidName" + #define NAME_W_SPEC_CHARS "In%v@1id name" +diff --git a/sound/core/timer.c b/sound/core/timer.c +--- a/sound/core/timer.c ++++ b/sound/core/timer.c +@@ -1546,11 +1546,8 @@ static int realloc_user_queue(struct snd_timer_user *tu, int size) + static int snd_timer_user_open(struct inode *inode, struct file *file) + { + struct snd_timer_user *tu; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + tu = kzalloc_obj(*tu); + if (tu == NULL) +@@ -2176,9 +2173,9 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon + } + + static const struct file_operations snd_utimer_fops = { +- .llseek = noop_llseek, +- .release = snd_utimer_release, +- .unlocked_ioctl = snd_utimer_ioctl, ++ .llseek = noop_llseek, ++ .release = snd_utimer_release, ++ .unlocked_ioctl = snd_utimer_ioctl, + }; + + static int snd_utimer_start(struct snd_timer *t) +@@ -2530,16 +2527,15 @@ static __poll_t snd_timer_user_poll(struct file *file, poll_table * wait) + #define snd_timer_user_ioctl_compat NULL + #endif + +-static const struct file_operations snd_timer_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_timer_user_read, +- .open = snd_timer_user_open, +- .release = snd_timer_user_release, +- .poll = snd_timer_user_poll, +- .unlocked_ioctl = snd_timer_user_ioctl, +- .compat_ioctl = snd_timer_user_ioctl_compat, +- .fasync = snd_timer_user_fasync, ++static const struct file_operations snd_timer_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_timer_user_read, ++ .open = snd_timer_user_open, ++ .release = snd_timer_user_release, ++ .poll = snd_timer_user_poll, ++ .unlocked_ioctl = snd_timer_user_ioctl, ++ .compat_ioctl = snd_timer_user_ioctl_compat, ++ .fasync = snd_timer_user_fasync, + }; + + /* unregister the system timer */ +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1157,10 +1157,11 @@ static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) + return -ENODEV; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { +- err = snd_rawmidi_kernel_open(&ump->core, 0, +- SNDRV_RAWMIDI_LFLG_OUTPUT | +- SNDRV_RAWMIDI_LFLG_APPEND, +- &ump->legacy_out_rfile); ++ err = snd_rawmidi_kernel_open_nested(&ump->core, 0, ++ SNDRV_RAWMIDI_LFLG_OUTPUT | ++ SNDRV_RAWMIDI_LFLG_APPEND, ++ &ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + if (err < 0) + return err; + } +diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c +--- a/sound/drivers/aloop.c ++++ b/sound/drivers/aloop.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -55,7 +56,7 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver."); + module_param_array(pcm_notify, int, NULL, 0444); + MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes."); + module_param_array(timer_source, charp, NULL, 0444); +-MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default]."); ++MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default], 'hrtimer' for high-resolution timer."); + + #define NO_PITCH 100000 + +@@ -161,8 +162,11 @@ struct loopback_pcm { + unsigned int period_size_frac; /* period size in jiffies ticks */ + unsigned int last_drift; + unsigned long last_jiffies; +- /* If jiffies timer is used */ ++ /* If jiffies / hrtimer is used */ + struct timer_list timer; ++#ifdef CONFIG_HIGH_RES_TIMERS ++ struct hrtimer hrtimer; ++#endif + + /* size of per channel buffer in case of non-interleaved access */ + unsigned int channel_buf_n; +@@ -232,6 +236,31 @@ static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static int loopback_hrtimer_start(struct loopback_pcm *dpcm) ++{ ++ unsigned long tick; ++ unsigned int rate_shift = get_rate_shift(dpcm); ++ ++ if (rate_shift != dpcm->pcm_rate_shift) { ++ dpcm->pcm_rate_shift = rate_shift; ++ dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size); ++ } ++ if (dpcm->period_size_frac <= dpcm->irq_pos) { ++ dpcm->irq_pos %= dpcm->period_size_frac; ++ dpcm->period_update_pending = 1; ++ } ++ tick = dpcm->period_size_frac - dpcm->irq_pos; ++ tick = DIV_ROUND_UP(tick, dpcm->pcm_bps); ++ hrtimer_start(&dpcm->hrtimer, ++ ns_to_ktime(div_u64((u64)tick * NSEC_PER_SEC, HZ)), ++ HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_start(struct loopback_pcm *dpcm) + { +@@ -270,6 +299,16 @@ static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static inline int loopback_hrtimer_stop(struct loopback_pcm *dpcm) ++{ ++ hrtimer_try_to_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_stop(struct loopback_pcm *dpcm) + { +@@ -300,6 +339,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static inline int loopback_hrtimer_stop_sync(struct loopback_pcm *dpcm) ++{ ++ hrtimer_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in loopback->cable_lock */ + static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) + { +@@ -734,6 +782,30 @@ static void loopback_jiffies_timer_function(struct timer_list *t) + snd_pcm_period_elapsed(dpcm->substream); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static enum hrtimer_restart loopback_hrtimer_function(struct hrtimer *t) ++{ ++ struct loopback_pcm *dpcm = container_of(t, struct loopback_pcm, hrtimer); ++ bool period_elapsed = false; ++ ++ scoped_guard(spinlock_irqsave, &dpcm->cable->lock) { ++ if (loopback_jiffies_timer_pos_update(dpcm->cable) & ++ (1 << dpcm->substream->stream)) { ++ loopback_hrtimer_start(dpcm); ++ if (dpcm->period_update_pending) { ++ dpcm->period_update_pending = 0; ++ period_elapsed = true; ++ } ++ } ++ } ++ ++ if (period_elapsed) ++ snd_pcm_period_elapsed(dpcm->substream); ++ ++ return HRTIMER_NORESTART; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime, + unsigned long resolution) +@@ -907,6 +979,21 @@ static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm, + snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static void loopback_hrtimer_dpcm_info(struct loopback_pcm *dpcm, ++ struct snd_info_buffer *buffer) ++{ ++ snd_iprintf(buffer, " update_pending:\t%u\n", ++ dpcm->period_update_pending); ++ snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos); ++ snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac); ++ snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n", ++ dpcm->last_jiffies, jiffies); ++ snd_iprintf(buffer, " timer_expires:\t%llu\n", ++ ktime_to_ns(hrtimer_get_expires(&dpcm->hrtimer))); ++} ++#endif ++ + static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer) + { +@@ -1097,6 +1184,26 @@ static const struct loopback_ops loopback_jiffies_timer_ops = { + .dpcm_info = loopback_jiffies_timer_dpcm_info, + }; + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static int loopback_hrtimer_open(struct loopback_pcm *dpcm) ++{ ++ hrtimer_setup(&dpcm->hrtimer, loopback_hrtimer_function, ++ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++ ++static const struct loopback_ops loopback_hrtimer_ops = { ++ .open = loopback_hrtimer_open, ++ .start = loopback_hrtimer_start, ++ .stop = loopback_hrtimer_stop, ++ .stop_sync = loopback_hrtimer_stop_sync, ++ .close_substream = loopback_hrtimer_stop_sync, ++ .pos_update = loopback_jiffies_timer_pos_update, ++ .dpcm_info = loopback_hrtimer_dpcm_info, ++}; ++#endif ++ + static int loopback_parse_timer_id(const char *str, + struct snd_timer_id *tid) + { +@@ -1274,7 +1381,12 @@ static int loopback_open(struct snd_pcm_substream *substream) + spin_lock_init(&cable->lock); + snd_refcount_init(&cable->stop_count); + cable->hw = loopback_pcm_hardware; +- if (loopback->timer_source) ++#ifdef CONFIG_HIGH_RES_TIMERS ++ if (loopback->timer_source && !strcmp(loopback->timer_source, "hrtimer")) ++ cable->ops = &loopback_hrtimer_ops; ++ else ++#endif ++ if (loopback->timer_source && loopback->timer_source[0]) + cable->ops = &loopback_snd_timer_ops; + else + cable->ops = &loopback_jiffies_timer_ops; +@@ -1491,7 +1603,7 @@ static int loopback_format_info(struct snd_kcontrol *kcontrol, + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; +- uinfo->value.integer.max = (__force int)SNDRV_PCM_FORMAT_LAST; ++ uinfo->value.integer.max = SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.step = 1; + return 0; + } +@@ -1502,7 +1614,7 @@ static int loopback_format_get(struct snd_kcontrol *kcontrol, + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = +- (__force int)loopback->setup[kcontrol->id.subdevice] ++ loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].format; + return 0; + } +@@ -1556,7 +1668,7 @@ static int loopback_channels_get(struct snd_kcontrol *kcontrol, + static int loopback_access_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +- const char * const texts[] = {"Interleaved", "Non-interleaved"}; ++ static const char * const texts[] = {"Interleaved", "Non-interleaved"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -100,6 +100,7 @@ struct dummy_timer_ops { + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); ++ int (*sync_stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); + }; + +@@ -285,6 +286,14 @@ static int dummy_systimer_stop(struct snd_pcm_substream *substream) + return 0; + } + ++static int dummy_systimer_sync_stop(struct snd_pcm_substream *substream) ++{ ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_delete_sync(&dpcm->timer); ++ return 0; ++} ++ + static int dummy_systimer_prepare(struct snd_pcm_substream *substream) + { + struct snd_pcm_runtime *runtime = substream->runtime; +@@ -341,7 +350,10 @@ static int dummy_systimer_create(struct snd_pcm_substream *substream) + + static void dummy_systimer_free(struct snd_pcm_substream *substream) + { +- kfree(substream->runtime->private_data); ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_shutdown_sync(&dpcm->timer); ++ kfree(dpcm); + } + + static const struct dummy_timer_ops dummy_systimer_ops = { +@@ -350,6 +362,7 @@ static const struct dummy_timer_ops dummy_systimer_ops = { + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, ++ .sync_stop = dummy_systimer_sync_stop, + .pointer = dummy_systimer_pointer, + }; + +@@ -407,9 +420,12 @@ static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) + return 0; + } + +-static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) ++static int dummy_hrtimer_sync_stop(struct snd_pcm_substream *substream) + { ++ struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; ++ + hrtimer_cancel(&dpcm->timer); ++ return 0; + } + + static snd_pcm_uframes_t +@@ -435,7 +451,6 @@ static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) + long sec; + unsigned long nsecs; + +- dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; +@@ -463,7 +478,7 @@ static int dummy_hrtimer_create(struct snd_pcm_substream *substream) + static void dummy_hrtimer_free(struct snd_pcm_substream *substream) + { + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; +- dummy_hrtimer_sync(dpcm); ++ + kfree(dpcm); + } + +@@ -473,6 +488,7 @@ static const struct dummy_timer_ops dummy_hrtimer_ops = { + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, ++ .sync_stop = dummy_hrtimer_sync_stop, + .pointer = dummy_hrtimer_pointer, + }; + +@@ -495,6 +511,13 @@ static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) + return -EINVAL; + } + ++static int dummy_pcm_sync_stop(struct snd_pcm_substream *substream) ++{ ++ if (get_dummy_ops(substream)->sync_stop) ++ return get_dummy_ops(substream)->sync_stop(substream); ++ return 0; ++} ++ + static int dummy_pcm_prepare(struct snd_pcm_substream *substream) + { + return get_dummy_ops(substream)->prepare(substream); +@@ -646,6 +669,7 @@ static const struct snd_pcm_ops dummy_pcm_ops = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + }; + +@@ -655,6 +679,7 @@ static const struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + .copy = dummy_pcm_copy, + .fill_silence = dummy_pcm_silence, +diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c +--- a/sound/hda/codecs/ca0132.c ++++ b/sound/hda/codecs/ca0132.c +@@ -5788,7 +5788,8 @@ static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; +- sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); ++ snprintf(namestr, sizeof(namestr), "%d %s", ++ (uinfo->value.enumerated.item * 10), sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5840,9 +5841,9 @@ static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX; + if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX) + uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1; +- sprintf(namestr, "%s %s", +- ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, +- sfx); ++ snprintf(namestr, sizeof(namestr), "%s %s", ++ ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, ++ sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5894,8 +5895,8 @@ static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX; + if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX) + uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1; +- sprintf(namestr, "%s", +- ae5_filter_presets[uinfo->value.enumerated.item].name); ++ snprintf(namestr, sizeof(namestr), "%s", ++ ae5_filter_presets[uinfo->value.enumerated.item].name); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -6632,7 +6633,7 @@ static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + +- sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Volume", pfx, dirstr[dir]); + + knew.tlv.c = NULL; + +@@ -6671,9 +6672,9 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, + * prefix to OutFX or InFX enable controls. + */ + if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID)) +- sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Switch", pfx, dirstr[dir]); + else +- sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "%s %s Switch", pfx, dirstr[dir]); + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + } +@@ -8530,10 +8531,9 @@ static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) + + static bool ca0132_download_dsp_images(struct hda_codec *codec) + { +- bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; + const struct dsp_image_seg *dsp_os_image; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case +@@ -8573,15 +8573,10 @@ static bool ca0132_download_dsp_images(struct hda_codec *codec) + dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); + if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { + codec_err(codec, "ca0132 DSP load image failed\n"); +- goto exit_download; ++ return false; + } + +- dsp_loaded = dspload_wait_loaded(codec); +- +-exit_download: +- release_firmware(fw_entry); +- +- return dsp_loaded; ++ return dspload_wait_loaded(codec); + } + + static void ca0132_download_dsp(struct hda_codec *codec) +@@ -9634,6 +9629,7 @@ static void ca0132_free(struct hda_codec *codec) + #endif + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void dbpro_free(struct hda_codec *codec) +@@ -9644,6 +9640,7 @@ static void dbpro_free(struct hda_codec *codec) + + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void ca0132_config(struct hda_codec *codec) +diff --git a/sound/hda/codecs/cm9825.c b/sound/hda/codecs/cm9825.c +--- a/sound/hda/codecs/cm9825.c ++++ b/sound/hda/codecs/cm9825.c +@@ -15,7 +15,8 @@ + + enum { + QUIRK_CM_STD = 0x0, +- QUIRK_GENE_TWL7_SSID = 0x160dc000 ++ QUIRK_GENE_TWL7_SSID = 0x160dc000, ++ QUIRK_IBP_SSID = 0x15bd3275 + }; + + /* CM9825 Offset Definitions */ +@@ -40,13 +41,19 @@ enum { + #define CM9825_VERB_SET_GAD 0x7b4 + #define CM9825_VERB_SET_TMOD 0x7b5 + #define CM9825_VERB_SET_SNR 0x7b6 ++#define CM9825_VERB_SET_OMTP 0x7ef ++#define CM9825_VERB_READ_OMTP 0xfec + + struct cmi_spec { + struct hda_gen_spec gen; + const struct hda_verb *chip_d0_verbs; + const struct hda_verb *chip_d3_verbs; ++ const struct hda_verb *chip_playback_start_verbs; ++ const struct hda_verb *chip_playback_stop_verbs; + const struct hda_verb *chip_hp_present_verbs; + const struct hda_verb *chip_hp_remove_verbs; ++ const struct hda_verb *chip_lineout_present_verbs; ++ const struct hda_verb *chip_lineout_remove_verbs; + struct hda_codec *codec; + struct delayed_work unsol_inputs_work; + struct delayed_work unsol_lineout_work; +@@ -193,6 +200,94 @@ static const struct hda_verb cm9825_gene_twl7_playback_stop_verbs[] = { + {} + }; + ++/* ++ * To save power, AD/CLK is turned off. ++ */ ++static const struct hda_verb cm9825_ibp_d3_verbs[] = { ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_PLL, 0x01}, ++ {0x43, CM9825_VERB_SET_NEG, 0xc2}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x50}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x04}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf6}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {} ++}; ++ ++/* ++ * D0 configuration: enable PLL/CLK/ADC/DAC and optimize performance ++ */ ++static const struct hda_verb cm9825_ibp_d0_verbs[] = { ++ {0x34, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {0x43, CM9825_VERB_SET_PLL, 0x00}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x56}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_DACTRL, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x0c}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf4}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {0x43, CM9825_VERB_SET_MTCBA, 0x61}, ++ {0x43, CM9825_VERB_SET_OCP, 0x33}, ++ {0x43, CM9825_VERB_SET_GAD, 0x07}, ++ {0x43, CM9825_VERB_SET_TMOD, 0x26}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0xa0, 0x2d}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0x90, 0x2d}, ++ {0x43, CM9825_VERB_SET_HPF_1, 0x40}, ++ {0x43, CM9825_VERB_SET_HPF_2, 0x40}, ++ {} ++}; ++ ++/* ++ * Enable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_present_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x8c}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x10}, ++ {} ++}; ++ ++/* ++ * Disable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_remove_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {} ++}; ++ ++/* ++ * Turn on the DAC (widget NID 0x43) in the playback path by writing ++ * a sequence of vendor-specific verbs. ++ */ ++static const struct hda_verb cm9825_ibp_playback_start_verbs[] = { ++ {0x43, CM9825_VERB_SET_DACL, 0xaa}, ++ {0x43, CM9825_VERB_SET_D2S, 0xf2}, ++ {0x43, CM9825_VERB_SET_VDO, 0xc4}, ++ {0x43, CM9825_VERB_SET_SNR, 0x30}, ++ {} ++}; ++ ++/* ++ * Shut down the playback path. The order differs slightly from the ++ * enable sequence, likely to avoid audible pop noise when powering ++ * down the output stage. ++ */ ++static const struct hda_verb cm9825_ibp_playback_stop_verbs[] = { ++ {0x43, CM9825_VERB_SET_VDO, 0xc0}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_VDO, 0x80}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {} ++}; ++ + static void cm9825_update_jk_plug_status(struct hda_codec *codec, hda_nid_t nid) + { + struct cmi_spec *spec = codec->spec; +@@ -232,6 +327,23 @@ static void cm9825_unsol_lineout_delayed(struct work_struct *work) + struct cmi_spec *spec = + container_of(to_delayed_work(work), struct cmi_spec, + unsol_lineout_work); ++ hda_nid_t line_out_pin = spec->gen.autocfg.line_out_pins[0]; ++ bool line_out_jack_plugin = false; ++ ++ line_out_jack_plugin = snd_hda_jack_detect(spec->codec, line_out_pin); ++ ++ codec_dbg(spec->codec, "lineout_jack_plugin %d, lineout_pin 0x%X\n", ++ (int)line_out_jack_plugin, line_out_pin); ++ ++ if (!line_out_jack_plugin) { ++ /* Jack plugout */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_remove_verbs); ++ } else { ++ /* Jack plugin */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_present_verbs); ++ } + + cm9825_update_jk_plug_status(spec->codec, + spec->gen.autocfg.line_out_pins[0]); +@@ -362,12 +474,10 @@ static void cm9825_playback_pcm_hook(struct hda_pcm_stream *hinfo, + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_start_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_start_verbs); + break; + case HDA_GEN_PCM_ACT_CLEANUP: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_stop_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_stop_verbs); + break; + default: + return; +@@ -471,7 +581,8 @@ static int cm9825_resume(struct hda_codec *codec) + + if (codec->core.subsystem_id == QUIRK_CM_STD) + cm9825_cm_std_resume(codec); +- else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID) { ++ else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID || ++ codec->core.subsystem_id == QUIRK_IBP_SSID) { + snd_hda_codec_init(codec); + snd_hda_sequence_write(codec, spec->chip_d0_verbs); + } +@@ -487,6 +598,7 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + struct cmi_spec *spec; + struct auto_pin_cfg *cfg; + int err = 0; ++ unsigned int val; + + spec = kzalloc_obj(*spec); + if (spec == NULL) +@@ -523,9 +635,33 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->chip_d0_verbs = cm9825_gene_twl7_d0_verbs; + spec->chip_d3_verbs = cm9825_gene_twl7_d3_verbs; + spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_playback_start_verbs = ++ cm9825_gene_twl7_playback_start_verbs; ++ spec->chip_playback_stop_verbs = ++ cm9825_gene_twl7_playback_stop_verbs; + /* Internal fixed device, Rear, Mic-in, 3.5mm */ + snd_hda_codec_set_pincfg(codec, 0x37, 0x24A70100); + break; ++ case QUIRK_IBP_SSID: ++ snd_hda_codec_set_name(codec, "CM9825 IBP"); ++ spec->chip_d0_verbs = cm9825_ibp_d0_verbs; ++ spec->chip_d3_verbs = cm9825_ibp_d3_verbs; ++ spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_lineout_present_verbs = ++ cm9825_ibp_lineout_present_verbs; ++ spec->chip_lineout_remove_verbs = ++ cm9825_ibp_lineout_remove_verbs; ++ spec->chip_playback_start_verbs = ++ cm9825_ibp_playback_start_verbs; ++ spec->chip_playback_stop_verbs = cm9825_ibp_playback_stop_verbs; ++ ++ /* OMTP */ ++ val = ++ snd_hda_codec_read(codec, 0x46, 0, CM9825_VERB_READ_OMTP, ++ 0x0); ++ snd_hda_codec_write(codec, 0x46, 0, CM9825_VERB_SET_OMTP, ++ (val >> 24) & 0x7f); ++ break; + default: + err = -ENXIO; + break; +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -292,6 +292,7 @@ enum { + CXT_PINCFG_TOP_SPEAKER, + CXT_FIXUP_HP_A_U, + CXT_FIXUP_ACER_SWIFT_HP, ++ CXT_FIXUP_HUAWEI_MATEBOOK_HP, + }; + + /* for hda_fixup_thinkpad_acpi() */ +@@ -451,7 +452,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + if (!spec->dc_enable) { + /* disable DC bias path and pin for port F */ + update_mic_pin(codec, 0x1e, 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, false, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, false, false); + + /* update port B (ext mic) and C (int mic) */ + /* OLPC defers mic widget control until when capture is +@@ -487,7 +489,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + update_mic_pin(codec, 0x1b, 0); + /* enable DC bias path and pin */ + update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, true, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, true, false); + } + } + +@@ -1033,6 +1036,13 @@ static const struct hda_fixup cxt_fixups[] = { + { } + }, + }, ++ [CXT_FIXUP_HUAWEI_MATEBOOK_HP] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x18, 0x03211020 }, /* Headphone */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk cxt5045_fixups[] = { +@@ -1097,6 +1107,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), ++ SND_PCI_QUIRK(0x103c, 0x837b, "HP ProBook 440 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK), +@@ -1133,6 +1144,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), ++ SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), + SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), + SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), +diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c +--- a/sound/hda/codecs/generic.c ++++ b/sound/hda/codecs/generic.c +@@ -2695,30 +2695,13 @@ static const struct snd_kcontrol_new out_jack_mode_enum = { + .put = out_jack_mode_put, + }; + +-static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) +-{ +- struct hda_gen_spec *spec = codec->spec; +- const struct snd_kcontrol_new *kctl; +- int i; +- +- snd_array_for_each(&spec->kctls, i, kctl) { +- if (!strcmp(kctl->name, name) && kctl->index == idx) +- return true; +- } +- return false; +-} +- + static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin, + char *name, size_t name_len) + { + struct hda_gen_spec *spec = codec->spec; +- int idx = 0; + +- snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx); +- strlcat(name, " Jack Mode", name_len); +- +- for (; find_kctl_name(codec, name, idx); idx++) +- ; ++ snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len); ++ hda_append_suffix(name, " Jack Mode", name_len); + } + + static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin) +@@ -5695,7 +5678,7 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx, + break; + } + } +- strlcat(str, sfx, len); ++ hda_append_suffix(str, sfx, len); + } + + /* copy PCM stream info from @default_str, and override non-NULL entries +diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c +--- a/sound/hda/codecs/hdmi/hdmi.c ++++ b/sound/hda/codecs/hdmi/hdmi.c +@@ -2236,8 +2236,10 @@ void snd_hda_hdmi_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ +- if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) ++ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) { ++ codec->acomp_requested_resume = 1; + return; ++ } + + snd_hda_hdmi_check_presence_and_report(codec, pin_nid, dev_id); + } +@@ -2346,6 +2348,7 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { + HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), ++ HDA_CODEC_ID_MODEL(0x4c545020, "Lisuan HDMI/DP", MODEL_GENERIC), + HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), +diff --git a/sound/hda/codecs/helpers/razer_blade16_2025.c b/sound/hda/codecs/helpers/razer_blade16_2025.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/helpers/razer_blade16_2025.c +@@ -0,0 +1,820 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Razer Blade 16 (2025) external smart-amplifier programming and fixup, ++ * to be included from the codec driver. ++ * ++ * The internal speakers are driven by an external smart amplifier whose ++ * crossover, voicing and protection live in a DSP reached over a vendor ++ * coef mailbox on NID 0x20. These tables hold the programming captured ++ * from the Windows driver, reduced to the NID-0x20 traffic this machine ++ * needs (everything else the HDA parser rebuilds). ++ * ++ * Each entry is either a plain codec coef write or a mailbox write: ++ * strobe == 0: coef[addr] = lo ++ * strobe != 0: amp[addr] = (hi << 16) | lo, with the bank selected on ++ * port 0x89 and the write committed by the strobe opcode. ++ */ ++ ++struct alc298_razer_blade16_2025_op { ++ u16 bank; /* mailbox bank (port 0x89); unused for a plain coef */ ++ u16 addr; /* mailbox register, or plain coef index */ ++ u16 hi; /* mailbox high word; unused for a plain coef */ ++ u16 lo; /* mailbox low word, or plain coef value */ ++ u16 strobe; /* mailbox commit opcode; 0 marks a plain coef write */ ++}; ++ ++/* program the external DSP at init; the amp is woken/parked separately */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_init[] = { ++ { 0x0000, 0xf102, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf103, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc000, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xf20d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf212, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xc001, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc003, 0x0000, 0x0022, 0xb031 }, ++ { 0x0000, 0xc004, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc005, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc007, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xc00e, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xf223, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xf224, 0x0000, 0x00db, 0xb031 }, ++ { 0x0000, 0xf225, 0x0000, 0x00ee, 0xb031 }, ++ { 0x0000, 0xf226, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xf227, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xf21a, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf242, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc120, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc200, 0x0000, 0x00d8, 0xb031 }, ++ { 0x0000, 0xc201, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xc202, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xc400, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc401, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc402, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xc403, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc404, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xc406, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc407, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc408, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xc300, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdf5f, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xdf60, 0x0000, 0x00a7, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x0084, 0xb031 }, ++ { 0x0000, 0xc206, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf10a, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0065, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xe706, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe707, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xe806, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe807, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xce04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xce05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xce07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xcf04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xcf05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xcf06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xcf07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xc130, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xe000, 0x0000, 0x00a8, 0xb031 }, ++ { 0x4100, 0x1888, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xed00, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xcc2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcc2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcc2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcd2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcd2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcc26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcc27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcd24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcd26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcd27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcc20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcc17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcd17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcc2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcd29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcd2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcc31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcc32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcc37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcd37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcc0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcc0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcd09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcd0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcd0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcc0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcc10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcc11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xcd0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcd10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcd11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xccd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccdb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcddb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc320, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xe604, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdd00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdc19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xdc1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xdc1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xdc1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xdc1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xdc20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xde19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xde1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xde1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xde1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xde1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xde20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xdb32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdd33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdb34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdd34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdb15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdd15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdd17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdb94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdd94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdb19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x001c, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdbbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xdbbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdbbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xdbbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xddbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xddbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xddbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xddbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xdb01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdd01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdc52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xde52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xdf0a, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdf0b, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdf01, 0x0000, 0x0073, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xebcb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xeb13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xeb14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xeb16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xeb21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xeb22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xeb23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xeb27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xeb2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xeb2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xeb30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xeb37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xeb40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xeb43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xeb44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xeb4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xeb4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xeb52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xeb53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xeb56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xeb57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xeb60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xeb63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xeb65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xeb66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xeb67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xeb6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xeb6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xebb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xeb75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xeb77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xebb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xeb92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xeb94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xeb95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xeb96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeba0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeba2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeba3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeba4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeba5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeba7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeba8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeba9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xebaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xebab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xeb01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xeb02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xeb03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xeb05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xeb06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xeb07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd0cb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xd0b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd010, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd011, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd012, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd013, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd014, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd015, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd016, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd017, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd020, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd021, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd022, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd023, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd024, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd025, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd026, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd027, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd028, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd029, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd02a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd02b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce6a, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xcc00, 0x0000, 0x00c1, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca62, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca65, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca68, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca75, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca7b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc860, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc861, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc862, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc863, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc864, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc865, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc867, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc868, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc869, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc86a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc86b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc875, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc87b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc836, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc833, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc834, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc83f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc83e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc83a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc83b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc830, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc826, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc890, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc823, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc824, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc82f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc82e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc820, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc816, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc880, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc813, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc814, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc81f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc81e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc81a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc81b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc810, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc846, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc844, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc84f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc84e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc84a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc84b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc500, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc501, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc502, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc503, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc504, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc505, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc506, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc507, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc510, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc511, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc512, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc513, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc514, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc515, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc516, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc517, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc518, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd251, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xecb0, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xec13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xec14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xec16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xec21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xec22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xec23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xec24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xec27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xec2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xec2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb2, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xec37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xec40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xec43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xec44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xec4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xec4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xecb4, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xec52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xec53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xec56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xec57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xec63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xec65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xec66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xec67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xec6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xec6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xec75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xec77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xecb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xec92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xec94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xec95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xec96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeca0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeca2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeca3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeca4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeca5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeca7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeca8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeca9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xecaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xecab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xec01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xec02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xec03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xec05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xec06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xec07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd1b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd110, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd111, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd112, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd113, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd114, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd115, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd116, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd117, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd120, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd121, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd122, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd123, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd124, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd125, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd126, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd127, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd128, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd129, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd12a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd12b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca82, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca85, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca88, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca95, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca9b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc960, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc961, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc962, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc963, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc964, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc965, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc967, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc968, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc969, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc96a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc96b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc975, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc97b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc936, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc933, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc934, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc93f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc93e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc93a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc93b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc930, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc926, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc990, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc923, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc924, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc92f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc92e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc920, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc916, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc980, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc913, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc914, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc91f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc91e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc91a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc91b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc910, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc946, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc944, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc94f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc94e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc94a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc94b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc600, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc601, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc602, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc603, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc604, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc605, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc606, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc607, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc610, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc611, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc612, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc613, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc614, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc615, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc616, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc617, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc618, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xc802, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc902, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd252, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdbb5, 0x0041, 0xd27f, 0xb037 }, ++ { 0x0000, 0xddb5, 0x0040, 0x6e3b, 0xb037 }, ++ { 0x0000, 0xdb93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdd93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdb12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdd12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0x0010, 0x0000, 0x0f21, 0x0000 }, ++}; ++ ++/* wake the amp (ea00=0x43, c121=0x0b, f109=0xe0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_wake[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++}; ++ ++/* park the amp (ea00=0x47, c121=0x0a, f109=0xa0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_sleep[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00a0, 0xb031 }, ++}; ++ ++/* ++ * Razer Blade 16 (2025): Realtek ALC298 driving an external smart amp. ++ * Crossover, voicing and speaker protection live in an external DSP ++ * reached over a vendor-coef mailbox on NID 0x20; the captured DSP ++ * programming is in the tables above. NID 0x14, the BIOS-disabled ++ * tweeter pin, is re-exposed via a pin-config override so the parser ++ * drives it as a normal internal speaker (shared volume/mute, auto-mute ++ * on headphone insertion). ++ * ++ * alc298_razer_blade16_2025_apply() runs one op-table: a plain coef write ++ * (strobe == 0) goes through alc_write_coef_idx(), while a mailbox op ++ * selects a bank and streams four PROC_COEF words (addr/hi/lo/commit) ++ * into a single coef index (0x23) on NID 0x20 to reach the external DSP ++ * -- that one-index/many-words form is why it cannot be expressed as an ++ * alc_process_coef_fw() table (one value per index). ++ */ ++static void alc298_razer_blade16_2025_apply(struct hda_codec *codec, ++ const struct alc298_razer_blade16_2025_op *seq, ++ int num) ++{ ++ int i; ++ ++ for (i = 0; i < num; i++) { ++ if (!seq[i].strobe) { ++ alc_write_coef_idx(codec, seq[i].addr, seq[i].lo); ++ continue; ++ } ++ alc_write_coef_idx(codec, 0x89, seq[i].bank); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x23); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].addr); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].hi); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].lo); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].strobe); ++ } ++} ++ ++static void alc298_razer_blade16_2025_pcm_hook(struct hda_pcm_stream *hinfo, ++ struct hda_codec *codec, ++ struct snd_pcm_substream *substream, ++ int action) ++{ ++ /* power the external amp only while a stream is running */ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_PREPARE: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} ++ ++static void alc298_fixup_razer_blade16_2025(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Pin every output to a fixed converter so the routing does not ++ * rely on the generic parser's DAC-allocation heuristics: without ++ * a fixed assignment the parser brings up only one speaker pin and ++ * leaves the other silent, and the allocation could change between ++ * kernel versions. Both speaker pins share DAC 0x03 through mixer ++ * 0x0d: that is the only converter that drives the woofer (NID ++ * 0x17) on this machine -- feeding it from DAC 0x02 or the digital ++ * converter 0x06 leaves the woofer silent. Both pins carry the ++ * same stereo stream and the external amp does the crossover. ++ * Headphone NID 0x21 keeps DAC 0x02. ++ */ ++ static const hda_nid_t preferred_pairs[] = { ++ 0x21, 0x02, ++ 0x14, 0x03, ++ 0x17, 0x03, ++ 0 ++ }; ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.preferred_dacs = preferred_pairs; ++ break; ++ case HDA_FIXUP_ACT_PROBE: ++ spec->gen.pcm_playback_hook = alc298_razer_blade16_2025_pcm_hook; ++ break; ++ case HDA_FIXUP_ACT_INIT: ++ /* ++ * Wake the amp, program its DSP at boot and on resume, then ++ * park it. It is woken again for playback and parked ++ * afterwards from the pcm_playback_hook. ++ */ ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_init, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_init)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -296,7 +296,8 @@ static void alc282_init(struct hda_codec *codec) + msleep(100); + + /* Headphone capless set to normal mode */ +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static void alc282_shutup(struct hda_codec *codec) +@@ -333,7 +334,8 @@ static void alc282_shutup(struct hda_codec *codec) + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static const struct coef_fw alc283_coefs[] = { +@@ -585,15 +587,19 @@ static void alc285_hp_init(struct hda_codec *codec) + + alc_write_coefex_idx(codec, 0x58, 0x00, 0xf888); /* HP depop procedure start */ + val = alc_read_coefex_idx(codec, 0x58, 0x00); +- for (i = 0; i < 20 && val & 0x8000; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x8000; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x00); + } /* Wait for depop procedure finish */ + +- alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ +- alc_update_coef_idx(codec, 0x38, 1<<4, coef38); +- alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); +- alc_update_coef_idx(codec, 0x36, 3<<13, coef36); ++ if (val != -1) ++ alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ ++ if (coef38 != -1) ++ alc_update_coef_idx(codec, 0x38, 1<<4, coef38); ++ if (coef0d != -1) ++ alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); ++ if (coef36 != -1) ++ alc_update_coef_idx(codec, 0x36, 3<<13, coef36); + + msleep(50); + alc_update_coef_idx(codec, 0x4a, 1<<15, 0); +@@ -858,7 +864,7 @@ static void alc294_hp_init(struct hda_codec *codec) + + /* Wait for depop procedure finish */ + val = alc_read_coefex_idx(codec, 0x58, 0x01); +- for (i = 0; i < 20 && val & 0x0080; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x0080; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x01); + } +@@ -1392,6 +1398,15 @@ static void alc245_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); + } + ++static void alc245_fixup_minisforum_jack_detect(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ if (action == HDA_FIXUP_ACT_INIT) ++ /* Clear Reset HP JD while preserving the other coefficient bits. */ ++ alc_update_coef_idx(codec, 0x4a, BIT(15), 0); ++} ++ + /* turn on/off mic-mute LED per capture hook via VREF change */ + static int vref_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +@@ -2557,6 +2572,13 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, + } + } + ++static void alc285_lenovo_dac_rename(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ if (action == HDA_FIXUP_ACT_BUILD) ++ rename_ctl(codec, "Line Out Playback Volume", ++ "Headphone Playback Volume"); ++} + static void alc290_fixup_mono_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3262,6 +3284,36 @@ static void find_cirrus_companion_amps(struct hda_codec *cdc) + comp_generic_fixup(cdc, HDA_FIXUP_ACT_PRE_PROBE, bus, acpi_ids[i].hid, match, count); + } + ++static void aw88399_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) ++{ ++ comp_generic_fixup(cdc, action, "i2c", "AWDZ8399", "-%s:00-aw88399-hda.%d", 2); ++} ++ ++static void alc287_fixup_legion_16iax10h_aw88399(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Force DAC 0x02 for the bass speaker 0x17, as the default 0x06 lacks volume controls. ++ */ ++ static const hda_nid_t conn[] = { 0x02 }; ++ struct alc_spec *spec = codec->spec; ++ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++ alc_fixup_headset_mode_no_hp_mic(codec, fix, action); ++ alc_fixup_headset_jack(codec, fix, action); ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.suppress_auto_mic = 1; ++ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); ++ break; ++ case HDA_FIXUP_ACT_BUILD: ++ spec->gen.multiout.max_channels = 2; ++ spec->gen.pcm_rec[0]->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 2; ++ break; ++ } ++} ++ + static void cs35l41_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) + { + comp_generic_fixup(cdc, action, "i2c", "CSC3551", "-%s:00-cs35l41-hda.%d", 2); +@@ -3357,6 +3409,9 @@ static void alc287_fixup_acer_micmute_led(struct hda_codec *codec, + /* for alc285_fixup_ideapad_s740_coef() */ + #include "../helpers/ideapad_s740.c" + ++/* for alc298_fixup_razer_blade16_2025() */ ++#include "../helpers/razer_blade16_2025.c" ++ + static const struct coef_fw alc256_fixup_set_coef_defaults_coefs[] = { + WRITE_COEF(0x10, 0x0020), WRITE_COEF(0x24, 0x0000), + WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x29, 0x3000), +@@ -3939,6 +3994,7 @@ enum { + ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, + ALC269_FIXUP_ACER_AC700, + ALC269_FIXUP_LIMIT_INT_MIC_BOOST, ++ ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST, + ALC269VB_FIXUP_ASUS_ZENBOOK, + ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, + ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4007,6 +4063,7 @@ enum { + ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_FIXUP_DISABLE_DAC3, + ALC285_FIXUP_SPEAKER2_TO_DAC1, ++ ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_HEADSET_MIC, + ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS, +@@ -4076,7 +4133,6 @@ enum { + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, +- ALC294_FIXUP_ASUS_ALLY_SPEAKER, + ALC294_FIXUP_ASUS_HPE, + ALC294_FIXUP_ASUS_COEF_1B, + ALC294_FIXUP_ASUS_GX502_HP, +@@ -4101,6 +4157,8 @@ enum { + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, + ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, + ALC298_FIXUP_LG_GRAM_STYLE_14, ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS, ++ ALC298_FIXUP_RAZER_BLADE16_2025, + ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4118,7 +4176,6 @@ enum { + ALC269_FIXUP_LEMOTE_A190X, + ALC256_FIXUP_INTEL_NUC8_RUGGED, + ALC233_FIXUP_INTEL_NUC8_DMIC, +- ALC233_FIXUP_INTEL_NUC8_BOOST, + ALC256_FIXUP_INTEL_NUC10, + ALC255_FIXUP_XIAOMI_HEADSET_MIC, + ALC274_FIXUP_HP_MIC, +@@ -4149,6 +4206,7 @@ enum { + ALC298_FIXUP_LENOVO_C940_DUET7, + ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, + ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK, + ALC287_FIXUP_13S_GEN2_SPEAKERS, + ALC256_FIXUP_SET_COEF_DEFAULTS, + ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, +@@ -4193,7 +4251,6 @@ enum { + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC2XX_FIXUP_HEADSET_MIC, + ALC289_FIXUP_DELL_CS35L41_SPI_2, +- ALC294_FIXUP_CS35L41_I2C_2, + ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_HEADPHONE_AMP_VOL, + ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, +@@ -4207,6 +4264,7 @@ enum { + ALC287_FIXUP_LENOVO_THKPAD_WH_ALC1318, + ALC256_FIXUP_CHROME_BOOK, + ALC245_FIXUP_CLEVO_NOISY_MIC, ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT, + ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE, + ALC233_FIXUP_MEDION_MTL_SPK, + ALC233_FIXUP_STARLABS_STARFIGHTER, +@@ -4216,7 +4274,6 @@ enum { + ALC274_FIXUP_HP_AIO_BIND_DACS, + ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2, + ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC, +- ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1, + ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC, + ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK, + ALC256_FIXUP_VAIO_RPL_MIC_NO_PRESENCE, +@@ -4236,6 +4293,10 @@ enum { + ALC236_FIXUP_DELL_HP_POP_NOISE, + ALC274_FIXUP_HP_89E9_GPIO, + ALC274_FIXUP_HP_VERBS, ++ ALC287_FIXUP_AW88399_I2C_2, ++ ALC287_FIXUP_LENOVO_LEGION_AW88399, ++ ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ++ ALC285_LENOVO_DAC_RENAME, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4273,6 +4334,20 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + } + + static const struct hda_fixup alc269_fixups[] = { ++ [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170121 }, /* tweeter as internal speaker, seq 1 */ ++ { 0x17, 0x90170120 }, /* woofer as internal speaker, seq 0 */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC298_FIXUP_RAZER_BLADE16_2025, ++ }, ++ [ALC298_FIXUP_RAZER_BLADE16_2025] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc298_fixup_razer_blade16_2025, ++ }, + [ALC233_FIXUP_WUJIE_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -4713,6 +4788,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chain_id = ALC271_FIXUP_DMIC, + }, + [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc269_fixup_limit_int_mic_boost ++ }, ++ [ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, +@@ -5088,11 +5167,7 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + .chained = true, +- .chain_id = ALC233_FIXUP_INTEL_NUC8_BOOST, +- }, +- [ALC233_FIXUP_INTEL_NUC8_BOOST] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc269_fixup_limit_int_mic_boost ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC255_FIXUP_DELL_SPK_NOISE] = { + .type = HDA_FIXUP_FUNC, +@@ -5153,6 +5228,10 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC285_FIXUP_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, ++ }, ++ [ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, +@@ -5672,11 +5751,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER +- }, +- [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC285_FIXUP_THINKPAD_X1_GEN7] = { + .type = HDA_FIXUP_FUNC, +@@ -6223,6 +6298,16 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_LENOVO_XPAD_ACPI, + }, ++ [ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ /* Same as ALC285_FIXUP_THINKPAD_HEADSET_JACK, except that the ++ * mute LEDs are driven through ideapad_laptop rather than ++ * thinkpad_acpi. ++ */ ++ .chain_id = ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ }, + [ALC623_FIXUP_LENOVO_THINKSTATION_P340] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_shutup, +@@ -6662,10 +6747,6 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC289_FIXUP_DUAL_SPK + }, +- [ALC294_FIXUP_CS35L41_I2C_2] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = cs35l41_fixup_i2c_two, +- }, + [ALC256_FIXUP_ACER_SFG16_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, +@@ -6738,6 +6819,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, + }, ++ [ALC245_FIXUP_MINISFORUM_JACK_DETECT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc245_fixup_minisforum_jack_detect, ++ }, + [ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -6758,6 +6843,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC233_FIXUP_STARLABS_STARFIGHTER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_starlabs_starfighter, ++ .chained = true, ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC294_FIXUP_BASS_SPEAKER_15] = { + .type = HDA_FIXUP_FUNC, +@@ -6785,11 +6872,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1 +- }, +- [ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, +@@ -6907,6 +6990,26 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC274_FIXUP_HP_89E9_GPIO, + }, ++ [ALC287_FIXUP_AW88399_I2C_2] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = aw88399_fixup_i2c_two, ++ }, ++ [ALC287_FIXUP_LENOVO_LEGION_AW88399] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc287_fixup_legion_16iax10h_aw88399, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_AW88399_I2C_2, ++ }, ++ [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ++ }, ++ [ALC285_LENOVO_DAC_RENAME] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_lenovo_dac_rename, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -6960,8 +7063,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), +@@ -6970,6 +7075,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1025, 0x1909, "Acer Nitro ANV16-42", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS), +@@ -7154,10 +7260,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), ++ SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), +@@ -7268,6 +7376,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8a0e, "HP Pavilion Laptop 15-eh2xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a0f, "HP Pavilion 14-ec1xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a1b, "HP 255 15.6 inch G9 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8a1f, "HP Laptop 14s-dr5xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7359,10 +7468,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), + SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), +- SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre 16", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c48, "HP EliteBook 860 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7511,6 +7621,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x8f7e, "HP Messi", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f94, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f95, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x1043, 0x1024, "ASUS Zephyrus G14 2025", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), +@@ -7518,6 +7629,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1034, "ASUS GU605C", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), ++ SND_PCI_QUIRK(0x1043, 0x1044, "ASUS GA403UM", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1054, "ASUS G614FH/FM/FP", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x106f, "ASUS VivoBook X515UA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), +@@ -7525,16 +7637,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), ++ SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1194, "ASUS UM3406KA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), +- HDA_CODEC_QUIRK(0x1043, 0x1204, "ASUS Strix G16 G615JMR", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TAS2781_I2C), +- HDA_CODEC_QUIRK(0x1043, 0x1214, "ASUS ROG Strix G615LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TXNW2781_I2C_ASUS), ++ SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1271, "ASUS X430UN", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), +@@ -7583,7 +7694,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1863, "ASUS UX6404VI/VV", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), + SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), +@@ -7594,7 +7705,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x19f4, "ASUS UM3405GA", ALC294_FIXUP_ASUS_I2C_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), + SND_PCI_QUIRK(0x1043, 0x1a63, "ASUS UX3405MA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1a8e, "ASUS G712LWS", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x1043, 0x1a8f, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1b11, "ASUS UX431DA", ALC294_FIXUP_ASUS_COEF_1B), +@@ -7623,6 +7734,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1264, "ASUS UM5606KA", ALC294_FIXUP_BASS_SPEAKER_15), + SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1e10, "ASUS VivoBook X507UAR", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), ++ SND_PCI_QUIRK(0x1043, 0x1e3e, "ASUS VivoBook M515DA/X515DAP", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502), + SND_PCI_QUIRK(0x1043, 0x1e12, "ASUS UM3402", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1e1f, "ASUS Vivobook 15 X1504VAP", ALC2XX_FIXUP_HEADSET_MIC), +@@ -7634,8 +7746,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X), + SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), +- HDA_CODEC_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), +- SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f12, "ASUS UM5302", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7846,7 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2218, "Thinkpad X1 Carbon 2nd", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2223, "ThinkPad T550", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2226, "ThinkPad X250", ALC292_FIXUP_TPT440_DOCK), +@@ -7862,7 +7973,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x225c, "Lenovo ThinkPad X1 Carbon 6th Gen", ALC285_LENOVO_DAC_RENAME), ++ SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2288, "Thinkpad X390", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), +@@ -7937,7 +8049,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_SPEAKER2_TO_DAC1), ++ SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7972,6 +8084,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C), ++ HDA_CODEC_QUIRK(0x17aa, 0x3926, "Legion Pro 5 16ADR10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ /* Legion R9000P ADR10 shares PCI SSID 17aa:38bb with Yoga S780-14.5 Air AMD quad AAC; ++ * use codec SSID to distinguish them ++ */ ++ HDA_CODEC_QUIRK(0x17aa, 0x3927, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3928, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), + SND_PCI_QUIRK(0x17aa, 0x38bb, "Yoga S780-14.5 Air AMD quad AAC", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38be, "Yoga S980-14.5 proX YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38bf, "Yoga S980-14.5 proX LX Dual", ALC287_FIXUP_TAS2781_I2C), +@@ -7989,7 +8107,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), + /* Legion 7 15ASH11 shares PCI SSID 17aa:38f9 with Thinkbook 16P Gen5; + * use codec SSID to distinguish them + */ +@@ -7999,7 +8116,9 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38fc, "Lenovo Yoga Pro 7 15ASH11", ALC287_FIXUP_LENOVO_YOGA_PRO7), + SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), +- SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ HDA_CODEC_QUIRK(0x17aa, 0x3906, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), + SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), +@@ -8007,14 +8126,19 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), ++ HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3937, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3939, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x393e, "Lenovo ThinkBook 14 G8+ IPH", ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK), + HDA_CODEC_QUIRK(0x17aa, 0x394c, "Lenovo Yoga Slim 7 14AGP11", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI), + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), +- SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x501a, "Thinkpad", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x501e, "Thinkpad L440", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x5034, "Thinkpad T450", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5036, "Thinkpad T450s", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x503c, "Thinkpad L450", ALC292_FIXUP_TPT440_DOCK), +@@ -8027,7 +8151,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), +- SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x511e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), +@@ -8041,9 +8165,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1854, 0x0489, "LG gram 16 (16Z90R-A)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x048a, "LG gram 17 (17ZD90R)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), ++ SND_PCI_QUIRK(0x1854, 0x0554, "LG gram 16 (16Z90TR)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), ++ SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), + SND_PCI_QUIRK(0x1b35, 0x1235, "CZC B20", ALC269_FIXUP_CZC_B20), + SND_PCI_QUIRK(0x1b35, 0x1236, "CZC TMI", ALC269_FIXUP_CZC_TMI), + SND_PCI_QUIRK(0x1b35, 0x1237, "CZC L101", ALC269_FIXUP_CZC_L101), +@@ -8078,13 +8207,17 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1e39, 0xca14, "MEDION NM14LNL", ALC233_FIXUP_MEDION_MTL_SPK), + SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), + SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), ++ SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), ++ SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +@@ -8095,6 +8228,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), + SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), ++ SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), +@@ -8168,6 +8302,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), ++ SND_PCI_QUIRK_VENDOR(0x2145, "Star Labs", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + {} + }; + +@@ -8307,6 +8442,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { + {.id = ALC2XX_FIXUP_HEADSET_MIC, .name = "alc2xx-fixup-headset-mic"}, + {.id = ALC245_FIXUP_BASS_HP_DAC, .name = "alc245-fixup-bass-hp-dac"}, + {.id = ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, .name = "alc256-honor-mrb-xxx-m1020-audio"}, ++ {.id = ALC287_FIXUP_LENOVO_LEGION_AW88399, .name = "alc287-lenovo-legion-aw88399"}, + {} + }; + #define ALC225_STANDARD_PINS \ +@@ -8912,6 +9048,7 @@ static int alc269_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */ + if (codec->core.vendor_id == 0x10ec0236 && ++ codec->bus->pci && + codec->bus->pci->vendor != PCI_VENDOR_ID_AMD) + spec->en_3kpull_low = false; + break; +diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c +--- a/sound/hda/codecs/realtek/alc662.c ++++ b/sound/hda/codecs/realtek/alc662.c +@@ -325,6 +325,7 @@ enum { + ALC897_FIXUP_UNIS_H3C_X500S, + ALC897_FIXUP_HEADSET_MIC_PIN3, + ALC662_FIXUP_CSL_GPIO, ++ ALC897_FIXUP_BEELINK_SER6_AMP, + }; + + static const struct hda_fixup alc662_fixups[] = { +@@ -782,6 +783,10 @@ static const struct hda_fixup alc662_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_csl_amp, + }, ++ [ALC897_FIXUP_BEELINK_SER6_AMP] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_gpio4, ++ }, + }; + + static const struct hda_quirk alc662_fixup_tbl[] = { +@@ -853,6 +858,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { + SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), ++ SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), + + #if 0 + /* Below is a quirk table taken from the old code. +diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig +--- a/sound/hda/codecs/side-codecs/Kconfig ++++ b/sound/hda/codecs/side-codecs/Kconfig +@@ -13,6 +13,24 @@ config SND_HDA_CIRRUS_SCODEC_KUNIT_TEST + Documentation/dev-tools/kunit/. + If in doubt, say "N". + ++config SND_HDA_SCODEC_AW88399 ++ tristate ++ select SND_HDA_GENERIC ++ ++config SND_HDA_SCODEC_AW88399_I2C ++ tristate "Build AW88399 HD-audio side codec support for I2C Bus" ++ depends on I2C ++ depends on ACPI ++ depends on SND_SOC ++ select SND_HDA_SCODEC_AW88399 ++ select SND_SOC_AW88399_LIB ++ help ++ Say Y or M here to include AW88399 I2C HD-audio side codec support ++ in snd-hda-intel driver, such as ALC287. ++ ++comment "Set to Y if you want auto-loading the side codec driver" ++ depends on SND_HDA=y && SND_HDA_SCODEC_AW88399_I2C=m ++ + config SND_HDA_SCODEC_CS35L41 + tristate + select SND_HDA_GENERIC +diff --git a/sound/hda/codecs/side-codecs/Makefile b/sound/hda/codecs/side-codecs/Makefile +--- a/sound/hda/codecs/side-codecs/Makefile ++++ b/sound/hda/codecs/side-codecs/Makefile +@@ -3,6 +3,8 @@ subdir-ccflags-y += -I$(src)/../../common + + snd-hda-cirrus-scodec-y := cirrus_scodec.o + snd-hda-cirrus-scodec-test-y := cirrus_scodec_test.o ++snd-hda-scodec-aw88399-y := aw88399_hda.o ++snd-hda-scodec-aw88399-i2c-y := aw88399_hda_i2c.o + snd-hda-scodec-cs35l41-y := cs35l41_hda.o cs35l41_hda_property.o + snd-hda-scodec-cs35l41-i2c-y := cs35l41_hda_i2c.o + snd-hda-scodec-cs35l41-spi-y := cs35l41_hda_spi.o +@@ -16,6 +18,8 @@ snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o + + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC) += snd-hda-cirrus-scodec.o + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC_KUNIT_TEST) += snd-hda-cirrus-scodec-test.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399) += snd-hda-scodec-aw88399.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399_I2C) += snd-hda-scodec-aw88399-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.c +@@ -0,0 +1,373 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA side codec driver ++// ++// Based on cs35l41_hda.c and aw88399.c ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../generic.h" ++#include "hda_component.h" ++#include "aw88399_hda.h" ++ ++#define AW88399_HDA_I2C_BASE_ADDR 0x34 ++ ++static void aw88399_hda_playback_hook(struct device *dev, int action) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct aw88399 *core = aw88399->core; ++ int ret = 0; ++ ++ dev_dbg(aw88399->dev, "Playback action: %d\n", action); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_OPEN: ++ pm_runtime_get_sync(dev); ++ aw88399->playing = true; ++ break; ++ case HDA_GEN_PCM_ACT_PREPARE: ++ if (core) ++ aw88399_start(core, AW88399_SYNC_START); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ if (aw88399->aw_dev) ++ ret = aw88399_stop(aw88399->aw_dev); ++ if (ret) ++ dev_err(aw88399->dev, "Failed to stop amplifier: %d\n", ret); ++ break; ++ case HDA_GEN_PCM_ACT_CLOSE: ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ aw88399->playing = false; ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_put_autosuspend(dev); ++ break; ++ default: ++ dev_warn(aw88399->dev, "Unsupported action: %d\n", action); ++ break; ++ } ++} ++ ++static int aw88399_hda_bind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (!comp) ++ return -EINVAL; ++ ++ if (comp->dev) ++ return -EBUSY; ++ ++ comp->dev = dev; ++ ++ strscpy(comp->name, dev_name(dev), sizeof(comp->name)); ++ ++ comp->playback_hook = aw88399_hda_playback_hook; ++ ++ dev_info(aw88399->dev, ++ "AW88399 Bound - SSID: %s, channel: %d\n", ++ aw88399->acpi_subsystem_id, aw88399->channel); ++ ++ return 0; ++} ++ ++static void aw88399_hda_unbind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (comp && (comp->dev == dev)) ++ memset(comp, 0, sizeof(*comp)); ++ ++ dev_dbg(aw88399->dev, "Unbound from HDA codec\n"); ++} ++ ++static const struct component_ops aw88399_hda_comp_ops = { ++ .bind = aw88399_hda_bind, ++ .unbind = aw88399_hda_unbind, ++}; ++ ++static int aw88399_hda_index_from_i2c(struct aw88399_hda *aw88399) ++{ ++ return to_i2c_client(aw88399->dev)->addr - AW88399_HDA_I2C_BASE_ADDR; ++} ++ ++static int aw88399_hda_init(struct aw88399_hda *aw88399) ++{ ++ struct device *dev = aw88399->dev; ++ struct i2c_client *i2c = to_i2c_client(dev); ++ struct aw88399 *core; ++ int ret; ++ ++ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); ++ if (!core) ++ return -ENOMEM; ++ ++ mutex_init(&core->lock); ++ core->reset_gpio = aw88399->reset_gpio; ++ core->regmap = aw88399->regmap; ++ core->bsts_unreliable = aw88399->bsts_unreliable; ++ ++ aw88399_hw_reset(core); ++ ++ ret = aw88399_init(core, i2c, aw88399->regmap); ++ if (ret) ++ return ret; ++ ++ /* Set channel BEFORE loading firmware so ACF parser sees correct value */ ++ if (core->aw_pa) ++ aw88399_dev_set_channel(core, aw88399->channel); ++ ++ ret = aw88399_request_firmware_file(core); ++ if (ret) ++ return ret; ++ ++ aw88399->core = core; ++ aw88399->aw_dev = core->aw_pa; ++ ++ return 0; ++} ++ ++static int aw88399_swap_channels(struct aw88399_hda *aw88399) ++{ ++ /* ++ * Certain Lenovo Legion laptops have their ++ * I2C wiring reversed: 0x34 is physically the right speaker, ++ * 0x35 is the left. Swap channels to correct L/R assignment. ++ * This is a model-specific hardware wiring issue, not a driver bug. ++ */ ++ aw88399->channel = 1 - aw88399->channel; ++ dev_dbg(aw88399->dev, ++ "Channel swap applied: index %d -> channel %d\n", ++ aw88399->index, aw88399->channel); ++ return 0; ++} ++ ++static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) ++{ ++ /* ++ * BSTS (boost-finished) status bit does not reliably report on ++ * some hardware. On certain Lenovo Legion laptops, both amps ++ * report BSTS=0 (boost not finished) during normal playback ++ * despite clean audio output. Skip BSTS in the startup status ++ * check to avoid false init failures. ++ */ ++ aw88399->bsts_unreliable = true; ++ dev_dbg(aw88399->dev, "BSTS status check disabled\n"); ++ return 0; ++} ++ ++static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) ++{ ++ aw88399_swap_channels(aw88399); ++ aw88399_skip_bsts_check(aw88399); ++ return 0; ++} ++ ++struct aw88399_prop_model { ++ const char *ssid; ++ int (*apply_prop)(struct aw88399_hda *aw88399); ++}; ++ ++static const struct aw88399_prop_model aw88399_prop_model_table[] = { ++ { "17AA3906", aw88399_apply_legion_quirks }, ++ { "17AA3907", aw88399_apply_legion_quirks }, ++ { "17AA3927", aw88399_apply_legion_quirks }, ++ { "17AA3928", aw88399_apply_legion_quirks }, ++ { "17AA3936", aw88399_apply_legion_quirks }, ++ { "17AA3937", aw88399_apply_legion_quirks }, ++ { "17AA3938", aw88399_apply_legion_quirks }, ++ { "17AA3939", aw88399_apply_legion_quirks }, ++ { } ++}; ++ ++static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) ++{ ++ struct acpi_device *adev; ++ const char *sub; ++ const struct aw88399_prop_model *model; ++ ++ aw88399->index = aw88399_hda_index_from_i2c(aw88399); ++ aw88399->channel = aw88399->index; ++ aw88399->acpi_subsystem_id = NULL; ++ ++ adev = acpi_dev_get_first_match_dev("AWDZ8399", NULL, -1); ++ if (!adev) { ++ dev_err(aw88399->dev, "Failed to find an ACPI device for AWDZ8399\n"); ++ return -ENODEV; ++ } ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); ++ acpi_dev_put(adev); ++ if (!physdev) ++ return -ENODEV; ++ ++ sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); ++ if (IS_ERR_OR_NULL(sub)) ++ return 0; ++ ++ aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); ++ kfree(sub); ++ if (!aw88399->acpi_subsystem_id) ++ return -ENOMEM; ++ ++ for (model = aw88399_prop_model_table; model->ssid; model++) { ++ if (!strcasecmp(model->ssid, aw88399->acpi_subsystem_id)) { ++ dev_info(aw88399->dev, ++ "Applying properties for SSID %s\n", ++ aw88399->acpi_subsystem_id); ++ return model->apply_prop(aw88399); ++ } ++ } ++ ++ return 0; ++} ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap) ++{ ++ struct aw88399_hda *aw88399; ++ int ret; ++ ++ aw88399 = devm_kzalloc(dev, sizeof(*aw88399), GFP_KERNEL); ++ if (!aw88399) ++ return -ENOMEM; ++ ++ if (IS_ERR(regmap)) ++ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to obtain regmap\n"); ++ ++ aw88399->dev = dev; ++ aw88399->regmap = regmap; ++ dev_set_drvdata(dev, aw88399); ++ ++ aw88399->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); ++ if (IS_ERR(aw88399->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(aw88399->reset_gpio), ++ "Failed to get reset GPIO\n"); ++ ++ ret = aw88399_hda_acpi_probe(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "ACPI probe failed\n"); ++ ++ ret = aw88399_hda_init(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "Chip initialization failed\n"); ++ ++ /* Enable runtime PM */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ ret = component_add(dev, &aw88399_hda_comp_ops); ++ if (ret) { ++ pm_runtime_disable(dev); ++ return dev_err_probe(dev, ret, "Failed to register component\n"); ++ } ++ ++ dev_info(dev, "AW88399 HDA side codec registered successfully\n"); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_probe, "SND_HDA_SCODEC_AW88399"); ++ ++void aw88399_hda_remove(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ pm_runtime_disable(dev); ++ ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ ++ component_del(dev, &aw88399_hda_comp_ops); ++ ++ dev_dbg(aw88399->dev, "AW88399 HDA side codec removed\n"); ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_remove, "SND_HDA_SCODEC_AW88399"); ++ ++static int aw88399_hda_runtime_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ return 0; ++} ++ ++static int aw88399_hda_runtime_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime resume\n"); ++ ++ if (aw88399->core && aw88399->aw_dev && aw88399->playing) ++ aw88399_start(aw88399->core, AW88399_SYNC_START); ++ ++ return 0; ++} ++ ++static int aw88399_hda_system_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ if (aw88399->core) ++ aw88399->core->fw_needs_reload = true; ++ ++ ret = pm_runtime_force_suspend(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force suspend failed: %d\n", ret); ++ ++ return ret; ++} ++ ++static int aw88399_hda_system_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System resume\n"); ++ ++ if (aw88399->aw_dev) ++ aw88399_hw_reset(aw88399->core); ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force resume failed: %d\n", ret); ++ ++ return ret; ++} ++ ++const struct dev_pm_ops aw88399_hda_pm_ops = { ++ RUNTIME_PM_OPS(aw88399_hda_runtime_suspend, aw88399_hda_runtime_resume, NULL) ++ SYSTEM_SLEEP_PM_OPS(aw88399_hda_system_suspend, aw88399_hda_system_resume) ++}; ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); ++ ++MODULE_DESCRIPTION("AW88399 HDA driver"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_AUTHOR("Marco Giunta "); ++MODULE_LICENSE("GPL"); ++MODULE_FIRMWARE("aw88399_acf.bin"); +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.h +@@ -0,0 +1,36 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * AW88399 HDA side codec driver ++ */ ++ ++#ifndef __AW88399_HDA_H__ ++#define __AW88399_HDA_H__ ++ ++#include ++#include ++#include ++ ++struct aw88399; ++struct aw_device; ++ ++struct aw88399_hda { ++ struct device *dev; ++ struct regmap *regmap; ++ struct gpio_desc *reset_gpio; ++ struct aw_device *aw_dev; ++ struct aw88399 *core; ++ bool bsts_unreliable; ++ ++ const char *acpi_subsystem_id; ++ int index; ++ int channel; ++ ++ bool playing; ++}; ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap); ++void aw88399_hda_remove(struct device *dev); ++ ++extern const struct dev_pm_ops aw88399_hda_pm_ops; ++ ++#endif /* __AW88399_HDA_H__ */ +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +@@ -0,0 +1,54 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA I2C driver ++// ++// Based on cs35l41_hda_i2c.c ++// ++ ++#include ++#include ++#include ++ ++#include "aw88399_hda.h" ++ ++static int aw88399_hda_i2c_probe(struct i2c_client *clt) ++{ ++ if (!strstr(dev_name(&clt->dev), "AWDZ8399")) ++ return -ENODEV; ++ ++ return aw88399_hda_probe(&clt->dev, ++ devm_regmap_init_i2c(clt, &aw88399_remap_config)); ++} ++ ++static void aw88399_hda_i2c_remove(struct i2c_client *clt) ++{ ++ aw88399_hda_remove(&clt->dev); ++} ++ ++static const struct i2c_device_id aw88399_hda_i2c_id[] = { ++ { .name = "aw88399-hda" }, ++ { } ++}; ++ ++static const struct acpi_device_id aw88399_acpi_hda_match[] = { ++ { "AWDZ8399", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, aw88399_acpi_hda_match); ++ ++static struct i2c_driver aw88399_hda_i2c_driver = { ++ .driver = { ++ .name = "aw88399-hda", ++ .acpi_match_table = aw88399_acpi_hda_match, ++ .pm = &aw88399_hda_pm_ops, ++ }, ++ .probe = aw88399_hda_i2c_probe, ++ .remove = aw88399_hda_i2c_remove, ++ .id_table = aw88399_hda_i2c_id, ++}; ++module_i2c_driver(aw88399_hda_i2c_driver); ++ ++MODULE_DESCRIPTION("HDA AW88399 I2C driver"); ++MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l41_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c +@@ -170,6 +170,7 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + char *s, c; + int ret = 0; + ++ *firmware = NULL; + if (spkid > -1 && ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "cirrus/%s-%s-%s-%s-spkid%d-%s.%s", CS35L41_PART, + dsp_name, cs35l41_hda_fw_ids[cs35l41->firmware_type], +@@ -528,8 +529,8 @@ static int cs35l41_read_tuning_params(struct cs35l41_hda *cs35l41, const struct + + static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_filename) + { +- const struct firmware *tuning_param_file = NULL; +- char *tuning_param_filename = NULL; ++ const struct firmware *tuning_param_file __free(firmware) = NULL; ++ char *tuning_param_filename __free(kfree) = NULL; + int ret; + + ret = cs35l41_request_tuning_param_file(cs35l41, tuning_filename, &tuning_param_file, +@@ -548,19 +549,16 @@ static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_ + cs35l41_set_default_tuning_params(cs35l41); + } + +- release_firmware(tuning_param_file); +- kfree(tuning_param_filename); +- + return ret; + } + + static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + int ret; + + if (!cs35l41->halo_initialized) { +@@ -592,20 +590,13 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, + cs35l41_hda_fw_ids[cs35l41->firmware_type]); +- if (ret) +- goto err; ++ if (ret) { ++ cs35l41_set_default_tuning_params(cs35l41); ++ return ret; ++ } + + cs35l41_hda_apply_calibration(cs35l41); +- +-err: +- if (ret) +- cs35l41_set_default_tuning_params(cs35l41); +- release_firmware(wmfw_firmware); +- release_firmware(coeff_firmware); +- kfree(wmfw_filename); +- kfree(coeff_filename); +- +- return ret; ++ return 0; + } + + static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) +@@ -1924,7 +1915,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) + { + struct acpi_device *adev; +- struct device *physdev; + struct spi_device *spi; + const char *sub; + int ret; +@@ -1936,7 +1926,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + cs35l41->dacpi = adev; +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + if (!physdev) { + acpi_dev_put(adev); + return -ENODEV; +@@ -1954,13 +1945,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + ret = cs35l41_hda_parse_acpi(cs35l41, physdev, id); +- if (ret) { +- put_device(physdev); ++ if (ret) + return ret; +- } + out: +- put_device(physdev); +- + cs35l41->bypass_fw = false; + if (cs35l41->control_bus == SPI) { + spi = to_spi_device(cs35l41->dev); +diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l56_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c +@@ -527,18 +527,6 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56, + base_name, NULL, NULL, "bin"); + } + +-static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmware, +- char *wmfw_filename, +- const struct firmware *coeff_firmware, +- char *coeff_filename) +-{ +- release_firmware(wmfw_firmware); +- kfree(wmfw_filename); +- +- release_firmware(coeff_firmware); +- kfree(coeff_filename); +-} +- + static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + { + int ret; +@@ -561,10 +549,10 @@ static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + + static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + unsigned int preloaded_fw_ver; + bool firmware_missing; + int ret; +@@ -606,14 +594,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing) { + if (!wmfw_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "wmfw"); +- goto err_fw_release; ++ return; + } else if (!coeff_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "bin"); +- goto err_fw_release; ++ return; + } + } + +- mutex_lock(&cs35l56->base.irq_lock); ++ guard(mutex)(&cs35l56->base.irq_lock); + + /* + * If the firmware hasn't been patched it must be shutdown before +@@ -624,14 +612,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing && (wmfw_firmware || coeff_firmware)) { + ret = cs35l56_firmware_shutdown(&cs35l56->base); + if (ret) +- goto err; ++ return; + } + + ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, "misc"); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret); +- goto err; ++ return; + } + + if (wmfw_filename) +@@ -679,11 +667,6 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + err_powered_up: + if (!cs35l56->base.fw_patched) + cs_dsp_power_down(&cs35l56->cs_dsp); +-err: +- mutex_unlock(&cs35l56->base.irq_lock); +-err_fw_release: +- cs35l56_hda_release_firmware_files(wmfw_firmware, wmfw_filename, +- coeff_firmware, coeff_filename); + } + + static void cs35l56_hda_dsp_work(struct work_struct *work) +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -88,7 +88,6 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + { + struct gpio_desc *speaker_id; + struct acpi_device *adev; +- struct device *physdev; + LIST_HEAD(resources); + const char *sub; + uint32_t subid; +@@ -101,7 +100,8 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + return -ENODEV; + } + +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); + if (ret < 0) { + dev_err(p->dev, "Failed to get ACPI resource.\n"); +@@ -151,14 +151,12 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + end_2563: + acpi_dev_free_resource_list(&resources); + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- put_device(physdev); + acpi_dev_put(adev); + + return 0; + + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); + acpi_dev_put(adev); + + return ret; +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -328,7 +328,6 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + { + struct tasdevice_priv *p = tas_hda->priv; + struct acpi_device *adev; +- struct device *physdev; + u32 values[HDA_MAX_COMPONENTS]; + const char *property; + size_t nval; +@@ -341,7 +340,9 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + } + + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + if (!physdev) + return -ENODEV; +@@ -381,13 +382,11 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + goto err; + } + } +- put_device(physdev); + + return 0; ++ + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); +- + return ret; + } + +diff --git a/sound/hda/common/auto_parser.c b/sound/hda/common/auto_parser.c +--- a/sound/hda/common/auto_parser.c ++++ b/sound/hda/common/auto_parser.c +@@ -601,9 +601,9 @@ static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) + return -1; + } + +-/* get a unique suffix or an index number */ ++/* get a unique suffix */ + static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, +- int num_pins, int *indexp) ++ int num_pins) + { + static const char * const channel_sfx[] = { + " Front", " Surround", " CLFE", " Side" +@@ -615,11 +615,8 @@ static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, + return NULL; + if (num_pins == 1) + return ""; +- if (num_pins > ARRAY_SIZE(channel_sfx)) { +- if (indexp) +- *indexp = i; ++ if (num_pins > ARRAY_SIZE(channel_sfx)) + return ""; +- } + return channel_sfx[i]; + } + +@@ -638,27 +635,9 @@ static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid) + return ""; + } + +-static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid, +- const hda_nid_t *pins, int num_pins) +-{ +- int i, j, idx = 0; +- +- const char *pfx = check_output_pfx(codec, nid); +- +- i = find_idx_in_nid_list(nid, pins, num_pins); +- if (i < 0) +- return -1; +- for (j = 0; j < i; j++) +- if (pfx == check_output_pfx(codec, pins[j])) +- idx++; +- +- return idx; +-} +- + static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- const char *name, char *label, int maxlen, +- int *indexp) ++ const char *name, char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int attr = snd_hda_get_input_pin_attr(def_conf); +@@ -671,19 +650,11 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + + if (cfg) { + /* try to give a unique suffix if needed */ +- sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs, +- indexp); ++ sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs); ++ if (!sfx) ++ sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs); + if (!sfx) +- sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs, +- indexp); +- if (!sfx) { +- /* don't add channel suffix for Headphone controls */ +- int idx = get_hp_label_index(codec, nid, cfg->hp_pins, +- cfg->hp_outs); +- if (idx >= 0 && indexp) +- *indexp = idx; + sfx = ""; +- } + } + snprintf(label, maxlen, "%s%s%s", pfx, name, sfx); + return 1; +@@ -699,7 +670,6 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * @cfg: the parsed pin configuration + * @label: the string buffer to store + * @maxlen: the max length of string buffer (including termination) +- * @indexp: the pointer to return the index number (for multiple ctls) + * + * Get a label for the given pin. This function works for both input and + * output pins. When @cfg is given as non-NULL, the function tries to get +@@ -708,47 +678,33 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * This function tries to give a unique label string for the pin as much as + * possible. For example, when the multiple line-outs are present, it adds + * the channel suffix like "Front", "Surround", etc (only when @cfg is given). +- * If no unique name with a suffix is available and @indexp is non-NULL, the +- * index number is stored in the pointer. + */ + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp) ++ char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + const char *name = NULL; + int i; + bool hdmi; + +- if (indexp) +- *indexp = 0; + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + return 0; + + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + return fill_audio_out_name(codec, nid, cfg, "Line Out", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPEAKER: + return fill_audio_out_name(codec, nid, cfg, "Speaker", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_HP_OUT: + return fill_audio_out_name(codec, nid, cfg, "Headphone", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + hdmi = is_hdmi_cfg(def_conf); + name = hdmi ? "HDMI" : "SPDIF"; +- if (cfg && indexp) +- for (i = 0; i < cfg->dig_outs; i++) { +- hda_nid_t pin = cfg->dig_out_pins[i]; +- unsigned int c; +- if (pin == nid) +- break; +- c = snd_hda_codec_get_pincfg(codec, pin); +- if (hdmi == is_hdmi_cfg(c)) +- (*indexp)++; +- } + break; + default: + if (cfg) { +diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c +--- a/sound/hda/common/codec.c ++++ b/sound/hda/common/codec.c +@@ -39,6 +39,12 @@ static int call_exec_verb(struct hda_bus *bus, struct hda_codec *codec, + int err; + + CLASS(snd_hda_power_pm, pm)(codec); ++ if (pm.err < 0 && !bus->core.chip_init) { ++ codec_warn(codec, ++ "Failed to send cmd 0x%x ret=[%d], hda control stopped\n", ++ cmd, pm.err); ++ return pm.err; ++ } + guard(mutex)(&bus->core.cmd_mutex); + if (flags & HDA_RW_NO_RESPONSE_FALLBACK) + bus->no_response_fallback = 1; +@@ -2969,8 +2975,11 @@ static void hda_codec_pm_complete(struct device *dev) + dev->power.power_state = PMSG_RESUME; + + if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || +- hda_codec_need_resume(codec) || codec->forced_resume)) ++ hda_codec_need_resume(codec) || codec->forced_resume || ++ codec->acomp_requested_resume)) { ++ codec->acomp_requested_resume = 0; + pm_request_resume(dev); ++ } + } + + static int hda_codec_pm_suspend(struct device *dev) +@@ -3372,7 +3381,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, + for (; knew->name; knew++) { + struct snd_kcontrol *kctl; + int addr = 0, idx = 0; +- if (knew->iface == (__force snd_ctl_elem_iface_t)-1) ++ if (knew->iface == -1) + continue; /* skip this codec private value */ + for (;;) { + kctl = snd_ctl_new1(knew, codec); +diff --git a/sound/hda/common/hda_auto_parser.h b/sound/hda/common/hda_auto_parser.h +--- a/sound/hda/common/hda_auto_parser.h ++++ b/sound/hda/common/hda_auto_parser.h +@@ -46,7 +46,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, + int input); + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp); ++ char *label, int maxlen); + + enum { + INPUT_PIN_ATTR_UNUSED, /* pin not connected */ +diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h +--- a/sound/hda/common/hda_local.h ++++ b/sound/hda/common/hda_local.h +@@ -723,4 +723,11 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable); + #define codec_dbg(codec, fmt, args...) \ + dev_dbg(hda_codec_dev(codec), fmt, ##args) + ++/* append a suffix string safely; equivalent with strlcat() */ ++static inline void hda_append_suffix(char *str, const char *suffix, size_t size) ++{ ++ size_t len = strnlen(str, size); ++ strscpy(str + len, suffix, size - len); ++} ++ + #endif /* __SOUND_HDA_LOCAL_H */ +diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c +--- a/sound/hda/common/jack.c ++++ b/sound/hda/common/jack.c +@@ -612,10 +612,10 @@ static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, + if (base_name) + strscpy(name, base_name, sizeof(name)); + else +- snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL); ++ snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name)); + if (phantom_jack) + /* Example final name: "Internal Mic Phantom Jack" */ +- strncat(name, " Phantom", sizeof(name) - strlen(name) - 1); ++ hda_append_suffix(name, " Phantom", sizeof(name)); + err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL); + if (err < 0) + return err; +diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c +--- a/sound/hda/controllers/intel.c ++++ b/sound/hda/controllers/intel.c +@@ -100,6 +100,8 @@ enum { + #define ATIHDMI_NUM_CAPTURE 0 + #define ATIHDMI_NUM_PLAYBACK 8 + ++/* Hygon HD Audio controller */ ++#define PCI_DEVICE_ID_HYGON_18H_M05H_HDA 0x14a9 + + static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; + static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +@@ -241,6 +243,7 @@ enum { + AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_ZHAOXINHDMI, + AZX_DRIVER_LOONGSON, ++ AZX_DRIVER_HYGON, + AZX_DRIVER_GENERIC, + AZX_NUM_DRIVERS, /* keep this as last entry */ + }; +@@ -360,6 +363,7 @@ static const char * const driver_short_names[] = { + [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_ZHAOXINHDMI] = "HDA Zhaoxin HDMI", + [AZX_DRIVER_LOONGSON] = "HDA Loongson", ++ [AZX_DRIVER_HYGON] = "HDA Hygon", + [AZX_DRIVER_GENERIC] = "HD-Audio Generic", + }; + +@@ -1926,6 +1930,10 @@ static int azx_first_init(struct azx *chip) + if (chip->driver_type == AZX_DRIVER_ZHAOXINHDMI) + bus->polling_mode = 1; + ++ if (chip->driver_type == AZX_DRIVER_HYGON && ++ chip->pci->device == PCI_DEVICE_ID_HYGON_18H_M05H_HDA) ++ bus->access_sdnctl_in_dword = 1; ++ + bus->remap_addr = pcim_iomap_region(pci, 0, "ICH HD audio"); + if (IS_ERR(bus->remap_addr)) + return PTR_ERR(bus->remap_addr); +@@ -2189,6 +2197,15 @@ static int azx_probe(struct pci_dev *pci, + dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n"); + } + ++ /* A sanity check against wild device binding; ++ * here the range 0x200 is enough for the registers used at probe, ++ * but it doesn't mean covering all HD-audio registers ++ */ ++ if (pci_resource_len(pci, 0) < 0x200) { ++ dev_err(&pci->dev, "Too small PCI BAR0\n"); ++ return -EINVAL; ++ } ++ + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { +@@ -2381,7 +2398,7 @@ static int azx_probe_continue(struct azx *chip) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (patch[dev] && *patch[dev]) { +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&pci->dev, "Applying patch firmware '%s'\n", + patch[dev]); +@@ -2390,7 +2407,6 @@ static int azx_probe_continue(struct azx *chip) + "Cannot load firmware, continue without patching\n"); + } else { + err = snd_hda_load_patch(&chip->bus, fw->size, fw->data); +- release_firmware(fw); + if (err < 0) + goto out_free; + } +@@ -2838,6 +2854,11 @@ static const struct pci_device_id azx_ids[] = { + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, + { PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, ++ /* Hygon HDAudio */ ++ { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_HDA), ++ .driver_data = AZX_DRIVER_HYGON | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_NO_MSI }, ++ /* Lisuan HD-audio */ ++ { PCI_DEVICE(0x4c54, 0x5010), .driver_data = AZX_DRIVER_GENERIC }, + { 0, } + }; + MODULE_DEVICE_TABLE(pci, azx_ids); +diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c +--- a/sound/hda/core/controller.c ++++ b/sound/hda/core/controller.c +@@ -511,7 +511,10 @@ void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus) + { + unsigned long timeout; + +- snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ else ++ snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); + + timeout = jiffies + msecs_to_jiffies(100); + while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout)) +@@ -576,7 +579,10 @@ static void azx_int_disable(struct hdac_bus *bus) + + /* disable interrupts in stream descriptor */ + list_for_each_entry(azx_dev, &bus->stream_list, list) +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); + + /* disable SIE for all streams & disable controller CIE and GIE */ + snd_hdac_chip_writel(bus, INTCTL, 0); +@@ -682,6 +688,11 @@ int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status, + sd_status = snd_hdac_stream_readb(azx_dev, SD_STS); + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); + handled |= 1 << azx_dev->index; ++ if (sd_status & (SD_INT_FIFO_ERR | SD_INT_DESC_ERR)) { ++ dev_warn_ratelimited(bus->dev, ++ "stream %u dma error: 0x%02x\n", ++ azx_dev->index, sd_status); ++ } + if ((!azx_dev->substream && !azx_dev->cstream) || + !azx_dev->running || !(sd_status & SD_INT_COMPLETE)) + continue; +@@ -713,7 +724,7 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + BDL_SIZE, &s->bdl); + num_streams++; + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + } + + if (WARN_ON(!num_streams)) +@@ -722,12 +733,24 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + err = snd_dma_alloc_pages(dma_type, bus->dev, + num_streams * 8, &bus->posbuf); + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + list_for_each_entry(s, &bus->stream_list, list) + s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8); + + /* single page (at least 4096 bytes) must suffice for both ringbuffes */ +- return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ err = snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ if (err < 0) ++ goto error_posbuf; ++ return 0; ++ ++error_posbuf: ++ snd_dma_free_pages(&bus->posbuf); ++error_bdl: ++ list_for_each_entry(s, &bus->stream_list, list) { ++ if (s->bdl.area) ++ snd_dma_free_pages(&s->bdl); ++ } ++ return -ENOMEM; + } + EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages); + +diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -664,6 +664,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, + { 0x434d, "C-Media" }, ++ { 0x4c54, "Lisuan" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +@@ -766,7 +767,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor + + params_set_format(¶ms, snd_hdac_format_normalize(format)); + snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), +- (__force unsigned int)subformat); ++ subformat); + + bits = snd_pcm_hw_params_bits(¶ms); + if (maxbits) +diff --git a/sound/hda/core/hda_bus_type.c b/sound/hda/core/hda_bus_type.c +--- a/sound/hda/core/hda_bus_type.c ++++ b/sound/hda/core/hda_bus_type.c +@@ -39,10 +39,7 @@ EXPORT_SYMBOL_GPL(hdac_get_device_id); + + static int hdac_codec_match(struct hdac_device *dev, const struct hdac_driver *drv) + { +- if (hdac_get_device_id(dev, drv)) +- return 1; +- else +- return 0; ++ return !!hdac_get_device_id(dev, drv); + } + + static int hda_bus_match(struct device *dev, const struct device_driver *drv) +@@ -59,9 +56,7 @@ static int hda_bus_match(struct device *dev, const struct device_driver *drv) + */ + if (hdrv->match) + return hdrv->match(hdev, hdrv); +- else +- return hdac_codec_match(hdev, hdrv); +- return 1; ++ return hdac_codec_match(hdev, hdrv); + } + + static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env) +diff --git a/sound/hda/core/stream.c b/sound/hda/core/stream.c +--- a/sound/hda/core/stream.c ++++ b/sound/hda/core/stream.c +@@ -146,8 +146,12 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) + stripe_ctl = snd_hdac_get_stream_stripe_ctl(bus, azx_dev->substream); + else + stripe_ctl = 0; +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, +- stripe_ctl); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); + } + /* set DMA start and interrupt mask */ + if (bus->access_sdnctl_in_dword) +@@ -166,11 +170,22 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_start); + */ + static void snd_hdac_stream_clear(struct hdac_stream *azx_dev) + { +- snd_hdac_stream_updateb(azx_dev, SD_CTL, +- SD_CTL_DMA_START | SD_INT_MASK, 0); +- snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ +- if (azx_dev->stripe) +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ struct hdac_bus *bus = azx_dev->bus; ++ ++ if (bus->access_sdnctl_in_dword) { ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } else { ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } ++ + azx_dev->running = false; + } + +@@ -225,12 +240,16 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + { + unsigned char val; + int dma_run_state; ++ struct hdac_bus *bus = azx_dev->bus; + + snd_hdac_stream_clear(azx_dev); + + dma_run_state = snd_hdac_stream_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START; + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); + + /* wait for hardware to report that the stream entered reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, (val & SD_CTL_STREAM_RESET), 3, 300); +@@ -238,7 +257,10 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + if (azx_dev->bus->dma_stop_delay && dma_run_state) + udelay(azx_dev->bus->dma_stop_delay); + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); + + /* wait for hardware to report that the stream is out of reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, !(val & SD_CTL_STREAM_RESET), 3, 300); +diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c +--- a/sound/isa/msnd/msnd_pinnacle.c ++++ b/sound/isa/msnd/msnd_pinnacle.c +@@ -367,7 +367,8 @@ static int snd_msnd_init_sma(struct snd_msnd *chip) + static int upload_dsp_code(struct snd_card *card) + { + struct snd_msnd *chip = card->private_data; +- const struct firmware *init_fw = NULL, *perm_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; ++ const struct firmware *perm_fw __free(firmware) = NULL; + int err; + + outb(HPBLKSEL_0, chip->io + HP_BLKS); +@@ -375,28 +376,21 @@ static int upload_dsp_code(struct snd_card *card) + err = request_firmware(&init_fw, INITCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " INITCODEFILE); +- goto cleanup1; ++ return err; + } + err = request_firmware(&perm_fw, PERMCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " PERMCODEFILE); +- goto cleanup; ++ return err; + } + + memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size); + if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) { + dev_warn(card->dev, LOGNAME ": Error uploading to DSP\n"); +- err = -ENODEV; +- goto cleanup; ++ return -ENODEV; + } + dev_info(card->dev, LOGNAME ": DSP firmware uploaded\n"); +- err = 0; +- +-cleanup: +- release_firmware(perm_fw); +-cleanup1: +- release_firmware(init_fw); +- return err; ++ return 0; + } + + #ifdef MSND_CLASSIC +diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c +--- a/sound/isa/sscape.c ++++ b/sound/isa/sscape.c +@@ -526,7 +526,7 @@ static int upload_dma_data(struct soundscape *s, const unsigned char *data, + static int sscape_upload_bootblock(struct snd_card *card) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + int data = 0; + int ret; + +@@ -537,8 +537,6 @@ static int sscape_upload_bootblock(struct snd_card *card) + } + ret = upload_dma_data(sscape, init_fw->data, init_fw->size); + +- release_firmware(init_fw); +- + guard(spinlock_irqsave)(&sscape->lock); + if (ret == 0) + data = host_read_ctrl_unsafe(sscape->io_base, 100); +@@ -562,7 +560,7 @@ static int sscape_upload_bootblock(struct snd_card *card) + static int sscape_upload_microcode(struct snd_card *card, int version) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + char name[14]; + int err; + +@@ -579,8 +577,6 @@ static int sscape_upload_microcode(struct snd_card *card, int version) + dev_info(card->dev, "sscape: MIDI firmware loaded %zu KBs\n", + init_fw->size >> 10); + +- release_firmware(init_fw); +- + return err; + } + +diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c +--- a/sound/isa/wavefront/wavefront_fx.c ++++ b/sound/isa/wavefront/wavefront_fx.c +@@ -232,41 +232,32 @@ snd_wavefront_fx_start (snd_wavefront_t *dev) + { + unsigned int i; + int err; +- const struct firmware *firmware = NULL; ++ const struct firmware *firmware __free(firmware) = NULL; + + if (dev->fx_initialized) + return 0; + + err = request_firmware(&firmware, "yamaha/yss225_registers.bin", + dev->card->dev); +- if (err < 0) { +- err = -1; +- goto out; +- } ++ if (err < 0) ++ return -1; + + for (i = 0; i + 1 < firmware->size; i += 2) { + if (firmware->data[i] >= 8 && firmware->data[i] < 16) { + outb(firmware->data[i + 1], + dev->base + firmware->data[i]); + } else if (firmware->data[i] == WAIT_IDLE) { +- if (!wavefront_fx_idle(dev)) { +- err = -1; +- goto out; +- } ++ if (!wavefront_fx_idle(dev)) ++ return -1; + } else { + dev_err(dev->card->dev, + "invalid address in register data\n"); +- err = -1; +- goto out; ++ return -1; + } + } + + dev->fx_initialized = 1; +- err = 0; +- +-out: +- release_firmware(firmware); +- return err; ++ return 0; + } + + MODULE_FIRMWARE("yamaha/yss225_registers.bin"); +diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c +--- a/sound/isa/wavefront/wavefront_synth.c ++++ b/sound/isa/wavefront/wavefront_synth.c +@@ -2053,7 +2053,7 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + const unsigned char *buf; + int len, err; + int section_cnt_downloaded = 0; +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + + err = request_firmware(&firmware, path, dev->card->dev); + if (err < 0) { +@@ -2108,11 +2108,9 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + section_cnt_downloaded++; + } + +- release_firmware(firmware); + return 0; + + failure: +- release_firmware(firmware); + dev_err(dev->card->dev, "firmware download failed!!!\n"); + return 1; + } +diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c +--- a/sound/pci/asihpi/asihpi.c ++++ b/sound/pci/asihpi/asihpi.c +@@ -280,7 +280,7 @@ static void print_hwparams(struct snd_pcm_substream *substream, + snd_pcm_format_width(params_format(p)) / 8); + } + +-#define INVALID_FORMAT (__force snd_pcm_format_t)(-1) ++#define INVALID_FORMAT -1 + + static const snd_pcm_format_t hpi_to_alsa_formats[] = { + INVALID_FORMAT, /* INVALID */ +diff --git a/sound/pci/asihpi/hpidspcd.c b/sound/pci/asihpi/hpidspcd.c +--- a/sound/pci/asihpi/hpidspcd.c ++++ b/sound/pci/asihpi/hpidspcd.c +@@ -23,7 +23,7 @@ struct dsp_code_private { + short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + u32 *os_error_code) + { +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + struct pci_dev *dev = os_data; + struct code_header header; + char fw_name[20]; +@@ -37,11 +37,11 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + if (err || !firmware) { + dev_err(&dev->dev, "%d, request_firmware failed for %s\n", + err, fw_name); +- goto error1; ++ goto error; + } + if (firmware->size < sizeof(header)) { + dev_err(&dev->dev, "Header size too small %s\n", fw_name); +- goto error2; ++ goto error; + } + memcpy(&header, firmware->data, sizeof(header)); + +@@ -51,7 +51,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Invalid firmware header size %d != file %zd\n", + header.size, firmware->size); +- goto error2; ++ goto error; + } + + if (HPI_VER_MAJOR(header.version) != HPI_VER_MAJOR(HPI_VER)) { +@@ -59,7 +59,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Incompatible firmware version DSP image %X != Driver %X\n", + header.version, HPI_VER); +- goto error2; ++ goto error; + } + + if (header.version != HPI_VER) { +@@ -72,19 +72,17 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dsp_code->pvt = kmalloc_obj(*dsp_code->pvt); + if (!dsp_code->pvt) { + err_ret = HPI_ERROR_MEMORY_ALLOC; +- goto error2; ++ goto error; + } + + dsp_code->pvt->dev = dev; +- dsp_code->pvt->firmware = firmware; ++ dsp_code->pvt->firmware = no_free_ptr(firmware); + dsp_code->header = header; + dsp_code->block_length = header.size / sizeof(u32); + dsp_code->word_count = sizeof(header) / sizeof(u32); + return 0; + +-error2: +- release_firmware(firmware); +-error1: ++error: + dsp_code->block_length = 0; + return err_ret; + } +diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c +--- a/sound/pci/asihpi/hpioctl.c ++++ b/sound/pci/asihpi/hpioctl.c +@@ -385,8 +385,7 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { + memlen = pci_resource_len(pci_dev, idx); + pci.ap_mem_base[idx] = +- ioremap(pci_resource_start(pci_dev, idx), +- memlen); ++ pcim_iomap(pci_dev, idx, memlen); + if (!pci.ap_mem_base[idx]) { + HPI_DEBUG_LOG(ERROR, + "ioremap failed, aborting\n"); +@@ -509,13 +508,6 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + return 0; + + err: +- while (--idx >= 0) { +- if (pci.ap_mem_base[idx]) { +- iounmap(pci.ap_mem_base[idx]); +- pci.ap_mem_base[idx] = NULL; +- } +- } +- + if (adapter.p_buffer) { + adapter.buffer_size = 0; + vfree(adapter.p_buffer); +@@ -527,14 +519,11 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + + void asihpi_adapter_remove(struct pci_dev *pci_dev) + { +- int idx; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter *pa; +- struct hpi_pci pci; + + pa = pci_get_drvdata(pci_dev); +- pci = pa->adapter->pci; + + /* Disable IRQ generation on DSP side */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, +@@ -550,10 +539,6 @@ void asihpi_adapter_remove(struct pci_dev *pci_dev) + hm.adapter_index = pa->adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + +- /* unmap PCI memory space, mapped during device init. */ +- for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; ++idx) +- iounmap(pci.ap_mem_base[idx]); +- + if (pa->irq) + free_irq(pa->irq, pa); + +diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c +--- a/sound/pci/cs46xx/cs46xx_lib.c ++++ b/sound/pci/cs46xx/cs46xx_lib.c +@@ -387,7 +387,7 @@ static int load_firmware(struct snd_cs46xx *chip, + unsigned int nums, fwlen, fwsize; + const __le32 *fwdat; + struct dsp_module_desc *module = NULL; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + char fw_path[32]; + + sprintf(fw_path, "cs46xx/%s", fw_name); +@@ -395,10 +395,8 @@ static int load_firmware(struct snd_cs46xx *chip, + if (err < 0) + return err; + fwsize = fw->size / 4; +- if (fwsize < 2) { +- err = -EINVAL; +- goto error; +- } ++ if (fwsize < 2) ++ return -EINVAL; + + err = -ENOMEM; + module = kzalloc_obj(*module); +@@ -454,14 +452,12 @@ static int load_firmware(struct snd_cs46xx *chip, + } + + *module_ret = module; +- release_firmware(fw); + return 0; + + error_inval: + err = -EINVAL; + error: + free_module_desc(module); +- release_firmware(fw); + return err; + } + +@@ -500,22 +496,18 @@ MODULE_FIRMWARE("cs46xx/ba1"); + + static int load_firmware(struct snd_cs46xx *chip) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int i, size, err; + + err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev); + if (err < 0) + return err; +- if (fw->size != sizeof(*chip->ba1)) { +- err = -EINVAL; +- goto error; +- } ++ if (fw->size != sizeof(*chip->ba1)) ++ return -EINVAL; + + chip->ba1 = vmalloc(sizeof(*chip->ba1)); +- if (!chip->ba1) { +- err = -ENOMEM; +- goto error; +- } ++ if (!chip->ba1) ++ return -ENOMEM; + + memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1)); + +@@ -524,11 +516,9 @@ static int load_firmware(struct snd_cs46xx *chip) + for (i = 0; i < BA1_MEMORY_COUNT; i++) + size += chip->ba1->memory[i].size; + if (size > BA1_DWORD_SIZE * 4) +- err = -EINVAL; ++ return -EINVAL; + +- error: +- release_firmware(fw); +- return err; ++ return 0; + } + + static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip) +diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c +--- a/sound/pci/emu10k1/emufx.c ++++ b/sound/pci/emu10k1/emufx.c +@@ -990,7 +990,7 @@ static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu, + i < icode->gpr_list_control_count) { + memset(gctl, 0, sizeof(*gctl)); + id = &ctl->kcontrol->id; +- gctl->id.iface = (__force int)id->iface; ++ gctl->id.iface = id->iface; + strscpy(gctl->id.name, id->name, sizeof(gctl->id.name)); + gctl->id.index = id->index; + gctl->id.device = id->device; +@@ -1156,7 +1156,7 @@ static void + snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + if (high_res_gpr_volume) { +@@ -1180,7 +1180,7 @@ static void + snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + if (high_res_gpr_volume) { +@@ -1205,7 +1205,7 @@ static void + snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1218,7 +1218,7 @@ static void + snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1542,7 +1542,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[nctl + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -1551,7 +1551,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + ctl->value[0] = ctl->value[1] = 20; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[nctl + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2137,7 +2137,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[i + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2147,7 +2147,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + ctl->tlv = snd_emu10k1_bass_treble_db_scale; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[i + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c +--- a/sound/pci/korg1212/korg1212.c ++++ b/sound/pci/korg1212/korg1212.c +@@ -1980,7 +1980,7 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + __maybe_unused unsigned ioport_size; + __maybe_unused unsigned iomem2_size; + struct snd_korg1212 *korg1212 = card->private_data; +- const struct firmware *dsp_code; ++ const struct firmware *dsp_code __free(firmware) = NULL; + + err = pcim_enable_device(pci); + if (err < 0) +@@ -2147,10 +2147,8 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + korg1212->dma_dsp = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + dsp_code->size); +- if (!korg1212->dma_dsp) { +- release_firmware(dsp_code); ++ if (!korg1212->dma_dsp) + return -ENOMEM; +- } + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP Code area = 0x%p (0x%08x) %d bytes [%s]\n", + korg1212->dma_dsp->area, korg1212->dma_dsp->addr, dsp_code->size, +@@ -2158,8 +2156,6 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + memcpy(korg1212->dma_dsp->area, dsp_code->data, dsp_code->size); + +- release_firmware(dsp_code); +- + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_RebootCard, 0, 0, 0, 0); + + if (rc) +diff --git a/sound/pci/mixart/mixart_hwdep.c b/sound/pci/mixart/mixart_hwdep.c +--- a/sound/pci/mixart/mixart_hwdep.c ++++ b/sound/pci/mixart/mixart_hwdep.c +@@ -560,12 +560,11 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + "miXart8.xlx", "miXart8.elf", "miXart8AES.xlx" + }; + char path[32]; +- +- const struct firmware *fw_entry; + int i, err; + + for (i = 0; i < 3; i++) { + sprintf(path, "mixart/%s", fw_files[i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "miXart: can't load firmware %s\n", path); +@@ -573,7 +572,6 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + } + /* fake hwdep dsp record */ + err = mixart_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/pcxhr/pcxhr_hwdep.c b/sound/pci/pcxhr/pcxhr_hwdep.c +--- a/sound/pci/pcxhr/pcxhr_hwdep.c ++++ b/sound/pci/pcxhr/pcxhr_hwdep.c +@@ -367,7 +367,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + }; + char path[32]; + +- const struct firmware *fw_entry; + int i, err; + int fw_set = mgr->fw_file_set; + +@@ -375,6 +374,7 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + if (!fw_files[fw_set][i]) + continue; + sprintf(path, "pcxhr/%s", fw_files[fw_set][i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "pcxhr: can't load firmware %s\n", +@@ -383,7 +383,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + } + /* fake hwdep dsp record */ + err = pcxhr_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c +--- a/sound/pci/sis7019.c ++++ b/sound/pci/sis7019.c +@@ -1260,16 +1260,16 @@ static int sis_chip_create(struct snd_card *card, + sis->irq = -1; + sis->ioport = pci_resource_start(pci, 0); + +- rc = pcim_request_all_regions(pci, "SiS7019"); ++ rc = pcim_request_region(pci, 0, "SiS7019"); + if (rc) { +- dev_err(&pci->dev, "unable request regions\n"); ++ dev_err(&pci->dev, "unable request I/O region\n"); + return rc; + } + +- sis->ioaddr = devm_ioremap(&pci->dev, pci_resource_start(pci, 1), 0x4000); +- if (!sis->ioaddr) { ++ sis->ioaddr = pcim_iomap_region(pci, 1, "SiS7019"); ++ if (IS_ERR(sis->ioaddr)) { + dev_err(&pci->dev, "unable to remap MMIO, aborting\n"); +- return -EIO; ++ return PTR_ERR(sis->ioaddr); + } + + rc = sis_alloc_suspend(sis); +diff --git a/sound/sh/aica.c b/sound/sh/aica.c +--- a/sound/sh/aica.c ++++ b/sound/sh/aica.c +@@ -518,8 +518,9 @@ static const struct snd_kcontrol_new snd_aica_pcmvolume_control = { + static int load_aica_firmware(void) + { + int err; +- const struct firmware *fw_entry; + spu_reset(); ++ ++ const struct firmware *fw_entry __free(firmware) = NULL; + err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); + if (unlikely(err)) + return err; +@@ -527,7 +528,6 @@ static int load_aica_firmware(void) + spu_disable(); + spu_memload(0, fw_entry->data, fw_entry->size); + spu_enable(); +- release_firmware(fw_entry); + return err; + } + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -726,6 +726,10 @@ config SND_SOC_AW87390 + sound quality, which is a new high efficiency, low + noise, constant large volume, 6th Smart K audio amplifier. + ++config SND_SOC_AW88399_LIB ++ tristate ++ select SND_SOC_AW88395_LIB ++ + config SND_SOC_AW88399 + tristate "Soc Audio for awinic aw88399" + depends on I2C +@@ -733,6 +737,7 @@ config SND_SOC_AW88399 + select REGMAP_I2C + select GPIOLIB + select SND_SOC_AW88395_LIB ++ select SND_SOC_AW88399_LIB + help + This option enables support for aw88399 Smart PA. + The awinic AW88399 is an I2S/TDM input, high efficiency +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -55,6 +55,7 @@ snd-soc-aw88395-y := aw88395/aw88395.o + snd-soc-aw88166-y := aw88166.o + snd-soc-aw88261-y := aw88261.o + snd-soc-aw88399-y := aw88399.o ++snd-soc-aw88399-lib-y := aw88399-lib.o + snd-soc-bd28623-y := bd28623.o + snd-soc-bt-sco-y := bt-sco.o + snd-soc-chv3-codec-y := chv3-codec.o +@@ -496,6 +497,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o + obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o + obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o + obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o ++obj-$(CONFIG_SND_SOC_AW88399_LIB) += snd-soc-aw88399-lib.o + obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o + obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o + obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -0,0 +1,1411 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// aw88399-lib.c -- AW88399 Common functions for ASoC and HDA audio drivers ++// ++// Copyright (c) 2023 AWINIC Technology CO., LTD ++// ++// Author: Weidong Wang ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "aw88395/aw88395_device.h" ++ ++const struct regmap_config aw88399_remap_config = { ++ .val_bits = 16, ++ .reg_bits = 8, ++ .max_register = AW88399_REG_MAX, ++ .reg_format_endian = REGMAP_ENDIAN_LITTLE, ++ .val_format_endian = REGMAP_ENDIAN_BIG, ++}; ++EXPORT_SYMBOL_GPL(aw88399_remap_config); ++ ++static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) ++{ ++ int ret; ++ ++ if (pwd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); ++ if (ret) ++ dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); ++ else ++ *int_status = reg_val; ++ ++ dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); ++} ++ ++static void aw_dev_clear_int_status(struct aw_device *aw_dev) ++{ ++ u16 int_status; ++ ++ /* read int status and clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ /* make sure int status is clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ if (int_status) ++ dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); ++} ++ ++static int aw_dev_get_iis_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { ++ dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode1 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ reg_val &= (~AW88399_CCO_MUX_MASK); ++ if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { ++ dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); ++ return -EPERM; ++ } ++ ++ /* change mode2 */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ ++ /* change mode1 */ ++ regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); ++ if (ret == 0) { ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ } ++ ++ return ret; ++} ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev) ++{ ++ int ret; ++ ++ ret = aw_dev_check_mode1_pll(aw_dev); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); ++ ret = aw_dev_check_mode2_pll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 check iis failed"); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_check_syspll); ++ ++static int aw_dev_check_sysst(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int check_val; ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) ++ check_val = AW88399_BIT_SYSST_NOSWS_CHECK; ++ else ++ check_val = AW88399_BIT_SYSST_SWS_CHECK; ++ ++ /* ++ * On some hardware the BSTS (boost-finished) status bit does not ++ * reliably assert even when audio output is working normally. ++ * Allow per-instance bypass when flagged by the side-codec driver. ++ */ ++ if (aw88399->bsts_unreliable) ++ check_val &= ~AW88399_BSTS_FINISHED_VALUE; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { ++ dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", ++ i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) ++{ ++ int ret; ++ ++ if (amppd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) ++{ ++ int ret; ++ ++ if (is_enable) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed\n", __func__); ++} ++EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); ++ ++static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) ++{ ++ uint16_t icalkh_val, icalkl_val, icalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ icalk_val = icalkh_val & icalkl_val; ++ else ++ icalk_val = icalkh_val | icalkl_val; ++ ++ if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) ++ icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; ++ *icalk = (int16_t)icalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = vcalkh_val & vcalkl_val; ++ else ++ vcalk_val = vcalkh_val | vcalkl_val; ++ ++ if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) ++ vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ else ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ ++ if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) ++ vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; ++ ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw_dev_set_vcalb(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int vsense_select, vsense_value; ++ int32_t ical_k, vcal_k, vcalb; ++ int16_t icalk, vcalk; ++ uint16_t reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); ++ if (ret) ++ return ret; ++ ++ vsense_select = vsense_value & (~AW88399_VDSEL_MASK); ++ ++ ret = aw88399_dev_get_icalk(aw88399, &icalk); ++ if (ret) { ++ dev_err(aw_dev->dev, "get icalk failed\n"); ++ return ret; ++ } ++ ++ ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ ++ switch (vsense_select) { ++ case AW88399_DEV_VDSEL_VSENSE: ++ ret = aw88399_dev_get_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * ++ ical_k / vcal_k * aw88399->vcalb_init_val; ++ break; ++ case AW88399_DEV_VDSEL_DAC: ++ ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / ++ AW88399_ISCAL_DAC_FACTOR * ical_k / ++ vcal_k * aw88399->vcalb_init_val; ++ break; ++ default: ++ dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); ++ ret = -EINVAL; ++ break; ++ } ++ if (ret) ++ return ret; ++ ++ vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; ++ reg_val = (uint32_t)vcalb; ++ ++ regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); ++ ++ return 0; ++} ++ ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ uint16_t re_lbits, re_hbits; ++ u32 cali_re; ++ int ret; ++ ++ if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || ++ (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) ++ return -EINVAL; ++ ++ cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + ++ aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); ++ ++ re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; ++ re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); ++ if (ret) { ++ dev_err(aw_dev->dev, "set cali re error"); ++ return ret; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); ++ if (ret) ++ dev_err(aw_dev->dev, "set cali re error"); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); ++ ++static int aw_dev_fw_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, fw_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate fw_end_addr */ ++ fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; ++ ++ /* write fw_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, fw_len_val); ++ if (ret) ++ return ret; ++ /* enable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); ++ ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ /* read crc check result */ ++ regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", ++ __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, cfg_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate cfg end addr */ ++ cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; ++ ++ /* write cfg_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); ++ if (ret) ++ return ret; ++ ++ /* enable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ /* read crc check result */ ++ ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", ++ check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_hw_crc_check(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); ++ if (ret) ++ return ret; ++ ++ ret = aw_dev_fw_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = aw_dev_cfg_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "cfg_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ ++ return ret; ++ ++crc_check_failed: ++ regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ return ret; ++} ++ ++static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) ++{ ++ int ret; ++ ++ if (flag) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++int aw_dev_get_dsp_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); ++ if (ret) ++ return ret; ++ if (!(reg_val & (~AW88399_WDT_CNT_MASK))) ++ return -EPERM; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); ++ ++static int aw_dev_dsp_check(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ switch (aw_dev->dsp_cfg) { ++ case AW88399_DEV_DSP_BYPASS: ++ dev_dbg(aw_dev->dev, "dsp bypass"); ++ ret = 0; ++ break; ++ case AW88399_DEV_DSP_WORK: ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_dsp_enable(aw_dev, true); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { ++ ret = aw_dev_get_dsp_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } ++ } ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) ++{ ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ unsigned int reg_value; ++ u16 real_value; ++ int ret; ++ ++ real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); ++ ++ real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_set_volume); ++ ++static void aw_dev_fade_in(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ u16 fade_in_vol = desc->ctl_volume; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_in_time == 0) { ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++ return; ++ } ++ ++ for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); ++ } ++ ++ if (i != fade_in_vol) ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++} ++ ++static void aw_dev_fade_out(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_out_time == 0) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ return; ++ } ++ ++ for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++ ++ if (i != AW88399_MUTE_VOL) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++} ++ ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) ++{ ++ if (is_mute) { ++ aw_dev_fade_out(aw_dev); ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); ++ } else { ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); ++ aw_dev_fade_in(aw_dev); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_mute); ++ ++static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ ++ if (dither) ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); ++ else ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); ++} ++ ++static int aw88399_dev_start(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ if (aw_dev->status == AW88399_DEV_PW_ON) { ++ dev_dbg(aw_dev->dev, "already power on"); ++ return 0; ++ } ++ ++ aw88399_dev_set_dither(aw88399, false); ++ ++ /* power on */ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ ret = aw_dev_check_syspll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "pll check failed cannot start"); ++ goto pll_check_fail; ++ } ++ ++ /* amppd on */ ++ aw_dev_amppd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 50); ++ ++ /* check i2s status */ ++ ret = aw_dev_check_sysst(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "sysst check failed"); ++ goto sysst_check_fail; ++ } ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { ++ ret = aw_dev_hw_crc_check(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp crc check failed"); ++ goto crc_check_fail; ++ } ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_set_vcalb(aw88399); ++ aw_dev_update_cali_re(&aw_dev->cali_desc); ++ ++ ret = aw_dev_dsp_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp status check failed"); ++ goto dsp_check_fail; ++ } ++ } else { ++ dev_dbg(aw_dev->dev, "start pa with dsp bypass"); ++ } ++ ++ /* enable tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, true); ++ ++ if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) ++ aw88399_dev_set_dither(aw88399, true); ++ ++ /* close mute */ ++ aw88399_dev_mute(aw_dev, false); ++ /* clear inturrupt */ ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev->status = AW88399_DEV_PW_ON; ++ ++ return 0; ++ ++dsp_check_fail: ++crc_check_fail: ++ aw_dev_dsp_enable(aw_dev, false); ++sysst_check_fail: ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev_amppd(aw_dev, true); ++pll_check_fail: ++ aw_dev_pwd(aw_dev, true); ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_container(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len, unsigned short base) ++{ ++ u32 tmp_len; ++ int i, ret; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { ++ tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); ++ ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, ++ &data[i], tmp_len); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ u32 dsp_ra; ++ int ret; ++ ++ ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, ++ &dsp_ra, AW_DSP_32_DATA); ++ if (ret) { ++ dev_err(aw_dev->dev, "read ra error"); ++ return ret; ++ } ++ ++ cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, ++ AW88399_DSP_RE_SHIFT); ++ ++ return 0; ++} ++ ++static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp config len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp config data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); ++ if (ret) ++ return ret; ++ ++ aw_dev->dsp_cfg_len = len; ++ ++ ret = aw_dev_get_ra(&aw_dev->cali_desc); ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ aw_dev->dsp_fw_len = len; ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); ++ ++ return ret; ++} ++ ++static int aw_dev_check_sram(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ ++ /* read dsp_rom_check_reg */ ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { ++ dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", ++ reg_val, AW88399_DSP_ROM_CHECK_DATA); ++ return -EPERM; ++ } ++ ++ /* check dsp_cfg_base_addr */ ++ aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, ++ AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { ++ dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", ++ reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); ++ return -EPERM; ++ } ++ ++ return 0; ++} ++ ++static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) ++{ ++ int ret; ++ ++ switch (flag) { ++ case AW88399_DEV_MEMCLK_PLL: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_DAPHCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select pll failed"); ++ break; ++ case AW88399_DEV_MEMCLK_OSC: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_OSCCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select OSC failed"); ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); ++ break; ++ } ++} ++ ++static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) ++{ ++ struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++ return; ++ } ++ if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) ++ profctrl_desc->cur_mode = AW88399_RCV_MODE; ++ else ++ profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; ++} ++ ++static int aw_dev_update_reg_container(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ u16 read_vol, reg_val; ++ int data_len, i, ret; ++ int16_t *reg_data; ++ u8 reg_addr; ++ ++ reg_data = (int16_t *)data; ++ data_len = len >> 1; ++ ++ if (data_len & 0x1) { ++ dev_err(aw_dev->dev, "data len:%d unsupported", data_len); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < data_len; i += 2) { ++ reg_addr = reg_data[i]; ++ reg_val = reg_data[i + 1]; ++ ++ if (reg_addr == AW88399_DSPVCALB_REG) { ++ aw88399->vcalb_init_val = reg_val; ++ continue; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL_REG) { ++ if (reg_val & (~AW88399_DSPBY_MASK)) ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; ++ else ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; ++ ++ reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | ++ AW88399_DSPBY_MASK); ++ reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | ++ AW88399_DSPBY_BYPASS_VALUE); ++ } ++ ++ if (reg_addr == AW88399_I2SCTRL3_REG) { ++ reg_val &= AW88399_I2STXEN_MASK; ++ reg_val |= AW88399_I2STXEN_DISABLE_VALUE; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL2_REG) { ++ read_vol = (reg_val & (~AW88399_VOL_MASK)) >> ++ AW88399_VOL_START_BIT; ++ aw_dev->volume_desc.init_volume = read_vol; ++ } ++ ++ if (reg_addr == AW88399_DBGCTRL_REG) { ++ if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) ++ aw88399->check_val = AW_EF_OR_CHECK; ++ else ++ aw88399->check_val = AW_EF_AND_CHECK; ++ ++ aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); ++ } ++ ++ if (reg_addr == AW88399_CRCCTRL_REG) ++ aw88399->crc_init_val = reg_val; ++ ++ ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); ++ if (ret) ++ return ret; ++ } ++ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ aw_dev_get_cur_mode_st(aw_dev); ++ ++ if (aw_dev->prof_cur != aw_dev->prof_index) ++ vol_desc->ctl_volume = 0; ++ else ++ aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); ++ ++ return 0; ++} ++ ++static int aw_dev_reg_update(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ if (!len || !data) { ++ dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_update_reg_container(aw88399, data, len); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "reg update failed"); ++ ++ return ret; ++} ++ ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) ++{ ++ struct aw_prof_info *prof_info = &aw_dev->prof_info; ++ struct aw_prof_desc *prof_desc; ++ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "index[%d] overflow count[%d]", ++ index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ *prof_name = prof_info->prof_name_list[prof_desc->id]; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); ++ ++static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, ++ struct aw_prof_desc **prof_desc) ++{ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", ++ __func__, index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ *prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ return 0; ++} ++ ++static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_prof_desc *prof_index_desc; ++ struct aw_sec_data_desc *sec_desc; ++ char *prof_name; ++ int ret; ++ ++ if ((aw_dev->prof_cur == aw_dev->prof_index) && ++ (force_up_en == AW88399_FORCE_UPDATE_OFF)) { ++ dev_dbg(aw_dev->dev, "scene no change, not update"); ++ return 0; ++ } ++ ++ if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { ++ dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); ++ return -EPERM; ++ } ++ ++ ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "start update %s", prof_name); ++ ++ ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); ++ if (ret) ++ return ret; ++ ++ /* update reg */ ++ sec_desc = prof_index_desc->sec_desc; ++ ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, ++ sec_desc[AW88395_DATA_TYPE_REG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update reg failed"); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ ++ ret = aw_dev_check_sram(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "check sram failed"); ++ goto error; ++ } ++ ++ if (up_dsp_fw_en) { ++ dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); ++ ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_FW].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp fw failed"); ++ goto error; ++ } ++ } ++ ++ /* update dsp config */ ++ ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp cfg failed"); ++ goto error; ++ } ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ ++ aw_dev->prof_cur = aw_dev->prof_index; ++ ++ return 0; ++ ++error: ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ return ret; ++} ++ ++static void aw88399_start_pa(struct aw88399 *aw88399) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_START_RETRIES; i++) { ++ ret = aw88399_dev_start(aw88399); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); ++ ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed"); ++ continue; ++ } ++ } else { ++ dev_dbg(aw88399->aw_pa->dev, "start success\n"); ++ break; ++ } ++ } ++} ++ ++void aw88399_startup_work(struct work_struct *work) ++{ ++ struct aw88399 *aw88399 = ++ container_of(work, struct aw88399, start_work.work); ++ ++ mutex_lock(&aw88399->lock); ++ aw88399_start_pa(aw88399); ++ mutex_unlock(&aw88399->lock); ++} ++EXPORT_SYMBOL_GPL(aw88399_startup_work); ++ ++void aw88399_start(struct aw88399 *aw88399, bool sync_start) ++{ ++ int ret; ++ ++ if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) ++ return; ++ ++ if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) ++ return; ++ ++ ret = aw88399_dev_fw_update(aw88399, aw88399->fw_needs_reload ? ++ AW88399_DSP_FW_UPDATE_ON : AW88399_DSP_FW_UPDATE_OFF, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed."); ++ return; ++ } ++ ++ aw88399->fw_needs_reload = false; ++ ++ if (sync_start == AW88399_SYNC_START) ++ aw88399_start_pa(aw88399); ++ else ++ queue_delayed_work(system_dfl_wq, ++ &aw88399->start_work, ++ AW88399_START_WORK_DELAY_MS); ++} ++EXPORT_SYMBOL_GPL(aw88399_start); ++ ++static int aw_dev_check_sysint(struct aw_device *aw_dev) ++{ ++ u16 reg_val; ++ ++ aw_dev_get_int_status(aw_dev, ®_val); ++ if (reg_val & AW88399_BIT_SYSINT_CHECK) { ++ dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++int aw88399_stop(struct aw_device *aw_dev) ++{ ++ struct aw_sec_data_desc *dsp_cfg = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; ++ struct aw_sec_data_desc *dsp_fw = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; ++ int int_st; ++ ++ if (aw_dev->status == AW88399_DEV_PW_OFF) { ++ dev_dbg(aw_dev->dev, "already power off"); ++ return 0; ++ } ++ ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ aw88399_dev_mute(aw_dev, true); ++ usleep_range(AW88399_4000_US, AW88399_4000_US + 100); ++ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ int_st = aw_dev_check_sysint(aw_dev); ++ ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_amppd(aw_dev, true); ++ ++ if (int_st) { ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); ++ aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ } ++ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_stop); ++ ++static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); ++ if (ret) { ++ dev_err(aw_dev->dev, "aw_dev acf parse failed"); ++ return -EINVAL; ++ } ++ aw_dev->fade_in_time = AW88399_1000_US / 10; ++ aw_dev->fade_out_time = AW88399_1000_US >> 1; ++ aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; ++ aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; ++ ++ ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ /* close tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ /* enable amppd */ ++ aw_dev_amppd(aw_dev, true); ++ ++ /* close dsp */ ++ aw_dev_dsp_enable(aw_dev, false); ++ /* set power down */ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++ ++int aw88399_request_firmware_file(struct aw88399 *aw88399) ++{ ++ const struct firmware *cont = NULL; ++ int ret; ++ ++ aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; ++ ++ ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", ++ AW88399_ACF_FILE, cont ? cont->size : 0); ++ ++ aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, ++ struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); ++ if (!aw88399->aw_cfg) { ++ release_firmware(cont); ++ return -ENOMEM; ++ } ++ aw88399->aw_cfg->len = (int)cont->size; ++ memcpy(aw88399->aw_cfg->data, cont->data, cont->size); ++ release_firmware(cont); ++ ++ ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ mutex_lock(&aw88399->lock); ++ /* aw device init */ ++ ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "dev init failed"); ++ mutex_unlock(&aw88399->lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); ++ ++void aw88399_hw_reset(struct aw88399 *aw88399) ++{ ++ if (aw88399->reset_gpio) { ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 0); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_hw_reset); ++ ++static void aw88399_parse_channel_dt(struct aw_device *aw_dev) ++{ ++ struct device_node *np = aw_dev->dev->of_node; ++ u32 channel_value; ++ int ret; ++ ++ ret = of_property_read_u32(np, "awinic,audio-channel", &channel_value); ++ if (ret) { ++ /* ++ * On ACPI systems, DT properties don't exist. Derive channel ++ * from I2C address: 0x34 -> channel 0 (left), 0x35 -> channel 1 (right) ++ */ ++ aw_dev->channel = aw_dev->i2c->addr - 0x34; ++ dev_dbg(aw_dev->dev, ++ "DT channel property not found, using I2C address-based channel %d (addr 0x%02x)\n", ++ aw_dev->channel, aw_dev->i2c->addr); ++ return; ++ } ++ aw_dev->channel = channel_value; ++} ++ ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) ++{ ++ struct aw_device *aw_dev; ++ unsigned int chip_id; ++ int ret; ++ ++ ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); ++ if (ret) { ++ dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); ++ return ret; ++ } ++ if (chip_id != AW88399_CHIP_ID) { ++ dev_err(&i2c->dev, "unsupported device"); ++ return -ENXIO; ++ } ++ dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); ++ ++ aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); ++ if (!aw_dev) ++ return -ENOMEM; ++ aw88399->aw_pa = aw_dev; ++ ++ aw_dev->i2c = i2c; ++ aw_dev->dev = &i2c->dev; ++ aw_dev->regmap = regmap; ++ mutex_init(&aw_dev->dsp_lock); ++ ++ aw_dev->chip_id = chip_id; ++ aw_dev->acf = NULL; ++ aw_dev->prof_info.prof_desc = NULL; ++ aw_dev->prof_info.count = 0; ++ aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; ++ aw_dev->channel = AW88399_DEV_DEFAULT_CH; ++ aw_dev->fw_status = AW88399_DEV_FW_FAILED; ++ ++ aw_dev->fade_step = AW88399_VOLUME_STEP_DB; ++ aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; ++ ++ aw88399_parse_channel_dt(aw_dev); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_init); ++ ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) ++{ ++ aw88399->aw_pa->channel = channel; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); ++ ++MODULE_DESCRIPTION("AW88399 common device library"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -10,1217 +10,12 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include + #include "aw88399.h" + #include "aw88395/aw88395_device.h" + +-static const struct regmap_config aw88399_remap_config = { +- .val_bits = 16, +- .reg_bits = 8, +- .max_register = AW88399_REG_MAX, +- .reg_format_endian = REGMAP_ENDIAN_LITTLE, +- .val_format_endian = REGMAP_ENDIAN_BIG, +-}; +- +-static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) +-{ +- int ret; +- +- if (pwd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); +- if (ret) +- dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); +- else +- *int_status = reg_val; +- +- dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); +-} +- +-static void aw_dev_clear_int_status(struct aw_device *aw_dev) +-{ +- u16 int_status; +- +- /* read int status and clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- /* make sure int status is clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- if (int_status) +- dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); +-} +- +-static int aw_dev_get_iis_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { +- dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode1 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); +- if (ret) +- return ret; +- +- reg_val &= (~AW88399_CCO_MUX_MASK); +- if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { +- dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); +- return -EPERM; +- } +- +- /* change mode2 */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); +- if (ret) +- return ret; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- +- /* change mode1 */ +- regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); +- if (ret == 0) { +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- } +- +- return ret; +-} +- +-static int aw_dev_check_syspll(struct aw_device *aw_dev) +-{ +- int ret; +- +- ret = aw_dev_check_mode1_pll(aw_dev); +- if (ret) { +- dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); +- ret = aw_dev_check_mode2_pll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 check iis failed"); +- return ret; +- } +- } +- +- return 0; +-} +- +-static int aw_dev_check_sysst(struct aw_device *aw_dev) +-{ +- unsigned int check_val; +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); +- if (ret) +- return ret; +- +- if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) +- check_val = AW88399_BIT_SYSST_NOSWS_CHECK; +- else +- check_val = AW88399_BIT_SYSST_SWS_CHECK; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- +- if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { +- dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", +- i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) +-{ +- int ret; +- +- if (amppd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) +-{ +- int ret; +- +- if (is_enable) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed\n", __func__); +-} +- +-static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) +-{ +- uint16_t icalkh_val, icalkl_val, icalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); +- if (ret) +- return ret; +- icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); +- if (ret) +- return ret; +- icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- icalk_val = icalkh_val & icalkl_val; +- else +- icalk_val = icalkh_val | icalkl_val; +- +- if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) +- icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; +- *icalk = (int16_t)icalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = vcalkh_val & vcalkl_val; +- else +- vcalk_val = vcalkh_val | vcalkl_val; +- +- if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) +- vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); +- if (ret) +- return ret; +- vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); +- if (ret) +- return ret; +- vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- else +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- +- if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) +- vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; +- +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw_dev_set_vcalb(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int vsense_select, vsense_value; +- int32_t ical_k, vcal_k, vcalb; +- int16_t icalk, vcalk; +- uint16_t reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); +- if (ret) +- return ret; +- +- vsense_select = vsense_value & (~AW88399_VDSEL_MASK); +- +- ret = aw88399_dev_get_icalk(aw88399, &icalk); +- if (ret) { +- dev_err(aw_dev->dev, "get icalk failed\n"); +- return ret; +- } +- +- ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- +- switch (vsense_select) { +- case AW88399_DEV_VDSEL_VSENSE: +- ret = aw88399_dev_get_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * +- ical_k / vcal_k * aw88399->vcalb_init_val; +- break; +- case AW88399_DEV_VDSEL_DAC: +- ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / +- AW88399_ISCAL_DAC_FACTOR * ical_k / +- vcal_k * aw88399->vcalb_init_val; +- break; +- default: +- dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); +- ret = -EINVAL; +- break; +- } +- if (ret) +- return ret; +- +- vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; +- reg_val = (uint32_t)vcalb; +- +- regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); +- +- return 0; +-} +- +-static int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- uint16_t re_lbits, re_hbits; +- u32 cali_re; +- int ret; +- +- if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || +- (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) +- return -EINVAL; +- +- cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + +- aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); +- +- re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; +- re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); +- if (ret) { +- dev_err(aw_dev->dev, "set cali re error"); +- return ret; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); +- if (ret) +- dev_err(aw_dev->dev, "set cali re error"); +- +- return ret; +-} +- +-static int aw_dev_fw_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, fw_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate fw_end_addr */ +- fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; +- +- /* write fw_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, fw_len_val); +- if (ret) +- return ret; +- /* enable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); +- +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- /* read crc check result */ +- regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", +- __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, cfg_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate cfg end addr */ +- cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; +- +- /* write cfg_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); +- if (ret) +- return ret; +- +- /* enable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); +- if (ret) +- return ret; +- +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- /* read crc check result */ +- ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", +- check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_hw_crc_check(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); +- if (ret) +- return ret; +- +- ret = aw_dev_fw_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "fw_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = aw_dev_cfg_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "cfg_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); +- if (ret) +- return ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- +- return ret; +- +-crc_check_failed: +- regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- return ret; +-} +- +-static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) +-{ +- int ret; +- +- if (flag) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static int aw_dev_get_dsp_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); +- if (ret) +- return ret; +- if (!(reg_val & (~AW88399_WDT_CNT_MASK))) +- return -EPERM; +- +- return 0; +-} +- +-static int aw_dev_dsp_check(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- switch (aw_dev->dsp_cfg) { +- case AW88399_DEV_DSP_BYPASS: +- dev_dbg(aw_dev->dev, "dsp bypass"); +- ret = 0; +- break; +- case AW88399_DEV_DSP_WORK: +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_dsp_enable(aw_dev, true); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { +- ret = aw_dev_get_dsp_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } +- } +- break; +- default: +- dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); +- ret = -EINVAL; +- break; +- } +- +- return ret; +-} +- +-static int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) +-{ +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- unsigned int reg_value; +- u16 real_value; +- int ret; +- +- real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); +- +- real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); +- +- ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); +- +- return ret; +-} +- +-static void aw_dev_fade_in(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- u16 fade_in_vol = desc->ctl_volume; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_in_time == 0) { +- aw_dev_set_volume(aw_dev, fade_in_vol); +- return; +- } +- +- for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); +- } +- +- if (i != fade_in_vol) +- aw_dev_set_volume(aw_dev, fade_in_vol); +-} +- +-static void aw_dev_fade_out(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_out_time == 0) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- return; +- } +- +- for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +- +- if (i != AW88399_MUTE_VOL) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +-} +- +-static void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) +-{ +- if (is_mute) { +- aw_dev_fade_out(aw_dev); +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); +- } else { +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); +- aw_dev_fade_in(aw_dev); +- } +-} +- +-static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- +- if (dither) +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); +- else +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); +-} +- +-static int aw88399_dev_start(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- if (aw_dev->status == AW88399_DEV_PW_ON) { +- dev_dbg(aw_dev->dev, "already power on"); +- return 0; +- } +- +- aw88399_dev_set_dither(aw88399, false); +- +- /* power on */ +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- ret = aw_dev_check_syspll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "pll check failed cannot start"); +- goto pll_check_fail; +- } +- +- /* amppd on */ +- aw_dev_amppd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 50); +- +- /* check i2s status */ +- ret = aw_dev_check_sysst(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "sysst check failed"); +- goto sysst_check_fail; +- } +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { +- ret = aw_dev_hw_crc_check(aw88399); +- if (ret) { +- dev_err(aw_dev->dev, "dsp crc check failed"); +- goto crc_check_fail; +- } +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_set_vcalb(aw88399); +- aw_dev_update_cali_re(&aw_dev->cali_desc); +- +- ret = aw_dev_dsp_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp status check failed"); +- goto dsp_check_fail; +- } +- } else { +- dev_dbg(aw_dev->dev, "start pa with dsp bypass"); +- } +- +- /* enable tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, true); +- +- if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) +- aw88399_dev_set_dither(aw88399, true); +- +- /* close mute */ +- aw88399_dev_mute(aw_dev, false); +- /* clear inturrupt */ +- aw_dev_clear_int_status(aw_dev); +- aw_dev->status = AW88399_DEV_PW_ON; +- +- return 0; +- +-dsp_check_fail: +-crc_check_fail: +- aw_dev_dsp_enable(aw_dev, false); +-sysst_check_fail: +- aw_dev_clear_int_status(aw_dev); +- aw_dev_amppd(aw_dev, true); +-pll_check_fail: +- aw_dev_pwd(aw_dev, true); +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- return ret; +-} +- +-static int aw_dev_dsp_update_container(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len, unsigned short base) +-{ +- u32 tmp_len; +- int i, ret; +- +- ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); +- if (ret) +- return ret; +- +- for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { +- tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); +- ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, +- &data[i], tmp_len); +- if (ret) +- return ret; +- } +- +- return 0; +-} +- +-static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- u32 dsp_ra; +- int ret; +- +- ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, +- &dsp_ra, AW_DSP_32_DATA); +- if (ret) { +- dev_err(aw_dev->dev, "read ra error"); +- return ret; +- } +- +- cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, +- AW88399_DSP_RE_SHIFT); +- +- return 0; +-} +- +-static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp config len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp config data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); +- if (ret) +- return ret; +- +- aw_dev->dsp_cfg_len = len; +- +- ret = aw_dev_get_ra(&aw_dev->cali_desc); +- +- return ret; +-} +- +-static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); +- return -EINVAL; +- } +- +- aw_dev->dsp_fw_len = len; +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); +- +- return ret; +-} +- +-static int aw_dev_check_sram(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- +- /* read dsp_rom_check_reg */ +- aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { +- dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", +- reg_val, AW88399_DSP_ROM_CHECK_DATA); +- return -EPERM; +- } +- +- /* check dsp_cfg_base_addr */ +- aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, +- AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); +- aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { +- dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", +- reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); +- return -EPERM; +- } +- +- return 0; +-} +- +-static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) +-{ +- int ret; +- +- switch (flag) { +- case AW88399_DEV_MEMCLK_PLL: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_DAPHCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select pll failed"); +- break; +- case AW88399_DEV_MEMCLK_OSC: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_OSCCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select OSC failed"); +- break; +- default: +- dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); +- break; +- } +-} +- +-static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) +-{ +- struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); +- if (ret) { +- dev_dbg(aw_dev->dev, "%s failed", __func__); +- return; +- } +- if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) +- profctrl_desc->cur_mode = AW88399_RCV_MODE; +- else +- profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; +-} +- +-static int aw_dev_update_reg_container(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- u16 read_vol, reg_val; +- int data_len, i, ret; +- int16_t *reg_data; +- u8 reg_addr; +- +- reg_data = (int16_t *)data; +- data_len = len >> 1; +- +- if (data_len & 0x1) { +- dev_err(aw_dev->dev, "data len:%d unsupported", data_len); +- return -EINVAL; +- } +- +- for (i = 0; i < data_len; i += 2) { +- reg_addr = reg_data[i]; +- reg_val = reg_data[i + 1]; +- +- if (reg_addr == AW88399_DSPVCALB_REG) { +- aw88399->vcalb_init_val = reg_val; +- continue; +- } +- +- if (reg_addr == AW88399_SYSCTRL_REG) { +- if (reg_val & (~AW88399_DSPBY_MASK)) +- aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; +- else +- aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; +- +- reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | +- AW88399_DSPBY_MASK); +- reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | +- AW88399_DSPBY_BYPASS_VALUE); +- } +- +- if (reg_addr == AW88399_I2SCTRL3_REG) { +- reg_val &= AW88399_I2STXEN_MASK; +- reg_val |= AW88399_I2STXEN_DISABLE_VALUE; +- } +- +- if (reg_addr == AW88399_SYSCTRL2_REG) { +- read_vol = (reg_val & (~AW88399_VOL_MASK)) >> +- AW88399_VOL_START_BIT; +- aw_dev->volume_desc.init_volume = read_vol; +- } +- +- if (reg_addr == AW88399_DBGCTRL_REG) { +- if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) +- aw88399->check_val = AW_EF_OR_CHECK; +- else +- aw88399->check_val = AW_EF_AND_CHECK; +- +- aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); +- } +- +- if (reg_addr == AW88399_CRCCTRL_REG) +- aw88399->crc_init_val = reg_val; +- +- ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); +- if (ret) +- return ret; +- } +- +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- aw_dev_get_cur_mode_st(aw_dev); +- +- if (aw_dev->prof_cur != aw_dev->prof_index) +- vol_desc->ctl_volume = 0; +- else +- aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); +- +- return 0; +-} +- +-static int aw_dev_reg_update(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- if (!len || !data) { +- dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_update_reg_container(aw88399, data, len); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "reg update failed"); +- +- return ret; +-} +- +-static int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) +-{ +- struct aw_prof_info *prof_info = &aw_dev->prof_info; +- struct aw_prof_desc *prof_desc; +- +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "index[%d] overflow count[%d]", +- index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- *prof_name = prof_info->prof_name_list[prof_desc->id]; +- +- return 0; +-} +- +-static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, +- struct aw_prof_desc **prof_desc) +-{ +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", +- __func__, index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- *prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- return 0; +-} +- +-static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_prof_desc *prof_index_desc; +- struct aw_sec_data_desc *sec_desc; +- char *prof_name; +- int ret; +- +- if ((aw_dev->prof_cur == aw_dev->prof_index) && +- (force_up_en == AW88399_FORCE_UPDATE_OFF)) { +- dev_dbg(aw_dev->dev, "scene no change, not update"); +- return 0; +- } +- +- if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { +- dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); +- return -EPERM; +- } +- +- ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "start update %s", prof_name); +- +- ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); +- if (ret) +- return ret; +- +- /* update reg */ +- sec_desc = prof_index_desc->sec_desc; +- ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, +- sec_desc[AW88395_DATA_TYPE_REG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update reg failed"); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- +- ret = aw_dev_check_sram(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "check sram failed"); +- goto error; +- } +- +- if (up_dsp_fw_en) { +- dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); +- ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, +- sec_desc[AW88395_DATA_TYPE_DSP_FW].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp fw failed"); +- goto error; +- } +- } +- +- /* update dsp config */ +- ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, +- sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp cfg failed"); +- goto error; +- } +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- +- aw_dev->prof_cur = aw_dev->prof_index; +- +- return 0; +- +-error: +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- return ret; +-} +- +-static void aw88399_start_pa(struct aw88399 *aw88399) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_START_RETRIES; i++) { +- ret = aw88399_dev_start(aw88399); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed"); +- continue; +- } +- } else { +- dev_dbg(aw88399->aw_pa->dev, "start success\n"); +- break; +- } +- } +-} +- +-static void aw88399_startup_work(struct work_struct *work) +-{ +- struct aw88399 *aw88399 = +- container_of(work, struct aw88399, start_work.work); +- +- mutex_lock(&aw88399->lock); +- aw88399_start_pa(aw88399); +- mutex_unlock(&aw88399->lock); +-} +- +-static void aw88399_start(struct aw88399 *aw88399, bool sync_start) +-{ +- int ret; +- +- if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) +- return; +- +- if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) +- return; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed."); +- return; +- } +- +- if (sync_start == AW88399_SYNC_START) +- aw88399_start_pa(aw88399); +- else +- queue_delayed_work(system_dfl_wq, +- &aw88399->start_work, +- AW88399_START_WORK_DELAY_MS); +-} +- +-static int aw_dev_check_sysint(struct aw_device *aw_dev) +-{ +- u16 reg_val; +- +- aw_dev_get_int_status(aw_dev, ®_val); +- if (reg_val & AW88399_BIT_SYSINT_CHECK) { +- dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw88399_stop(struct aw_device *aw_dev) +-{ +- struct aw_sec_data_desc *dsp_cfg = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; +- struct aw_sec_data_desc *dsp_fw = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; +- int int_st; +- +- if (aw_dev->status == AW88399_DEV_PW_OFF) { +- dev_dbg(aw_dev->dev, "already power off"); +- return 0; +- } +- +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- aw88399_dev_mute(aw_dev, true); +- usleep_range(AW88399_4000_US, AW88399_4000_US + 100); +- +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- int_st = aw_dev_check_sysint(aw_dev); +- +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_amppd(aw_dev, true); +- +- if (int_st) { +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); +- aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- } +- +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- + static struct snd_soc_dai_driver aw88399_dai[] = { + { + .name = "aw88399-aif", +@@ -1869,86 +664,6 @@ static int aw88399_calib_set(struct snd_kcontrol *kcontrol, + return 0; + } + +-static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); +- if (ret) { +- dev_err(aw_dev->dev, "aw_dev acf parse failed"); +- return -EINVAL; +- } +- aw_dev->fade_in_time = AW88399_1000_US / 10; +- aw_dev->fade_out_time = AW88399_1000_US >> 1; +- aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; +- aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); +- if (ret) { +- dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- /* close tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- /* enable amppd */ +- aw_dev_amppd(aw_dev, true); +- +- /* close dsp */ +- aw_dev_dsp_enable(aw_dev, false); +- /* set power down */ +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- +-static int aw88399_request_firmware_file(struct aw88399 *aw88399) +-{ +- const struct firmware *cont = NULL; +- int ret; +- +- aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +- +- ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", +- AW88399_ACF_FILE, cont ? cont->size : 0); +- +- aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, +- struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); +- return -ENOMEM; +- } +- aw88399->aw_cfg->len = (int)cont->size; +- memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); +- +- ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- mutex_lock(&aw88399->lock); +- /* aw device init */ +- ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88399->lock); +- +- return ret; +-} +- + static const struct snd_kcontrol_new aw88399_controls[] = { + SOC_SINGLE_EXT("PCM Playback Volume", AW88399_SYSCTRL2_REG, + 6, AW88399_MUTE_VOL, 0, aw88399_volume_get, +@@ -2040,70 +755,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88399 = { + .num_controls = ARRAY_SIZE(aw88399_controls), + }; + +-static void aw88399_hw_reset(struct aw88399 *aw88399) +-{ +- if (aw88399->reset_gpio) { +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 0); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- } +-} +- +-static void aw88399_parse_channel_dt(struct aw_device *aw_dev) +-{ +- struct device_node *np = aw_dev->dev->of_node; +- u32 channel_value; +- +- of_property_read_u32(np, "awinic,audio-channel", &channel_value); +- aw_dev->channel = channel_value; +-} +- +-static int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) +-{ +- struct aw_device *aw_dev; +- unsigned int chip_id; +- int ret; +- +- ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); +- if (ret) { +- dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); +- return ret; +- } +- if (chip_id != AW88399_CHIP_ID) { +- dev_err(&i2c->dev, "unsupported device"); +- return -ENXIO; +- } +- dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); +- +- aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); +- if (!aw_dev) +- return -ENOMEM; +- aw88399->aw_pa = aw_dev; +- +- aw_dev->i2c = i2c; +- aw_dev->dev = &i2c->dev; +- aw_dev->regmap = regmap; +- mutex_init(&aw_dev->dsp_lock); +- +- aw_dev->chip_id = chip_id; +- aw_dev->acf = NULL; +- aw_dev->prof_info.prof_desc = NULL; +- aw_dev->prof_info.count = 0; +- aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; +- aw_dev->channel = AW88399_DEV_DEFAULT_CH; +- aw_dev->fw_status = AW88399_DEV_FW_FAILED; +- +- aw_dev->fade_step = AW88399_VOLUME_STEP_DB; +- aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; +- +- aw88399_parse_channel_dt(aw_dev); +- +- return 0; +-} +- + static int aw88399_i2c_probe(struct i2c_client *i2c) + { + struct aw88399 *aw88399; +diff --git a/sound/soc/codecs/aw88399.h b/sound/soc/codecs/aw88399.h +--- a/sound/soc/codecs/aw88399.h ++++ b/sound/soc/codecs/aw88399.h +@@ -10,512 +10,10 @@ + #ifndef __AW88399_H__ + #define __AW88399_H__ + +-/* registers list */ +-#define AW88399_ID_REG (0x00) +-#define AW88399_SYSST_REG (0x01) +-#define AW88399_SYSINT_REG (0x02) +-#define AW88399_SYSINTM_REG (0x03) +-#define AW88399_SYSCTRL_REG (0x04) +-#define AW88399_SYSCTRL2_REG (0x05) +-#define AW88399_I2SCTRL1_REG (0x06) +-#define AW88399_I2SCTRL2_REG (0x07) +-#define AW88399_I2SCTRL3_REG (0x08) +-#define AW88399_DACCFG1_REG (0x09) +-#define AW88399_DACCFG2_REG (0x0A) +-#define AW88399_DACCFG3_REG (0x0B) +-#define AW88399_DACCFG4_REG (0x0C) +-#define AW88399_DACCFG5_REG (0x0D) +-#define AW88399_DACCFG6_REG (0x0E) +-#define AW88399_DACCFG7_REG (0x0F) +-#define AW88399_MPDCFG1_REG (0x10) +-#define AW88399_MPDCFG2_REG (0x11) +-#define AW88399_MPDCFG3_REG (0x12) +-#define AW88399_MPDCFG4_REG (0x13) +-#define AW88399_PWMCTRL1_REG (0x14) +-#define AW88399_PWMCTRL2_REG (0x15) +-#define AW88399_PWMCTRL3_REG (0x16) +-#define AW88399_I2SCFG1_REG (0x17) +-#define AW88399_DBGCTRL_REG (0x18) +-#define AW88399_HAGCST_REG (0x20) +-#define AW88399_VBAT_REG (0x21) +-#define AW88399_TEMP_REG (0x22) +-#define AW88399_PVDD_REG (0x23) +-#define AW88399_ISNDAT_REG (0x24) +-#define AW88399_VSNDAT_REG (0x25) +-#define AW88399_I2SINT_REG (0x26) +-#define AW88399_I2SCAPCNT_REG (0x27) +-#define AW88399_ANASTA1_REG (0x28) +-#define AW88399_ANASTA2_REG (0x29) +-#define AW88399_ANASTA3_REG (0x2A) +-#define AW88399_TESTDET_REG (0x2B) +-#define AW88399_DSMCFG1_REG (0x30) +-#define AW88399_DSMCFG2_REG (0x31) +-#define AW88399_DSMCFG3_REG (0x32) +-#define AW88399_DSMCFG4_REG (0x33) +-#define AW88399_DSMCFG5_REG (0x34) +-#define AW88399_DSMCFG6_REG (0x35) +-#define AW88399_DSMCFG7_REG (0x36) +-#define AW88399_DSMCFG8_REG (0x37) +-#define AW88399_TESTIN_REG (0x38) +-#define AW88399_TESTOUT_REG (0x39) +-#define AW88399_MEMTEST_REG (0x3A) +-#define AW88399_VSNCTRL1_REG (0x3B) +-#define AW88399_ISNCTRL1_REG (0x3C) +-#define AW88399_ISNCTRL2_REG (0x3D) +-#define AW88399_DSPMADD_REG (0x40) +-#define AW88399_DSPMDAT_REG (0x41) +-#define AW88399_WDT_REG (0x42) +-#define AW88399_ACR1_REG (0x43) +-#define AW88399_ACR2_REG (0x44) +-#define AW88399_ASR1_REG (0x45) +-#define AW88399_ASR2_REG (0x46) +-#define AW88399_DSPCFG_REG (0x47) +-#define AW88399_ASR3_REG (0x48) +-#define AW88399_ASR4_REG (0x49) +-#define AW88399_DSPVCALB_REG (0x4A) +-#define AW88399_CRCCTRL_REG (0x4B) +-#define AW88399_DSPDBG1_REG (0x4C) +-#define AW88399_DSPDBG2_REG (0x4D) +-#define AW88399_DSPDBG3_REG (0x4E) +-#define AW88399_PLLCTRL1_REG (0x50) +-#define AW88399_PLLCTRL2_REG (0x51) +-#define AW88399_PLLCTRL3_REG (0x52) +-#define AW88399_CDACTRL1_REG (0x53) +-#define AW88399_CDACTRL2_REG (0x54) +-#define AW88399_CDACTRL3_REG (0x55) +-#define AW88399_SADCCTRL1_REG (0x56) +-#define AW88399_SADCCTRL2_REG (0x57) +-#define AW88399_BOPCTRL1_REG (0x58) +-#define AW88399_BOPCTRL2_REG (0x5A) +-#define AW88399_BOPCTRL3_REG (0x5B) +-#define AW88399_BOPCTRL4_REG (0x5C) +-#define AW88399_BOPCTRL5_REG (0x5D) +-#define AW88399_BOPCTRL6_REG (0x5E) +-#define AW88399_BOPCTRL7_REG (0x5F) +-#define AW88399_BSTCTRL1_REG (0x60) +-#define AW88399_BSTCTRL2_REG (0x61) +-#define AW88399_BSTCTRL3_REG (0x62) +-#define AW88399_BSTCTRL4_REG (0x63) +-#define AW88399_BSTCTRL5_REG (0x64) +-#define AW88399_BSTCTRL6_REG (0x65) +-#define AW88399_BSTCTRL7_REG (0x66) +-#define AW88399_BSTCTRL8_REG (0x67) +-#define AW88399_BSTCTRL9_REG (0x68) +-#define AW88399_BSTCTRL10_REG (0x69) +-#define AW88399_CPCTRL_REG (0x6A) +-#define AW88399_EFWH_REG (0x6C) +-#define AW88399_EFWM2_REG (0x6D) +-#define AW88399_EFWM1_REG (0x6E) +-#define AW88399_EFWL_REG (0x6F) +-#define AW88399_TESTCTRL1_REG (0x70) +-#define AW88399_TESTCTRL2_REG (0x71) +-#define AW88399_EFCTRL1_REG (0x72) +-#define AW88399_EFCTRL2_REG (0x73) +-#define AW88399_EFRH4_REG (0x74) +-#define AW88399_EFRH3_REG (0x75) +-#define AW88399_EFRH2_REG (0x76) +-#define AW88399_EFRH1_REG (0x77) +-#define AW88399_EFRL4_REG (0x78) +-#define AW88399_EFRL3_REG (0x79) +-#define AW88399_EFRL2_REG (0x7A) +-#define AW88399_EFRL1_REG (0x7B) +-#define AW88399_TM_REG (0x7C) +-#define AW88399_TM2_REG (0x7D) +- +-#define AW88399_REG_MAX (0x7E) +-#define AW88399_MUTE_VOL (1023) +- +-#define AW88399_DSP_CFG_ADDR (0x9B00) +-#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) +-#define AW88399_DSP_FW_ADDR (0x8980) +-#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) +-#define AW88399_DSP_ROM_CHECK_DATA (0x4638) +- +-#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) +-#define AW88399_CALI_RE_HBITS_SHIFT (16) +- +-#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) +-#define AW88399_CALI_RE_LBITS_SHIFT (0) +- +-#define AW88399_I2STXEN_START_BIT (9) +-#define AW88399_I2STXEN_BITS_LEN (1) +-#define AW88399_I2STXEN_MASK \ +- (~(((1<> (shift)) +-#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) +-#define AW88399_CRC_CHECK_PASS_VAL (0x4) +- +-#define AW88399_CRC_CFG_BASE_ADDR (0xD80) +-#define AW88399_CRC_FW_BASE_ADDR (0x4C0) +-#define AW88399_ACF_FILE "aw88399_acf.bin" +-#define AW88399_DEV_SYSST_CHECK_MAX (10) +-#define AW88399_CHIP_ID 0x2183 ++#include + + #define AW88399_I2C_NAME "aw88399" + +-#define AW88399_START_RETRIES (5) +-#define AW88399_START_WORK_DELAY_MS (0) +- + #define AW88399_RATES (SNDRV_PCM_RATE_8000_48000 | \ + SNDRV_PCM_RATE_96000) + #define AW88399_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ +@@ -550,80 +48,4 @@ + .put = profile_set, \ + } + +-enum { +- AW_EF_AND_CHECK = 0, +- AW_EF_OR_CHECK, +-}; +- +-enum { +- AW88399_DEV_VDSEL_DAC = 0, +- AW88399_DEV_VDSEL_VSENSE = 32, +-}; +- +-enum { +- AW88399_DSP_CRC_NA = 0, +- AW88399_DSP_CRC_OK = 1, +-}; +- +-enum { +- AW88399_DSP_FW_UPDATE_OFF = 0, +- AW88399_DSP_FW_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_FORCE_UPDATE_OFF = 0, +- AW88399_FORCE_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_1000_US = 1000, +- AW88399_2000_US = 2000, +- AW88399_3000_US = 3000, +- AW88399_4000_US = 4000, +-}; +- +-enum AW88399_DEV_STATUS { +- AW88399_DEV_PW_OFF = 0, +- AW88399_DEV_PW_ON, +-}; +- +-enum AW88399_DEV_FW_STATUS { +- AW88399_DEV_FW_FAILED = 0, +- AW88399_DEV_FW_OK, +-}; +- +-enum AW88399_DEV_MEMCLK { +- AW88399_DEV_MEMCLK_OSC = 0, +- AW88399_DEV_MEMCLK_PLL = 1, +-}; +- +-enum AW88399_DEV_DSP_CFG { +- AW88399_DEV_DSP_WORK = 0, +- AW88399_DEV_DSP_BYPASS = 1, +-}; +- +-enum { +- AW88399_NOT_RCV_MODE = 0, +- AW88399_RCV_MODE = 1, +-}; +- +-enum { +- AW88399_SYNC_START = 0, +- AW88399_ASYNC_START, +-}; +- +-struct aw88399 { +- struct aw_device *aw_pa; +- struct mutex lock; +- struct gpio_desc *reset_gpio; +- struct delayed_work start_work; +- struct regmap *regmap; +- struct aw_container *aw_cfg; +- +- unsigned int check_val; +- unsigned int crc_init_val; +- unsigned int vcalb_init_val; +- unsigned int dither_st; +-}; +- +-#endif ++#endif /* __AW88399_H__ */ +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -1849,16 +1849,26 @@ static int tasdev_load_blk(struct tasdevice_priv *tas_priv, + } + } + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the hardware time to stabilize before ++ * next block re-transmission attempt. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + + if (block->is_pchksum_present) { + ret = tasdev_block_chksum(tas_priv, block, chn); + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the bus time to recover after ++ * a checksum mismatch error. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + } +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -1118,7 +1118,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-format", &asrc_fmt); +- priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ priv->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -1358,7 +1358,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- asrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ asrc->asrc_format = asrc_fmt; + if (ret) { + ret = of_property_read_u32(np, "fsl,asrc-width", &width); + if (ret) { +diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c +--- a/sound/soc/fsl/fsl_asrc_m2m.c ++++ b/sound/soc/fsl/fsl_asrc_m2m.c +@@ -367,13 +367,13 @@ static int fsl_asrc_m2m_comp_set_params(struct snd_compr_stream *stream, + if (ret) + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.format) & cap.fmt_in) +- pair->sample_format[IN] = (__force snd_pcm_format_t)params->codec.format; ++ if (pcm_format_to_bits(params->codec.format) & cap.fmt_in) ++ pair->sample_format[IN] = params->codec.format; + else + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.pcm_format) & cap.fmt_out) +- pair->sample_format[OUT] = (__force snd_pcm_format_t)params->codec.pcm_format; ++ if (pcm_format_to_bits(params->codec.pcm_format) & cap.fmt_out) ++ pair->sample_format[OUT] = params->codec.pcm_format; + else + return -EINVAL; + +@@ -600,7 +600,7 @@ static int fsl_asrc_m2m_fill_codec_caps(struct fsl_asrc *asrc, + cap.rate_in, + cap.rate_in_count * sizeof(__u32)); + codec->descriptor[j].num_sample_rates = cap.rate_in_count; +- codec->descriptor[j].formats = (__force __u32)k; ++ codec->descriptor[j].formats = k; + codec->descriptor[j].pcm_formats = cap.fmt_out; + codec->descriptor[j].src.out_sample_rate_min = cap.rate_out[0]; + codec->descriptor[j].src.out_sample_rate_max = +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -2227,7 +2227,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- easrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ easrc->asrc_format = asrc_fmt; + if (ret) { + dev_err(dev, "failed to asrc format\n"); + return ret; +diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c +--- a/sound/soc/fsl/fsl_qmc_audio.c ++++ b/sound/soc/fsl/fsl_qmc_audio.c +@@ -503,8 +503,8 @@ static int qmc_dai_constraints_interleaved(struct snd_pcm_substream *substream, + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_INTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +@@ -532,8 +532,8 @@ static int qmc_dai_constraints_noninterleaved(struct snd_pcm_substream *substrea + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c +--- a/sound/soc/fsl/imx-card.c ++++ b/sound/soc/fsl/imx-card.c +@@ -531,7 +531,7 @@ static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); +- snd_mask_set(mask, (__force unsigned int)data->asrc_format); ++ snd_mask_set(mask, data->asrc_format); + + return 0; + } +@@ -684,7 +684,7 @@ static int imx_card_parse_of(struct imx_card_data *data) + } + + ret = of_property_read_u32(args.np, "fsl,asrc-format", &asrc_fmt); +- data->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ data->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(args.np, "fsl,asrc-width", &width); +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -144,7 +144,7 @@ static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; +- bps = snd_pcm_format_physical_width((__force snd_pcm_format_t)params->codec.format); ++ bps = snd_pcm_format_physical_width(params->codec.format); + if (bps < 0) + return bps; + format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); +diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +@@ -172,7 +172,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +@@ -184,7 +184,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +@@ -99,7 +99,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +@@ -112,7 +112,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366.c b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +--- a/sound/soc/mediatek/mt8186/mt8186-mt6366.c ++++ b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +@@ -387,7 +387,7 @@ static int mt8186_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, fmt); + +diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +--- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c ++++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +@@ -623,7 +623,7 @@ static int mt8188_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8189/mt8189-nau8825.c b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +--- a/sound/soc/mediatek/mt8189/mt8189-nau8825.c ++++ b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +@@ -174,7 +174,7 @@ static int mt8189_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -382,7 +382,7 @@ static int mt8192_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359.c b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +--- a/sound/soc/mediatek/mt8195/mt8195-mt6359.c ++++ b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +@@ -387,7 +387,7 @@ static int mt8195_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +@@ -650,7 +650,7 @@ static int mt8195_etdm_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -180,7 +180,7 @@ static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c +--- a/sound/soc/meson/meson-codec-glue.c ++++ b/sound/soc/meson/meson-codec-glue.c +@@ -74,7 +74,7 @@ int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream, + data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params)); + data->params.rate_min = params_rate(params); + data->params.rate_max = params_rate(params); +- data->params.formats = 1ULL << (__force int) params_format(params); ++ data->params.formats = 1ULL << params_format(params); + data->params.channels_min = params_channels(params); + data->params.channels_max = params_channels(params); + data->params.sig_bits = dai->driver->playback.sig_bits; +diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c +--- a/sound/sparc/dbri.c ++++ b/sound/sparc/dbri.c +@@ -1840,6 +1840,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + int rval = D_INTR_GETRVAL(x); + #endif + ++#ifdef DBRI_DEBUG + if (channel == D_INTR_CMD) { + dprintk(D_CMD, "INTR: Command: %-5s Value:%d\n", + cmds[command], val); +@@ -1847,6 +1848,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n", + channel, code, rval); + } ++#endif + + switch (code) { + case D_INTR_CMDI: +diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c +--- a/sound/usb/6fire/firmware.c ++++ b/sound/usb/6fire/firmware.c +@@ -194,23 +194,20 @@ static int usb6fire_fw_ezusb_upload( + int ret; + u8 data; + struct usb_device *device = interface_to_usbdev(intf); +- const struct firmware *fw = NULL; +- struct ihex_record *rec = kmalloc_obj(struct ihex_record); ++ struct ihex_record *rec __free(kfree) = kmalloc_obj(struct ihex_record); + + if (!rec) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { +- kfree(rec); + dev_err(&intf->dev, + "error requesting ezusb firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "error validating ezusb firmware %s.\n", fwname); + return ret; +@@ -219,8 +216,6 @@ static int usb6fire_fw_ezusb_upload( + data = 0x01; /* stop ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: begin message.\n", + fwname); +@@ -231,8 +226,6 @@ static int usb6fire_fw_ezusb_upload( + ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, + rec->data, rec->len); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: data urb.\n", + fwname); +@@ -240,8 +233,6 @@ static int usb6fire_fw_ezusb_upload( + } + } + +- release_firmware(fw); +- kfree(rec); + if (postdata) { /* write data after firmware has been uploaded */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, + postdata, postlen); +@@ -270,19 +261,18 @@ static int usb6fire_fw_fpga_upload( + int ret; + int i; + struct usb_device *device = interface_to_usbdev(intf); +- u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); ++ u8 *buffer __free(kfree) = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); + const char *c; + const char *end; +- const struct firmware *fw; + + if (!buffer) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + dev_err(&intf->dev, "unable to get fpga firmware %s.\n", + fwname); +- kfree(buffer); + return -EIO; + } + +@@ -291,8 +281,6 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); + if (ret) { +- kfree(buffer); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload fpga firmware: begin urb.\n"); + return ret; +@@ -304,15 +292,11 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_fpga_write(device, buffer, i); + if (ret < 0) { +- release_firmware(fw); +- kfree(buffer); + dev_err(&intf->dev, + "unable to upload fpga firmware: fw urb.\n"); + return ret; + } + } +- release_firmware(fw); +- kfree(buffer); + + ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0); + if (ret) { +diff --git a/sound/usb/card.c b/sound/usb/card.c +--- a/sound/usb/card.c ++++ b/sound/usb/card.c +@@ -715,7 +715,7 @@ static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) + + /* old style option found: the position-based integer value */ + if (quirk_flags[idx] && +- !kstrtou32(quirk_flags[idx], 0, &chip->quirk_flags)) { ++ !kstrtou64(quirk_flags[idx], 0, &chip->quirk_flags)) { + snd_usb_apply_flag_dbg("module param", chip, chip->quirk_flags); + return; + } +@@ -905,6 +905,40 @@ static int try_to_register_card(struct snd_usb_audio *chip, int ifnum) + return 0; + } + ++static void usb_audio_disconnect_components(struct snd_usb_audio *chip) ++{ ++ struct snd_usb_stream *as; ++ struct snd_usb_endpoint *ep; ++ struct usb_mixer_interface *mixer; ++ struct list_head *p; ++ ++ /* release the pcm resources */ ++ list_for_each_entry(as, &chip->pcm_list, list) { ++ snd_usb_stream_disconnect(as); ++ } ++ /* release the endpoint resources */ ++ list_for_each_entry(ep, &chip->ep_list, list) { ++ snd_usb_endpoint_release(ep); ++ } ++ /* release the midi resources */ ++ list_for_each(p, &chip->midi_list) { ++ snd_usbmidi_disconnect(p); ++ } ++ snd_usb_midi_v2_disconnect_all(chip); ++ /* ++ * Nice to check quirk && quirk->shares_media_device and ++ * then call the snd_media_device_delete(). Don't have ++ * access to the quirk here. snd_media_device_delete() ++ * accesses mixer_list ++ */ ++ snd_media_device_delete(chip); ++ ++ /* release mixer resources */ ++ list_for_each_entry(mixer, &chip->mixer_list, list) { ++ snd_usb_mixer_disconnect(mixer); ++ } ++} ++ + /* + * probe the active usb device + * +@@ -1077,8 +1111,10 @@ static int usb_audio_probe(struct usb_interface *intf, + * decrement before memory is possibly returned. + */ + atomic_dec(&chip->active); +- if (!chip->num_interfaces) ++ if (!chip->num_interfaces) { ++ usb_audio_disconnect_components(chip); + snd_card_free(chip->card); ++ } + } + return err; + } +@@ -1091,48 +1127,18 @@ static bool __usb_audio_disconnect(struct usb_interface *intf, + struct snd_usb_audio *chip, + struct snd_card *card) + { +- struct list_head *p; +- + guard(mutex)(®ister_mutex); + + if (platform_ops && platform_ops->disconnect_cb) + platform_ops->disconnect_cb(chip); + + if (atomic_inc_return(&chip->shutdown) == 1) { +- struct snd_usb_stream *as; +- struct snd_usb_endpoint *ep; +- struct usb_mixer_interface *mixer; +- + /* wait until all pending tasks done; + * they are protected by snd_usb_lock_shutdown() + */ + snd_refcount_sync(&chip->usage_count); + snd_card_disconnect(card); +- /* release the pcm resources */ +- list_for_each_entry(as, &chip->pcm_list, list) { +- snd_usb_stream_disconnect(as); +- } +- /* release the endpoint resources */ +- list_for_each_entry(ep, &chip->ep_list, list) { +- snd_usb_endpoint_release(ep); +- } +- /* release the midi resources */ +- list_for_each(p, &chip->midi_list) { +- snd_usbmidi_disconnect(p); +- } +- snd_usb_midi_v2_disconnect_all(chip); +- /* +- * Nice to check quirk && quirk->shares_media_device and +- * then call the snd_media_device_delete(). Don't have +- * access to the quirk here. snd_media_device_delete() +- * accesses mixer_list +- */ +- snd_media_device_delete(chip); +- +- /* release mixer resources */ +- list_for_each_entry(mixer, &chip->mixer_list, list) { +- snd_usb_mixer_disconnect(mixer); +- } ++ usb_audio_disconnect_components(chip); + } + + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) +diff --git a/sound/usb/clock.c b/sound/usb/clock.c +--- a/sound/usb/clock.c ++++ b/sound/usb/clock.c +@@ -600,7 +600,7 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, + static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) + { +- int cur_rate, prev_rate; ++ int cur_rate, prev_rate = 0; + int clock; + + /* First, try to find a valid clock. This may trigger +@@ -625,9 +625,12 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + return clock; + } + +- prev_rate = get_sample_rate_v2v3(chip, fmt->iface, fmt->altsetting, clock); +- if (prev_rate == rate) +- goto validation; ++ if (!(chip->quirk_flags & QUIRK_FLAG_ALWAYS_SET_RATE)) { ++ prev_rate = get_sample_rate_v2v3(chip, fmt->iface, ++ fmt->altsetting, clock); ++ if (prev_rate == rate) ++ goto validation; ++ } + + cur_rate = snd_usb_set_sample_rate_v2v3(chip, fmt, clock, rate); + if (cur_rate < 0) { +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -1237,7 +1237,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + /* try to use enough URBs to contain an entire ALSA buffer */ + max_urbs = min((unsigned) MAX_URBS, + MAX_QUEUE * packs_per_ms / urb_packs); +- ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ ep->nurbs = MAX_URBS; ++ else ++ ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); + } + + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) +@@ -1262,6 +1265,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + goto out_of_memory; + u->urb->pipe = ep->pipe; + u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ u->urb->transfer_flags |= URB_ISO_ASAP; + u->urb->interval = 1 << ep->datainterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,6 +505,19 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + ++/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels ++ * and sits on the monitor mixer branch, but it is traced through to the ++ * Speaker output terminal and gets named "Speaker Playback Volume". Userspace ++ * then adopts it as the stream's hardware volume, and any setting below 0 dB ++ * attenuates some channels but not others (20 dB imbalance at 80%). Give it a ++ * non-standard name so that it is no longer taken for the stream's master ++ * volume, while remaining reachable for anyone who wants the monitor gain. ++ */ ++static const struct usbmix_name_map audient_id14_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -588,6 +601,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2573, 0x0008), + .map = maya44_map, + }, ++ { ++ /* Audient iD14 */ ++ .id = USB_ID(0x2708, 0x0008), ++ .map = audient_id14_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3920,6 +3920,7 @@ static int snd_rme_digiface_controls_create(struct usb_mixer_interface *mixer) + #define SND_DJM_450_IDX 0x5 + #define SND_DJM_A9_IDX 0x6 + #define SND_DJM_V10_IDX 0x7 ++#define SND_DJM_S11_IDX 0x8 + + #define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ + .name = _name, \ +@@ -4260,6 +4261,21 @@ static const struct snd_djm_ctl snd_djm_ctls_v10[] = { + // playback channels are fixed and controlled by hardware knobs on the mixer + }; + ++// DJM-S11 ++static const u16 snd_djm_opts_s11_cap1[] = { ++ 0x0100, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d }; ++static const u16 snd_djm_opts_s11_cap2[] = { ++ 0x0200, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d }; ++static const u16 snd_djm_opts_s11_cap3[] = { ++ 0x0307, 0x0308, 0x0309, 0x030a, 0x030d, 0x0311, 0x0312 }; ++ ++static const struct snd_djm_ctl snd_djm_ctls_s11[] = { ++ SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), ++ SND_DJM_CTL("Input 1 Capture Switch", s11_cap1, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 2 Capture Switch", s11_cap2, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 3 Capture Switch", s11_cap3, 3, SND_DJM_WINDEX_CAP) ++}; ++ + static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_250MK2_IDX] = SND_DJM_DEVICE(250mk2), + [SND_DJM_750_IDX] = SND_DJM_DEVICE(750), +@@ -4269,6 +4285,7 @@ static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_450_IDX] = SND_DJM_DEVICE(450), + [SND_DJM_A9_IDX] = SND_DJM_DEVICE(a9), + [SND_DJM_V10_IDX] = SND_DJM_DEVICE(v10), ++ [SND_DJM_S11_IDX] = SND_DJM_DEVICE(s11), + }; + + static int snd_djm_controls_info(struct snd_kcontrol *kctl, +@@ -4573,6 +4590,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) + case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */ + err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX); + break; ++ case USB_ID(0x2b73, 0x0037): /* Pioneer DJ DJM-S11 */ ++ err = snd_djm_controls_create(mixer, SND_DJM_S11_IDX); ++ break; + case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ + err = hp_dock_mixer_create(mixer); + break; +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -3320,6 +3320,73 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * Pioneer DJ / AlphaTheta DJM-S11 ++ */ ++ USB_DEVICE(0x2b73, 0x0037), ++ QUIRK_DRIVER_INFO { ++ QUIRK_DATA_COMPOSITE { ++ { ++ QUIRK_DATA_STANDARD_MIXER(0) ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(1) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 14, ++ .iface = 1, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x01, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(2) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 10, ++ .iface = 2, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x82, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC | ++ USB_ENDPOINT_USAGE_IMPLICIT_FB, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ /* Audio Control (unknown purpose) */ ++ .ifnum = 3, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ { ++ .ifnum = 4, ++ .type = QUIRK_MIDI_STANDARD_INTERFACE ++ }, ++ { ++ /* HID */ ++ .ifnum = 5, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* + * MacroSilicon MS2100/MS2106 based AV capture cards +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2145,7 +2145,7 @@ struct usb_string_match { + + struct usb_audio_quirk_flags_table { + u32 id; +- u32 flags; ++ u64 flags; + const struct usb_string_match *usb_string_match; + }; + +@@ -2314,7 +2314,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0661, 0x0883, /* iBasso DC04 Ultra */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x0666, 0x0880, /* SPACETOUCH USB Audio */ +- QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), ++ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY | ++ QUIRK_FLAG_CTL_MSG_DELAY_5M), + DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ +@@ -2337,6 +2338,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x0951, 0x16ad, /* Kingston HyperX */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), ++ DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ ++ QUIRK_FLAG_ALWAYS_SET_RATE), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2432,6 +2435,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), ++ DEVICE_FLG(0x1e0b, 0xd01e, /* Generic USB Audio Device */ ++ QUIRK_FLAG_PLAYBACK_URB_FIXUP), + DEVICE_FLG(0x1ff7, 0x0f81, /* SC13A Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */ +@@ -2528,6 +2533,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ + QUIRK_FLAG_ALIGN_TRANSFER), ++ DEVICE_FLG(0x84ef, 0x002a, /* Valeton GP-200 */ ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), + +@@ -2641,18 +2648,12 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), ++ QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL + }; + +-const char *snd_usb_quirk_flag_find_name(unsigned long index) +-{ +- if (index >= ARRAY_SIZE(snd_usb_audio_quirk_flag_names)) +- return NULL; +- +- return snd_usb_audio_quirk_flag_names[index]; +-} +- +-u32 snd_usb_quirk_flags_from_name(const char *name) ++static u64 snd_usb_quirk_flags_from_name(const char *name) + { + int i; + +@@ -2661,7 +2662,7 @@ u32 snd_usb_quirk_flags_from_name(const char *name) + + for (i = 0; snd_usb_audio_quirk_flag_names[i]; i++) { + if (strcasecmp(name, snd_usb_audio_quirk_flag_names[i]) == 0) +- return BIT_U32(i); ++ return BIT_U64(i); + } + + return 0; +@@ -2719,7 +2720,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + { + u16 chip_vid = USB_ID_VENDOR(chip->usb_id); + u16 chip_pid = USB_ID_PRODUCT(chip->usb_id); +- u32 mask_flags, unmask_flags, bit; ++ u64 mask_flags, unmask_flags, bit; + char *p, *field, *flag; + bool is_unmask; + u16 vid, pid; +@@ -2773,7 +2774,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + is_unmask = false; + } + +- if (!kstrtou32(flag, 16, &bit)) { ++ if (!kstrtou64(flag, 16, &bit)) { + if (is_unmask) + unmask_flags |= bit; + else +diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h +--- a/sound/usb/quirks.h ++++ b/sound/usb/quirks.h +@@ -56,7 +56,4 @@ void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip); + void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + const char *str); + +-const char *snd_usb_quirk_flag_find_name(unsigned long flag); +-u32 snd_usb_quirk_flags_from_name(const char *name); +- + #endif /* __USBAUDIO_QUIRKS_H */ +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -44,7 +44,7 @@ struct snd_usb_audio { + atomic_t active; + atomic_t shutdown; + struct snd_refcount usage_count; +- unsigned int quirk_flags; ++ u64 quirk_flags; + unsigned int need_delayed_register:1; /* warn for delayed registration */ + int num_interfaces; + int last_iface; +@@ -254,6 +254,16 @@ extern bool snd_usb_skip_validation; + * check being non-fatal and only disabling GET_CUR instead of the whole mixer. + * The current volume will then be provided by the internal cache that stores + * the last set volume ++ * QUIRK_FLAG_PLAYBACK_URB_FIXUP ++ * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. ++ * This is needed for devices that exhibit boot-time audio stuttering due ++ * to insufficient buffer depth combined with xHCI scheduling variability. ++ * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling ++ * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. ++ * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * Issue SET_CUR for the sample rate even when the clock already reports the ++ * requested rate. A device advertising a single rate is otherwise never sent ++ * the request at all, and some require it before streaming will start. + */ + + enum { +@@ -288,10 +298,12 @@ enum { + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, + QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, ++ QUIRK_TYPE_ALWAYS_SET_RATE = 32, + /* Please also edit snd_usb_audio_quirk_flag_names */ + }; + +-#define QUIRK_FLAG(x) BIT_U32(QUIRK_TYPE_ ## x) ++#define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) + + #define QUIRK_FLAG_GET_SAMPLE_RATE QUIRK_FLAG(GET_SAMPLE_RATE) + #define QUIRK_FLAG_SHARE_MEDIA_DEVICE QUIRK_FLAG(SHARE_MEDIA_DEVICE) +@@ -324,5 +336,7 @@ enum { + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) + #define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) ++#define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + + #endif /* __USBAUDIO_H */ +diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pcm.h +--- a/sound/usb/usx2y/us144mkii_pcm.h ++++ b/sound/usb/usx2y/us144mkii_pcm.h +@@ -7,7 +7,7 @@ + #include "us144mkii.h" + + /** +- * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. ++ * var tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period sizes + * for the TASCAM US-144MKII audio interface. +@@ -15,14 +15,14 @@ + extern const struct snd_pcm_hardware tascam_pcm_hw; + + /** +- * tascam_playback_ops - ALSA PCM operations for playback. ++ * var tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream operations. + */ + extern const struct snd_pcm_ops tascam_playback_ops; + + /** +- * tascam_capture_ops - ALSA PCM operations for capture. ++ * var tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operations. + */ +diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c +--- a/sound/usb/usx2y/usbusx2yaudio.c ++++ b/sound/usb/usx2y/usbusx2yaudio.c +@@ -167,7 +167,6 @@ static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, i + if (!urb) + return -ENODEV; + urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks +- urb->hcpriv = NULL; + urb->dev = subs->usx2y->dev; /* we need to set this at each time */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { diff --git a/pkgbuilds/linux-omarchy-eevdf/0510-sound-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0510-sound-updates.patch.sig new file mode 100644 index 0000000..3f24244 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0510-sound-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0511-asoc-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0511-asoc-updates.patch new file mode 100644 index 0000000..75c49b4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0511-asoc-updates.patch @@ -0,0 +1,37891 @@ +diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +--- a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c ++++ b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +@@ -147,7 +147,7 @@ static const struct reg_default halo_register_defaults[] = { + { 0x2b805c0, 0 }, /* HALO_SCRATCH1 */ + { 0x2b805c8, 0 }, /* HALO_SCRATCH2 */ + { 0x2b805d0, 0 }, /* HALO_SCRATCH3 */ +- { 0x2b805c8, 0 }, /* HALO_SCRATCH4 */ ++ { 0x2b805d8, 0 }, /* HALO_SCRATCH4 */ + { 0x2bc1000, 0 }, /* HALO_CCM_CORE_CONTROL */ + { 0x2bc7000, 0 }, /* HALO_WDT_CONTROL */ + +diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c +--- a/drivers/soundwire/bus_type.c ++++ b/drivers/soundwire/bus_type.c +@@ -105,6 +105,9 @@ static int sdw_bus_probe(struct device *dev) + } + slave->index = ret; + ++ /* Create IRQ mapping now so the driver can get it in probe() */ ++ sdw_irq_create_mapping(slave); ++ + ret = drv->probe(slave, id); + if (ret) { + ida_free(&slave->bus->slave_ida, slave->index); +@@ -117,9 +120,6 @@ static int sdw_bus_probe(struct device *dev) + if (drv->ops && drv->ops->read_prop) + drv->ops->read_prop(slave); + +- if (slave->prop.use_domain_irq) +- sdw_irq_create_mapping(slave); +- + /* init the dynamic sysfs attributes we need */ + ret = sdw_slave_sysfs_dpn_init(slave); + if (ret < 0) +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -152,6 +152,10 @@ + #define LPI_MI2S_TX_4 146 + #define SENARY_MI2S_RX 147 + #define SENARY_MI2S_TX 148 ++#define LPI_MI2S_RX_5 149 ++#define LPI_MI2S_TX_5 150 ++#define LPI_MI2S_RX_6 151 ++#define LPI_MI2S_TX_6 152 + + #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 + #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 +@@ -233,11 +237,39 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + ++#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++ + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 + #define LPASS_HW_DCODEC_VOTE 103 +- +-#define Q6AFE_MAX_CLK_ID 104 ++#define LPASS_HW_LPR_VOTE 104 + + #define LPASS_CLK_ATTRIBUTE_INVALID 0x0 + #define LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +diff --git a/include/linux/platform_data/omap-twl4030.h b/include/linux/platform_data/omap-twl4030.h +deleted file mode 100644 +--- a/include/linux/platform_data/omap-twl4030.h ++++ /dev/null +@@ -1,42 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/** +- * omap-twl4030.h - ASoC machine driver for TI SoC based boards with twl4030 +- * codec, header. +- * +- * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com +- * All rights reserved. +- * +- * Author: Peter Ujfalusi +- */ +- +-#ifndef _OMAP_TWL4030_H_ +-#define _OMAP_TWL4030_H_ +- +-/* To select if only one channel is connected in a stereo port */ +-#define OMAP_TWL4030_LEFT (1 << 0) +-#define OMAP_TWL4030_RIGHT (1 << 1) +- +-struct omap_tw4030_pdata { +- const char *card_name; +- /* Voice port is connected to McBSP3 */ +- bool voice_connected; +- +- /* The driver will parse the connection flags if this flag is set */ +- bool custom_routing; +- /* Flags to indicate connected audio ports. */ +- u8 has_hs; +- u8 has_hf; +- u8 has_predriv; +- u8 has_carkit; +- bool has_ear; +- +- bool has_mainmic; +- bool has_submic; +- bool has_hsmic; +- bool has_carkitmic; +- bool has_digimic0; +- bool has_digimic1; +- u8 has_linein; +-}; +- +-#endif /* _OMAP_TWL4030_H_ */ +diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h +--- a/include/sound/cs35l56.h ++++ b/include/sound/cs35l56.h +@@ -417,7 +417,6 @@ void cs35l56_wait_control_port_ready(void); + void cs35l56_wait_min_reset_pulse(void); + void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); +diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h +--- a/include/sound/dmaengine_pcm.h ++++ b/include/sound/dmaengine_pcm.h +@@ -175,12 +175,6 @@ int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream, + struct dmaengine_pcm { + struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1]; + const struct snd_dmaengine_pcm_config *config; +- struct snd_soc_component component; + unsigned int flags; + }; +- +-static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p) +-{ +- return container_of(p, struct dmaengine_pcm, component); +-} + #endif +diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h +--- a/include/sound/hda-mlink.h ++++ b/include/sound/hda-mlink.h +@@ -9,6 +9,22 @@ + struct hdac_bus; + struct hdac_ext_link; + ++/** ++ * enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA ++ * allocator constraints (see struct sof_intel_hda_dev). ++ * ++ * @HDA_BUS_ML_LINK_HDA: non-alt link, i.e. HDA codec or iDisp ++ * @HDA_BUS_ML_LINK_SDW: alt link, SoundWire ++ * @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload ++ * @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC ++ */ ++enum hda_bus_ml_link_type { ++ HDA_BUS_ML_LINK_HDA, ++ HDA_BUS_ML_LINK_SDW, ++ HDA_BUS_ML_LINK_UAOL, ++ HDA_BUS_ML_LINK_OTHER, ++}; ++ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK) + + int hda_bus_ml_init(struct hdac_bus *bus); +@@ -49,11 +65,12 @@ int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num); + int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + int channel_mask, int stream_id, int dir); + +-void hda_bus_ml_put_all(struct hdac_bus *bus); + void hda_bus_ml_reset_losidv(struct hdac_bus *bus); + int hda_bus_ml_resume(struct hdac_bus *bus); + int hda_bus_ml_suspend(struct hdac_bus *bus); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink); ++ + struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus); +@@ -169,11 +186,13 @@ hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } + +-static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { } + static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { } + static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; } + static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; } + ++static inline enum hda_bus_ml_link_type ++hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; } ++ + static inline struct hdac_ext_link * + hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; } + +diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h +--- a/include/sound/sdca_asoc.h ++++ b/include/sound/sdca_asoc.h +@@ -25,6 +25,8 @@ struct snd_soc_dai_driver; + struct snd_soc_dai_ops; + struct snd_soc_dapm_route; + struct snd_soc_dapm_widget; ++struct snd_soc_pcm_stream; ++struct sdca_entity; + + /* convenient macro to handle the mono volume in 7.8 fixed format representation */ + #define SDCA_SINGLE_Q78_TLV(xname, xreg, xmin, xmax, xstep, tlv_array) \ +@@ -82,6 +84,11 @@ int sdca_asoc_populate_component(struct device *dev, + struct snd_soc_dai_driver **dai_drv, int *num_dai_drv, + const struct snd_soc_dai_ops *ops); + ++int sdca_asoc_populate_rate_format(struct device *dev, ++ struct sdca_function_data *function, ++ struct sdca_entity *entity, ++ struct snd_soc_pcm_stream *stream); ++ + int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, + struct sdca_function_data *function, + struct snd_pcm_substream *substream, +@@ -100,9 +107,9 @@ int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays); ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays); + #endif // __SDCA_ASOC_H__ +diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h +--- a/include/sound/sdca_function.h ++++ b/include/sound/sdca_function.h +@@ -298,7 +298,7 @@ enum sdca_xu_controls { + }; + + /** +- * enum sdca_set_index_range - Column definitions UMP SetIndex ++ * enum sdca_fdl_set_index_range - Column definitions UMP SetIndex + */ + enum sdca_fdl_set_index_range { + SDCA_FDL_SET_INDEX_SET_NUMBER = 0, +@@ -803,6 +803,8 @@ struct sdca_control_range { + * @mode: Access mode of the Control. + * @layers: Bitmask of access layers of the Control. + * @deferrable: Indicates if the access to the Control can be deferred. ++ * @is_volatile: Indicates the Control registers are forced to be treated ++ * as volatile. + * @has_default: Indicates the Control has a default value to be written. + * @has_reset: Indicates the Control has a defined reset value. + * @has_fixed: Indicates the Control only supports a single value. +@@ -1116,7 +1118,6 @@ struct sdca_entity_ge { + + /** + * struct sdca_entity_hide - information specific to HIDE Entities +- * @hid: HID device structure + * @num_hidtx_ids: number of HIDTx Report ID + * @num_hidrx_ids: number of HIDRx Report ID + * @hidtx_ids: HIDTx Report ID +@@ -1127,11 +1128,8 @@ struct sdca_entity_ge { + * within this Device + * @max_delay: the maximum time in microseconds allowed for the Device + * to change the ownership from Device to Host +- * @hid_report_desc: HID Report Descriptor for the HIDE Entity +- * @hid_desc: HID descriptor for the HIDE Entity + */ + struct sdca_entity_hide { +- struct hid_device *hid; + unsigned int *hidtx_ids; + unsigned int *hidrx_ids; + int num_hidtx_ids; +@@ -1139,12 +1137,10 @@ struct sdca_entity_hide { + unsigned int af_number_list[SDCA_MAX_FUNCTION_COUNT]; + unsigned int hide_reside_function_num; + unsigned int max_delay; +- unsigned char *hid_report_desc; +- struct hid_descriptor hid_desc; + }; + + /** +- * enum sdca_xu_reset_machanism - SDCA FDL Resets ++ * enum sdca_xu_reset_mechanism - SDCA FDL Resets + */ + enum sdca_xu_reset_mechanism { + SDCA_XU_RESET_FUNCTION = 0x0, +@@ -1401,6 +1397,16 @@ struct sdca_fdl_data { + int num_sets; + }; + ++/** ++ * struct sdca_function_hid - information about a function's HID descriptors ++ * @report_desc: HID Report Descriptor for the HID Function ++ * @desc: HID descriptor for the HID Function ++ */ ++struct sdca_function_hid { ++ unsigned char *report_desc; ++ struct hid_descriptor desc; ++}; ++ + /** + * struct sdca_function_data - top-level information for one SDCA function + * @desc: Pointer to short descriptor from initial parsing. +@@ -1415,6 +1421,7 @@ struct sdca_fdl_data { + * @reset_max_delay: Maximum Function reset delay in microseconds, before an + * error should be reported. + * @fdl_data: FDL data for this Function, if available. ++ * @hid: HID data for this Function, if available. + */ + struct sdca_function_data { + struct sdca_function_desc *desc; +@@ -1430,6 +1437,10 @@ struct sdca_function_data { + unsigned int reset_max_delay; + + struct sdca_fdl_data fdl_data; ++ ++ union { ++ struct sdca_function_hid hid; ++ }; + }; + + static inline u32 sdca_range(struct sdca_control_range *range, +@@ -1451,8 +1462,7 @@ static inline u32 sdca_range_search(struct sdca_control_range *range, + return 0; + } + +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function); ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function); + + const char *sdca_find_terminal_name(enum sdca_terminal_type type); + +@@ -1469,5 +1479,7 @@ struct sdca_control_range *sdca_selector_find_range(struct device *dev, + struct sdca_cluster *sdca_id_find_cluster(struct device *dev, + struct sdca_function_data *function, + const int id); ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, ++ const char *entity_label); + + #endif +diff --git a/include/sound/sdca_hid.h b/include/sound/sdca_hid.h +--- a/include/sound/sdca_hid.h ++++ b/include/sound/sdca_hid.h +@@ -16,18 +16,21 @@ struct sdca_interrupt; + + #if IS_ENABLED(CONFIG_SND_SOC_SDCA_HID) + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity); ++int sdca_add_hid_device(struct sdca_interrupt *interrupt); ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt); + int sdca_hid_process_report(struct sdca_interrupt *interrupt); + + #else + +-static inline int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++static inline int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { + return 0; + } + ++static inline void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + return 0; +diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h +--- a/include/sound/simple_card_utils.h ++++ b/include/sound/simple_card_utils.h +@@ -181,7 +181,7 @@ void simple_util_canonicalize_platform(struct snd_soc_dai_link_component *platfo + void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + int is_single_links); + +-void simple_util_clean_reference(struct snd_soc_card *card); ++void simple_util_clean_reference(struct simple_util_priv *priv); + + void simple_util_parse_convert(struct device_node *np, char *prefix, + struct simple_util_data *data); +@@ -189,18 +189,37 @@ bool simple_util_is_convert_required(const struct simple_util_data *data); + + int simple_util_get_sample_fmt(struct simple_util_data *data); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix); ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property); ++static inline int simple_util_parse_routing(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_routing, ++ prefix, "routing"); ++} ++ ++static inline int simple_util_parse_widgets(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_simple_widgets, ++ prefix, "widgets"); ++} ++ ++static inline int simple_util_parse_pin_switches(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_pin_switches, ++ prefix, "pin-switches"); ++} ++ ++static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_aux_devs, ++ prefix, "aux-devs"); ++} + + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, char *pin); +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, +- char *prefix); ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); + int simple_util_init_priv(struct simple_util_priv *priv, + struct link_info *li); + void simple_util_remove(struct platform_device *pdev); +diff --git a/include/sound/soc-acpi.h b/include/sound/soc-acpi.h +--- a/include/sound/soc-acpi.h ++++ b/include/sound/soc-acpi.h +@@ -57,7 +57,7 @@ static inline struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg) + #endif + + /** +- * snd_soc_acpi_mach_params: interface for machine driver configuration ++ * struct snd_soc_acpi_mach_params - interface for machine driver configuration + * + * @acpi_ipc_irq_index: used for BYT-CR detection + * @platform: string used for HDAudio codec support +@@ -93,7 +93,7 @@ struct snd_soc_acpi_mach_params { + }; + + /** +- * snd_soc_acpi_endpoint - endpoint descriptor ++ * struct snd_soc_acpi_endpoint - endpoint descriptor + * @num: endpoint number (mandatory, unique per device) + * @aggregated: 0 (independent) or 1 (logically grouped) + * @group_position: zero-based order (only when @aggregated is 1) +@@ -107,7 +107,7 @@ struct snd_soc_acpi_endpoint { + }; + + /** +- * snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device ++ * struct snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device + * @adr: 64 bit ACPI _ADR value + * @num_endpoints: number of endpoints for this device + * @endpoints: array of endpoints +@@ -121,7 +121,7 @@ struct snd_soc_acpi_adr_device { + }; + + /** +- * snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices ++ * struct snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices + * @mask: one bit set indicates the link this list applies to + * @num_adr: ARRAY_SIZE of devices + * @adr_d: array of devices +@@ -167,8 +167,8 @@ struct snd_soc_acpi_link_adr { + #define SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME BIT(4) + + /** +- * snd_soc_acpi_mach: ACPI-based machine descriptor. Most of the fields are +- * related to the hardware, except for the firmware and topology file names. ++ * struct snd_soc_acpi_mach - ACPI-based machine descriptor. Most of the fields ++ * are related to the hardware, except for the firmware and topology file names. + * A platform supported by legacy and Sound Open Firmware (SOF) would expose + * all firmware/topology related fields. + * +@@ -192,6 +192,7 @@ struct snd_soc_acpi_link_adr { + * the initial selection in the snd_soc_acpi_mach table. + * @pdata: intended for platform data or machine specific-ops. This structure + * is not constant since this field may be updated at run-time ++ * @mach_params: machine driver configuration + * @sof_tplg_filename: Sound Open Firmware topology file name, if enabled + * @tplg_quirk_mask: quirks to select different topology files dynamically + * @get_function_tplg_files: This is an optional callback, if specified then instead of +@@ -199,11 +200,11 @@ struct snd_soc_acpi_link_adr { + * files to be loaded. + * Return value: The number of the files or negative ERRNO. 0 means that the single topology + * file should be used, no function topology split can be used on the machine. +- * @card: the pointer of the card +- * @mach: the pointer of the machine driver +- * @prefix: the prefix of the topology file name. Typically, it is the path. +- * @tplg_files: the pointer of the array of the topology file names. +- * @best_effort: ignore non supported links and try to build the card in best effort ++ * card: the pointer of the card ++ * mach: the pointer of the machine driver ++ * prefix: the prefix of the topology file name. Typically, it is the path. ++ * tplg_files: the pointer of the array of the topology file names. ++ * best_effort: ignore non supported links and try to build the card in best effort + * with supported links + */ + /* Descriptor for SST ASoC machine driver */ +diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h +--- a/include/sound/soc-component.h ++++ b/include/sound/soc-component.h +@@ -253,6 +253,9 @@ struct snd_soc_component { + void *mark_pm; + + struct dentry *debugfs_root; ++ ++ /* Component private data */ ++ void *priv; + }; + + #define for_each_component_dais(component, dai)\ +@@ -283,6 +286,14 @@ static inline int snd_soc_component_cache_sync( + return regcache_sync(component->regmap); + } + ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); ++const char *snd_soc_component_name(struct snd_soc_component *component); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); ++void *snd_soc_component_to_priv(struct snd_soc_component *component); ++ + void snd_soc_component_set_aux(struct snd_soc_component *component, + struct snd_soc_aux_dev *aux); + int snd_soc_component_init(struct snd_soc_component *component); +diff --git a/include/sound/soc.h b/include/sound/soc.h +--- a/include/sound/soc.h ++++ b/include/sound/soc.h +@@ -447,15 +447,16 @@ static inline int snd_soc_resume(struct device *dev) + } + #endif + int snd_soc_poweroff(struct device *dev); +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev); +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai); +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, int num_dai); ++#define snd_soc_register_component(x, ...) _Generic((x), \ ++struct device * : snd_soc_register_component_d, \ ++struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) ++ + int devm_snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); +diff --git a/include/sound/sof/dai-amd.h b/include/sound/sof/dai-amd.h +--- a/include/sound/sof/dai-amd.h ++++ b/include/sound/sof/dai-amd.h +@@ -18,6 +18,7 @@ struct sof_ipc_dai_acp_params { + uint32_t fsync_rate; /* FSYNC frequency in Hz */ + uint32_t tdm_slots; + uint32_t tdm_mode; ++ uint32_t format; + } __packed; + + /* ACPDMIC Configuration Request - SOF_IPC_DAI_AMD_CONFIG */ +diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h +--- a/include/sound/sof/dai.h ++++ b/include/sound/sof/dai.h +@@ -90,6 +90,8 @@ enum sof_ipc_dai_type { + SOF_DAI_AMD_HS_VIRTUAL, /**< AMD ACP HS VIRTUAL */ + SOF_DAI_IMX_MICFIL, /** < i.MX MICFIL PDM */ + SOF_DAI_AMD_SDW, /**< AMD ACP SDW */ ++ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ ++ SOF_DAI_AMD_I2S, /**< AMD ACP I2S */ + }; + + /* general purpose DAI configuration */ +@@ -121,6 +123,7 @@ struct sof_ipc_dai_config { + struct sof_ipc_dai_mtk_afe_params afe; + struct sof_ipc_dai_micfil_params micfil; + struct sof_ipc_dai_acp_sdw_params acp_sdw; ++ struct sof_ipc_dai_acp_params acp_i2s; + }; + } __packed; + +diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h +--- a/include/sound/sof/header.h ++++ b/include/sound/sof/header.h +@@ -179,7 +179,7 @@ struct sof_ipc_compound_hdr { + uint32_t count; /**< count of 0 means end of compound sequence */ + } __packed; + +-/** ++/* + * OOPS header architecture specific data. + */ + struct sof_ipc_dsp_oops_arch_hdr { +@@ -187,7 +187,7 @@ struct sof_ipc_dsp_oops_arch_hdr { + uint32_t totalsize; /* Total size of oops message */ + } __packed; + +-/** ++/* + * OOPS header platform specific data. + */ + struct sof_ipc_dsp_oops_plat_hdr { +diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h +--- a/include/sound/sof/ipc4/header.h ++++ b/include/sound/sof/ipc4/header.h +@@ -187,6 +187,10 @@ enum sof_ipc4_pipeline_state { + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_MASK GENMASK(23, 20) + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT) + ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT 29 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_MASK BIT(29) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT) ++ + /* pipeline set state ipc msg */ + #define SOF_IPC4_GLB_PIPE_STATE_ID_SHIFT 16 + #define SOF_IPC4_GLB_PIPE_STATE_ID_MASK GENMASK(23, 16) +@@ -536,12 +540,63 @@ enum sof_ipc4_notification_type { + SOF_IPC4_NOTIFY_TYPE_LAST, + }; + ++enum sof_ipc4_resource_type { ++ SOF_IPC4_MODULE_INSTANCE, ++ SOF_IPC4_PIPELINE, ++ SOF_IPC4_GATEWAY, ++ SOF_IPC4_EDF_TASK, ++ SOF_IPC4_INVALID_RESOURCE_TYPE, ++}; ++ ++enum sof_ipc4_event_type { ++ /* Underrun detected by the Mixer */ ++ SOF_IPC4_MIXER_UNDERRUN_DETECTED = 1, ++ /* Error caught during data processing */ ++ SOF_IPC4_PROCESS_DATA_ERROR = 3, ++ /* Underrun detected by gateway. */ ++ SOF_IPC4_GATEWAY_UNDERRUN_DETECTED = 6, ++ /* Overrun detected by gateway */ ++ SOF_IPC4_GATEWAY_OVERRUN_DETECTED, ++}; ++ ++/** ++ * struct sof_ipc4_process_data_error_event_data - process data error event payload ++ * @error_code: Error code returned by data processing function ++ */ ++struct sof_ipc4_process_data_error_event_data { ++ uint32_t error_code; ++}; ++ ++/** ++ * struct sof_ipc4_mixer_underrun_event_data - mixer underrun event payload ++ * @eos_flag: Indicates EndOfStream ++ * @data_mixed: Data processed by module (in bytes) ++ * @expected_data_mixed: Expected data to be processed (in bytes) ++ */ ++struct sof_ipc4_mixer_underrun_event_data { ++ uint32_t eos_flag; ++ uint32_t data_mixed; ++ uint32_t expected_data_mixed; ++}; ++ ++/** ++ * union sof_ipc4_resource_event_data - resource event specific payload ++ * @dws: Raw event data payload as six dwords ++ * @process_data_error: SOF_IPC4_PROCESS_DATA_ERROR payload ++ * @mixer_underrun: SOF_IPC4_MIXER_UNDERRUN_DETECTED payload ++ */ ++union sof_ipc4_resource_event_data { ++ uint32_t dws[6]; ++ struct sof_ipc4_process_data_error_event_data process_data_error; ++ struct sof_ipc4_mixer_underrun_event_data mixer_underrun; ++}; ++ + struct sof_ipc4_notify_resource_data { + uint32_t resource_type; + uint32_t resource_id; + uint32_t event_type; + uint32_t reserved; +- uint32_t data[6]; ++ union sof_ipc4_resource_event_data data; + } __packed __aligned(4); + + #define SOF_IPC4_DEBUG_DESCRIPTOR_SIZE 12 /* 3 x u32 */ +@@ -654,11 +709,81 @@ enum sof_ipc4_mod_init_ext_obj_id { + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + }; + +-/* DP module memory configuration data object for ext_init object array */ ++/* DP module memory configuration data object for object array */ + struct sof_ipc4_mod_init_ext_dp_memory_data { ++ u32 domain_id; /* userspace domain ID */ ++ u32 stack_bytes; /* required stack size in bytes */ ++ u32 heap_bytes; /* required heap size in bytes */ ++} __packed __aligned(4); ++ ++/* ++ * This set of macros are very similar to the set above, but these are ++ * for building payload to SOF_IPC4_GLB_CREATE_PIPELINE message. ++ * ++ * Macros for creating struct sof_ipc4_glb_pipe_payload payload with ++ * its associated data. struct sof_ipc4_glb_pipe_payload should be the ++ * first piece of payload following SOF_IPC4_GLB_CREATE_PIPELINE msg, ++ * and its existence is indicated with SOF_IPC4_GLB_PIPE_PAYLOAD bit. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_payload.word0 ++ */ ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_MASK GENMASK(23, 0) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT 24 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK BIT(24) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT) ++ ++struct sof_ipc4_glb_pipe_payload { ++ u32 word0; ++ u32 rsvd1; ++ u32 rsvd2; ++} __packed __aligned(4); ++ ++/* ++ * SOF_IPC4_GLB_CREATE_PIPELINE payload may be followed by arbitrary ++ * number of object array objects. SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY-bit ++ * indicates that an array object follows struct ++ * sof_ipc4_glb_pipe_payload. ++ * ++ * The object header's SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST-bit in struct ++ * sof_ipc4_glb_pipe_ext_object indicates if the array is continued ++ * with another object. The header has also fields to identify the ++ * object, SOF_IPC4_GLB_PIPE_EXT_OBJ_ID, and to indicate the object's ++ * size in 32-bit words, SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS, not ++ * including the header itself. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_ext_object.header ++ */ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK BIT(0) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT 1 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_MASK GENMASK(15, 1) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT 16 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_MASK GENMASK(31, 16) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT) ++ ++struct sof_ipc4_glb_pipe_ext_object { ++ u32 header; ++ u32 data[]; ++} __packed __aligned(4); ++ ++enum sof_ipc4_glb_pipe_ext_obj_id { ++ SOF_IPC4_GLB_PIPE_DATA_ID_INVALID = 0, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MAX = SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++}; ++ ++/* Pipeline memory configuration data object for ext_init object array */ ++struct sof_ipc4_glb_pipe_ext_obj_memory_data { + u32 domain_id; /* userspace domain ID */ +- u32 stack_bytes; /* stack size in bytes, 0 means default size */ +- u32 heap_bytes; /* stack size in bytes, 0 means default size */ ++ u32 stack_bytes; /* stack size in bytes */ ++ u32 heap_bytes; /* heap size in bytes */ + } __packed __aligned(4); + + /** @}*/ +diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h +--- a/include/sound/tas2781-dsp.h ++++ b/include/sound/tas2781-dsp.h +@@ -201,6 +201,11 @@ struct tasdevice_rca { + int ncfgs; + struct tasdevice_config_info **cfg_info; + int profile_cfg_id; ++ /* ++ * Used among SmartAMP for PDM microphone recording or IV data ++ * capture. ++ */ ++ int capture_profile_id; + /* + * Since version 0x105, the keyword 'init' was introduced into the + * profile, which is used for chip initialization, particularly to +@@ -222,7 +227,7 @@ void tasdevice_calbin_remove(void *context); + int tasdevice_select_tuningprm_cfg(void *context, int prm, + int cfg_no, int rca_conf_no); + int tasdevice_prmg_load(void *context, int prm_no); +-void tasdevice_tuning_switch(void *context, int state); ++void tasdevice_tuning_switch(void *context, int state, bool is_cap); + int tas2781_load_calibration(void *context, char *file_name, + unsigned short i); + +diff --git a/include/sound/tlv320aic32x4.h b/include/sound/tlv320aic32x4.h +deleted file mode 100644 +--- a/include/sound/tlv320aic32x4.h ++++ /dev/null +@@ -1,43 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- * tlv320aic32x4.h -- TLV320AIC32X4 Soc Audio driver platform data +- * +- * Copyright 2011 Vista Silicon S.L. +- * +- * Author: Javier Martin +- */ +- +-#ifndef _AIC32X4_PDATA_H +-#define _AIC32X4_PDATA_H +- +-#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 +-#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 +-#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 +-#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 +-#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 +- +-#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 +-#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 +- +-/* GPIO API */ +-#define AIC32X4_MFPX_DEFAULT_VALUE 0xff +- +-#define AIC32X4_MFP1_DIN_DISABLED 0 +-#define AIC32X4_MFP1_DIN_ENABLED 0x2 +-#define AIC32X4_MFP1_GPIO_IN 0x4 +- +-#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 +- +-#define AIC32X4_MFP_GPIO_ENABLED 0x4 +- +-#define AIC32X4_MFP5_GPIO_DISABLED 0x0 +-#define AIC32X4_MFP5_GPIO_INPUT 0x8 +-#define AIC32X4_MFP5_GPIO_OUTPUT 0xc +-#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 +- +-struct aic32x4_setup_data { +- unsigned int gpio_func[5]; +-}; +-#endif +diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h +--- a/include/sound/wm8904.h ++++ b/include/sound/wm8904.h +@@ -116,7 +116,7 @@ + #define WM8904_DRC_REGS 4 + #define WM8904_EQ_REGS 24 + +-/** ++/* + * DRC configurations are specified with a label and a set of register + * values to write (the enable bits will be ignored). At runtime an + * enumerated control will be presented for each DRC block allowing +@@ -131,7 +131,7 @@ struct wm8904_drc_cfg { + u16 regs[WM8904_DRC_REGS]; + }; + +-/** ++/* + * ReTune Mobile configurations are specified with a label, sample + * rate and set of values to write (the enable bits will be ignored). + * +diff --git a/include/uapi/sound/skl-tplg-interface.h b/include/uapi/sound/skl-tplg-interface.h +deleted file mode 100644 +--- a/include/uapi/sound/skl-tplg-interface.h ++++ /dev/null +@@ -1,168 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * skl-tplg-interface.h - Intel DSP FW private data interface +- * +- * Copyright (C) 2015 Intel Corp +- * Author: Jeeja KP +- * Nilofer, Samreen +- */ +- +-#ifndef __HDA_TPLG_INTERFACE_H__ +-#define __HDA_TPLG_INTERFACE_H__ +- +-#include +- +-/* +- * Default types range from 0~12. type can range from 0 to 0xff +- * SST types start at higher to avoid any overlapping in future +- */ +-#define SKL_CONTROL_TYPE_BYTE_TLV 0x100 +-#define SKL_CONTROL_TYPE_MIC_SELECT 0x102 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT 0x103 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC 0x104 +- +-#define HDA_SST_CFG_MAX 900 /* size of copier cfg*/ +-#define MAX_IN_QUEUE 8 +-#define MAX_OUT_QUEUE 8 +- +-#define SKL_UUID_STR_SZ 40 +-/* Event types goes here */ +-/* Reserve event type 0 for no event handlers */ +-enum skl_event_types { +- SKL_EVENT_NONE = 0, +- SKL_MIXER_EVENT, +- SKL_MUX_EVENT, +- SKL_VMIXER_EVENT, +- SKL_PGA_EVENT +-}; +- +-/** +- * enum skl_ch_cfg - channel configuration +- * +- * @SKL_CH_CFG_MONO: One channel only +- * @SKL_CH_CFG_STEREO: L & R +- * @SKL_CH_CFG_2_1: L, R & LFE +- * @SKL_CH_CFG_3_0: L, C & R +- * @SKL_CH_CFG_3_1: L, C, R & LFE +- * @SKL_CH_CFG_QUATRO: L, R, Ls & Rs +- * @SKL_CH_CFG_4_0: L, C, R & Cs +- * @SKL_CH_CFG_5_0: L, C, R, Ls & Rs +- * @SKL_CH_CFG_5_1: L, C, R, Ls, Rs & LFE +- * @SKL_CH_CFG_DUAL_MONO: One channel replicated in two +- * @SKL_CH_CFG_I2S_DUAL_STEREO_0: Stereo(L,R) in 4 slots, 1st stream:[ L, R, -, - ] +- * @SKL_CH_CFG_I2S_DUAL_STEREO_1: Stereo(L,R) in 4 slots, 2nd stream:[ -, -, L, R ] +- * @SKL_CH_CFG_INVALID: Invalid +- */ +-enum skl_ch_cfg { +- SKL_CH_CFG_MONO = 0, +- SKL_CH_CFG_STEREO = 1, +- SKL_CH_CFG_2_1 = 2, +- SKL_CH_CFG_3_0 = 3, +- SKL_CH_CFG_3_1 = 4, +- SKL_CH_CFG_QUATRO = 5, +- SKL_CH_CFG_4_0 = 6, +- SKL_CH_CFG_5_0 = 7, +- SKL_CH_CFG_5_1 = 8, +- SKL_CH_CFG_DUAL_MONO = 9, +- SKL_CH_CFG_I2S_DUAL_STEREO_0 = 10, +- SKL_CH_CFG_I2S_DUAL_STEREO_1 = 11, +- SKL_CH_CFG_7_1 = 12, +- SKL_CH_CFG_4_CHANNEL = SKL_CH_CFG_7_1, +- SKL_CH_CFG_INVALID +-}; +- +-enum skl_module_type { +- SKL_MODULE_TYPE_MIXER = 0, +- SKL_MODULE_TYPE_COPIER, +- SKL_MODULE_TYPE_UPDWMIX, +- SKL_MODULE_TYPE_SRCINT, +- SKL_MODULE_TYPE_ALGO, +- SKL_MODULE_TYPE_BASE_OUTFMT, +- SKL_MODULE_TYPE_KPB, +- SKL_MODULE_TYPE_MIC_SELECT, +-}; +- +-enum skl_core_affinity { +- SKL_AFFINITY_CORE_0 = 0, +- SKL_AFFINITY_CORE_1, +- SKL_AFFINITY_CORE_MAX +-}; +- +-enum skl_pipe_conn_type { +- SKL_PIPE_CONN_TYPE_NONE = 0, +- SKL_PIPE_CONN_TYPE_FE, +- SKL_PIPE_CONN_TYPE_BE +-}; +- +-enum skl_hw_conn_type { +- SKL_CONN_NONE = 0, +- SKL_CONN_SOURCE = 1, +- SKL_CONN_SINK = 2 +-}; +- +-enum skl_dev_type { +- SKL_DEVICE_BT = 0x0, +- SKL_DEVICE_DMIC = 0x1, +- SKL_DEVICE_I2S = 0x2, +- SKL_DEVICE_SLIMBUS = 0x3, +- SKL_DEVICE_HDALINK = 0x4, +- SKL_DEVICE_HDAHOST = 0x5, +- SKL_DEVICE_NONE +-}; +- +-/** +- * enum skl_interleaving - interleaving style +- * +- * @SKL_INTERLEAVING_PER_CHANNEL: [s1_ch1...s1_chN,...,sM_ch1...sM_chN] +- * @SKL_INTERLEAVING_PER_SAMPLE: [s1_ch1...sM_ch1,...,s1_chN...sM_chN] +- */ +-enum skl_interleaving { +- SKL_INTERLEAVING_PER_CHANNEL = 0, +- SKL_INTERLEAVING_PER_SAMPLE = 1, +-}; +- +-enum skl_sample_type { +- SKL_SAMPLE_TYPE_INT_MSB = 0, +- SKL_SAMPLE_TYPE_INT_LSB = 1, +- SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- SKL_SAMPLE_TYPE_FLOAT = 4 +-}; +- +-enum module_pin_type { +- /* All pins of the module takes same PCM inputs or outputs +- * e.g. mixout +- */ +- SKL_PIN_TYPE_HOMOGENEOUS, +- /* All pins of the module takes different PCM inputs or outputs +- * e.g mux +- */ +- SKL_PIN_TYPE_HETEROGENEOUS, +-}; +- +-enum skl_module_param_type { +- SKL_PARAM_DEFAULT = 0, +- SKL_PARAM_INIT, +- SKL_PARAM_SET, +- SKL_PARAM_BIND +-}; +- +-struct skl_dfw_algo_data { +- __u32 set_params:2; +- __u32 rsvd:30; +- __u32 param_id; +- __u32 max; +- char params[]; +-} __packed; +- +-enum skl_tkn_dir { +- SKL_DIR_IN, +- SKL_DIR_OUT +-}; +- +-enum skl_tuple_type { +- SKL_TYPE_TUPLE, +- SKL_TYPE_DATA +-}; +- +-#endif +diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h +--- a/include/uapi/sound/snd_ar_tokens.h ++++ b/include/uapi/sound/snd_ar_tokens.h +@@ -58,7 +58,7 @@ enum ar_event_types { + #define SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX 256 + #define SND_SOC_AR_TPLG_VOL_CTL 257 + +-/** ++/* + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: Sub Graph Instance Id + * + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE: Performance mode of subgraph +@@ -168,6 +168,60 @@ enum ar_event_types { + * LOG_WAIT = 0, + * LOG_IMMEDIATELY = 1 + * ++ * %AR_TKN_U16_MODULE_SYNC_SRC: Frame sync source ++ * AR_AUDIO_IF_SYNC_SRC_EXTERNAL = 0, ++ * AR_AUDIO_IF_SYNC_SRC_INTERNAL = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: Enable data-out tri-state control ++ * AR_AUDIO_IF_CTRL_DATA_OE_DISABLE = 0, ++ * AR_AUDIO_IF_CTRL_DATA_OE_ENABLE = 1 ++ * ++ * %AR_TKN_U32_MODULE_SLOT_MASK: Active TDM slot bitmask ++ * ++ * %AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: Number of slots per TDM frame ++ * ++ * %AR_TKN_U16_MODULE_SLOT_WIDTH: Slot width in bits (16 or 32) ++ * ++ * %AR_TKN_U16_MODULE_SYNC_MODE: Frame sync mode ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT = 0, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT = 1, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_LONG = 2 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: Invert frame sync pulse polarity ++ * AR_AUDIO_IF_SYNC_NORMAL = 0, ++ * AR_AUDIO_IF_SYNC_INVERTED = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: Data delay relative to frame sync ++ * AR_AUDIO_IF_DATA_DELAY_NONE = 0, ++ * AR_AUDIO_IF_DATA_DELAY_1_CYCLE = 1, ++ * AR_AUDIO_IF_DATA_DELAY_2_CYCLE = 2 ++ * ++ * %AR_TKN_U16_MODULE_INTF_MODE: Audio IF interface mode ++ * AR_AUDIO_IF_INTF_MODE_TDM = 0, ++ * AR_AUDIO_IF_INTF_MODE_PCM = 1, ++ * AR_AUDIO_IF_INTF_MODE_I2S = 2 ++ * ++ * %AR_TKN_U16_MODULE_QAIF_TYPE: QAIF hardware port type index ++ * AR_AUDIO_IF_QAIF = 0, ++ * AR_AUDIO_IF_QAIF_VA = 1 ++ * ++ * %AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: Active lane bitmask for multi-lane ++ * ++ * %AR_TKN_U32_MODULE_FRAME_SYNC_RATE: Frame sync rate in Hz ++ * ++ * %AR_TKN_U16_MODULE_BIT_CLK_TYPE: Bit clock type ++ * AR_AUDIO_IF_BIT_CLK_INTERNAL = 0, ++ * AR_AUDIO_IF_BIT_CLK_EXTERNAL = 1, ++ * AR_AUDIO_IF_BIT_CLK_SKIP = 2 ++ * ++ * %AR_TKN_U8_MODULE_INV_INT_BIT_CLK: Invert internal bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * ++ * %AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: Invert external bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * + * %AR_TKN_DAI_INDEX: dai index + * + */ +@@ -240,6 +294,45 @@ enum ar_event_types { + #define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 + #define AR_TKN_U32_MODULE_LOG_MODE 261 + ++#define AR_TKN_U16_MODULE_SYNC_SRC 262 ++#define AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE 263 ++#define AR_TKN_U32_MODULE_SLOT_MASK 264 ++#define AR_TKN_U16_MODULE_NSLOTS_PER_FRAME 265 ++#define AR_TKN_U16_MODULE_SLOT_WIDTH 266 ++#define AR_TKN_U16_MODULE_SYNC_MODE 267 ++#define AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE 268 ++#define AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY 269 ++#define AR_TKN_U16_MODULE_INTF_MODE 270 ++#define AR_TKN_U16_MODULE_QAIF_TYPE 271 ++#define AR_TKN_U32_MODULE_ACTIVE_LANE_MASK 272 ++#define AR_TKN_U32_MODULE_FRAME_SYNC_RATE 273 ++#define AR_TKN_U16_MODULE_BIT_CLK_TYPE 274 ++#define AR_TKN_U8_MODULE_INV_INT_BIT_CLK 275 ++#define AR_TKN_U8_MODULE_INV_EXT_BIT_CLK 276 ++ ++#define AR_AUDIO_IF_SYNC_SRC_EXTERNAL 0 ++#define AR_AUDIO_IF_SYNC_SRC_INTERNAL 1 ++#define AR_AUDIO_IF_CTRL_DATA_OE_DISABLE 0 ++#define AR_AUDIO_IF_CTRL_DATA_OE_ENABLE 1 ++#define AR_AUDIO_IF_INTF_MODE_TDM 0 ++#define AR_AUDIO_IF_INTF_MODE_PCM 1 ++#define AR_AUDIO_IF_INTF_MODE_I2S 2 ++#define AR_AUDIO_IF_QAIF 0 ++#define AR_AUDIO_IF_QAIF_VA 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT 0 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_LONG 2 ++#define AR_AUDIO_IF_SYNC_NORMAL 0 ++#define AR_AUDIO_IF_SYNC_INVERTED 1 ++#define AR_AUDIO_IF_DATA_DELAY_NONE 0 ++#define AR_AUDIO_IF_DATA_DELAY_1_CYCLE 1 ++#define AR_AUDIO_IF_DATA_DELAY_2_CYCLE 2 ++#define AR_AUDIO_IF_BIT_CLK_INTERNAL 0 ++#define AR_AUDIO_IF_BIT_CLK_EXTERNAL 1 ++#define AR_AUDIO_IF_BIT_CLK_SKIP 2 ++#define AR_AUDIO_IF_CLK_NORMAL 0 ++#define AR_AUDIO_IF_CLK_INVERTED 1 ++ + #define SND_SOC_AR_TPLG_MODULE_CFG_TYPE 0x01001006 + struct audioreach_module_priv_data { + __le32 size; /* size in bytes of the array, including all elements */ +diff --git a/include/uapi/sound/snd_sst_tokens.h b/include/uapi/sound/snd_sst_tokens.h +deleted file mode 100644 +--- a/include/uapi/sound/snd_sst_tokens.h ++++ /dev/null +@@ -1,324 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * snd_sst_tokens.h - Intel SST tokens definition +- * +- * Copyright (C) 2016 Intel Corp +- * Author: Shreyas NC +- */ +-#ifndef __SND_SST_TOKENS_H__ +-#define __SND_SST_TOKENS_H__ +- +-/** +- * %SKL_TKN_UUID: Module UUID +- * +- * %SKL_TKN_U8_BLOCK_TYPE: Type of the private data block.Can be: +- * tuples, bytes, short and words +- * +- * %SKL_TKN_U8_IN_PIN_TYPE: Input pin type, +- * homogenous=0, heterogenous=1 +- * +- * %SKL_TKN_U8_OUT_PIN_TYPE: Output pin type, +- * homogenous=0, heterogenous=1 +- * %SKL_TKN_U8_DYN_IN_PIN: Configure Input pin dynamically +- * if true +- * +- * %SKL_TKN_U8_DYN_OUT_PIN: Configure Output pin dynamically +- * if true +- * +- * %SKL_TKN_U8_IN_QUEUE_COUNT: Store the number of Input pins +- * +- * %SKL_TKN_U8_OUT_QUEUE_COUNT: Store the number of Output pins +- * +- * %SKL_TKN_U8_TIME_SLOT: TDM slot number +- * +- * %SKL_TKN_U8_CORE_ID: Stores module affinity value.Can take +- * the values: +- * SKL_AFFINITY_CORE_0 = 0, +- * SKL_AFFINITY_CORE_1, +- * SKL_AFFINITY_CORE_MAX +- * +- * %SKL_TKN_U8_MOD_TYPE: Module type value. +- * +- * %SKL_TKN_U8_CONN_TYPE: Module connection type can be a FE, +- * BE or NONE as defined : +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA) +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA) +- * +- * %SKL_TKN_U8_DEV_TYPE: Type of device to which the module is +- * connected +- * Can take the values: +- * SKL_DEVICE_BT = 0x0, +- * SKL_DEVICE_DMIC = 0x1, +- * SKL_DEVICE_I2S = 0x2, +- * SKL_DEVICE_SLIMBUS = 0x3, +- * SKL_DEVICE_HDALINK = 0x4, +- * SKL_DEVICE_HDAHOST = 0x5, +- * SKL_DEVICE_NONE +- * +- * %SKL_TKN_U8_HW_CONN_TYPE: Connection type of the HW to which the +- * module is connected +- * SKL_CONN_NONE = 0, +- * SKL_CONN_SOURCE = 1, +- * SKL_CONN_SINK = 2 +- * +- * %SKL_TKN_U16_PIN_INST_ID: Stores the pin instance id +- * +- * %SKL_TKN_U16_MOD_INST_ID: Stores the mdule instance id +- * +- * %SKL_TKN_U32_MAX_MCPS: Module max mcps value +- * +- * %SKL_TKN_U32_MEM_PAGES: Module resource pages +- * +- * %SKL_TKN_U32_OBS: Stores Output Buffer size +- * +- * %SKL_TKN_U32_IBS: Stores input buffer size +- * +- * %SKL_TKN_U32_VBUS_ID: Module VBUS_ID. PDM=0, SSP0=0, +- * SSP1=1,SSP2=2, +- * SSP3=3, SSP4=4, +- * SSP5=5, SSP6=6,INVALID +- * +- * %SKL_TKN_U32_PARAMS_FIXUP: Module Params fixup mask +- * %SKL_TKN_U32_CONVERTER: Module params converter mask +- * %SKL_TKN_U32_PIPE_ID: Stores the pipe id +- * +- * %SKL_TKN_U32_PIPE_CONN_TYPE: Type of the token to which the pipe is +- * connected to. It can be +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA), +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA), +- * +- * %SKL_TKN_U32_PIPE_PRIORITY: Pipe priority value +- * %SKL_TKN_U32_PIPE_MEM_PGS: Pipe resource pages +- * +- * %SKL_TKN_U32_DIR_PIN_COUNT: Value for the direction to set input/output +- * formats and the pin count. +- * The first 4 bits have the direction +- * value and the next 4 have +- * the pin count value. +- * SKL_DIR_IN = 0, SKL_DIR_OUT = 1. +- * The input and output formats +- * share the same set of tokens +- * with the distinction between input +- * and output made by reading direction +- * token. +- * +- * %SKL_TKN_U32_FMT_CH: Supported channel count +- * +- * %SKL_TKN_U32_FMT_FREQ: Supported frequency/sample rate +- * +- * %SKL_TKN_U32_FMT_BIT_DEPTH: Supported container size +- * +- * %SKL_TKN_U32_FMT_SAMPLE_SIZE:Number of samples in the container +- * +- * %SKL_TKN_U32_FMT_CH_CONFIG: Supported channel configurations for the +- * input/output. +- * +- * %SKL_TKN_U32_FMT_INTERLEAVE: Interleaving style which can be per +- * channel or per sample. The values can be : +- * SKL_INTERLEAVING_PER_CHANNEL = 0, +- * SKL_INTERLEAVING_PER_SAMPLE = 1, +- * +- * %SKL_TKN_U32_FMT_SAMPLE_TYPE: +- * Specifies the sample type. Can take the +- * values: SKL_SAMPLE_TYPE_INT_MSB = 0, +- * SKL_SAMPLE_TYPE_INT_LSB = 1, +- * SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- * SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- * SKL_SAMPLE_TYPE_FLOAT = 4 +- * +- * %SKL_TKN_U32_CH_MAP: Channel map values +- * %SKL_TKN_U32_MOD_SET_PARAMS: It can take these values: +- * SKL_PARAM_DEFAULT, SKL_PARAM_INIT, +- * SKL_PARAM_SET, SKL_PARAM_BIND +- * +- * %SKL_TKN_U32_MOD_PARAM_ID: ID of the module params +- * +- * %SKL_TKN_U32_CAPS_SET_PARAMS: +- * Set params value +- * +- * %SKL_TKN_U32_CAPS_PARAMS_ID: Params ID +- * +- * %SKL_TKN_U32_CAPS_SIZE: Caps size +- * +- * %SKL_TKN_U32_PROC_DOMAIN: Specify processing domain +- * +- * %SKL_TKN_U32_LIB_COUNT: Specifies the number of libraries +- * +- * %SKL_TKN_STR_LIB_NAME: Specifies the library name +- * +- * %SKL_TKN_U32_PMODE: Specifies the power mode for pipe +- * +- * %SKL_TKL_U32_D0I3_CAPS: Specifies the D0i3 capability for module +- * +- * %SKL_TKN_U32_DMA_BUF_SIZE: DMA buffer size in millisec +- * +- * %SKL_TKN_U32_PIPE_DIR: Specifies pipe direction. Can be +- * playback/capture. +- * +- * %SKL_TKN_U32_NUM_CONFIGS: Number of pipe configs +- * +- * %SKL_TKN_U32_PATH_MEM_PGS: Size of memory (in pages) required for pipeline +- * and its data +- * +- * %SKL_TKN_U32_PIPE_CONFIG_ID: Config id for the modules in the pipe +- * and PCM params supported by that pipe +- * config. This is used as index to fill +- * up the pipe config and module config +- * structure. +- * +- * %SKL_TKN_U32_CFG_FREQ: +- * %SKL_TKN_U8_CFG_CHAN: +- * %SKL_TKN_U8_CFG_BPS: PCM params (freq, channels, bits per sample) +- * supported for each of the pipe configs. +- * +- * %SKL_TKN_CFG_MOD_RES_ID: Module's resource index for each of the +- * pipe config +- * +- * %SKL_TKN_CFG_MOD_FMT_ID: Module's interface index for each of the +- * pipe config +- * +- * %SKL_TKN_U8_NUM_MOD: Number of modules in the manifest +- * +- * %SKL_TKN_MM_U8_MOD_IDX: Current index of the module in the manifest +- * +- * %SKL_TKN_MM_U8_NUM_RES: Number of resources for the module +- * +- * %SKL_TKN_MM_U8_NUM_INTF: Number of interfaces for the module +- * +- * %SKL_TKN_MM_U32_RES_ID: Resource index for the resource info to +- * be filled into. +- * A module can support multiple resource +- * configuration and is represnted as a +- * resource table. This index is used to +- * fill information into appropriate index. +- * +- * %SKL_TKN_MM_U32_CPS: DSP cycles per second +- * +- * %SKL_TKN_MM_U32_DMA_SIZE: Allocated buffer size for gateway DMA +- * +- * %SKL_TKN_MM_U32_CPC: DSP cycles allocated per frame +- * +- * %SKL_TKN_MM_U32_RES_PIN_ID: Resource pin index in the module +- * +- * %SKL_TKN_MM_U32_INTF_PIN_ID: Interface index in the module +- * +- * %SKL_TKN_MM_U32_PIN_BUF: Buffer size of the module pin +- * +- * %SKL_TKN_MM_U32_FMT_ID: Format index for each of the interface/ +- * format information to be filled into. +- * +- * %SKL_TKN_MM_U32_NUM_IN_FMT: Number of input formats +- * %SKL_TKN_MM_U32_NUM_OUT_FMT: Number of output formats +- * +- * %SKL_TKN_U32_ASTATE_IDX: Table Index for the A-State entry to be filled +- * with kcps and clock source +- * +- * %SKL_TKN_U32_ASTATE_COUNT: Number of valid entries in A-State table +- * +- * %SKL_TKN_U32_ASTATE_KCPS: Specifies the core load threshold (in kilo +- * cycles per second) below which DSP is clocked +- * from source specified by clock source. +- * +- * %SKL_TKN_U32_ASTATE_CLK_SRC: Clock source for A-State entry +- * +- * %SKL_TKN_U32_FMT_CFG_IDX: Format config index +- * +- * module_id and loadable flags dont have tokens as these values will be +- * read from the DSP FW manifest +- * +- * Tokens defined can be used either in the manifest or widget private data. +- * +- * SKL_TKN_MM is used as a suffix for all tokens that represent +- * module data in the manifest. +- */ +-enum SKL_TKNS { +- SKL_TKN_UUID = 1, +- SKL_TKN_U8_NUM_BLOCKS, +- SKL_TKN_U8_BLOCK_TYPE, +- SKL_TKN_U8_IN_PIN_TYPE, +- SKL_TKN_U8_OUT_PIN_TYPE, +- SKL_TKN_U8_DYN_IN_PIN, +- SKL_TKN_U8_DYN_OUT_PIN, +- SKL_TKN_U8_IN_QUEUE_COUNT, +- SKL_TKN_U8_OUT_QUEUE_COUNT, +- SKL_TKN_U8_TIME_SLOT, +- SKL_TKN_U8_CORE_ID, +- SKL_TKN_U8_MOD_TYPE, +- SKL_TKN_U8_CONN_TYPE, +- SKL_TKN_U8_DEV_TYPE, +- SKL_TKN_U8_HW_CONN_TYPE, +- SKL_TKN_U16_MOD_INST_ID, +- SKL_TKN_U16_BLOCK_SIZE, +- SKL_TKN_U32_MAX_MCPS, +- SKL_TKN_U32_MEM_PAGES, +- SKL_TKN_U32_OBS, +- SKL_TKN_U32_IBS, +- SKL_TKN_U32_VBUS_ID, +- SKL_TKN_U32_PARAMS_FIXUP, +- SKL_TKN_U32_CONVERTER, +- SKL_TKN_U32_PIPE_ID, +- SKL_TKN_U32_PIPE_CONN_TYPE, +- SKL_TKN_U32_PIPE_PRIORITY, +- SKL_TKN_U32_PIPE_MEM_PGS, +- SKL_TKN_U32_DIR_PIN_COUNT, +- SKL_TKN_U32_FMT_CH, +- SKL_TKN_U32_FMT_FREQ, +- SKL_TKN_U32_FMT_BIT_DEPTH, +- SKL_TKN_U32_FMT_SAMPLE_SIZE, +- SKL_TKN_U32_FMT_CH_CONFIG, +- SKL_TKN_U32_FMT_INTERLEAVE, +- SKL_TKN_U32_FMT_SAMPLE_TYPE, +- SKL_TKN_U32_FMT_CH_MAP, +- SKL_TKN_U32_PIN_MOD_ID, +- SKL_TKN_U32_PIN_INST_ID, +- SKL_TKN_U32_MOD_SET_PARAMS, +- SKL_TKN_U32_MOD_PARAM_ID, +- SKL_TKN_U32_CAPS_SET_PARAMS, +- SKL_TKN_U32_CAPS_PARAMS_ID, +- SKL_TKN_U32_CAPS_SIZE, +- SKL_TKN_U32_PROC_DOMAIN, +- SKL_TKN_U32_LIB_COUNT, +- SKL_TKN_STR_LIB_NAME, +- SKL_TKN_U32_PMODE, +- SKL_TKL_U32_D0I3_CAPS, /* Typo added at v4.10 */ +- SKL_TKN_U32_D0I3_CAPS = SKL_TKL_U32_D0I3_CAPS, +- SKL_TKN_U32_DMA_BUF_SIZE, +- +- SKL_TKN_U32_PIPE_DIRECTION, +- SKL_TKN_U32_PIPE_CONFIG_ID, +- SKL_TKN_U32_NUM_CONFIGS, +- SKL_TKN_U32_PATH_MEM_PGS, +- +- SKL_TKN_U32_CFG_FREQ, +- SKL_TKN_U8_CFG_CHAN, +- SKL_TKN_U8_CFG_BPS, +- SKL_TKN_CFG_MOD_RES_ID, +- SKL_TKN_CFG_MOD_FMT_ID, +- SKL_TKN_U8_NUM_MOD, +- +- SKL_TKN_MM_U8_MOD_IDX, +- SKL_TKN_MM_U8_NUM_RES, +- SKL_TKN_MM_U8_NUM_INTF, +- SKL_TKN_MM_U32_RES_ID, +- SKL_TKN_MM_U32_CPS, +- SKL_TKN_MM_U32_DMA_SIZE, +- SKL_TKN_MM_U32_CPC, +- SKL_TKN_MM_U32_RES_PIN_ID, +- SKL_TKN_MM_U32_INTF_PIN_ID, +- SKL_TKN_MM_U32_PIN_BUF, +- SKL_TKN_MM_U32_FMT_ID, +- SKL_TKN_MM_U32_NUM_IN_FMT, +- SKL_TKN_MM_U32_NUM_OUT_FMT, +- +- SKL_TKN_U32_ASTATE_IDX, +- SKL_TKN_U32_ASTATE_COUNT, +- SKL_TKN_U32_ASTATE_KCPS, +- SKL_TKN_U32_ASTATE_CLK_SRC, +- +- SKL_TKN_U32_FMT_CFG_IDX = 96, +- SKL_TKN_MAX = SKL_TKN_U32_FMT_CFG_IDX, +-}; +- +-#endif +diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h +--- a/include/uapi/sound/sof/tokens.h ++++ b/include/uapi/sound/sof/tokens.h +@@ -111,8 +111,8 @@ + + #define SOF_TKN_COMP_SCHED_DOMAIN 418 + #define SOF_TKN_COMP_DOMAIN_ID 419 +-#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 420 +-#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 421 ++#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 420 ++#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 421 + + /* SSP */ + #define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 +@@ -223,6 +223,7 @@ + #define SOF_TKN_AMD_ACPI2S_RATE 1700 + #define SOF_TKN_AMD_ACPI2S_CH 1701 + #define SOF_TKN_AMD_ACPI2S_TDM_MODE 1702 ++#define SOF_TKN_AMD_ACPI2S_FORMAT 1703 + + /* MICFIL PDM */ + #define SOF_TKN_IMX_MICFIL_RATE 2000 +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -171,13 +171,13 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + tas_hda->priv->playback_started = true; + } + break; + case HDA_GEN_PCM_ACT_CLOSE: + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -720,7 +720,7 @@ static int tas2781_runtime_suspend(struct device *dev) + * Stop the playback if it's unused. + */ + if (tas_hda->priv->playback_started) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -750,7 +750,7 @@ static int tas2781_system_suspend(struct device *dev) + + /* Shutdown chip before system suspend */ + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + + /* + * Reset GPIO may be shared, so cannot reset here. +@@ -783,7 +783,7 @@ static int tas2781_system_resume(struct device *dev) + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + + return 0; + } +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -399,11 +399,11 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + pm_runtime_get_sync(dev); + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + } else if (action == HDA_GEN_PCM_ACT_CLOSE) { + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + pm_runtime_put_autosuspend(dev); + } + } +@@ -847,7 +847,7 @@ static int tas2781_runtime_suspend(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + tas_priv->tasdevice[tas_priv->index].cur_book = -1; + tas_priv->tasdevice[tas_priv->index].cur_conf = -1; +@@ -864,7 +864,7 @@ static int tas2781_runtime_resume(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + + return 0; + } +@@ -882,7 +882,7 @@ static int tas2781_system_suspend(struct device *dev) + /* Shutdown chip before system suspend */ + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + return 0; + } +@@ -917,7 +917,7 @@ static int tas2781_system_resume(struct device *dev) + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + + if (tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + } + + return ret; +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -63,6 +63,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA1"), ++ }, ++ }, + {} + }; + +@@ -379,5 +386,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { + }; + EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); + ++struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { ++ { ++ .id = "AMDI1010", ++ .drv_name = "acp7x-dsp", ++ .pdata = &acp_quirk_data, ++ .fw_filename = "sof-acp7x.ri", ++ .sof_tplg_filename = "sof-acp7x.tplg", ++ }, ++ {}, ++}; ++EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); ++ + MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); + MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c +--- a/sound/soc/amd/acp-pcm-dma.c ++++ b/sound/soc/amd/acp-pcm-dma.c +@@ -691,12 +691,10 @@ static irqreturn_t dma_irq_handler(int irq, void *arg) + { + u16 dscr_idx; + u32 intr_flag, ext_intr_status; +- struct audio_drv_data *irq_data; ++ struct audio_drv_data *irq_data = arg; + void __iomem *acp_mmio; +- struct device *dev = arg; + bool valid_irq = false; + +- irq_data = dev_get_drvdata(dev); + acp_mmio = irq_data->acp_mmio; + + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); +@@ -1291,10 +1289,10 @@ static int acp_audio_probe(struct platform_device *pdev) + + irq = platform_get_irq(pdev, 0); + if (irq < 0) +- return -ENODEV; ++ return irq; + + status = devm_request_irq(&pdev->dev, irq, dma_irq_handler, +- 0, "ACP_IRQ", &pdev->dev); ++ 0, "ACP_IRQ", audio_drv_data); + if (status) { + dev_err(&pdev->dev, "ACP IRQ request failed\n"); + return status; +diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c +--- a/sound/soc/amd/acp/acp-i2s.c ++++ b/sound/soc/amd/acp/acp-i2s.c +@@ -686,6 +686,10 @@ static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_d + return 0; + } + ++static const u64 acp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, +@@ -693,6 +697,8 @@ const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .trigger = acp_i2s_trigger, + .set_fmt = acp_i2s_set_fmt, + .set_tdm_slot = acp_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, "SND_SOC_ACP_COMMON"); + +diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c +--- a/sound/soc/amd/acp/acp-mach-common.c ++++ b/sound/soc/amd/acp/acp-mach-common.c +@@ -938,15 +938,10 @@ static int acp_max98388_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai = + snd_soc_card_get_codec_dai(card, + MAX98388_CODEC_DAI); +- int ret; + +- ret = snd_soc_dai_set_fmt(codec_dai, +- SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | +- SND_SOC_DAIFMT_NB_NF); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_fmt(codec_dai, ++ SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_NF); + } + + static const struct snd_soc_ops acp_max98388_ops = { +diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c +--- a/sound/soc/amd/acp/acp-pci.c ++++ b/sound/soc/amd/acp/acp-pci.c +@@ -118,17 +118,10 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + if (!chip) + return -ENOMEM; + +- if (pci_enable_device(pci)) ++ if (pcim_enable_device(pci)) + return dev_err_probe(&pci->dev, -ENODEV, + "pci_enable_device failed\n"); + +- ret = pci_request_regions(pci, "AMD ACP3x audio"); +- if (ret < 0) { +- dev_err(&pci->dev, "pci_request_regions failed\n"); +- ret = -ENOMEM; +- goto disable_pci; +- } +- + pci_set_master(pci); + + chip->acp_rev = pci->revision; +@@ -161,24 +154,21 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + break; + default: + dev_err(dev, "Unsupported device revision:0x%x\n", pci->revision); +- ret = -EINVAL; +- goto release_regions; ++ return -EINVAL; + } + chip->flag = flag; + + addr = pci_resource_start(pci, 0); +- chip->base = devm_ioremap(&pci->dev, addr, pci_resource_len(pci, 0)); +- if (!chip->base) { +- ret = -ENOMEM; +- goto release_regions; +- } ++ chip->base = pcim_iomap_region(pci, 0, "AMD ACP3x audio"); ++ if (IS_ERR(chip->base)) ++ return PTR_ERR(chip->base); + + chip->addr = addr; + + chip->acp_hw_ops_init(chip); + ret = acp_hw_init(chip); + if (ret) +- goto release_regions; ++ goto de_init; + + ret = devm_request_irq(dev, pci->irq, irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", chip); +@@ -214,10 +204,6 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + + de_init: + acp_hw_deinit(chip); +-release_regions: +- pci_release_regions(pci); +-disable_pci: +- pci_disable_device(pci); + + return ret; + }; +diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +--- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +@@ -531,15 +531,15 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(soc_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + soc_sdw_quirk, quirk_override); + soc_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = soc_sdw_quirk; +- dev_dbg(card->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); ++ dev_dbg(&pdev->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ + for (i = 0; i < ctx->codec_info_list_count; i++) + codec_info_list[i].amp_num = 0; +@@ -556,12 +556,12 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:acp-dmic cfg-mics:%d", + card->components, + 1); +@@ -570,9 +570,9 @@ static int mc_probe(struct platform_device *pdev) + } + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -590,8 +590,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sdw", }, +- {} ++ { .name = "amd_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + if (ret < 0) + return ret; + +@@ -399,12 +399,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -423,15 +423,15 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -449,8 +449,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sof_sdw", }, +- {} ++ { .name = "amd_sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c +--- a/sound/soc/amd/acp/acp-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sof-mach.c +@@ -128,31 +128,31 @@ static int acp_sof_probe(struct platform_device *pdev) + static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data, + }, + { + .name = "rt5682-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_max_data, + }, + { + .name = "rt5682s-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data, + }, + { + .name = "rt5682s-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data, + }, + { + .name = "nau8825-max", +- .driver_data = (kernel_ulong_t)&sof_nau8825_data ++ .driver_data = (kernel_ulong_t)&sof_nau8825_data, + }, + { + .name = "rt5682s-hs-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data, + }, + { + .name = "nau8821-max", +- .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data ++ .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data, + }, + { } + }; +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -659,6 +659,56 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_l1u0_tas2783x2_l1u8b[] = { + {} + }; + ++static const struct snd_soc_acpi_adr_device rt712_vb_l1u0_adr[] = { ++ { ++ .adr = 0x000130025D071201ull, ++ /* ++ * On this platform speakers are provided by two TAS2783 amps, ++ * so the AIF2 amp path is left unused: jack + DMIC only. ++ */ ++ .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), ++ .endpoints = jack_dmic_endpoints, ++ .name_prefix = "rt712" ++ } ++}; ++ ++/* ++ * Unique ID 0xC drives the left speaker and 0x9 the right one. The order of the ++ * entries matters as much as the endpoints: asoc_sdw_parse_sdw_endpoints() ++ * appends them to the dailink in array order and sdw_compute_slave_ports() ++ * hands out payload block offsets in that same order, so the first entry is the ++ * one that receives channel 0. ++ */ ++static const struct snd_soc_acpi_adr_device tas2783x2_l0u9c_adr[] = { ++ { ++ .adr = 0x00003C0102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_l_endpoint, ++ .name_prefix = "tas2783-1", ++ }, ++ { ++ .adr = 0x0000390102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_r_endpoint, ++ .name_prefix = "tas2783-2", ++ }, ++}; ++ ++/* HP OmniBook X Flip 14-kc0xxx (board 8EA1) */ ++static const struct snd_soc_acpi_link_adr acp70_tas2783x2_l0u9c_rt712_vb_l1u0[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783x2_l0u9c_adr), ++ .adr_d = tas2783x2_l0u9c_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_l1u0_adr), ++ .adr_d = rt712_vb_l1u0_adr, ++ }, ++ {} ++}; ++ + static const struct snd_soc_acpi_endpoint rt721_endpoints[] = { + { /* Jack Playback/Capture Endpoint (AIF1) */ + .num = 0, +@@ -704,6 +754,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { + .links = acp70_tas2783_2, + .drv_name = "amd_sdw", + }, ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783x2_l0u9c_rt712_vb_l1u0, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_rt1320_l0_rt722_l1, +diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h +--- a/sound/soc/amd/mach-config.h ++++ b/sound/soc/amd/mach-config.h +@@ -28,6 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; ++extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; + + struct config_entry { + u32 flags; +diff --git a/sound/soc/amd/raven/acp3x-i2s.c b/sound/soc/amd/raven/acp3x-i2s.c +--- a/sound/soc/amd/raven/acp3x-i2s.c ++++ b/sound/soc/amd/raven/acp3x-i2s.c +@@ -250,11 +250,17 @@ static int acp3x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp3x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp3x_i2s_dai_ops = { + .hw_params = acp3x_i2s_hwparams, + .trigger = acp3x_i2s_trigger, + .set_fmt = acp3x_i2s_set_fmt, + .set_tdm_slot = acp3x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp3x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp3x_dai_component = { +diff --git a/sound/soc/amd/vangogh/acp5x-i2s.c b/sound/soc/amd/vangogh/acp5x-i2s.c +--- a/sound/soc/amd/vangogh/acp5x-i2s.c ++++ b/sound/soc/amd/vangogh/acp5x-i2s.c +@@ -337,11 +337,17 @@ static int acp5x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp5x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = { + .hw_params = acp5x_i2s_hwparams, + .trigger = acp5x_i2s_trigger, + .set_fmt = acp5x_i2s_set_fmt, + .set_tdm_slot = acp5x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp5x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp5x_dai_component = { +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -795,12 +795,12 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + } + }, + { +- .driver_data = &acp6x_card, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), +- DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), +- } +- }, ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c +--- a/sound/soc/apple/mca.c ++++ b/sound/soc/apple/mca.c +@@ -133,12 +133,17 @@ struct mca_cluster { + struct clk *clk_parent; + struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; + +- bool port_started[SNDRV_PCM_STREAM_LAST + 1]; +- int port_driver; /* The cluster driving this cluster's port */ ++ bool clk_provider; ++ ++ bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1]; ++ int port_clk_driver; /* The cluster driving this cluster's port */ + + bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; + struct device_link *pd_link; + ++ /* In case of clock consumer FE */ ++ int syncgen_in_use; ++ + unsigned int bclk_ratio; + + /* Masks etc. picked up via the set_tdm_slot method */ +@@ -157,7 +162,7 @@ struct mca_data { + struct reset_control *rstc; + struct device_link *pd_link; + +- /* Mutex for accessing port_driver of foreign clusters */ ++ /* Mutex for accessing port_clk_driver of foreign clusters */ + struct mutex port_mutex; + + int nclusters; +@@ -220,6 +225,12 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, + FIELD_PREP(SERDES_CONF_SYNC_SEL, 0)); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1)); ++ /* ++ * ADMAC gets started right after this. This delay seems ++ * to be needed for that to be reliable, e.g. ensure the ++ * clock is stable? ++ */ ++ udelay(100); + break; + default: + break; +@@ -256,11 +267,29 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd, + return 0; + } + ++static int mca_fe_get_portmask(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dpcm *dpcm; ++ int mask = 0; ++ ++ for_each_dpcm_be(fe, substream->stream, dpcm) { ++ int no = mca_dai_to_cluster(snd_soc_rtd_to_cpu(dpcm->be, 0))->no; ++ ++ mask |= 1 << no; ++ } ++ ++ return mask; ++} ++ + static int mca_fe_enable_clocks(struct mca_cluster *cl) + { + struct mca_data *mca = cl->host; + int ret; + ++ if (!cl->clk_provider) ++ return -EINVAL; ++ + ret = clk_prepare_enable(cl->clk_parent); + if (ret) { + dev_err(mca->dev, +@@ -311,7 +340,7 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + for (i = 0; i < mca->nclusters; i++) { + be_cl = &mca->clusters[i]; + +- if (be_cl->port_driver != cl->no) ++ if (be_cl->port_clk_driver != cl->no) + continue; + + for_each_pcm_streams(stream) { +@@ -323,36 +352,6 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + return false; + } + +-static int mca_be_prepare(struct snd_pcm_substream *substream, +- struct snd_soc_dai *dai) +-{ +- struct mca_cluster *cl = mca_dai_to_cluster(dai); +- struct mca_data *mca = cl->host; +- struct mca_cluster *fe_cl; +- int ret; +- +- if (cl->port_driver < 0) +- return -EINVAL; +- +- fe_cl = &mca->clusters[cl->port_driver]; +- +- /* +- * Typically the CODECs we are paired with will require clocks +- * to be present at time of unmute with the 'mute_stream' op +- * or at time of DAPM widget power-up. We need to enable clocks +- * here at the latest (frontend prepare would be too late). +- */ +- if (!mca_fe_clocks_in_use(fe_cl)) { +- ret = mca_fe_enable_clocks(fe_cl); +- if (ret < 0) +- return ret; +- } +- +- cl->clocks_in_use[substream->stream] = true; +- +- return 0; +-} +- + static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +@@ -360,7 +359,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct mca_data *mca = cl->host; + struct mca_cluster *fe_cl; + +- if (cl->port_driver < 0) ++ if (cl->port_clk_driver < 0) + return -EINVAL; + + /* +@@ -368,7 +367,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + * belong to the same PCM, accesses should have been + * synchronized at ASoC level. + */ +- fe_cl = &mca->clusters[cl->port_driver]; ++ fe_cl = &mca->clusters[cl->port_clk_driver]; + if (!mca_fe_clocks_in_use(fe_cl)) + return 0; /* Nothing to do */ + +@@ -380,6 +379,56 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int mca_fe_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ /* Turn on the cluster power domain if not already in use */ ++ if (!cl->syncgen_in_use) { ++ int port = ffs(mca_fe_get_portmask(substream)); ++ ++ cl->pd_link = device_link_add(mca->dev, cl->pd_dev, ++ DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | ++ DL_FLAG_RPM_ACTIVE); ++ if (!cl->pd_link) { ++ dev_err(mca->dev, ++ "cluster %d: unable to prop-up power domain\n", cl->no); ++ return -EINVAL; ++ } ++ ++ mca_modify(cl, REG_SYNCGEN_MCLK_SEL, SYNCGEN_MCLK_SEL, BIT(port)); ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, ++ SYNCGEN_STATUS_EN); ++ } ++ cl->syncgen_in_use |= 1 << substream->stream; ++ ++ return 0; ++} ++ ++static int mca_fe_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ cl->syncgen_in_use &= ~(1 << substream->stream); ++ if (cl->syncgen_in_use) ++ return 0; ++ ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); ++ if (cl->pd_link) ++ device_link_del(cl->pd_link); ++ ++ return 0; ++} ++ + static unsigned int mca_crop_mask(unsigned int mask, int nchans) + { + while (hweight32(mask) > nchans) +@@ -390,7 +439,7 @@ static unsigned int mca_crop_mask(unsigned int mask, int nchans) + + static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + unsigned int mask, int slots, int nchans, +- int slot_width, bool is_tx, int port) ++ int slot_width, bool is_tx, int portmask) + { + __iomem void *serdes_base = cl->base + serdes_unit; + u32 serdes_conf, serdes_conf_mask; +@@ -449,7 +498,7 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + serdes_base + REG_RX_SERDES_SLOTMASK); + writel_relaxed(~((u32)mca_crop_mask(mask, nchans)), + serdes_base + REG_RX_SERDES_SLOTMASK + 0x4); +- writel_relaxed(1 << port, ++ writel_relaxed(portmask, + serdes_base + REG_RX_SERDES_PORT); + } + +@@ -505,9 +554,18 @@ static int mca_fe_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + u32 serdes_conf = 0; + u32 bitstart; + +- if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != +- SND_SOC_DAIFMT_BP_FP) ++ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { ++ case SND_SOC_DAIFMT_BP_FP: ++ cl->clk_provider = true; ++ break; ++ ++ case SND_SOC_DAIFMT_BC_FC: ++ cl->clk_provider = false; ++ break; ++ ++ default: + goto err; ++ } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: +@@ -564,24 +622,6 @@ static int mca_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) + return 0; + } + +-static int mca_fe_get_port(struct snd_pcm_substream *substream) +-{ +- struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *be; +- struct snd_soc_dpcm *dpcm; +- +- be = NULL; +- for_each_dpcm_be(fe, substream->stream, dpcm) { +- be = dpcm->be; +- break; +- } +- +- if (!be) +- return -EINVAL; +- +- return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; +-} +- + static int mca_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +@@ -595,7 +635,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + unsigned long bclk_ratio; + unsigned int tdm_slots, tdm_slot_width, tdm_mask; + u32 regval, pad; +- int ret, port, nchans_ceiled; ++ int ret, portmask, nchans_ceiled; + + if (!cl->tdm_slot_width) { + /* +@@ -644,13 +684,13 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + tdm_mask = (1 << tdm_slots) - 1; + } + +- port = mca_fe_get_port(substream); +- if (port < 0) +- return port; ++ portmask = mca_fe_get_portmask(substream); ++ if (!portmask) ++ return -EINVAL; + + ret = mca_configure_serdes(cl, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF, + tdm_mask, tdm_slots, params_channels(params), +- tdm_slot_width, is_tx, port); ++ tdm_slot_width, is_tx, portmask); + if (ret) + return ret; + +@@ -699,6 +739,19 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 mca_fe_selectable_formats[] = ++{ ++ /* pattern 1 */ ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF, ++ ++ /* pattern 2 */ ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF ++}; ++ + static const struct snd_soc_dai_ops mca_fe_ops = { + .startup = mca_fe_startup, + .set_fmt = mca_fe_set_fmt, +@@ -706,66 +759,128 @@ static const struct snd_soc_dai_ops mca_fe_ops = { + .set_tdm_slot = mca_fe_set_tdm_slot, + .hw_params = mca_fe_hw_params, + .trigger = mca_fe_trigger, ++ .prepare = mca_fe_prepare, ++ .hw_free = mca_fe_hw_free, ++ .auto_selectable_formats = mca_fe_selectable_formats, ++ .num_auto_selectable_formats = ARRAY_SIZE(mca_fe_selectable_formats), + }; + +-static bool mca_be_started(struct mca_cluster *cl) ++/* ++ * Is there a FE attached which will be feeding this port's clocks? ++ */ ++static bool mca_be_clk_started(struct mca_cluster *cl) + { + int stream; + + for_each_pcm_streams(stream) +- if (cl->port_started[stream]) ++ if (cl->port_clk_started[stream]) + return true; + return false; + } + ++static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be, ++ int stream) ++{ ++ struct snd_soc_pcm_runtime *fe = NULL; ++ struct snd_soc_dpcm *dpcm; ++ ++ for_each_dpcm_fe(be, stream, dpcm) { ++ if (fe && dpcm->fe != fe) { ++ dev_err(be->dev, "many FE per one BE unsupported\n"); ++ return NULL; ++ } ++ ++ fe = dpcm->fe; ++ } ++ ++ return fe; ++} ++ ++static int mca_be_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ struct mca_cluster *fe_cl, *fe_clk_cl; ++ int ret; ++ ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (cl->port_clk_driver < 0) ++ return 0; ++ ++ fe_clk_cl = &mca->clusters[cl->port_clk_driver]; ++ ++ /* ++ * Typically the CODECs we are paired with will require clocks ++ * to be present at time of unmute with the 'mute_stream' op ++ * or at time of DAPM widget power-up. We need to enable clocks ++ * here at the latest (frontend prepare would be too late). ++ */ ++ if (!mca_fe_clocks_in_use(fe_clk_cl)) { ++ ret = mca_fe_enable_clocks(fe_clk_cl); ++ if (ret < 0) ++ return ret; ++ } ++ ++ cl->clocks_in_use[substream->stream] = true; ++ ++ return 0; ++} ++ + static int mca_be_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *fe; ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; +- struct snd_soc_dpcm *dpcm; +- +- fe = NULL; +- +- for_each_dpcm_fe(be, substream->stream, dpcm) { +- if (fe && dpcm->fe != fe) { +- dev_err(mca->dev, "many FE per one BE unsupported\n"); +- return -EINVAL; +- } +- +- fe = dpcm->fe; +- } + + if (!fe) + return -EINVAL; +- + fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), ++ cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, ++ PORT_ENABLES_TX_DATA); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (mca_be_clk_started(cl)) { + /* + * Port is already started in the other direction. + * Make sure there isn't a conflict with another cluster +- * driving the port. ++ * driving the port clocks. + */ +- if (cl->port_driver != fe_cl->no) ++ if (cl->port_clk_driver != fe_cl->no) + return -EINVAL; + +- cl->port_started[substream->stream] = true; ++ cl->port_clk_started[substream->stream] = true; + return 0; + } + +- writel_relaxed(PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA, +- cl->base + REG_PORT_ENABLES); + writel_relaxed(FIELD_PREP(PORT_CLOCK_SEL, fe_cl->no + 1), + cl->base + REG_PORT_CLOCK_SEL); ++ + writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), + cl->base + REG_PORT_DATA_SEL); ++ ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, ++ PORT_ENABLES_CLOCKS); ++ + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = fe_cl->no; +- cl->port_started[substream->stream] = true; ++ cl->port_clk_driver = fe_cl->no; ++ cl->port_clk_started[substream->stream] = true; + + return 0; + } +@@ -773,20 +888,34 @@ static int mca_be_startup(struct snd_pcm_substream *substream, + static void mca_be_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; + +- cl->port_started[substream->stream] = false; ++ if (!fe) ++ return; ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (!mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0); ++ writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return; ++ ++ cl->port_clk_started[substream->stream] = false; ++ if (!mca_be_clk_started(cl)) { + /* + * Were we the last direction to shutdown? +- * Turn off the lights. ++ * Turn off the lights (clocks). + */ +- writel_relaxed(0, cl->base + REG_PORT_ENABLES); +- writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, 0); ++ writel_relaxed(0, cl->base + REG_PORT_CLOCK_SEL); + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + } + } + +@@ -1092,7 +1221,7 @@ static int apple_mca_probe(struct platform_device *pdev) + cl->host = mca; + cl->no = i; + cl->base = base + CLUSTER_STRIDE * i; +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + cl->clk_parent = of_clk_get(pdev->dev.of_node, i); + if (IS_ERR(cl->clk_parent)) { + dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n", +diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c +--- a/sound/soc/atmel/atmel-i2s.c ++++ b/sound/soc/atmel/atmel-i2s.c +@@ -540,12 +540,16 @@ static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 atmel_i2s_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S; ++ + static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .probe = atmel_i2s_dai_probe, + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, ++ .auto_selectable_formats = &atmel_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_i2s_dai = { +diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c +--- a/sound/soc/atmel/atmel_ssc_dai.c ++++ b/sound/soc/atmel/atmel_ssc_dai.c +@@ -825,6 +825,11 @@ static int atmel_ssc_resume(struct snd_soc_component *component) + #define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + ++static const u64 atmel_ssc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .startup = atmel_ssc_startup, + .shutdown = atmel_ssc_shutdown, +@@ -833,6 +838,8 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .hw_params = atmel_ssc_hw_params, + .set_fmt = atmel_ssc_set_dai_fmt, + .set_clkdiv = atmel_ssc_set_dai_clkdiv, ++ .auto_selectable_formats = &atmel_ssc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_ssc_dai = { +diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c +--- a/sound/soc/atmel/atmel_wm8904.c ++++ b/sound/soc/atmel/atmel_wm8904.c +@@ -82,32 +82,33 @@ static struct snd_soc_card atmel_asoc_wm8904_card = { + + static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int ret; + + if (!np) { +- dev_err(&pdev->dev, "only device tree supported\n"); ++ dev_err(dev, "only device tree supported\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse card name\n"); ++ dev_err(dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio routing\n"); ++ dev_err(dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { +- dev_err(&pdev->dev, "failed to get dai and pcm info\n"); ++ dev_err(dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } +@@ -117,7 +118,7 @@ static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { +- dev_err(&pdev->dev, "failed to get codec info\n"); ++ dev_err(dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } +diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c +--- a/sound/soc/atmel/mchp-i2s-mcc.c ++++ b/sound/soc/atmel/mchp-i2s-mcc.c +@@ -912,6 +912,13 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 mchp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_GATED | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .probe = mchp_i2s_mcc_dai_probe, + .set_sysclk = mchp_i2s_mcc_set_sysclk, +@@ -922,6 +929,8 @@ static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, ++ .auto_selectable_formats = &mchp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 +diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c +--- a/sound/soc/atmel/mchp-pdmc.c ++++ b/sound/soc/atmel/mchp-pdmc.c +@@ -741,6 +741,8 @@ static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd, + return ret; + } + ++static const u64 mchp_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_PDM; ++ + static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .probe = mchp_pdmc_dai_probe, + .set_fmt = mchp_pdmc_set_fmt, +@@ -748,6 +750,8 @@ static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .hw_params = mchp_pdmc_hw_params, + .trigger = mchp_pdmc_trigger, + .pcm_new = &mchp_pdmc_pcm_new, ++ .auto_selectable_formats = &mchp_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver mchp_pdmc_dai = { +diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c +--- a/sound/soc/au1x/db1200.c ++++ b/sound/soc/au1x/db1200.c +@@ -42,7 +42,7 @@ static const struct platform_device_id db1200_pids[] = { + .name = "db1550-i2s", + .driver_data = 5, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, db1200_pids); + +diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c +--- a/sound/soc/au1x/i2sc.c ++++ b/sound/soc/au1x/i2sc.c +@@ -202,11 +202,22 @@ static int au1xi2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xi2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xi2s_dai_ops = { + .startup = au1xi2s_startup, + .trigger = au1xi2s_trigger, + .hw_params = au1xi2s_hw_params, + .set_fmt = au1xi2s_set_fmt, ++ .auto_selectable_formats = &au1xi2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver au1xi2s_dai_driver = { +diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c +--- a/sound/soc/au1x/psc-ac97.c ++++ b/sound/soc/au1x/psc-ac97.c +@@ -210,9 +210,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, + { + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + unsigned long r, ro, stat; +- int chans, t, stype = substream->stream; +- +- chans = params_channels(params); ++ int t, stype = substream->stream; + + r = ro = __raw_readl(AC97_CFG(pscdata)); + stat = __raw_readl(AC97_STAT(pscdata)); +diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c +--- a/sound/soc/au1x/psc-i2s.c ++++ b/sound/soc/au1x/psc-i2s.c +@@ -262,11 +262,22 @@ static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xpsc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { + .startup = au1xpsc_i2s_startup, + .trigger = au1xpsc_i2s_trigger, + .hw_params = au1xpsc_i2s_hw_params, + .set_fmt = au1xpsc_i2s_set_fmt, ++ .auto_selectable_formats = &au1xpsc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = { +diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c +--- a/sound/soc/bcm/bcm2835-i2s.c ++++ b/sound/soc/bcm/bcm2835-i2s.c +@@ -748,6 +748,17 @@ static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 bcm2835_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .probe = bcm2835_i2s_dai_probe, + .startup = bcm2835_i2s_startup, +@@ -758,6 +769,8 @@ static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .set_fmt = bcm2835_i2s_set_dai_fmt, + .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio, + .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot, ++ .auto_selectable_formats = &bcm2835_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver bcm2835_i2s_dai = { +diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c +--- a/sound/soc/bcm/cygnus-ssp.c ++++ b/sound/soc/bcm/cygnus-ssp.c +@@ -1133,6 +1133,11 @@ static int cygnus_ssp_resume(struct snd_soc_component *component) + #define cygnus_ssp_resume NULL + #endif + ++static const u64 cygnus_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, +@@ -1141,6 +1146,8 @@ static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, ++ .auto_selectable_formats = &cygnus_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops cygnus_spdif_dai_ops = { +diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c +--- a/sound/soc/cirrus/ep93xx-i2s.c ++++ b/sound/soc/cirrus/ep93xx-i2s.c +@@ -401,6 +401,15 @@ static int ep93xx_i2s_resume(struct snd_soc_component *component) + #define ep93xx_i2s_resume NULL + #endif + ++static const u64 ep93xx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .probe = ep93xx_i2s_dai_probe, + .startup = ep93xx_i2s_startup, +@@ -408,6 +417,8 @@ static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, ++ .auto_selectable_formats = &ep93xx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -238,6 +238,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_RT715_SDCA_SDW + imply SND_SOC_RT721_SDCA_SDW + imply SND_SOC_RT722_SDCA_SDW ++ imply SND_SOC_RT766_SDCA_SDW + imply SND_SOC_RT1308_SDW + imply SND_SOC_RT1316_SDW + imply SND_SOC_RT1318 +@@ -367,6 +368,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_WSA881X + imply SND_SOC_WSA883X + imply SND_SOC_WSA884X ++ imply SND_SOC_WSA885X + imply SND_SOC_ZL38060 + help + Normally ASoC codec drivers are only built if a machine driver which +@@ -615,8 +617,8 @@ config SND_SOC_AK4613 + depends on I2C + + config SND_SOC_AK4619 +- tristate "AKM AK4619 CODEC" +- depends on I2C ++ tristate "AKM AK4619 CODEC" ++ depends on I2C + + config SND_SOC_AK4642 + tristate "AKM AK4642 CODEC" +@@ -894,6 +896,7 @@ config SND_SOC_CS35L56_SDW + tristate "Cirrus Logic CS35L56 CODEC (SDW)" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE ++ select IRQ_DOMAIN + select SND_SOC_CS35L56 + select SND_SOC_CS35L56_SHARED + help +@@ -1206,16 +1209,16 @@ config SND_SOC_JZ4725B_CODEC + will be called snd-soc-jz4725b-codec. + + config SND_SOC_JZ4760_CODEC +- depends on MACH_INGENIC || COMPILE_TEST +- depends on OF +- select REGMAP +- tristate "Ingenic JZ4760 internal CODEC" +- help +- Enable support for the internal CODEC found in the JZ4760 SoC +- from Ingenic. ++ depends on MACH_INGENIC || COMPILE_TEST ++ depends on OF ++ select REGMAP ++ tristate "Ingenic JZ4760 internal CODEC" ++ help ++ Enable support for the internal CODEC found in the JZ4760 SoC ++ from Ingenic. + +- This driver can also be built as a module. If so, the module +- will be called snd-soc-jz4760-codec. ++ This driver can also be built as a module. If so, the module ++ will be called snd-soc-jz4760-codec. + + config SND_SOC_JZ4770_CODEC + depends on MACH_INGENIC || COMPILE_TEST +@@ -1893,7 +1896,7 @@ config SND_SOC_RT5670 + depends on I2C + + config SND_SOC_RT5677 +- tristate ++ tristate "Realtek RT5677 Codec" + depends on I2C + select REGMAP_I2C + select REGMAP_IRQ +@@ -1983,6 +1986,15 @@ config SND_SOC_RT715_SDCA_SDW + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + ++config SND_SOC_RT766_SDCA_SDW ++ tristate "Realtek RT766 SDCA Codec - SDW" ++ depends on SOUNDWIRE ++ depends on SND_SOC_SDCA ++ select SND_SOC_SDCA_HID ++ select SND_SOC_SDCA_IRQ ++ select REGMAP_SOUNDWIRE ++ select REGMAP_SOUNDWIRE_MBQ ++ + config SND_SOC_RT9120 + tristate "Richtek RT9120 Stereo Class-D Amplifier" + depends on I2C +@@ -2302,8 +2314,8 @@ config SND_SOC_TLV320ADC3XXX + depends on I2C + depends on GPIOLIB + help +- Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 +- ADCs. ++ Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 ++ ADCs. + + config SND_SOC_TLV320AIC23 + tristate +@@ -2776,6 +2788,16 @@ config SND_SOC_WSA884X + This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D + Smart Speaker Amplifier. + ++config SND_SOC_WSA885X ++ tristate "WSA885X Codec" ++ depends on I2C ++ select REGMAP_I2C ++ help ++ This enables support for Qualcomm WSA885X Stereo Smart Speaker ++ Amplifier. The codec driver programs the amplifier register ++ map and exposes the DAI and mixer controls used by Qualcomm ++ audio machine drivers. ++ + config SND_SOC_ZL38060 + tristate "Microsemi ZL38060 Connected Home Audio Processor" + depends on SPI_MASTER +@@ -2899,28 +2921,32 @@ config SND_SOC_TPA6130A2 + depends on I2C + + config SND_SOC_LPASS_MACRO_COMMON +- tristate ++ tristate + + config SND_SOC_LPASS_WSA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm WSA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_VA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm VA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_RX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm RX Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_TX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -285,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o + snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o + snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o + snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o ++snd-soc-rt766-sdca-y := rt766-sdca.o rt766-sdca-sdw.o + snd-soc-rt9120-y := rt9120.o + snd-soc-rt9123-y := rt9123.o + snd-soc-rt9123p-y := rt9123p.o +@@ -427,6 +428,7 @@ snd-soc-wm-hubs-y := wm_hubs.o + snd-soc-wsa881x-y := wsa881x.o + snd-soc-wsa883x-y := wsa883x.o + snd-soc-wsa884x-y := wsa884x.o ++snd-soc-wsa885x-y := wsa885x.o + snd-soc-zl38060-y := zl38060.o + # Amp + snd-soc-max9877-y := max9877.o +@@ -726,6 +728,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o + obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o + obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o + obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o ++obj-$(CONFIG_SND_SOC_RT766_SDCA_SDW) += snd-soc-rt766-sdca.o + obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o + obj-$(CONFIG_SND_SOC_RT9123) += snd-soc-rt9123.o + obj-$(CONFIG_SND_SOC_RT9123P) += snd-soc-rt9123p.o +@@ -878,6 +881,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o + obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o + obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o + obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o ++obj-$(CONFIG_SND_SOC_WSA885X) += snd-soc-wsa885x.o + obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o + + # Amp +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,14 +14,17 @@ + * for ST-Ericsson. + */ + ++#include + #include + #include + #include + #include + #include + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -51,9 +54,32 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + ++/* Nr of FIR/IIR-coeff banks in ANC-block */ ++#define AB8500_NR_OF_ANC_COEFF_BANKS 2 ++ ++/* Minimum duration to keep ANC IIR Init bit high or ++low before proceeding with the configuration sequence */ ++#define AB8500_ANC_SM_DELAY 2000 ++ ++/* Sidetone states */ ++static const char * const enum_sid_state[] = { ++ "Unconfigured", ++ "Apply FIR", ++ "FIR is configured", ++}; ++enum sid_state { ++ SID_UNCONFIGURED = 0, ++ SID_APPLY_FIR = 1, ++ SID_FIR_CONFIGURED = 2, ++}; ++ + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; ++ struct mutex ctrl_lock; ++ ++ /* Sidetone */ ++ enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -616,6 +642,9 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + ++ SND_SOC_DAPM_INPUT("ANC Configure Input"), ++ SND_SOC_DAPM_OUTPUT("ANC Configure Output"), ++ + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -658,6 +687,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + ++ /* ANC Configure */ ++ {"ANC Configure Input", NULL, "Main Supply"}, ++ {"ANC Configure Output", NULL, "ANC Configure Input"}, ++ + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -939,6 +972,75 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + ++/* ++ * Control-events ++ */ ++ ++static int sid_status_control_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ ++ guard(mutex)(&drvdata->ctrl_lock); ++ ucontrol->value.enumerated.item[0] = drvdata->sid_status; ++ ++ return 0; ++} ++ ++/* Write sidetone FIR-coefficients configuration sequence */ ++static int sid_status_control_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ unsigned int param, sidconf; ++ int status = 1; ++ ++ dev_dbg(component->dev, "%s: Enter\n", __func__); ++ ++ if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { ++ dev_err(component->dev, ++ "%s: ERROR: This control supports '%s' only!\n", ++ __func__, enum_sid_state[SID_APPLY_FIR]); ++ return -EIO; ++ } ++ ++ guard(mutex)(&drvdata->ctrl_lock); ++ ++ sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); ++ if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { ++ if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { ++ dev_err(component->dev, "%s: Sidetone busy while off!\n", ++ __func__); ++ status = -EPERM; ++ } else { ++ status = -EBUSY; ++ } ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ return status; ++ } ++ ++ snd_soc_component_write(component, AB8500_SIDFIRADR, 0); ++ ++ for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); ++ } ++ ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), ++ BIT(AB8500_SIDFIRADR_FIRSIDSET)); ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); ++ ++ drvdata->sid_status = SID_FIR_CONFIGURED; ++ ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ ++ return status; ++} ++ + /* + * Controls - Non-DAPM ASoC + */ +@@ -1222,6 +1324,9 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + ++/* Sidetone */ ++static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); ++ + /* ANC */ + + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1512,6 +1617,8 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ ++ SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, ++ sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2041,8 +2148,10 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + + static int ab8500_codec_probe(struct snd_soc_component *component) + { ++ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2075,6 +2184,10 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + ++ snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); ++ ++ mutex_init(&drvdata->ctrl_lock); ++ + return status; + } + +@@ -2103,6 +2216,7 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; ++ drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c +--- a/sound/soc/codecs/ad1836.c ++++ b/sound/soc/codecs/ad1836.c +@@ -193,9 +193,15 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 ad1836_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, ++ .auto_selectable_formats = &ad1836_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define AD183X_DAI(_name, num_dacs, num_adcs) \ +diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c +--- a/sound/soc/codecs/ad193x.c ++++ b/sound/soc/codecs/ad193x.c +@@ -394,6 +394,14 @@ static int ad193x_startup(struct snd_pcm_substream *substream, + &constr); + } + ++static const u64 ad193x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad193x_dai_ops = { + .startup = ad193x_startup, + .hw_params = ad193x_hw_params, +@@ -401,6 +409,8 @@ static const struct snd_soc_dai_ops ad193x_dai_ops = { + .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, + .set_fmt = ad193x_set_dai_fmt, ++ .auto_selectable_formats = &ad193x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/adau7118-hw.c b/sound/soc/codecs/adau7118-hw.c +--- a/sound/soc/codecs/adau7118-hw.c ++++ b/sound/soc/codecs/adau7118-hw.c +@@ -22,7 +22,7 @@ static const struct of_device_id adau7118_of_match[] = { + MODULE_DEVICE_TABLE(of, adau7118_of_match); + + static const struct platform_device_id adau7118_id[] = { +- { .name = "adau7118" }, ++ { .name = "adau7118" }, + { } + }; + MODULE_DEVICE_TABLE(platform, adau7118_id); +diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c +--- a/sound/soc/codecs/adav80x.c ++++ b/sound/soc/codecs/adav80x.c +@@ -743,11 +743,19 @@ static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + adav80x->rate = 0; + } + ++static const u64 adav80x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, ++ .auto_selectable_formats = &adav80x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ +diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c +--- a/sound/soc/codecs/ak4613.c ++++ b/sound/soc/codecs/ak4613.c +@@ -95,6 +95,7 @@ + * see + * AK4613_ENABLE_TDM_TEST + */ ++#include + #include + #include + #include +@@ -384,7 +385,7 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + priv->cnt--; + if (priv->cnt < 0) { + dev_err(dev, "unexpected counter error\n"); +@@ -392,7 +393,6 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + } + if (!priv->cnt) + priv->ctrl1 = 0; +- mutex_unlock(&priv->lock); + } + + static void ak4613_hw_constraints(struct ak4613_priv *priv, +@@ -507,10 +507,9 @@ static int ak4613_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + ak4613_hw_constraints(priv, substream); + priv->cnt++; +- mutex_unlock(&priv->lock); + + return 0; + } +@@ -599,45 +598,47 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + */ + ret = -EINVAL; + +- mutex_lock(&priv->lock); +- if (priv->cnt > 1) { +- /* +- * If it was already working, use current priv->ctrl1 +- */ +- ret = 0; +- } else { +- /* +- * It is not yet working, +- */ +- unsigned int channel = params_channels(params); +- u8 tdm; ++ scoped_guard(mutex, &priv->lock) { ++ if (priv->cnt > 1) { ++ /* ++ * If it was already working, use current priv->ctrl1 ++ */ ++ ret = 0; ++ } else { ++ /* ++ * It is not yet working, ++ */ ++ unsigned int channel = params_channels(params); ++ u8 tdm; + +- /* STEREO or TDM */ +- if (channel == 2) +- tdm = AK4613_CONFIG_MODE_STEREO; +- else +- tdm = AK4613_CONFIG_GET(priv, MODE); ++ /* STEREO or TDM */ ++ if (channel == 2) ++ tdm = AK4613_CONFIG_MODE_STEREO; ++ else ++ tdm = AK4613_CONFIG_GET(priv, MODE); + +- for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { +- const struct ak4613_interface *iface = ak4613_iface + i; ++ for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { ++ const struct ak4613_interface *iface = ak4613_iface + i; + +- if ((iface->fmt == fmt) && (iface->width == width)) { +- /* +- * Ctrl1 +- * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | +- * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| +- * < tdm > < iface->dif > +- */ +- priv->ctrl1 = (tdm << 6) | (iface->dif << 3); +- ret = 0; +- break; ++ if (iface->fmt == fmt && iface->width == width) { ++ /* ++ * Ctrl1 ++ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | ++ * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| ++ * < tdm > < iface->dif > ++ */ ++ priv->ctrl1 = (tdm << 6) | (iface->dif << 3); ++ ret = 0; ++ break; ++ } + } + } + } +- mutex_unlock(&priv->lock); + +- if (ret < 0) +- goto hw_params_end; ++ if (ret < 0) { ++ dev_warn(dev, "unsupported data width/format combination\n"); ++ return ret; ++ } + + snd_soc_component_update_bits(component, CTRL1, FMT_MASK, priv->ctrl1); + snd_soc_component_update_bits(component, CTRL2, DFS_MASK, ctrl2); +@@ -645,10 +646,6 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + snd_soc_component_update_bits(component, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_component_update_bits(component, OCTRL, OCTRL_MASK, priv->oc); + +-hw_params_end: +- if (ret < 0) +- dev_warn(dev, "unsupported data width/format combination\n"); +- + return ret; + } + +diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c +--- a/sound/soc/codecs/arizona-jack.c ++++ b/sound/soc/codecs/arizona-jack.c +@@ -5,6 +5,7 @@ + * Copyright (C) 2012-2014 Wolfson Microelectronics plc + */ + ++#include + #include + #include + #include +@@ -707,15 +708,13 @@ static void arizona_micd_timeout_work(struct work_struct *work) + struct arizona_priv, + micd_timeout_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + dev_dbg(info->arizona->dev, "MICD timed out, reporting HP\n"); + + info->detecting = false; + + arizona_identify_headphone(info); +- +- mutex_unlock(&info->lock); + } + + static int arizona_micd_adc_read(struct arizona_priv *info) +@@ -921,12 +920,11 @@ static void arizona_micd_detect(struct work_struct *work) + + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + /* If the cable was removed while measuring ignore the result */ + if (!(info->jack->status & SND_JACK_MECHANICAL)) { + dev_dbg(arizona->dev, "Ignoring MICDET for removed cable\n"); +- mutex_unlock(&info->lock); + return; + } + +@@ -936,7 +934,6 @@ static void arizona_micd_detect(struct work_struct *work) + arizona_button_reading(info); + + pm_runtime_mark_last_busy(arizona->dev); +- mutex_unlock(&info->lock); + } + + static irqreturn_t arizona_micdet(int irq, void *data) +@@ -948,10 +945,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) + cancel_delayed_work_sync(&info->micd_detect_work); + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); +- if (!info->detecting) +- debounce = 0; +- mutex_unlock(&info->lock); ++ scoped_guard(mutex, &info->lock) { ++ if (!info->detecting) ++ debounce = 0; ++ } + + if (debounce) + queue_delayed_work(system_power_efficient_wq, +@@ -969,9 +966,8 @@ static void arizona_hpdet_work(struct work_struct *work) + struct arizona_priv, + hpdet_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + arizona_start_hpdet_acc_id(info); +- mutex_unlock(&info->lock); + } + + static int arizona_hpdet_wait(struct arizona_priv *info) +@@ -1018,9 +1014,9 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work); + +- pm_runtime_get_sync(arizona->dev); ++ guard(pm_runtime_active_auto)(arizona->dev); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + if (info->micd_clamp) { + mask = ARIZONA_MICD_CLAMP_STS; +@@ -1036,8 +1032,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); + if (ret) { + dev_err(arizona->dev, "Failed to read jackdet status: %d\n", ret); +- mutex_unlock(&info->lock); +- pm_runtime_put_autosuspend(arizona->dev); + return IRQ_NONE; + } + +@@ -1056,62 +1050,61 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + &info->micd_timeout_work, + msecs_to_jiffies(micd_timeout)); + } ++ } else { ++ info->last_jackdet = val; + +- goto out; +- } +- info->last_jackdet = val; ++ if (info->last_jackdet == present) { ++ dev_dbg(arizona->dev, "Detected jack\n"); ++ snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); + +- if (info->last_jackdet == present) { +- dev_dbg(arizona->dev, "Detected jack\n"); +- snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); ++ info->detecting = true; ++ info->mic = false; ++ info->jack_flips = 0; + +- info->detecting = true; +- info->mic = false; +- info->jack_flips = 0; ++ if (!arizona->pdata.hpdet_acc_id) { ++ arizona_start_mic(info); ++ } else { ++ queue_delayed_work(system_power_efficient_wq, ++ &info->hpdet_work, ++ msecs_to_jiffies(HPDET_DEBOUNCE)); ++ } + +- if (!arizona->pdata.hpdet_acc_id) { +- arizona_start_mic(info); ++ if (info->micd_clamp || !arizona->pdata.jd_invert) ++ regmap_update_bits(arizona->regmap, ++ ARIZONA_JACK_DETECT_DEBOUNCE, ++ ARIZONA_MICD_CLAMP_DB | ++ ARIZONA_JD1_DB, 0); + } else { +- queue_delayed_work(system_power_efficient_wq, +- &info->hpdet_work, +- msecs_to_jiffies(HPDET_DEBOUNCE)); +- } ++ dev_dbg(arizona->dev, "Detected jack removal\n"); ++ ++ arizona_stop_mic(info); ++ ++ info->num_hpdet_res = 0; ++ for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) ++ info->hpdet_res[i] = 0; ++ info->mic = false; ++ info->hpdet_done = false; ++ info->hpdet_retried = false; ++ ++ snd_soc_jack_report(info->jack, 0, ++ ARIZONA_JACK_MASK | info->micd_button_mask); ++ ++ /* ++ * If the jack was removed during a headphone detection we ++ * need to wait for the headphone detection to finish, as ++ * it can not be aborted. We don't want to be able to start ++ * a new headphone detection from a fresh insert until this ++ * one is finished. ++ */ ++ arizona_hpdet_wait(info); + +- if (info->micd_clamp || !arizona->pdata.jd_invert) + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | +- ARIZONA_JD1_DB, 0); +- } else { +- dev_dbg(arizona->dev, "Detected jack removal\n"); +- +- arizona_stop_mic(info); +- +- info->num_hpdet_res = 0; +- for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) +- info->hpdet_res[i] = 0; +- info->mic = false; +- info->hpdet_done = false; +- info->hpdet_retried = false; +- +- snd_soc_jack_report(info->jack, 0, ARIZONA_JACK_MASK | info->micd_button_mask); +- +- /* +- * If the jack was removed during a headphone detection we +- * need to wait for the headphone detection to finish, as +- * it can not be aborted. We don't want to be able to start +- * a new headphone detection from a fresh insert until this +- * one is finished. +- */ +- arizona_hpdet_wait(info); +- +- regmap_update_bits(arizona->regmap, +- ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ } + } + +-out: + /* Clear trig_sts to make sure DCVDD is not forced up */ + regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, + ARIZONA_MICD_CLAMP_FALL_TRIG_STS | +@@ -1119,10 +1112,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ARIZONA_JD1_FALL_TRIG_STS | + ARIZONA_JD1_RISE_TRIG_STS); + +- mutex_unlock(&info->lock); +- +- pm_runtime_put_autosuspend(arizona->dev); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c +--- a/sound/soc/codecs/arizona.c ++++ b/sound/soc/codecs/arizona.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -1158,17 +1159,16 @@ int arizona_dvfs_up(struct snd_soc_component *component, unsigned int flags) + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + if (!priv->dvfs_cached && !priv->dvfs_reqs) { + ret = arizona_dvfs_enable(component); + if (ret) +- goto err; ++ return ret; + } + + priv->dvfs_reqs |= flags; +-err: +- mutex_unlock(&priv->dvfs_lock); ++ + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_up); +@@ -1179,7 +1179,7 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + unsigned int old_reqs; + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + old_reqs = priv->dvfs_reqs; + priv->dvfs_reqs &= ~flags; +@@ -1187,7 +1187,6 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + if (!priv->dvfs_cached && old_reqs && !priv->dvfs_reqs) + ret = arizona_dvfs_disable(component); + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_down); +@@ -1199,7 +1198,7 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +@@ -1222,7 +1221,6 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_sysclk_ev); +@@ -1657,13 +1655,11 @@ static void arizona_wm5102_set_dac_comp(struct snd_soc_component *component, + { 0x80, 0x0 }, + }; + +- mutex_lock(&arizona->dac_comp_lock); +- +- dac_comp[1].def = arizona->dac_comp_coeff; +- if (rate >= 176400) +- dac_comp[2].def = arizona->dac_comp_enabled; +- +- mutex_unlock(&arizona->dac_comp_lock); ++ scoped_guard(mutex, &arizona->dac_comp_lock) { ++ dac_comp[1].def = arizona->dac_comp_coeff; ++ if (rate >= 176400) ++ dac_comp[2].def = arizona->dac_comp_enabled; ++ } + + regmap_multi_reg_write(arizona->regmap, + dac_comp, +diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c +--- a/sound/soc/codecs/aw87390.c ++++ b/sound/soc/codecs/aw87390.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -225,11 +226,10 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + ret = aw87390_dev_set_profile_index(aw87390->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change\n"); +- mutex_unlock(&aw87390->lock); + return 0; + } + +@@ -238,8 +238,6 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + aw87390_power_on(aw87390->aw_pa); + } + +- mutex_unlock(&aw87390->lock); +- + return 1; + } + +@@ -250,7 +248,7 @@ static const struct snd_kcontrol_new aw87390_controls[] = { + + static int aw87390_request_firmware_file(struct aw87390 *aw87390) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw87390->aw_pa->fw_status = AW87390_DEV_FW_FAILED; +@@ -265,14 +263,11 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + + aw87390->aw_cfg = devm_kzalloc(aw87390->aw_pa->dev, + struct_size(aw87390->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw87390->aw_cfg) { +- release_firmware(cont); ++ if (!aw87390->aw_cfg) + return -ENOMEM; +- } + + aw87390->aw_cfg->len = cont->size; + memcpy(aw87390->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) { +@@ -280,14 +275,12 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + return ret; + } + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + + ret = aw88395_dev_cfg_load(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) + dev_err(aw87390->aw_pa->dev, "aw_dev acf parse failed\n"); + +- mutex_unlock(&aw87390->lock); +- + return ret; + } + +diff --git a/sound/soc/codecs/aw88081.c b/sound/soc/codecs/aw88081.c +--- a/sound/soc/codecs/aw88081.c ++++ b/sound/soc/codecs/aw88081.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -763,9 +764,8 @@ static void aw88081_startup_work(struct work_struct *work) + struct aw88081 *aw88081 = + container_of(work, struct aw88081, start_work.work); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + aw88081_start_pa(aw88081); +- mutex_unlock(&aw88081->lock); + } + + static void aw88081_start(struct aw88081 *aw88081, bool sync_start) +@@ -942,11 +942,10 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + ret = aw88081_dev_set_profile_index(aw88081->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88081->lock); + return 0; + } + +@@ -955,8 +954,6 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + aw88081_start(aw88081, AW88081_SYNC_START); + } + +- mutex_unlock(&aw88081->lock); +- + return 1; + } + +@@ -1136,7 +1133,7 @@ static int aw88081_dev_init(struct aw88081 *aw88081, struct aw_container *aw_cfg + + static int aw88081_request_firmware_file(struct aw88081 *aw88081) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -1150,26 +1147,21 @@ static int aw88081_request_firmware_file(struct aw88081 *aw88081) + AW88081_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88081->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); + + aw88081->aw_cfg = aw_cfg; + +- release_firmware(cont); +- + ret = aw88395_dev_load_acf_check(aw88081->aw_pa, aw88081->aw_cfg); + if (ret) + return ret; + +- mutex_lock(&aw88081->lock); +- ret = aw88081_dev_init(aw88081, aw88081->aw_cfg); +- mutex_unlock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + +- return ret; ++ return aw88081_dev_init(aw88081, aw88081->aw_cfg); + } + + static int aw88081_playback_event(struct snd_soc_dapm_widget *w, +@@ -1178,7 +1170,7 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88081 *aw88081 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88081_start(aw88081, AW88081_ASYNC_START); +@@ -1189,7 +1181,6 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88081->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c +--- a/sound/soc/codecs/aw88166.c ++++ b/sound/soc/codecs/aw88166.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -1173,9 +1174,8 @@ static void aw88166_startup_work(struct work_struct *work) + struct aw88166 *aw88166 = + container_of(work, struct aw88166, start_work.work); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + aw88166_start_pa(aw88166); +- mutex_unlock(&aw88166->lock); + } + + static void aw88166_start(struct aw88166 *aw88166, bool sync_start) +@@ -1413,11 +1413,10 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + ret = aw88166_dev_set_profile_index(aw88166->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88166->lock); + return 0; + } + +@@ -1426,8 +1425,6 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + aw88166_start(aw88166, AW88166_SYNC_START); + } + +- mutex_unlock(&aw88166->lock); +- + return 1; + } + +@@ -1573,7 +1570,7 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg + + static int aw88166_request_firmware_file(struct aw88166 *aw88166) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + const char *fw_name; + int ret; + +@@ -1593,13 +1590,11 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + + aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev, + struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88166->aw_cfg) { +- release_firmware(cont); ++ if (!aw88166->aw_cfg) + return -ENOMEM; +- } ++ + aw88166->aw_cfg->len = (int)cont->size; + memcpy(aw88166->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg); + if (ret) { +@@ -1607,12 +1602,12 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + return ret; + } + +- mutex_lock(&aw88166->lock); +- /* aw device init */ +- ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); +- if (ret) +- dev_err(aw88166->aw_pa->dev, "dev init failed\n"); +- mutex_unlock(&aw88166->lock); ++ scoped_guard(mutex, &aw88166->lock) { ++ /* aw device init */ ++ ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); ++ if (ret) ++ dev_err(aw88166->aw_pa->dev, "dev init failed\n"); ++ } + + return ret; + } +@@ -1639,7 +1634,7 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88166_start(aw88166, AW88166_ASYNC_START); +@@ -1650,7 +1645,6 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88166->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c +--- a/sound/soc/codecs/aw88261.c ++++ b/sound/soc/codecs/aw88261.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -960,11 +961,10 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + ret = aw88261_dev_set_profile_index(aw88261->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88261->lock); + return 0; + } + +@@ -973,8 +973,6 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + aw88261_start(aw88261); + } + +- mutex_unlock(&aw88261->lock); +- + return 1; + } + +@@ -1038,7 +1036,7 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88261_start(aw88261); +@@ -1049,7 +1047,6 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88261->lock); + + return 0; + } +@@ -1152,7 +1149,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg + + static int aw88261_request_firmware_file(struct aw88261 *aw88261) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + const char *fw_name; + int ret; +@@ -1172,13 +1169,11 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + fw_name, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88261->aw_cfg = aw_cfg; + +@@ -1188,12 +1183,12 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + return ret; + } + +- mutex_lock(&aw88261->lock); +- /* aw device init */ +- ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); +- if (ret) +- dev_err(aw88261->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88261->lock); ++ scoped_guard(mutex, &aw88261->lock) { ++ /* aw device init */ ++ ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); ++ if (ret) ++ dev_err(aw88261->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395.c b/sound/soc/codecs/aw88395/aw88395.c +--- a/sound/soc/codecs/aw88395/aw88395.c ++++ b/sound/soc/codecs/aw88395/aw88395.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -51,9 +52,8 @@ static void aw88395_startup_work(struct work_struct *work) + struct aw88395 *aw88395 = + container_of(work, struct aw88395, start_work.work); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + aw88395_start_pa(aw88395); +- mutex_unlock(&aw88395->lock); + } + + static void aw88395_start(struct aw88395 *aw88395, bool sync_start) +@@ -224,11 +224,10 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + ret = aw88395_dev_set_profile_index(aw88395->aw_pa, ucontrol->value.integer.value[0]); + if (ret < 0) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88395->lock); + return 0; + } + +@@ -237,8 +236,6 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + aw88395_start(aw88395, AW88395_SYNC_START); + } + +- mutex_unlock(&aw88395->lock); +- + return 1; + } + +@@ -366,7 +363,7 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88395 *aw88395 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88395_start(aw88395, AW88395_ASYNC_START); +@@ -377,7 +374,6 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88395->lock); + + return 0; + } +@@ -461,7 +457,7 @@ static void aw88395_hw_reset(struct aw88395 *aw88395) + + static int aw88395_request_firmware_file(struct aw88395 *aw88395) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -477,13 +473,11 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + AW88395_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88395->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88395->aw_cfg = aw_cfg; + +@@ -495,12 +489,12 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + + dev_dbg(aw88395->aw_pa->dev, "%s : bin load success\n", __func__); + +- mutex_lock(&aw88395->lock); +- /* aw device init */ +- ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); +- if (ret < 0) +- dev_err(aw88395->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88395->lock); ++ scoped_guard(mutex, &aw88395->lock) { ++ /* aw device init */ ++ ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); ++ if (ret < 0) ++ dev_err(aw88395->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395_device.c b/sound/soc/codecs/aw88395/aw88395_device.c +--- a/sound/soc/codecs/aw88395/aw88395_device.c ++++ b/sound/soc/codecs/aw88395/aw88395_device.c +@@ -8,6 +8,7 @@ + // Author: Ben Yi + // + ++#include + #include + #include + #include +@@ -70,7 +71,7 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_write_16bit(aw_dev, dsp_addr, dsp_data); +@@ -93,7 +94,6 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -156,7 +156,7 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_read_16bit(aw_dev, dsp_addr, dsp_data); +@@ -179,7 +179,6 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -1110,42 +1109,36 @@ static int aw_dev_dsp_update_container(struct aw_device *aw_dev, + #ifdef AW88395_DSP_I2C_WRITES + u32 tmp_len; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + + for (i = 0; i < len; i += AW88395_MAX_RAM_WRITE_BYTE_SIZE) { + tmp_len = min(len - i, AW88395_MAX_RAM_WRITE_BYTE_SIZE); + ret = regmap_raw_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + &data[i], tmp_len); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #else + __be16 reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* i2c write */ + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + for (i = 0; i < len; i += 2) { + reg_val = cpu_to_be16p((u16 *)(data + i)); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + (u16)reg_val); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #endif + + return 0; +- +-error_operation: +- mutex_unlock(&aw_dev->dsp_lock); +- return ret; + } + + static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +@@ -1231,14 +1224,14 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + { + unsigned int reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* check the odd bits of reg 0x40 */ + regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, AW88395_DSP_ODD_NUM_BIT_TEST); + regmap_read(aw_dev->regmap, AW88395_DSPMADD_REG, ®_val); + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 odd bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check the even bits of reg 0x40 */ +@@ -1247,7 +1240,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 even bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_fw_base_addr */ +@@ -1256,7 +1249,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp fw addr failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_cfg_base_addr */ +@@ -1265,15 +1258,10 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } +- mutex_unlock(&aw_dev->dsp_lock); + + return 0; +- +-error: +- mutex_unlock(&aw_dev->dsp_lock); +- return -EPERM; + } + + int aw88395_dev_fw_update(struct aw_device *aw_dev, bool up_dsp_fw_en, bool force_up_en) +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +--- a/sound/soc/codecs/aw88399-lib.c ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -1282,7 +1282,7 @@ static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg + + int aw88399_request_firmware_file(struct aw88399 *aw88399) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +@@ -1298,13 +1298,11 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399) + + aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, + struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); ++ if (!aw88399->aw_cfg) + return -ENOMEM; +- } ++ + aw88399->aw_cfg->len = (int)cont->size; + memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); + if (ret) { +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -497,11 +498,10 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + ret = aw88399_dev_set_profile_index(aw88399->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88399->lock); + return 0; + } + +@@ -510,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + aw88399_start(aw88399, AW88399_SYNC_START); + } + +- mutex_unlock(&aw88399->lock); +- + return 1; + } + +@@ -690,7 +688,7 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88399_start(aw88399, AW88399_ASYNC_START); +@@ -701,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88399->lock); + + return 0; + } +diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c +--- a/sound/soc/codecs/bt-sco.c ++++ b/sound/soc/codecs/bt-sco.c +@@ -85,13 +85,9 @@ static int bt_sco_probe(struct platform_device *pdev) + } + + static const struct platform_device_id bt_sco_driver_ids[] = { +- { +- .name = "dfbmcs320", +- }, +- { +- .name = "bt-sco", +- }, +- {}, ++ { .name = "dfbmcs320" }, ++ { .name = "bt-sco" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); + +diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c +--- a/sound/soc/codecs/cpcap.c ++++ b/sound/soc/codecs/cpcap.c +@@ -26,14 +26,6 @@ + /* Register 9 - CPCAP_REG_INTS2 --- Interrupt Sense 2 */ + #define CPCAP_BIT_PTT_S 11 /* Push To Talk */ + +-/* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */ +-#define CPCAP_BIT_AUDIO_LOW_PWR 6 +-#define CPCAP_BIT_AUD_LOWPWR_SPEED 5 +-#define CPCAP_BIT_VAUDIOPRISTBY 4 +-#define CPCAP_BIT_VAUDIO_MODE1 2 +-#define CPCAP_BIT_VAUDIO_MODE0 1 +-#define CPCAP_BIT_V_AUDIO_EN 0 +- + /* Register 513 CPCAP_REG_CC --- CODEC */ + #define CPCAP_BIT_CDC_CLK2 15 + #define CPCAP_BIT_CDC_CLK1 14 +@@ -239,7 +231,6 @@ struct cpcap_reg_info { + }; + + static const struct cpcap_reg_info cpcap_default_regs[] = { +- { CPCAP_REG_VAUDIOC, 0x003F, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, +@@ -1265,6 +1256,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = dai->component; + struct device *dev = component->dev; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1298,7 +1290,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + return err; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, rtd->dai_link->dai_fmt); + } + + static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, +@@ -1391,121 +1383,8 @@ static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + return 0; + } + +- +-/* +- * Configure codec for voice call if requested. +- * +- * We can configure most with snd_soc_dai_set_sysclk(), snd_soc_dai_set_fmt() +- * and snd_soc_dai_set_tdm_slot(). This function configures the rest of the +- * cpcap related hardware as CPU is not involved in the voice call. +- */ +-static int cpcap_voice_call(struct cpcap_audio *cpcap, struct snd_soc_dai *dai, +- bool voice_call) +-{ +- int mask, err; +- +- /* Modem to codec VAUDIO_MODE1 */ +- mask = BIT(CPCAP_BIT_VAUDIO_MODE1); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Clear MIC1_MUX for call */ +- mask = BIT(CPCAP_BIT_MIC1_MUX); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? 0 : mask); +- if (err) +- return err; +- +- /* Set MIC2_MUX for call */ +- mask = BIT(CPCAP_BIT_MB_ON1L) | BIT(CPCAP_BIT_MB_ON1R) | +- BIT(CPCAP_BIT_MIC2_MUX) | BIT(CPCAP_BIT_MIC2_PGA_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable LDSP for call */ +- mask = BIT(CPCAP_BIT_A2_LDSP_L_EN) | BIT(CPCAP_BIT_A2_LDSP_R_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable CPCAP_BIT_PGA_CDC_EN for call */ +- mask = BIT(CPCAP_BIT_PGA_CDC_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Unmute voice for call */ +- if (dai) { +- err = snd_soc_dai_digital_mute(dai, !voice_call, +- SNDRV_PCM_STREAM_PLAYBACK); +- if (err) +- return err; +- } +- +- /* Set modem to codec mic CDC and HPF for call */ +- mask = BIT(CPCAP_BIT_MIC2_CDC_EN) | BIT(CPCAP_BIT_CDC_EN_RX) | +- BIT(CPCAP_BIT_AUDOHPF_1) | BIT(CPCAP_BIT_AUDOHPF_0) | +- BIT(CPCAP_BIT_AUDIHPF_1) | BIT(CPCAP_BIT_AUDIHPF_0); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable modem to codec CDC for call*/ +- mask = BIT(CPCAP_BIT_CDC_CLK_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- mask, voice_call ? mask : 0); +- +- return err; +-} +- +-static int cpcap_voice_set_tdm_slot(struct snd_soc_dai *dai, +- unsigned int tx_mask, unsigned int rx_mask, +- int slots, int slot_width) +-{ +- struct snd_soc_component *component = dai->component; +- struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +- int err, ts_mask, mask; +- bool voice_call; +- +- /* +- * Primitive test for voice call, probably needs more checks +- * later on for 16-bit calls detected, Bluetooth headset etc. +- */ +- if (tx_mask == 0 && rx_mask == 1 && slot_width == 8) +- voice_call = true; +- else +- voice_call = false; +- +- ts_mask = 0x7 << CPCAP_BIT_MIC2_TIMESLOT0; +- ts_mask |= 0x7 << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- mask = (tx_mask & 0x7) << CPCAP_BIT_MIC2_TIMESLOT0; +- mask |= (rx_mask & 0x7) << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- ts_mask, mask); +- if (err) +- return err; +- +- err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, slot_width * 1000); +- if (err) +- return err; +- +- err = cpcap_voice_call(cpcap, dai, voice_call); +- if (err) +- return err; +- +- return 0; +-} +- +-static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int cpcap_voice_set_mute(struct snd_soc_dai *dai, ++ int mute, int direction) + { + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1526,7 +1405,6 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, +- .set_tdm_slot = cpcap_voice_set_tdm_slot, + .mute_stream = cpcap_voice_set_mute, + .no_capture_mute = 1, + }; +diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c +--- a/sound/soc/codecs/cros_ec_codec.c ++++ b/sound/soc/codecs/cros_ec_codec.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -319,10 +320,18 @@ static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + (uint8_t *)&p, sizeof(p), NULL, 0); + } + ++static const u64 i2s_rx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops i2s_rx_dai_ops = { + .hw_params = i2s_rx_hw_params, + .set_fmt = i2s_rx_set_fmt, + .set_bclk_ratio = i2s_rx_set_bclk_ratio, ++ .auto_selectable_formats = &i2s_rx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static int i2s_rx_event(struct snd_soc_dapm_widget *w, +@@ -608,10 +617,10 @@ static void wov_copy_work(struct work_struct *w) + container_of(w, struct cros_ec_codec_priv, wov_copy_work.work); + int ret; + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + if (!priv->wov_substream) { + dev_warn(priv->dev, "no pcm substream\n"); +- goto leave; ++ return; + } + + if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) +@@ -624,8 +633,6 @@ static void wov_copy_work(struct work_struct *w) + msecs_to_jiffies(10)); + else if (ret) + dev_err(priv->dev, "failed to read audio data\n"); +-leave: +- mutex_unlock(&priv->wov_dma_lock); + } + + static int wov_enable_get(struct snd_kcontrol *kcontrol, +@@ -895,12 +902,11 @@ static int wov_pcm_hw_params(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + priv->wov_substream = substream; + priv->wov_rp = priv->wov_wp = 0; + priv->wov_dma_offset = 0; + priv->wov_burst_read = true; +- mutex_unlock(&priv->wov_dma_lock); + + return 0; + } +@@ -911,10 +917,10 @@ static int wov_pcm_hw_free(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); +- wov_queue_dequeue(priv, wov_queue_size(priv)); +- priv->wov_substream = NULL; +- mutex_unlock(&priv->wov_dma_lock); ++ scoped_guard(mutex, &priv->wov_dma_lock) { ++ wov_queue_dequeue(priv, wov_queue_size(priv)); ++ priv->wov_substream = NULL; ++ } + + cancel_delayed_work_sync(&priv->wov_copy_work); + +diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c +--- a/sound/soc/codecs/cs-amp-lib.c ++++ b/sound/soc/codecs/cs-amp-lib.c +@@ -7,6 +7,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -83,10 +84,12 @@ static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, + KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); + + if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { +- mutex_lock(&dsp->pwr_lock); +- cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); +- ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); +- mutex_unlock(&dsp->pwr_lock); ++ scoped_guard(mutex, &dsp->pwr_lock) { ++ cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, ++ controls->mem_region, ++ controls->alg_id); ++ ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); ++ } + + if (ret < 0) { + dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); +diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c +--- a/sound/soc/codecs/cs35l32.c ++++ b/sound/soc/codecs/cs35l32.c +@@ -538,7 +538,15 @@ static int cs35l32_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + + regcache_cache_only(cs35l32->regmap, false); +- regcache_sync(cs35l32->regmap); ++ ret = regcache_sync(cs35l32->regmap); ++ if (ret) { ++ regcache_cache_only(cs35l32->regmap, true); ++ regcache_mark_dirty(cs35l32->regmap); ++ gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); ++ regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), ++ cs35l32->supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -231,7 +231,7 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + * a soft reset. + */ + if (cs35l56->base.init_done) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + out: + pm_runtime_put_autosuspend(cs35l56->base.dev); +@@ -240,47 +240,17 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, + struct sdw_slave_intr_status *status) + { +- struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +- +- /* SoundWire core holds our pm_runtime when calling this function. */ +- +- dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port); +- +- if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) +- return 0; +- + /* +- * Prevent bus manager suspending and possibly issuing a +- * bus-reset before the queued work has run. ++ * The IRQ itself was handled through the regmap_irq handler, this is ++ * just clearing up the additional Cirrus SoundWire registers that are ++ * not covered by the SoundWire framework or the IRQ handler itself. + */ +- pm_runtime_get_noresume(cs35l56->base.dev); +- +- /* +- * Mask and clear until it has been handled. +- * None of the interrupts are time-critical so use the +- * power-efficient queue. +- */ +- cs35l56_mask_soundwire_interrupts(peripheral); +- queue_work(system_power_efficient_wq, &cs35l56->sdw_irq_work); ++ sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + + return 0; + } + +-static void cs35l56_sdw_irq_work(struct work_struct *work) +-{ +- struct cs35l56_private *cs35l56 = container_of(work, +- struct cs35l56_private, +- sdw_irq_work); +- +- cs35l56_irq(-1, &cs35l56->base); +- +- /* unmask interrupts */ +- if (!cs35l56->sdw_irq_no_unmask) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); +- +- pm_runtime_put_autosuspend(cs35l56->base.dev); +-} +- + static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +@@ -302,6 +272,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); + prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); + prop->paging_support = true; ++ prop->use_domain_irq = true; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF; + +@@ -406,7 +377,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) + if (ret) + return ret; + +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + return 0; + } +@@ -418,21 +389,12 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + if (!cs35l56->base.init_done) + return 0; + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ /* runtime_resume unmasks the interrupt */ ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } + +-static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev) +-{ +- struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); +- +- cs35l56->sdw_irq_no_unmask = false; +- /* runtime_resume re-enables the interrupt */ +- +- return cs35l56_system_resume(dev); +-} +- + static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id) + { + struct device *dev = &peripheral->dev; +@@ -447,7 +409,6 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + cs35l56->base.dev = dev; + cs35l56->sdw_peripheral = peripheral; + cs35l56->sdw_link_num = peripheral->bus->link_id; +- INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); + + dev_set_drvdata(dev, cs35l56); + +@@ -457,6 +418,7 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + regmap_config = &cs35l56_regmap_sdw; + break; + case 0x3563: ++ case 0x3562: + regmap_config = &cs35l63_regmap_sdw; + break; + default: +@@ -483,21 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + /* Start in cache-only until device is enumerated */ + regcache_cache_only(cs35l56->base.regmap, true); + +- return cs35l56_common_probe(cs35l56, -EINVAL); ++ return cs35l56_common_probe(cs35l56, peripheral->irq); + } + + static void cs35l56_sdw_remove(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + cs35l56_remove(cs35l56); + } + + static const struct dev_pm_ops cs35l56_sdw_pm = { + SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL) +- SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume) ++ SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_system_resume) + LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early) + /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */ + }; +@@ -505,6 +467,7 @@ static const struct dev_pm_ops cs35l56_sdw_pm = { + static const struct sdw_device_id cs35l56_sdw_id[] = { + SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0x3556), + SDW_SLAVE_ENTRY(0x01FA, 0x3557, 0x3557), ++ SDW_SLAVE_ENTRY(0x01FA, 0x3562, 0x3562), + SDW_SLAVE_ENTRY(0x01FA, 0x3563, 0x3563), + {}, + }; +diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c +--- a/sound/soc/codecs/cs35l56-shared.c ++++ b/sound/soc/codecs/cs35l56-shared.c +@@ -96,6 +96,7 @@ int cs35l56_set_patch(struct cs35l56_base *cs35l56_base) + ARRAY_SIZE(cs35l56_patch_fw)); + break; + case 0x63: ++ case 0x62: + ret = regmap_register_patch(cs35l56_base->regmap, cs35l63_patch_fw, + ARRAY_SIZE(cs35l63_patch_fw)); + break; +@@ -389,6 +390,7 @@ static void cs35l56_set_fw_reg_table(struct cs35l56_base *cs35l56_base) + } + break; + case 0x63: ++ case 0x62: + cs35l56_base->fw_reg = &cs35l63_fw_reg; + break; + } +@@ -595,6 +597,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + break; + case 0x63: ++ case 0x62: + regmap_multi_reg_write_bypassed(cs35l56_base->regmap, + cs35l63_system_reset_seq, + ARRAY_SIZE(cs35l63_system_reset_seq)); +@@ -613,26 +616,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, "SND_SOC_CS35L56_SHARED"); + +-int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) +-{ +- int ret; +- +- if (irq < 1) +- return 0; +- +- ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, +- IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, +- "cs35l56", cs35l56_base); +- if (!ret) +- cs35l56_base->irq = irq; +- else +- dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); +- +- return ret; +-} +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); +- +-irqreturn_t cs35l56_irq(int irq, void *data) ++static irqreturn_t cs35l56_irq(int irq, void *data) + { + struct cs35l56_base *cs35l56_base = data; + unsigned int status1 = 0, status8 = 0, status20 = 0; +@@ -640,23 +624,22 @@ irqreturn_t cs35l56_irq(int irq, void *data) + unsigned int val; + int rv; + +- irqreturn_t ret = IRQ_NONE; +- + if (!cs35l56_base->init_done) + return IRQ_NONE; + +- mutex_lock(&cs35l56_base->irq_lock); ++ guard(mutex)(&cs35l56_base->irq_lock); + +- rv = pm_runtime_resume_and_get(cs35l56_base->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED(cs35l56_base->dev, pm); ++ rv = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (rv < 0) { + dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv); +- goto err_unlock; ++ return IRQ_NONE; + } + + regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val); + if ((val & CS35L56_IRQ1_STS_MASK) == 0) { + dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n"); +- goto err; ++ return IRQ_NONE; + } + + /* Ack interrupts */ +@@ -680,7 +663,7 @@ irqreturn_t cs35l56_irq(int irq, void *data) + + /* Check to see if unmasked bits are active */ + if (!status1 && !status8 && !status20) +- goto err; ++ return IRQ_NONE; + + if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Amp short error\n"); +@@ -688,16 +671,27 @@ irqreturn_t cs35l56_irq(int irq, void *data) + if (status8 & CS35L56_TEMP_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Overtemp error\n"); + +- ret = IRQ_HANDLED; ++ return IRQ_HANDLED; ++} + +-err: +- pm_runtime_put(cs35l56_base->dev); +-err_unlock: +- mutex_unlock(&cs35l56_base->irq_lock); ++int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) ++{ ++ int ret; ++ ++ if (irq < 1) ++ return 0; ++ ++ ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, ++ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, ++ "cs35l56", cs35l56_base); ++ if (!ret) ++ cs35l56_base->irq = irq; ++ else ++ dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); + + return ret; + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq, "SND_SOC_CS35L56_SHARED"); ++EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); + + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base) + { +@@ -816,14 +810,10 @@ int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_sou + if (!cs35l56_base->init_done) + return 0; + +- if (!cs35l56_base->can_hibernate) +- goto out_sync; +- +- /* Must be done before releasing cache-only */ +- if (!is_soundwire) ++ /* Hibernate wake must be done before releasing cache-only */ ++ if (cs35l56_base->can_hibernate && !is_soundwire) + cs35l56_issue_wake_event(cs35l56_base); + +-out_sync: + ret = cs35l56_wait_for_firmware_boot(cs35l56_base); + if (ret) { + dev_err(cs35l56_base->dev, "Hibernate wake failed: %d\n", ret); +@@ -1470,6 +1460,7 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base) + cs35l56_base->calibration_controls = &cs35l56_calibration_controls; + break; + case 0x35A630: ++ case 0x35A620: + cs35l56_base->calibration_controls = &cs35l63_calibration_controls; + devid = devid >> 4; + break; +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -18,11 +18,9 @@ + #include + #include + #include +-#include + #include + #include + #include +-#include + #include + #include + #include +@@ -39,55 +37,49 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + +-/* +- * snd_soc_register_component() can call component_probe() on all instances +- * in a card, so deferred registration must be protected across all instances. +- */ +-static DEFINE_MUTEX(cs35l56_component_register_lock); +-static bool cs35l56_shutting_down; +- +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* ++ * Mask unconditionally. ++ * + * The read of GEN_INT_STAT_1 is required as per the SoundWire spec + * for interrupt status bits to clear. + * GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1. + */ +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); +- sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); ++ sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_mask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); ++ if (!cs35l56->base.irq) ++ return; ++ ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, ++ CS35L56_SDW_INT_MASK_CODEC_IRQ); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_unmask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) + { + if (!cs35l56->sdw_peripheral) + return; + +- cs35l56->sdw_irq_no_unmask = true; +- flush_work(&cs35l56->sdw_irq_work); +- +- /* Mask interrupts and flush in case sdw_irq_work was queued again */ +- cs35l56_mask_soundwire_interrupts(cs35l56->sdw_peripheral); +- flush_work(&cs35l56->sdw_irq_work); ++ cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->base.irq) ++ disable_irq(cs35l56->base.irq); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_disable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) + { +- if (!cs35l56->sdw_peripheral) ++ if (!cs35l56->sdw_peripheral || !cs35l56->base.irq) + return; + +- cs35l56->sdw_irq_no_unmask = false; +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ enable_irq(cs35l56->base.irq); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_enable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + + static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +@@ -837,11 +829,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing + { + int ret; + +- /* +- * Disable SoundWire interrupts to prevent race with IRQ work. +- * Setting sdw_irq_no_unmask prevents the handler re-enabling +- * the SoundWire interrupt. +- */ ++ /* Disable SoundWire interrupts to prevent race with IRQ handler thread */ + cs35l56_disable_sdw_interrupts(cs35l56); + + ret = cs35l56_firmware_shutdown(&cs35l56->base); +@@ -1405,6 +1393,7 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) + ARRAY_SIZE(cs35l56_controls)); + break; + case 0x63: ++ case 0x62: + ret = snd_soc_add_component_controls(component, cs35l63_controls, + ARRAY_SIZE(cs35l63_controls)); + break; +@@ -1968,11 +1957,6 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); + int ret; + +- guard(mutex)(&cs35l56_component_register_lock); +- +- if (cs35l56_shutting_down) +- return; +- + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2233,37 +2217,6 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif + +-static int cs35l56_reboot_notify(struct notifier_block *nb, +- unsigned long action, void *data) +-{ +- guard(mutex)(&cs35l56_component_register_lock); +- cs35l56_shutting_down = true; +- +- return NOTIFY_DONE; +-} +- +-static struct notifier_block cs35l56_reboot_notifier = { +- .notifier_call = cs35l56_reboot_notify, +-}; +- +-static int __init cs35l56_modinit(void) +-{ +- /* +- * Use reboot notifier to prevent race between shutdown and +- * snd_soc_register_component(). Driver shutdown() callback would +- * run too late, after device_shutdown() is already walking the +- * device list that component registration can modify. +- */ +- return register_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_init(cs35l56_modinit); +- +-static void __exit cs35l56_modexit(void) +-{ +- unregister_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_exit(cs35l56_modexit); +- + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -32,7 +32,6 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; +- struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -40,11 +39,8 @@ struct cs35l56_private { + struct sdw_slave *sdw_peripheral; + struct regmap *sdw_bus_regmap; + const char *fallback_fw_suffix; +- struct work_struct sdw_irq_work; +- bool sdw_irq_no_unmask; + bool soft_resetting; + bool sdw_attached; +- bool component_registered; + struct completion init_completion; + + int speaker_id; +@@ -67,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b + + extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; + +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56); +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56); + + int cs35l56_system_suspend(struct device *dev); + int cs35l56_system_suspend_late(struct device *dev); +@@ -78,7 +72,6 @@ int cs35l56_system_suspend_no_irq(struct device *dev); + int cs35l56_system_resume_no_irq(struct device *dev); + int cs35l56_system_resume_early(struct device *dev); + int cs35l56_system_resume(struct device *dev); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); + int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + int cs35l56_init(struct cs35l56_private *cs35l56); +diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c +--- a/sound/soc/codecs/cs40l50-codec.c ++++ b/sound/soc/codecs/cs40l50-codec.c +@@ -288,8 +288,8 @@ static int cs40l50_codec_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id cs40l50_id[] = { +- { "cs40l50-codec", }, +- {} ++ { .name = "cs40l50-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs40l50_id); + +diff --git a/sound/soc/codecs/cs42l42-sdw.c b/sound/soc/codecs/cs42l42-sdw.c +--- a/sound/soc/codecs/cs42l42-sdw.c ++++ b/sound/soc/codecs/cs42l42-sdw.c +@@ -481,10 +481,20 @@ static int cs42l42_sdw_runtime_resume(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + + /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ ret = regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ if (ret) ++ goto err_sync; ++ ++ ret = regcache_sync(cs42l42->regmap); ++ if (ret) ++ goto err_sync; + + return 0; ++ ++err_sync: ++ regcache_cache_only(cs42l42->regmap, true); ++ regcache_mark_dirty(cs42l42->regmap); ++ return ret; + } + + static int cs42l42_sdw_resume(struct device *dev) +diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c +--- a/sound/soc/codecs/cs42l42.c ++++ b/sound/soc/codecs/cs42l42.c +@@ -9,6 +9,7 @@ + * Author: Michael White + */ + ++#include + #include + #include + #include +@@ -565,7 +566,7 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l42->irq_lock); ++ guard(mutex)(&cs42l42->irq_lock); + cs42l42->jack = jk; + + if (jk) { +@@ -581,7 +582,6 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + break; + } + } +- mutex_unlock(&cs42l42->irq_lock); + + return 0; + } +@@ -1668,13 +1668,10 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + unsigned int current_button_status; + unsigned int i; + +- pm_runtime_get_sync(cs42l42->dev); +- mutex_lock(&cs42l42->irq_lock); +- if (cs42l42->suspended || !cs42l42->init_done) { +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); ++ guard(pm_runtime_active_auto)(cs42l42->dev); ++ guard(mutex)(&cs42l42->irq_lock); ++ if (cs42l42->suspended || !cs42l42->init_done) + return IRQ_NONE; +- } + + /* Read sticky registers to clear interurpt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { +@@ -1774,9 +1771,6 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); +- + return IRQ_HANDLED; + } + EXPORT_SYMBOL_NS_GPL(cs42l42_irq_thread, "SND_SOC_CS42L42_CORE"); +@@ -2163,23 +2157,23 @@ int cs42l42_suspend(struct device *dev) + * future interrupts. This ensures a safe disable if the interrupt + * is shared. + */ +- mutex_lock(&cs42l42->irq_lock); +- cs42l42->suspended = true; ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ cs42l42->suspended = true; + +- /* Save register values that will be overwritten by shutdown sequence */ +- for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { +- regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); +- save_regs[i] = (u8)reg; ++ /* Save register values that will be overwritten by shutdown sequence */ ++ for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { ++ regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); ++ save_regs[i] = (u8)reg; ++ } ++ ++ /* Shutdown codec */ ++ regmap_multi_reg_write(cs42l42->regmap, ++ cs42l42_shutdown_seq, ++ ARRAY_SIZE(cs42l42_shutdown_seq)); ++ ++ /* All interrupt sources are now disabled */ + } + +- /* Shutdown codec */ +- regmap_multi_reg_write(cs42l42->regmap, +- cs42l42_shutdown_seq, +- ARRAY_SIZE(cs42l42_shutdown_seq)); +- +- /* All interrupt sources are now disabled */ +- mutex_unlock(&cs42l42->irq_lock); +- + /* Wait for power-down complete */ + msleep(CS42L42_PDN_DONE_TIME_MS); + ret = regmap_read_poll_timeout(cs42l42->regmap, +@@ -2250,13 +2244,13 @@ void cs42l42_resume_restore(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + regcache_mark_dirty(cs42l42->regmap); + +- mutex_lock(&cs42l42->irq_lock); +- /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ ++ regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ regcache_sync(cs42l42->regmap); + +- cs42l42->suspended = false; +- mutex_unlock(&cs42l42->irq_lock); ++ cs42l42->suspended = false; ++ } + + dev_dbg(dev, "System resumed\n"); + } +diff --git a/sound/soc/codecs/cs42l43-jack.c b/sound/soc/codecs/cs42l43-jack.c +--- a/sound/soc/codecs/cs42l43-jack.c ++++ b/sound/soc/codecs/cs42l43-jack.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + + #include ++#include + #include + #include + #include +@@ -67,6 +68,21 @@ static int cs42l43_find_index(struct cs42l43_codec *priv, const char * const pro + return -EINVAL; + } + ++static void cs42l43_apply_accdet_config(struct cs42l43_codec *priv, ++ unsigned int autocontrol, ++ unsigned int pdncntl) ++{ ++ struct cs42l43 *cs42l43 = priv->core; ++ ++ regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, ++ CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | ++ CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); ++ regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, ++ CS42L43_RING_SENSE_EN_MASK, pdncntl); ++ ++ dev_dbg(priv->dev, "Successfully configured accessory detect\n"); ++} ++ + int cs42l43_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *d) + { +@@ -80,31 +96,34 @@ int cs42l43_set_jack(struct snd_soc_component *component, + + dev_dbg(priv->dev, "Configure accessory detect\n"); + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for jack config: %d\n", ret); + return ret; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + priv->jack_hp = jack; + +- if (!jack) +- goto done; ++ if (!jack) { ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); ++ return 0; ++ } + + ret = device_property_count_u32(cs42l43->dev, "cirrus,buttons-ohms"); + if (ret != -EINVAL) { + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,buttons-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + + if (ret > CS42L43_N_BUTTONS) { + ret = -EINVAL; + dev_err(priv->dev, "Property cirrus,buttons-ohms too many entries\n"); +- goto error; ++ return ret; + } + + ret = device_property_read_u32_array(cs42l43->dev, "cirrus,buttons-ohms", +@@ -112,7 +131,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,button-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + } else { + priv->buttons[0] = 70; +@@ -124,7 +143,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + ret = cs42l43_find_index(priv, "cirrus,detect-us", 50000, &priv->detect_us, + cs42l43_accdet_us, ARRAY_SIZE(cs42l43_accdet_us)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_AUTO_HSDET_TIME_SHIFT; + +@@ -134,7 +153,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_ramp_ms, cs42l43_accdet_ramp_ms, + ARRAY_SIZE(cs42l43_accdet_ramp_ms)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_HSBIAS_RAMP_SHIFT; + +@@ -142,7 +161,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_sense_ua, cs42l43_accdet_bias_sense, + ARRAY_SIZE(cs42l43_accdet_bias_sense)); + if (ret < 0) +- goto error; ++ return ret; + + if (priv->bias_sense_ua) + autocontrol |= ret << CS42L43_HSBIAS_SENSE_TRIP_SHIFT; +@@ -154,7 +173,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_debounce_ms); + if (ret < 0 && ret != -EINVAL) { + dev_err(priv->dev, "Property cirrus,tip-debounce-ms malformed: %d\n", ret); +- goto error; ++ return ret; + } + + /* This tip sense invert is set normally, as TIPSENSE_INV already inverted */ +@@ -170,7 +189,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_fall_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_FALLING_DB_TIME_SHIFT; + +@@ -178,7 +197,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_rise_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_RISING_DB_TIME_SHIFT; + +@@ -199,7 +218,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_FALLING_DB_TIME_SHIFT; + +@@ -207,7 +226,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_RISING_DB_TIME_SHIFT; + pdncntl |= CS42L43_RING_SENSE_EN_MASK; +@@ -228,23 +247,9 @@ int cs42l43_set_jack(struct snd_soc_component *component, + CS42L43_HSBIAS_RAMP_MASK | CS42L43_HSDET_MODE_MASK | + CS42L43_AUTO_HSDET_TIME_MASK, hs2); + +-done: +- ret = 0; ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); + +- regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, +- CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | +- CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); +- regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, +- CS42L43_RING_SENSE_EN_MASK, pdncntl); +- +- dev_dbg(priv->dev, "Successfully configured accessory detect\n"); +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return ret; ++ return 0; + } + + static void cs42l43_start_hs_bias(struct cs42l43_codec *priv, bool type_detect) +@@ -370,22 +375,22 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + { + struct cs42l43_codec *priv = data; + struct cs42l43 *cs42l43 = priv->core; +- irqreturn_t iret = IRQ_NONE; + unsigned int buttons = 0; + unsigned int val = 0; + int i, ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button press: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button press IRQ\n"); +- goto error; ++ return IRQ_NONE; + } + + // Wait for 2 full cycles of comb filter to ensure good reading +@@ -396,12 +401,12 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + /* Bail if jack removed, the button is irrelevant and likely invalid */ + if (!cs42l43_jack_present(priv)) { + dev_dbg(priv->dev, "Button ignored due to removal\n"); +- goto error; ++ return IRQ_NONE; + } + + if (val & CS42L43_HSBIAS_CLAMP_STS_MASK) { + dev_dbg(priv->dev, "Button ignored due to bias sense\n"); +- goto error; ++ return IRQ_NONE; + } + + val = (val & CS42L43_HSDET_DC_STS_MASK) >> CS42L43_HSDET_DC_STS_SHIFT; +@@ -424,45 +429,32 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + + snd_soc_jack_report(priv->jack_hp, buttons, CS42L43_JACK_BUTTONS); + +- iret = IRQ_HANDLED; +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + irqreturn_t cs42l43_button_release(int irq, void *data) + { + struct cs42l43_codec *priv = data; +- irqreturn_t iret = IRQ_NONE; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button release: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (priv->button_detect_running) { +- dev_dbg(priv->dev, "Button release IRQ\n"); +- +- snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); +- +- iret = IRQ_HANDLED; +- } else { ++ if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button release IRQ\n"); ++ return IRQ_NONE; + } + +- mutex_unlock(&priv->jack_lock); ++ dev_dbg(priv->dev, "Button release IRQ\n"); ++ snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); + +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + void cs42l43_bias_sense_timeout(struct work_struct *work) +@@ -472,13 +464,14 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for bias sense: %d\n", ret); + return; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (cs42l43_jack_present(priv) && priv->button_detect_running) { + dev_dbg(priv->dev, "Bias sense timeout out, restore bias\n"); +@@ -491,10 +484,6 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK, + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK); + } +- +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); + } + + static const struct reg_sequence cs42l43_3pole_patch[] = { +@@ -896,9 +885,8 @@ int cs42l43_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + ucontrol->value.integer.value[0] = priv->jack_override; +- mutex_unlock(&priv->jack_lock); + + return 0; + } +@@ -914,17 +902,13 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + if (override >= e->items) + return -EINVAL; + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (!cs42l43_jack_present(priv)) { +- mutex_unlock(&priv->jack_lock); ++ if (!cs42l43_jack_present(priv)) + return -EBUSY; +- } + +- if (override == priv->jack_override) { +- mutex_unlock(&priv->jack_lock); ++ if (override == priv->jack_override) + return 0; +- } + + priv->jack_override = override; + +@@ -984,7 +968,5 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + cs42l43_jack_override_modes[override].report); + } + +- mutex_unlock(&priv->jack_lock); +- + return 1; + } +diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c +--- a/sound/soc/codecs/cs42l43.c ++++ b/sound/soc/codecs/cs42l43.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1207,14 +1208,12 @@ static void cs42l43_spk_vu_sync(struct cs42l43_codec *priv) + { + struct cs42l43 *cs42l43 = priv->core; + +- mutex_lock(&priv->spk_vu_lock); ++ guard(mutex)(&priv->spk_vu_lock); + + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, CS42L43_AMP1_2_VU_MASK); + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, 0); +- +- mutex_unlock(&priv->spk_vu_lock); + } + + static int cs42l43_shutter_get(struct cs42l43_codec *priv, unsigned int shift) +@@ -1601,7 +1600,7 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- mutex_lock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1626,8 +1625,6 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&cs42l43->pll_lock); +- + return ret; + } + +@@ -2565,13 +2562,10 @@ static int cs42l43_set_sysclk(struct snd_soc_component *component, int clk_id, + { + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + struct cs42l43 *cs42l43 = priv->core; +- int ret; + +- mutex_lock(&cs42l43->pll_lock); +- ret = cs42l43_set_pll(priv, src, freq); +- mutex_unlock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + +- return ret; ++ return cs42l43_set_pll(priv, src, freq); + } + + static int cs42l43_component_probe(struct snd_soc_component *component) +@@ -2941,8 +2935,8 @@ static const struct dev_pm_ops cs42l43_codec_pm_ops = { + }; + + static const struct platform_device_id cs42l43_codec_id_table[] = { +- { "cs42l43-codec", }, +- {} ++ { .name = "cs42l43-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs42l43_codec_id_table); + +diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c +--- a/sound/soc/codecs/cs42l84.c ++++ b/sound/soc/codecs/cs42l84.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -281,10 +282,9 @@ static int cs42l84_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + cs42l84->jack = jk; + snd_soc_jack_report(jk, cs42l84->hs_type, SND_JACK_HEADSET); +- mutex_unlock(&cs42l84->irq_lock); + + return 0; + } +@@ -831,7 +831,7 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + u8 current_ring_state; + int i; + +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + /* Read sticky registers to clear interrupt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + regmap_read(cs42l84->regmap, irq_params_table[i].status_addr, +@@ -902,8 +902,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + break; + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +@@ -919,8 +917,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c +--- a/sound/soc/codecs/cs43130.c ++++ b/sound/soc/codecs/cs43130.c +@@ -6,6 +6,7 @@ + * + * Authors: Li Xu + */ ++#include + #include + #include + #include +@@ -818,26 +819,26 @@ static int cs43130_dsd_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; +@@ -881,26 +882,26 @@ static int cs43130_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: +@@ -988,14 +989,13 @@ static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&cs43130->clk_mutex); ++ guard(mutex)(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_dsd_mix(false, cs43130->regmap); + } +- mutex_unlock(&cs43130->clk_mutex); + + return 0; + } +diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c +--- a/sound/soc/codecs/cs4349.c ++++ b/sound/soc/codecs/cs4349.c +@@ -340,7 +340,13 @@ static int cs4349_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs4349->reset_gpio, 1); + + regcache_cache_only(cs4349->regmap, false); +- regcache_sync(cs4349->regmap); ++ ret = regcache_sync(cs4349->regmap); ++ if (ret) { ++ regcache_cache_only(cs4349->regmap, true); ++ regcache_mark_dirty(cs4349->regmap); ++ gpiod_set_value_cansleep(cs4349->reset_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs47l15.c b/sound/soc/codecs/cs47l15.c +--- a/sound/soc/codecs/cs47l15.c ++++ b/sound/soc/codecs/cs47l15.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1285,9 +1286,8 @@ static int cs47l15_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1317,9 +1317,8 @@ static void cs47l15_component_remove(struct snd_soc_component *component) + struct cs47l15 *cs47l15 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l15->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l15->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs47l35.c b/sound/soc/codecs/cs47l35.c +--- a/sound/soc/codecs/cs47l35.c ++++ b/sound/soc/codecs/cs47l35.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1566,9 +1567,8 @@ static int cs47l35_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1600,9 +1600,8 @@ static void cs47l35_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l35->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L35_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l35->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l85.c b/sound/soc/codecs/cs47l85.c +--- a/sound/soc/codecs/cs47l85.c ++++ b/sound/soc/codecs/cs47l85.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2504,9 +2505,8 @@ static int cs47l85_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2537,9 +2537,8 @@ static void cs47l85_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l85->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L85_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l85->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l90.c b/sound/soc/codecs/cs47l90.c +--- a/sound/soc/codecs/cs47l90.c ++++ b/sound/soc/codecs/cs47l90.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2423,9 +2424,8 @@ static int cs47l90_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2456,9 +2456,8 @@ static void cs47l90_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l90->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L90_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l90->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l92.c b/sound/soc/codecs/cs47l92.c +--- a/sound/soc/codecs/cs47l92.c ++++ b/sound/soc/codecs/cs47l92.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1892,9 +1893,8 @@ static int cs47l92_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1922,9 +1922,8 @@ static void cs47l92_component_remove(struct snd_soc_component *component) + struct cs47l92 *cs47l92 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l92->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l92->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c +--- a/sound/soc/codecs/cs48l32.c ++++ b/sound/soc/codecs/cs48l32.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,13 @@ static int cs48l32_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v + int ret; + + /* Prevent any mixer mux changes while we do this */ +- mutex_lock(&cs48l32_codec->rate_lock); ++ guard(mutex)(&cs48l32_codec->rate_lock); + + /* The write must be guarded by a number of SYSCLK cycles */ + cs48l32_spin_sysclk(cs48l32_codec); + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + cs48l32_spin_sysclk(cs48l32_codec); + +- mutex_unlock(&cs48l32_codec->rate_lock); +- + return ret; + } + +@@ -2242,7 +2241,6 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + struct cs48l32_dai_priv *dai_priv = &cs48l32_codec->dai[dai->id - 1]; + unsigned int base = dai->driver->base; + unsigned int current_asp_rate, target_asp_rate; +- bool change_rate_domain = false; + int ret; + + if (clk_id == dai_priv->clk) +@@ -2284,19 +2282,15 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + + if ((current_asp_rate & CS48L32_ASP_RATE_MASK) != + (target_asp_rate & CS48L32_ASP_RATE_MASK)) { +- change_rate_domain = true; +- +- mutex_lock(&cs48l32_codec->rate_lock); + /* Guard the rate change with SYSCLK cycles */ +- cs48l32_spin_sysclk(cs48l32_codec); +- } +- +- snd_soc_component_update_bits(component, base + CS48L32_ASP_CONTROL1, +- CS48L32_ASP_RATE_MASK, target_asp_rate); +- +- if (change_rate_domain) { +- cs48l32_spin_sysclk(cs48l32_codec); +- mutex_unlock(&cs48l32_codec->rate_lock); ++ scoped_guard(mutex, &cs48l32_codec->rate_lock) { ++ cs48l32_spin_sysclk(cs48l32_codec); ++ snd_soc_component_update_bits(component, ++ base + CS48L32_ASP_CONTROL1, ++ CS48L32_ASP_RATE_MASK, ++ target_asp_rate); ++ cs48l32_spin_sysclk(cs48l32_codec); ++ } + } + } + +diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c +--- a/sound/soc/codecs/cx2072x.c ++++ b/sound/soc/codecs/cx2072x.c +@@ -9,6 +9,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -1408,7 +1409,7 @@ static int cx2072x_jack_status_check(void *data) + unsigned int type = 0; + int state = 0; + +- mutex_lock(&cx2072x->lock); ++ guard(mutex)(&cx2072x->lock); + + regmap_read(cx2072x->regmap, CX2072X_PORTA_PIN_SENSE, &jack); + jack = jack >> 24; +@@ -1434,8 +1435,6 @@ static int cx2072x_jack_status_check(void *data) + /* clear interrupt */ + regmap_write(cx2072x->regmap, CX2072X_UM_INTERRUPT_CRTL_E, 0x12 << 24); + +- mutex_unlock(&cx2072x->lock); +- + dev_dbg(codec->dev, "CX2072X_HSDETECT type=0x%X,Jack state = %x\n", + type, state); + return state; +diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c +--- a/sound/soc/codecs/da7213.c ++++ b/sound/soc/codecs/da7213.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -216,13 +217,10 @@ static int da7213_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -230,13 +228,10 @@ static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -244,13 +239,10 @@ static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -258,13 +250,10 @@ static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -465,9 +454,8 @@ static int da7213_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7213->ctrl_lock); ++ scoped_guard(mutex, &da7213->ctrl_lock) ++ ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -499,12 +487,11 @@ static int da7213_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + ret = regmap_raw_read(da7213->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7213->regmap, reg, + &val_new, sizeof(val_new)); +- mutex_unlock(&da7213->ctrl_lock); + + if (ret < 0) + return ret; +diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c +--- a/sound/soc/codecs/da7219.c ++++ b/sound/soc/codecs/da7219.c +@@ -8,6 +8,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -256,13 +257,10 @@ static int da7219_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -270,13 +268,10 @@ static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -284,13 +279,10 @@ static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -298,13 +290,10 @@ static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -422,9 +411,8 @@ static int da7219_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7219->ctrl_lock); ++ scoped_guard(mutex, &da7219->ctrl_lock) ++ ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -456,12 +444,11 @@ static int da7219_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + ret = regmap_raw_read(da7219->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7219->regmap, reg, +- &val_new, sizeof(val_new)); +- mutex_unlock(&da7219->ctrl_lock); ++ &val_new, sizeof(val_new)); + + if (ret < 0) + return ret; +@@ -1167,15 +1154,12 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- if ((da7219->clk_src == clk_id) && (da7219->mclk_rate == freq)) { +- mutex_unlock(&da7219->pll_lock); ++ if (da7219->clk_src == clk_id && da7219->mclk_rate == freq) + return 0; +- } + +- if ((freq < 2000000) || (freq > 54000000)) { +- mutex_unlock(&da7219->pll_lock); ++ if (freq < 2000000 || freq > 54000000) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; +@@ -1193,7 +1177,6 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); +- mutex_unlock(&da7219->pll_lock); + return -EINVAL; + } + +@@ -1203,17 +1186,13 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + freq = clk_round_rate(da7219->mclk, freq); + ret = clk_set_rate(da7219->mclk, freq); + if (ret) { +- dev_err(codec_dai->dev, "Failed to set clock rate %d\n", +- freq); +- mutex_unlock(&da7219->pll_lock); ++ dev_err(codec_dai->dev, "Failed to set clock rate %d\n", freq); + return ret; + } + } + + da7219->mclk_rate = freq; + +- mutex_unlock(&da7219->pll_lock); +- + return 0; + } + +@@ -1296,13 +1275,10 @@ static int da7219_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + { + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->pll_lock); +- ret = da7219_set_pll(component, source, fout); +- mutex_unlock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- return ret; ++ return da7219_set_pll(component, source, fout); + } + + static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c +--- a/sound/soc/codecs/es8316.c ++++ b/sound/soc/codecs/es8316.c +@@ -9,11 +9,13 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +32,13 @@ static const unsigned int supported_mclk_lrck_ratios[] = { + 256, 384, 400, 500, 512, 768, 1024 + }; + ++static const char * const es8316_supply_names[] = { ++ "avdd", ++ "cpvdd", ++ "dvdd", ++ "pvdd", ++}; ++ + struct es8316_priv { + struct mutex lock; + struct clk *mclk; +@@ -620,15 +629,15 @@ static irqreturn_t es8316_irq(int irq, void *data) + struct snd_soc_component *comp = es8316->component; + unsigned int flags; + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags); + if (flags == 0x00) +- goto out; /* Powered-down / reset */ ++ return IRQ_HANDLED; /* Powered-down / reset */ + + /* Catch spurious IRQ before set_jack is called */ + if (!es8316->jack) +- goto out; ++ return IRQ_HANDLED; + + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; +@@ -681,8 +690,6 @@ static irqreturn_t es8316_irq(int irq, void *data) + } + } + +-out: +- mutex_unlock(&es8316->lock); + return IRQ_HANDLED; + } + +@@ -699,18 +706,16 @@ static void es8316_enable_jack_detect(struct snd_soc_component *component, + es8316->jd_inverted = device_property_read_bool(component->dev, + "everest,jack-detect-inverted"); + +- mutex_lock(&es8316->lock); ++ scoped_guard(mutex, &es8316->lock) { ++ es8316->jack = jack; + +- es8316->jack = jack; ++ if (es8316->jack->status & SND_JACK_MICROPHONE) ++ es8316_enable_micbias_for_mic_gnd_short_detect(component); + +- if (es8316->jack->status & SND_JACK_MICROPHONE) +- es8316_enable_micbias_for_mic_gnd_short_detect(component); +- +- snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, +- ES8316_GPIO_ENABLE_INTERRUPT, +- ES8316_GPIO_ENABLE_INTERRUPT); +- +- mutex_unlock(&es8316->lock); ++ snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, ++ ES8316_GPIO_ENABLE_INTERRUPT, ++ ES8316_GPIO_ENABLE_INTERRUPT); ++ } + + /* Enable irq and sync initial jack state */ + enable_irq(es8316->irq); +@@ -726,7 +731,7 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + + disable_irq(es8316->irq); + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, + ES8316_GPIO_ENABLE_INTERRUPT, 0); +@@ -737,8 +742,6 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + } + + es8316->jack = NULL; +- +- mutex_unlock(&es8316->lock); + } + + static int es8316_set_jack(struct snd_soc_component *component, +@@ -871,6 +874,11 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) + + i2c_set_clientdata(i2c_client, es8316); + ++ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), ++ es8316_supply_names); ++ if (ret) ++ return dev_err_probe(dev, ret, "unable to enable supplies\n"); ++ + es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap); + if (IS_ERR(es8316->regmap)) + return PTR_ERR(es8316->regmap); +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -6,6 +6,7 @@ + // Authors: David Yang + // + ++#include + #include + #include + #include +@@ -790,7 +791,7 @@ static void es8326_jack_button_handler(struct work_struct *work) + if (!(es8326->jack->status & SND_JACK_HEADSET)) /* Jack unplugged */ + return; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + switch (iface) { + case 0x93: +@@ -845,7 +846,6 @@ static void es8326_jack_button_handler(struct work_struct *work) + } + es8326_disable_micbias(es8326->component); + } +- mutex_unlock(&es8326->lock); + } + + static void es8326_jack_detect_handler(struct work_struct *work) +@@ -855,7 +855,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + struct snd_soc_component *comp = es8326->component; + unsigned int iface; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + dev_dbg(comp->dev, "gpio flag %#04x", iface); + +@@ -873,7 +873,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + regmap_update_bits(es8326->regmap, ES8326_HPDET_TYPE, + ES8326_HP_DET_JACK_POL, (es8326->jd_inverted ? + ~es8326->jack_pol : es8326->jack_pol)); +- goto exit; ++ return; + } + + if ((iface & ES8326_HPINSERT_FLAG) == 0) { +@@ -930,7 +930,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + queue_delayed_work(system_dfl_wq, &es8326->jack_detect_work, + msecs_to_jiffies(400)); + es8326->hp = 1; +- goto exit; ++ return; + } + if (es8326->jack->status & SND_JACK_HEADSET) { + /* detect button */ +@@ -939,7 +939,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + (ES8326_INT_SRC_PIN9 | ES8326_INT_SRC_BUTTON)); + es8326_enable_micbias(es8326->component); + queue_delayed_work(system_dfl_wq, &es8326->button_press_work, 10); +- goto exit; ++ return; + } + if ((iface & ES8326_HPBUTTON_FLAG) == 0x01) { + dev_dbg(comp->dev, "Headphone detected\n"); +@@ -958,8 +958,6 @@ static void es8326_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + } +-exit: +- mutex_unlock(&es8326->lock); + } + + static irqreturn_t es8326_irq(int irq, void *dev_id) +@@ -1200,13 +1198,12 @@ static void es8326_enable_jack_detect(struct snd_soc_component *component, + { + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&es8326->lock); +- if (es8326->jd_inverted) +- snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, +- ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); +- es8326->jack = jack; +- +- mutex_unlock(&es8326->lock); ++ scoped_guard(mutex, &es8326->lock) { ++ if (es8326->jd_inverted) ++ snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, ++ ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); ++ es8326->jack = jack; ++ } + es8326_irq(es8326->irq, es8326); + } + +@@ -1219,13 +1216,12 @@ static void es8326_disable_jack_detect(struct snd_soc_component *component) + return; /* Already disabled (or never enabled) */ + cancel_delayed_work_sync(&es8326->jack_detect_work); + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + if (es8326->jack->status & SND_JACK_MICROPHONE) { + es8326_disable_micbias(component); + snd_soc_jack_report(es8326->jack, 0, SND_JACK_HEADSET); + } + es8326->jack = NULL; +- mutex_unlock(&es8326->lock); + } + + static int es8326_set_jack(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c +--- a/sound/soc/codecs/es8328.c ++++ b/sound/soc/codecs/es8328.c +@@ -405,6 +405,11 @@ static const struct snd_soc_dapm_route es8328_dapm_routes[] = { + + { "Mic Bias", NULL, "Mic Bias Gen" }, + ++ { "LINPUT1", NULL, "Mic Bias" }, ++ { "RINPUT1", NULL, "Mic Bias" }, ++ { "LINPUT2", NULL, "Mic Bias" }, ++ { "RINPUT2", NULL, "Mic Bias" }, ++ + { "Left Mixer", NULL, "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -36,6 +36,10 @@ struct es8389_private { + unsigned int sysclk; + int mastermode; + ++ u8 hpfl; ++ u8 hpfr; ++ u32 hpf_freq; ++ u32 capture_rate; + u8 mclk_src; + u8 vddd; + int version; +@@ -50,10 +54,29 @@ static const char * const es8389_core_supplies[] = { + static bool es8389_volatile_register(struct device *dev, + unsigned int reg) + { +- if ((reg <= 0xff)) +- return true; +- else ++ switch (reg) { ++ case ES8389_ADCL_VOL: ++ case ES8389_ADCR_VOL: ++ case ES8389_MIC1_GAIN: ++ case ES8389_MIC2_GAIN: ++ case ES8389_DACL_VOL: ++ case ES8389_DACR_VOL: ++ case ES8389_ALC_ON: ++ case ES8389_ALC_CTL: ++ case ES8389_ALC_TARGET: ++ case ES8389_ALC_GAIN: ++ case ES8389_ADC_MUTE: ++ case ES8389_OSR_VOL: ++ case ES8389_DAC_INV: ++ case ES8389_MIX_VOL: ++ case ES8389_DAC_MIX: ++ case ES8389_ADC_RESET: ++ case ES8389_ADC_MODE: ++ case ES8389_DMIC_EN: + return false; ++ default: ++ return true; ++ } + } + + static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9550, 50, 0); +@@ -63,6 +86,92 @@ static const DECLARE_TLV_DB_SCALE(mix_vol_tlv, -9500, 100, 0); + static const DECLARE_TLV_DB_SCALE(alc_target_tlv, -3200, 200, 0); + static const DECLARE_TLV_DB_SCALE(alc_max_level, -3200, 200, 0); + ++static const u32 hpf_table[10][10] = { ++ {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, ++ {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, ++ {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, ++ {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, ++ {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, ++ {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, ++ {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, ++ {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, ++ {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, ++ {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} ++}; ++ ++static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2, u32 *out) ++{ ++ int best_row = -1, best_col = -1; ++ u32 min_diff = U32_MAX; ++ u32 f, diff; ++ int i, j; ++ ++ if (target_hz > 1020) ++ return false; ++ ++ for (i = 0; i < 10; i++) { ++ for (j = i; j < 10; j++) { ++ f = hpf_table[i][j]; ++ ++ diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); ++ if (diff < min_diff) { ++ min_diff = diff; ++ best_row = i; ++ best_col = j; ++ *out = f; ++ } ++ } ++ } ++ ++ *hpf1 = best_col + ES8389_HPF_OFFSET; ++ *hpf2 = best_row + ES8389_HPF_OFFSET; ++ ++ return true; ++} ++ ++static int es8389_hpf_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = es8389->hpf_freq; ++ return 0; ++} ++ ++static int es8389_hpf_set(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ u32 freq; ++ bool hpf; ++ ++ if (es8389->hpf_freq == ucontrol->value.integer.value[0]) ++ return 0; ++ ++ if (es8389->capture_rate) { ++ freq = (ucontrol->value.integer.value[0] * 48000) / es8389->capture_rate; ++ ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) ++ return -EBUSY; ++ ++ if (es8389->hpf_freq != ucontrol->value.integer.value[0]) ++ dev_dbg(component->dev, "At the %u Hz sampling rate, %ld Hz could not be obtained." ++ "the frequency has been set to the closest value, %u Hz\n", ++ es8389->capture_rate, ucontrol->value.integer.value[0], es8389->hpf_freq); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); ++ } else { ++ es8389->hpf_freq = ucontrol->value.integer.value[0]; ++ dev_dbg(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); ++ } ++ ++ return 1; ++} ++ + static int es8389_dmic_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -143,6 +252,16 @@ static const struct soc_enum alc_ramprate = + static const struct soc_enum alc_winsize = + SOC_ENUM_SINGLE(ES8389_ALC_CTL, 0, 16, winsize); + ++static const char *const es8389_adcl_mux_txt[] = { ++ "Normal", ++ "ADC2 channel to ADC1 channel", ++}; ++ ++static const char *const es8389_adcr_mux_txt[] = { ++ "Normal", ++ "ADC1 channel to ADC2 channel", ++}; ++ + static const char *const es8389_outl_mux_txt[] = { + "Normal", + "DAC2 channel to DAC1 channel", +@@ -170,6 +289,20 @@ static const unsigned int es8389_pga_values[] = { + 1, 5, 6 + }; + ++static const struct soc_enum es8389_adcl_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 5, ++ ARRAY_SIZE(es8389_adcl_mux_txt), es8389_adcl_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcl_mux_controls = ++ SOC_DAPM_ENUM("INPUTL MUX", es8389_adcl_mux_enum); ++ ++static const struct soc_enum es8389_adcr_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 4, ++ ARRAY_SIZE(es8389_adcr_mux_txt), es8389_adcr_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcr_mux_controls = ++ SOC_DAPM_ENUM("INPUTR MUX", es8389_adcr_mux_enum); ++ + static const struct soc_enum es8389_outl_mux_enum = + SOC_ENUM_SINGLE(ES8389_DAC_MIX, 5, + ARRAY_SIZE(es8389_outl_mux_txt), es8389_outl_mux_txt); +@@ -240,6 +373,8 @@ static const struct snd_kcontrol_new es8389_snd_controls[] = { + SOC_DOUBLE("ADC OSR Volume ON Switch", ES8389_ADC_MUTE, 6, 7, 1, 0), + SOC_SINGLE_TLV("ADC OSR Volume", ES8389_OSR_VOL, 0, 0xFF, 0, adc_vol_tlv), + SOC_DOUBLE("ADC OUTPUT Invert Switch", ES8389_ADC_HPF2, 5, 6, 1, 0), ++ SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, ++ es8389_hpf_get, es8389_hpf_set), + + SOC_SINGLE_TLV("DACL Playback Volume", ES8389_DACL_VOL, 0, 0xFF, 0, dac_vol_tlv), + SOC_SINGLE_TLV("DACR Playback Volume", ES8389_DACR_VOL, 0, 0xFF, 0, dac_vol_tlv), +@@ -298,6 +433,8 @@ static const struct snd_soc_dapm_widget es8389_dapm_widgets[] = { + &es8389_adc_mixer_controls[0], + ARRAY_SIZE(es8389_adc_mixer_controls)), + SND_SOC_DAPM_MUX("ADC MUX", SND_SOC_NOPM, 0, 0, &es8389_dmic_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_adcl_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_adcr_mux_controls), + + SND_SOC_DAPM_MUX("OUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_outl_mux_controls), + SND_SOC_DAPM_MUX("OUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_outr_mux_controls), +@@ -311,10 +448,15 @@ static const struct snd_soc_dapm_route es8389_dapm_routes[] = { + {"ADCL", NULL, "PGAL"}, + {"ADCR", NULL, "PGAR"}, + ++ {"INPUTL MUX", "Normal", "ADCL"}, ++ {"INPUTL MUX", "ADC2 channel to ADC1 channel", "ADCR"}, ++ {"INPUTR MUX", "Normal", "ADCR"}, ++ {"INPUTR MUX", "ADC1 channel to ADC2 channel", "ADCL"}, ++ + {"ADC Mixer", "DACL ADCL Mixer", "DACL"}, + {"ADC Mixer", "DACR ADCR Mixer", "DACR"}, +- {"ADC Mixer", NULL, "ADCL"}, +- {"ADC Mixer", NULL, "ADCR"}, ++ {"ADC Mixer", NULL, "INPUTL MUX"}, ++ {"ADC Mixer", NULL, "INPUTR MUX"}, + + {"ADC MUX", "AMIC", "ADC Mixer"}, + {"ADC MUX", "DMIC", "DMIC"}, +@@ -415,52 +557,54 @@ static const struct _coeff_div coeff_div[] = { + {36, 576000, 16000, 0x00, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {48, 768000, 16000, 0x02, 0x57, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {50, 800000, 16000, 0x00, 0x7E, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {96, 1536000, 16000, 0x02, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {128, 2048000, 16000, 0x00, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {144, 2304000, 16000, 0x00, 0x51, 0x00, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {150, 2400000, 16000, 0x02, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {192, 3072000, 16000, 0x02, 0x65, 0x25, 0xE0, 0x00, 0xE1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {300, 4800000, 16000, 0x02, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {384, 6144000, 16000, 0x02, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {750, 12000000, 16000, 0x0E, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1152, 18432000, 16000, 0x08, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1200, 19200000, 16000, 0x0B, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1500, 24000000, 16000, 0x0E, 0x26, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1625, 26000000, 16000, 0x40, 0x6E, 0x05, 0xC8, 0x01, 0xC2, 0x90, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {800, 19200000, 24000, 0x07, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x1A, 0x49, 0x14, 2, 2}, + {375, 12000000, 32000, 0x0E, 0x2E, 0x05, 0xC8, 0x00, 0xC2, 0x80, 0x40, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 0}, +- {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, +- {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, ++ {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 2822400, 44100, 0x00, 0x51, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {48, 2304000, 48000, 0x02, 0x55, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {50, 2400000, 48000, 0x00, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 3072000, 48000, 0x00, 0x51, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {125, 6000000, 48000, 0x04, 0x6E, 0x05, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {250, 12000000, 48000, 0x04, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {800, 38400000, 48000, 0x18, 0x45, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {128, 11289600, 88200, 0x00, 0x50, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x32, 0x89, 0x25, 2, 2}, +- {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, ++ {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {96, 9216000, 96000, 0x02, 0x43, 0x00, 0xC0, 0x10, 0xC0, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {256, 24576000, 96000, 0x00, 0x40, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {128, 24576000, 192000, 0x00, 0x50, 0x00, 0xC0, 0x18, 0xC1, 0x81, 0xC0, 0x00, 0x8F, 0x7F, 0xBF, 0xC0, 0x3F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 2, 2}, ++ {64, 12288000, 192000, 0x00, 0x41, 0x00, 0xC0, 0x18, 0xC1, 0x80, 0x00, 0x00, 0x8F, 0x7F, 0xEF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 1, 0}, + }; + + static inline int get_coeff(u8 vddd, u8 dmic, int mclk, int rate) +@@ -564,6 +708,8 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + int coeff, ret; + u8 dmic_enable, state = 0; + unsigned int regv; ++ u32 freq; ++ bool hpf; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: +@@ -590,7 +736,7 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + + if (es8389->mclk_src == ES8389_SCLK_PIN) { + regmap_update_bits(es8389->regmap, ES8389_MASTER_CLK, +- ES8389_MCLK_SOURCE, es8389->mclk_src); ++ ES8389_MCLK_MASK, ES8389_MCLK_FROM_SCLK); + es8389->sysclk = params_channels(params) * params_width(params) * params_rate(params); + } + +@@ -641,6 +787,28 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + dev_warn(component->dev, "Clock coefficients do not match"); + } + ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ++ es8389->capture_rate = params_rate(params); ++ freq = (es8389->hpf_freq * 48000) / params_rate(params); ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) { ++ dev_err(component->dev, "The HPF frequency is invalid\n"); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ++ es8389->capture_rate = 0; ++ + return 0; + } + +@@ -721,8 +889,8 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8389->regmap, ES8389_DAC_FORMAT_MUTE, + 0x03, 0x00); + } else { +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x0a); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x0a); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); + regmap_update_bits(es8389->regmap, ES8389_ADC_FORMAT_MUTE, + 0x03, 0x00); + } +@@ -738,6 +906,7 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + + static const struct snd_soc_dai_ops es8389_ops = { + .hw_params = es8389_pcm_hw_params, ++ .hw_free = es8389_pcm_hw_free, + .set_fmt = es8389_set_dai_fmt, + .set_sysclk = es8389_set_dai_sysclk, + .set_tdm_slot = es8389_set_tdm_slot, +@@ -771,7 +940,7 @@ static void es8389_init(struct snd_soc_component *component) + + regmap_read(es8389->regmap, ES8389_MAX_REGISTER, ®); + es8389->version = reg; +- regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x00); ++ regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x56); + regmap_write(es8389->regmap, ES8389_RESET, 0x7E); + regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x38); + regmap_write(es8389->regmap, ES8389_ADC_HPF1, 0x64); +@@ -823,7 +992,7 @@ static void es8389_init(struct snd_soc_component *component) + regmap_write(es8389->regmap, ES8389_SCLK_DIV, 0x04); + regmap_write(es8389->regmap, ES8389_LRCK_DIV1, 0x01); + regmap_write(es8389->regmap, ES8389_LRCK_DIV2, 0x00); +- regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x00); ++ regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x10); + regmap_write(es8389->regmap, ES8389_ADC_OSR, 0x1F); + regmap_write(es8389->regmap, ES8389_ADC_DSP, 0x7F); + regmap_write(es8389->regmap, ES8389_ADC_MUTE, 0xC0); +@@ -861,13 +1030,13 @@ static int es8389_resume(struct snd_soc_component *component) + regcache_cache_only(es8389->regmap, false); + regcache_cache_bypass(es8389->regmap, true); + regmap_read(es8389->regmap, ES8389_RESET, ®v); +- regcache_cache_bypass(es8389->regmap, false); + + if (regv == 0xff) + es8389_init(component); + else + es8389_set_bias_level(component, SND_SOC_BIAS_ON); + ++ regcache_cache_bypass(es8389->regmap, false); + regcache_sync(es8389->regmap); + + return 0; +@@ -913,6 +1082,7 @@ static int es8389_probe(struct snd_soc_component *component) + return ret; + } + ++ es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); + es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + +diff --git a/sound/soc/codecs/es8389.h b/sound/soc/codecs/es8389.h +--- a/sound/soc/codecs/es8389.h ++++ b/sound/soc/codecs/es8389.h +@@ -106,6 +106,9 @@ + #define ES8389_MIC_SEL_MASK (7 << 4) + #define ES8389_MIC_DEFAULT (1 << 4) + ++#define ES8389_HPF_DEFAULT 16 ++#define ES8389_HPF_OFFSET 4 ++ + #define ES8389_MASTER_MODE_EN (1 << 0) + + #define ES8389_TDM_OFF (0 << 0) +@@ -116,9 +119,11 @@ + #define ES8389_TDM_SLOT (0x70 << 0) + #define ES8389_TDM_SHIFT 4 + +-#define ES8389_MCLK_SOURCE (1 << 6) +-#define ES8389_MCLK_PIN (1 << 6) +-#define ES8389_SCLK_PIN (0 << 6) ++#define ES8389_MCLK_MASK (3 << 6) ++#define ES8389_MCLK_FROM_SCLK (1 << 6) ++#define ES8389_MCLK_SOURCE ES8389_MCLK_PIN ++#define ES8389_MCLK_PIN 0 ++#define ES8389_SCLK_PIN 1 + + /* ES8389_FMT */ + #define ES8389_S24_LE (0 << 5) +diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c +--- a/sound/soc/codecs/es9356.c ++++ b/sound/soc/codecs/es9356.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -19,8 +20,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include +@@ -500,16 +499,19 @@ static int es9356_power_state(struct snd_soc_dai *dai, unsigned char ps, unsigne + } + + /* power state changes are not independent across functions */ +- mutex_lock(&es9356->pde_lock); +- ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps0:ps3); +- if (ret) { +- regmap_write(es9356->regmap, +- SDW_SDCA_CTL(func, pde_entity, ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ps?ps3:ps0); +- es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps3:ps0); +- } else +- dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ps?ps0:ps3); +- +- mutex_unlock(&es9356->pde_lock); ++ scoped_guard(mutex, &es9356->pde_lock) { ++ ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps0 : ps3); ++ if (ret) { ++ regmap_write(es9356->regmap, ++ SDW_SDCA_CTL(func, pde_entity, ++ ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ++ ps ? ps3 : ps0); ++ es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps3 : ps0); ++ } else { ++ dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ++ ps ? ps0 : ps3); ++ } ++ } + + if (rate) + regmap_write(es9356->regmap, +@@ -671,7 +673,7 @@ static int es9356_sdca_mbq_size(struct device *dev, unsigned int reg) + } + } + +-static struct regmap_sdw_mbq_cfg es9356_mbq_config = { ++static const struct regmap_sdw_mbq_cfg es9356_mbq_config = { + .mbq_size = es9356_sdca_mbq_size, + }; + +@@ -1091,9 +1093,8 @@ static int es9356_sdca_dev_system_suspend(struct device *dev) + { + struct es9356_sdw_priv *es9356 = dev_get_drvdata(dev); + +- mutex_lock(&es9356->disable_irq_lock); +- es9356->disable_irq = true; +- mutex_unlock(&es9356->disable_irq_lock); ++ scoped_guard(mutex, &es9356->disable_irq_lock) ++ es9356->disable_irq = true; + + return es9356_sdca_dev_suspend(dev); + } +diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c +--- a/sound/soc/codecs/fs-amp-lib.c ++++ b/sound/soc/codecs/fs-amp-lib.c +@@ -221,7 +221,7 @@ static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) + + int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + { +- const struct firmware *cont; ++ const struct firmware *cont __free(firmware) = NULL; + struct fs_fwm_header *hdr; + int ret; + +@@ -237,7 +237,6 @@ int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); +- release_firmware(cont); + if (!hdr) + return -ENOMEM; + +diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c +--- a/sound/soc/codecs/fs210x.c ++++ b/sound/soc/codecs/fs210x.c +@@ -4,6 +4,7 @@ + // + // Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + ++#include + #include + #include + #include +@@ -771,9 +772,8 @@ static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -EOPNOTSUPP; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_set_hw_params(fs210x); +- mutex_unlock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) ++ ret = fs210x_set_hw_params(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret); + +@@ -790,15 +790,11 @@ static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream) + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); +@@ -817,15 +813,11 @@ static int fs210x_dai_trigger(struct snd_pcm_substream *substream, + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: +@@ -853,13 +845,11 @@ static void fs210x_start_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, start_work.work); + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); +- +- mutex_unlock(&fs210x->lock); + } + + static void fs210x_fault_check_work(struct work_struct *work) +@@ -870,15 +860,13 @@ static void fs210x_fault_check_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, fault_check_work.work); + +- mutex_lock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) ++ return; + +- if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return; ++ ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + } + +- ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); +- mutex_unlock(&fs210x->lock); + if (ret) + return; + +@@ -991,7 +979,7 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + if (fs210x->scene_id < 1) + return -EINVAL; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + /* + * FS210x has scene(s) as below: + * init scene: id = 0 +@@ -1000,7 +988,6 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + */ + index = fs210x->scene_id - 1; + ucontrol->value.integer.value[0] = index; +- mutex_unlock(&fs210x->lock); + + return 0; + } +@@ -1019,7 +1006,7 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + /* + * FS210x has scene(s) as below: +@@ -1029,17 +1016,14 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + */ + scene_id = ucontrol->value.integer.value[0] + 1; + scene_count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */ +- if (scene_id < 1 || scene_id > scene_count) { +- mutex_unlock(&fs210x->lock); ++ if (scene_id < 1 || scene_id > scene_count) + return -ERANGE; +- } + + if (scene_id != fs210x->scene_id) + is_changed = true; + + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; +- mutex_unlock(&fs210x->lock); + return is_changed; + } + +@@ -1047,8 +1031,6 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + +- mutex_unlock(&fs210x->lock); +- + if (!ret && is_changed) + return 1; + +@@ -1062,12 +1044,10 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + struct fs210x_priv *fs210x = snd_soc_component_get_drvdata(cmpnt); + int ret = 0; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- if (fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); ++ if (fs210x->is_suspended) + return 0; +- } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1088,8 +1068,6 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&fs210x->lock); +- + return ret; + } + +@@ -1220,11 +1198,9 @@ static int fs210x_probe(struct snd_soc_component *cmpnt) + if (ret) + return ret; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_init_chip(fs210x); +- mutex_unlock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- return ret; ++ return fs210x_init_chip(fs210x); + } + + static void fs210x_remove(struct snd_soc_component *cmpnt) +@@ -1251,15 +1227,15 @@ static int fs210x_suspend(struct snd_soc_component *cmpnt) + + regcache_cache_only(fs210x->regmap, true); + +- mutex_lock(&fs210x->lock); +- fs210x->cur_scene = NULL; +- fs210x->is_inited = false; +- fs210x->is_playing = false; +- fs210x->is_suspended = true; ++ scoped_guard(mutex, &fs210x->lock) { ++ fs210x->cur_scene = NULL; ++ fs210x->is_inited = false; ++ fs210x->is_playing = false; ++ fs210x->is_suspended = true; + +- gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ +- fsleep(30000); /* >= 30ms */ +- mutex_unlock(&fs210x->lock); ++ gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ ++ fsleep(30000); /* >= 30ms */ ++ } + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); +@@ -1288,14 +1264,11 @@ static int fs210x_resume(struct snd_soc_component *cmpnt) + return ret; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + fs210x->is_suspended = false; +- ret = fs210x_init_chip(fs210x); + +- mutex_unlock(&fs210x->lock); +- +- return ret; ++ return fs210x_init_chip(fs210x); + } + #else + #define fs210x_suspend NULL +diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c +--- a/sound/soc/codecs/hdac_hda.c ++++ b/sound/soc/codecs/hdac_hda.c +@@ -437,7 +437,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (loadable_patch[hda_pvt->dev_index] && *loadable_patch[hda_pvt->dev_index]) { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&hdev->dev, "Applying patch firmware '%s'\n", + loadable_patch[hda_pvt->dev_index]); +@@ -451,7 +451,6 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + dev_err(&hdev->dev, "%s: failed to load hda patch %d\n", __func__, ret); + goto error_no_pm; + } +- release_firmware(fw); + } + } + #endif +diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c +--- a/sound/soc/codecs/hdac_hdmi.c ++++ b/sound/soc/codecs/hdac_hdmi.c +@@ -10,6 +10,7 @@ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + ++#include + #include + #include + #include +@@ -537,10 +538,11 @@ static struct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt( + continue; + + list_for_each_entry(port, &pcm->port_list, head) { +- mutex_lock(&pcm->lock); +- ret = hdac_hdmi_query_port_connlist(hdev, +- port->pin, port); +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ ret = hdac_hdmi_query_port_connlist(hdev, ++ port->pin, ++ port); ++ } + if (ret < 0) + continue; + +@@ -640,11 +642,11 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream, + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + + if (pcm) { +- mutex_lock(&pcm->lock); +- pcm->chmap_set = false; +- memset(pcm->chmap, 0, sizeof(pcm->chmap)); +- pcm->channels = 0; +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ pcm->chmap_set = false; ++ memset(pcm->chmap, 0, sizeof(pcm->chmap)); ++ pcm->channels = 0; ++ } + } + + if (dai_map->port) +@@ -922,7 +924,7 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + if (port == NULL) + return -EINVAL; + +- mutex_lock(&hdmi->pin_mutex); ++ guard(mutex)(&hdmi->pin_mutex); + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (list_empty(&pcm->port_list)) + continue; +@@ -945,12 +947,10 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + list_add_tail(&port->head, &pcm->port_list); + if (port->eld.monitor_present && port->eld.eld_valid) { + hdac_hdmi_jack_report_sync(pcm, port, true); +- mutex_unlock(&hdmi->pin_mutex); + return ret; + } + } + } +- mutex_unlock(&hdmi->pin_mutex); + + return ret; + } +@@ -1274,67 +1274,65 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, + * In case of non MST pin, get_eld info API expectes port + * to be -1. + */ +- mutex_lock(&hdmi->pin_mutex); +- port->eld.monitor_present = false; ++ scoped_guard(mutex, &hdmi->pin_mutex) { ++ port->eld.monitor_present = false; + +- if (pin->mst_capable) +- port_id = port->id; ++ if (pin->mst_capable) ++ port_id = port->id; + +- size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, +- &port->eld.monitor_present, +- port->eld.eld_buffer, +- ELD_MAX_SIZE); ++ size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, ++ &port->eld.monitor_present, ++ port->eld.eld_buffer, ++ ELD_MAX_SIZE); + +- if (size > 0) { +- size = min(size, ELD_MAX_SIZE); +- if (hdac_hdmi_parse_eld(hdev, port) < 0) +- size = -EINVAL; +- } +- +- eld_valid = port->eld.eld_valid; +- +- if (size > 0) { +- port->eld.eld_valid = true; +- port->eld.eld_size = size; +- } else { +- port->eld.eld_valid = false; +- port->eld.eld_size = 0; +- } +- +- eld_changed = (eld_valid != port->eld.eld_valid); +- +- pcm = hdac_hdmi_get_pcm(hdev, port); +- +- if (!port->eld.monitor_present || !port->eld.eld_valid) { +- +- dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", +- __func__, pin->nid, port->id); +- +- /* +- * PCMs are not registered during device probe, so don't +- * report jack here. It will be done in usermode mux +- * control select. +- */ +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, false); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ size = min(size, ELD_MAX_SIZE); ++ if (hdac_hdmi_parse_eld(hdev, port) < 0) ++ size = -EINVAL; + } + +- mutex_unlock(&hdmi->pin_mutex); +- return; +- } ++ eld_valid = port->eld.eld_valid; + +- if (port->eld.monitor_present && port->eld.eld_valid) { +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, true); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ port->eld.eld_valid = true; ++ port->eld.eld_size = size; ++ } else { ++ port->eld.eld_valid = false; ++ port->eld.eld_size = 0; + } + +- print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, +- port->eld.eld_buffer, port->eld.eld_size, false); ++ eld_changed = (eld_valid != port->eld.eld_valid); + ++ pcm = hdac_hdmi_get_pcm(hdev, port); ++ ++ if (!port->eld.monitor_present || !port->eld.eld_valid) { ++ ++ dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", ++ __func__, pin->nid, port->id); ++ ++ /* ++ * PCMs are not registered during device probe, so don't ++ * report jack here. It will be done in usermode mux ++ * control select. ++ */ ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, false); ++ schedule_work(&port->dapm_work); ++ } ++ ++ return; ++ } ++ ++ if (port->eld.monitor_present && port->eld.eld_valid) { ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, true); ++ schedule_work(&port->dapm_work); ++ } ++ ++ print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, ++ port->eld.eld_buffer, port->eld.eld_size, false); ++ } + } +- mutex_unlock(&hdmi->pin_mutex); + + if (eld_changed && pcm) + snd_ctl_notify(hdmi->card, +@@ -1795,13 +1793,12 @@ static void hdac_hdmi_set_chmap(struct hdac_device *hdev, int pcm_idx, + if (list_empty(&pcm->port_list)) + return; + +- mutex_lock(&pcm->lock); ++ guard(mutex)(&pcm->lock); + pcm->chmap_set = true; + memcpy(pcm->chmap, chmap, ARRAY_SIZE(pcm->chmap)); + list_for_each_entry(port, &pcm->port_list, head) + if (prepared) + hdac_hdmi_setup_audio_infoframe(hdev, pcm, port); +- mutex_unlock(&pcm->lock); + } + + static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdev, int pcm_idx) +@@ -1866,8 +1863,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + snd_hdac_ext_bus_link_get(hdev->bus, hlink); + + hdmi_priv = devm_kzalloc(&hdev->dev, sizeof(*hdmi_priv), GFP_KERNEL); +- if (hdmi_priv == NULL) ++ if (hdmi_priv == NULL) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENOMEM; ++ } + + snd_hdac_register_chmap_ops(hdev, &hdmi_priv->chmap); + hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap; +@@ -1876,8 +1875,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc; + hdmi_priv->hdev = hdev; + +- if (!hdac_id) ++ if (!hdac_id) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENODEV; ++ } + + if (hdac_id->driver_data) + hdmi_priv->drv_data = +@@ -1902,6 +1903,8 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + if (ret < 0) { + dev_err(&hdev->dev, + "Failed in parse and map nid with err: %d\n", ret); ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); ++ snd_hdac_display_power(hdev->bus, hdev->addr, false); + return ret; + } + snd_hdac_refresh_widgets(hdev); +diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c +--- a/sound/soc/codecs/hdmi-codec.c ++++ b/sound/soc/codecs/hdmi-codec.c +@@ -4,6 +4,7 @@ + * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Jyri Sarha + */ ++#include + #include + #include + #include +@@ -456,31 +457,30 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + if (!((has_playback && tx) || (has_capture && !tx))) + return 0; + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + if (hcp->busy) { + dev_err(dai->dev, "Only one simultaneous stream supported!\n"); +- mutex_unlock(&hcp->lock); + return -EINVAL; + } + + if (hcp->hcd.ops->audio_startup) { + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); + if (ret) +- goto err; ++ return ret; + } + + if (tx && hcp->hcd.ops->get_eld) { + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); + if (ret) +- goto err; ++ return ret; + + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + + ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); + if (ret) +- goto err; ++ return ret; + + /* Select chmap supported */ + hdmi_codec_eld_chmap(hcp); +@@ -488,8 +488,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + + hcp->busy = true; + +-err: +- mutex_unlock(&hcp->lock); + return ret; + } + +@@ -507,9 +505,8 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + hcp->busy = false; +- mutex_unlock(&hcp->lock); + } + + static int hdmi_codec_fill_codec_params(struct snd_soc_dai *dai, +diff --git a/sound/soc/codecs/idt821034.c b/sound/soc/codecs/idt821034.c +--- a/sound/soc/codecs/idt821034.c ++++ b/sound/soc/codecs/idt821034.c +@@ -7,6 +7,7 @@ + // Author: Herve Codina + + #include ++#include + #include + #include + #include +@@ -413,12 +414,12 @@ static int idt821034_kctrl_gain_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); +- if (IDT821034_ID_IS_OUT(mc->reg)) +- val = idt821034->amps.ch[ch].amp_out.gain; +- else +- val = idt821034->amps.ch[ch].amp_in.gain; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ if (IDT821034_ID_IS_OUT(mc->reg)) ++ val = idt821034->amps.ch[ch].amp_out.gain; ++ else ++ val = idt821034->amps.ch[ch].amp_in.gain; ++ } + + ucontrol->value.integer.value[0] = val & mask; + if (invert) +@@ -456,7 +457,7 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(mc->reg)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -466,22 +467,18 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->gain == val) { +- ret = 0; +- goto end; +- } ++ if (amp->gain == val) ++ return 0; + + if (!amp->is_muted) { + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, val); + if (ret) +- goto end; ++ return ret; + } + + amp->gain = val; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, +@@ -495,11 +492,11 @@ static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(id); + +- mutex_lock(&idt821034->mutex); +- is_muted = IDT821034_ID_IS_OUT(id) ? +- idt821034->amps.ch[ch].amp_out.is_muted : +- idt821034->amps.ch[ch].amp_in.is_muted; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ is_muted = IDT821034_ID_IS_OUT(id) ? ++ idt821034->amps.ch[ch].amp_out.is_muted : ++ idt821034->amps.ch[ch].amp_in.is_muted; ++ } + + ucontrol->value.integer.value[0] = !is_muted; + +@@ -521,7 +518,7 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + ch = IDT821034_ID_GET_CHAN(id); + is_mute = !ucontrol->value.integer.value[0]; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(id)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -531,21 +528,17 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->is_muted == is_mute) { +- ret = 0; +- goto end; +- } ++ if (amp->is_muted == is_mute) ++ return 0; + + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, + is_mute ? 0 : amp->gain); + if (ret) +- goto end; ++ return ret; + + amp->is_muted = is_mute; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static const DECLARE_TLV_DB_LINEAR(idt821034_gain_in, -300, 1300); +@@ -623,24 +616,20 @@ static int idt821034_power_event(struct snd_soc_dapm_widget *w, + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component); + unsigned int id = w->shift; + u8 power, mask; +- int ret; + u8 ch; + + ch = IDT821034_ID_GET_CHAN(id); + mask = IDT821034_ID_IS_OUT(id) ? IDT821034_CONF_PWRUP_RX : IDT821034_CONF_PWRUP_TX; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + power = idt821034_get_channel_power(idt821034, ch); + if (SND_SOC_DAPM_EVENT_ON(event)) + power |= mask; + else + power &= ~mask; +- ret = idt821034_set_channel_power(idt821034, ch, power); + +- mutex_unlock(&idt821034->mutex); +- +- return ret; ++ return idt821034_set_channel_power(idt821034, ch, power); + } + + static const struct snd_soc_dapm_widget idt821034_dapm_widgets[] = { +@@ -717,9 +706,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_RX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_RX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set tx tdm slot failed (%d)\n", + ch, ret); +@@ -742,9 +731,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_TX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_TX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set rx tdm slot failed (%d)\n", + ch, ret); +@@ -769,9 +758,8 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -785,13 +773,10 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + default: + dev_err(dai->dev, "Unsupported DAI format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, +@@ -800,9 +785,8 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -816,13 +800,10 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + default: + dev_err(dai->dev, "Unsupported PCM format 0x%x\n", + params_format(params)); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static const unsigned int idt821034_sample_bits[] = {8}; +@@ -897,11 +878,11 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + ret = idt821034_set_codec_conf(idt821034, 0); + if (ret) +- goto end; ++ return ret; + + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + idt821034->amps.ch[i].amp_out.gain = IDT821034_GAIN_OUT_INIT_RAW; +@@ -909,24 +890,21 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_RX, + idt821034->amps.ch[i].amp_out.gain); + if (ret) +- goto end; ++ return ret; + + idt821034->amps.ch[i].amp_in.gain = IDT821034_GAIN_IN_INIT_RAW; + idt821034->amps.ch[i].amp_in.is_muted = false; + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_TX, + idt821034->amps.ch[i].amp_in.gain); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_set_channel_power(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + } + +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ return 0; + } + + static int idt821034_component_probe(struct snd_soc_component *component) +@@ -965,7 +943,7 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_raw = idt821034_get_written_slic_raw(idt821034, ch); + if (val) +@@ -974,8 +952,6 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + slic_raw &= ~mask; + ret = idt821034_write_slic_raw(idt821034, ch, slic_raw); + +- mutex_unlock(&idt821034->mutex); +- + if (ret) + dev_err(&idt821034->spi->dev, "set gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -991,9 +967,8 @@ static int idt821034_chip_gpio_get(struct gpio_chip *c, unsigned int offset) + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); +- ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); + if (ret) { + dev_err(&idt821034->spi->dev, "get gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -1015,9 +990,8 @@ static int idt821034_chip_get_direction(struct gpio_chip *c, unsigned int offset + struct idt821034 *idt821034 = gpiochip_get_data(c); + u8 slic_dir; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + slic_dir = idt821034_get_slic_conf(idt821034, ch); +- mutex_unlock(&idt821034->mutex); + + return slic_dir & mask ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; + } +@@ -1034,7 +1008,7 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + if (mask & ~(IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN)) + return -EPERM; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) | mask; + +@@ -1044,7 +1018,6 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1060,7 +1033,7 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + if (ret) + return ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) & ~mask; + +@@ -1070,7 +1043,6 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1079,24 +1051,22 @@ static int idt821034_reset_gpio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + /* IO0 and IO1 as input for all channels and output IO set to 0 */ + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + ret = idt821034_set_slic_conf(idt821034, i, + IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_write_slic_raw(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + + } +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 0; + } + + static int idt821034_gpio_init(struct idt821034 *idt821034) +diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c +--- a/sound/soc/codecs/lochnagar-sc.c ++++ b/sound/soc/codecs/lochnagar-sc.c +@@ -137,15 +137,23 @@ static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBP_CFP); + } + ++static const u64 lochnagar_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { + .startup = lochnagar_sc_line_startup, + .shutdown = lochnagar_sc_line_shutdown, + .set_fmt = lochnagar_sc_set_line_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { + .startup = lochnagar_sc_startup, + .set_fmt = lochnagar_sc_set_usb_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver lochnagar_sc_dai[] = { +diff --git a/sound/soc/codecs/lpass-macro-common.c b/sound/soc/codecs/lpass-macro-common.c +--- a/sound/soc/codecs/lpass-macro-common.c ++++ b/sound/soc/codecs/lpass-macro-common.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2022, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -71,21 +72,16 @@ EXPORT_SYMBOL_GPL(lpass_macro_pds_exit); + + void lpass_macro_set_codec_version(enum lpass_codec_version version) + { +- mutex_lock(&lpass_codec_mutex); ++ guard(mutex)(&lpass_codec_mutex); + lpass_codec_version = version; +- mutex_unlock(&lpass_codec_mutex); + } + EXPORT_SYMBOL_GPL(lpass_macro_set_codec_version); + + enum lpass_codec_version lpass_macro_get_codec_version(void) + { +- enum lpass_codec_version ver; ++ guard(mutex)(&lpass_codec_mutex); + +- mutex_lock(&lpass_codec_mutex); +- ver = lpass_codec_version; +- mutex_unlock(&lpass_codec_mutex); +- +- return ver; ++ return lpass_codec_version; + } + EXPORT_SYMBOL_GPL(lpass_macro_get_codec_version); + +diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c +--- a/sound/soc/codecs/lpass-rx-macro.c ++++ b/sound/soc/codecs/lpass-rx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2030,9 +2031,10 @@ static struct snd_soc_dai_driver rx_macro_dai[] = { + }, + }; + +-static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) ++static int rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + { + struct regmap *regmap = rx->regmap; ++ int ret; + + if (mclk_enable) { + if (rx->rx_mclk_users == 0) { +@@ -2047,14 +2049,16 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_FS_MCLK_CNT_EN_MASK, + CDC_RX_FS_MCLK_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + rx->rx_mclk_users++; + } else { + if (rx->rx_mclk_users <= 0) { + dev_err(rx->dev, "%s: clock already disabled\n", __func__); + rx->rx_mclk_users = 0; +- return; ++ return 0; + } + rx->rx_mclk_users--; + if (rx->rx_mclk_users == 0) { +@@ -2068,6 +2072,8 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_CLK_MCLK2_EN_MASK, 0x0); + } + } ++ ++ return 0; + } + + static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, +@@ -2079,11 +2085,9 @@ static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- rx_macro_mclk_enable(rx, true); +- break; ++ return rx_macro_mclk_enable(rx, true); + case SND_SOC_DAPM_POST_PMD: +- rx_macro_mclk_enable(rx, false); +- break; ++ return rx_macro_mclk_enable(rx, false); + default: + dev_err(component->dev, "%s: invalid DAPM event %d\n", __func__, event); + ret = -EINVAL; +@@ -3671,14 +3675,16 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct rx_macro *rx = to_rx_macro(hw); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_runtime_resume_and_get(rx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = rx_macro_mclk_enable(rx, true); + if (ret) { +- dev_err(rx->dev, "unable to prepare mclk\n"); ++ clk_disable_unprepare(rx->mclk); + return ret; + } + +- rx_macro_mclk_enable(rx, true); +- + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, + CDC_RX_SWR_CLK_EN_MASK, 1); + +@@ -3693,7 +3699,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_RX_SWR_CLK_EN_MASK, 0); + + rx_macro_mclk_enable(rx, false); +- clk_disable_unprepare(rx->mclk); ++ pm_runtime_put_autosuspend(rx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -3864,28 +3870,31 @@ static int rx_macro_probe(struct platform_device *pdev) + rx->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(rx->mclk, MCLK_FREQ); +- clk_set_rate(rx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(rx->macro); ++ ret = clk_set_rate(rx->mclk, MCLK_FREQ); + if (ret) + return ret; + +- ret = clk_prepare_enable(rx->dcodec); ++ ret = clk_set_rate(rx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(rx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(rx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ return ret; + + /* reset swr block */ + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, +@@ -3902,46 +3911,25 @@ static int rx_macro_probe(struct platform_device *pdev) + rx_macro_dai, + ARRAY_SIZE(rx_macro_dai)); + if (ret) +- goto err_clkout; +- +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = rx_macro_register_mclk_output(rx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(rx->fsgen); +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +-err_mclk: +- clk_disable_unprepare(rx->dcodec); +-err_dcodec: +- clk_disable_unprepare(rx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + + return ret; + } + +-static void rx_macro_remove(struct platform_device *pdev) +-{ +- struct rx_macro *rx = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(rx->mclk); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->macro); +- clk_disable_unprepare(rx->dcodec); +-} +- + static const struct of_device_id rx_macro_dt_match[] = { + { + .compatible = "qcom,sc7280-lpass-rx-macro", +@@ -3969,13 +3957,17 @@ MODULE_DEVICE_TABLE(of, rx_macro_dt_match); + static int rx_macro_runtime_suspend(struct device *dev) + { + struct rx_macro *rx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rx->regmap, true); +- regcache_mark_dirty(rx->regmap); + +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(rx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(rx->regmap); + + return 0; + } +@@ -3985,33 +3977,23 @@ static int rx_macro_runtime_resume(struct device *dev) + struct rx_macro *rx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); + return ret; + } + +- ret = clk_prepare_enable(rx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(rx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } + regcache_cache_only(rx->regmap, false); +- regcache_sync(rx->regmap); ++ ret = regcache_sync(rx->regmap); ++ if (ret) { ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops rx_macro_pm_ops = { +@@ -4026,7 +4008,6 @@ static struct platform_driver rx_macro_driver = { + .pm = pm_ptr(&rx_macro_pm_ops), + }, + .probe = rx_macro_probe, +- .remove = rx_macro_remove, + }; + + module_platform_driver(rx_macro_driver); +diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c +--- a/sound/soc/codecs/lpass-tx-macro.c ++++ b/sound/soc/codecs/lpass-tx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -614,6 +615,7 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + bool mclk_enable) + { + struct regmap *regmap = tx->regmap; ++ int ret; + + if (mclk_enable) { + if (tx->tx_mclk_users == 0) { +@@ -626,7 +628,9 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + CDC_TX_FS_CNT_EN_MASK, + CDC_TX_FS_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + tx->tx_mclk_users++; + } else { +@@ -739,11 +743,9 @@ static int tx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- tx_macro_mclk_enable(tx, true); +- break; ++ return tx_macro_mclk_enable(tx, true); + case SND_SOC_DAPM_POST_PMD: +- tx_macro_mclk_enable(tx, false); +- break; ++ return tx_macro_mclk_enable(tx, false); + default: + break; + } +@@ -2149,17 +2151,20 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct regmap *regmap = tx->regmap; + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_runtime_resume_and_get(tx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = tx_macro_mclk_enable(tx, true); + if (ret) { +- dev_err(tx->dev, "failed to enable mclk\n"); ++ pm_runtime_put_autosuspend(tx->dev); + return ret; + } + +- tx_macro_mclk_enable(tx, true); +- + regmap_update_bits(regmap, CDC_TX_CLK_RST_CTRL_SWR_CONTROL, + CDC_TX_SWR_CLK_EN_MASK, + CDC_TX_SWR_CLK_ENABLE); ++ + return 0; + } + +@@ -2172,7 +2177,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_TX_SWR_CLK_EN_MASK, 0x0); + + tx_macro_mclk_enable(tx, false); +- clk_disable_unprepare(tx->mclk); ++ pm_runtime_put_autosuspend(tx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -2315,28 +2320,31 @@ static int tx_macro_probe(struct platform_device *pdev) + tx->active_decimator[TX_MACRO_AIF3_CAP] = -1; + + /* set MCLK and NPL rates */ +- clk_set_rate(tx->mclk, MCLK_FREQ); +- clk_set_rate(tx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(tx->macro); ++ ret = clk_set_rate(tx->mclk, MCLK_FREQ); + if (ret) + goto err; + +- ret = clk_prepare_enable(tx->dcodec); ++ ret = clk_set_rate(tx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ goto err; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- ret = clk_prepare_enable(tx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(tx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ goto err; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err; + + + /* reset soundwire block */ +@@ -2356,30 +2364,21 @@ static int tx_macro_probe(struct platform_device *pdev) + tx_macro_dai, + ARRAY_SIZE(tx_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = tx_macro_register_mclk_output(tx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(tx->fsgen); +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +-err_mclk: +- clk_disable_unprepare(tx->dcodec); +-err_dcodec: +- clk_disable_unprepare(tx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + err: + lpass_macro_pds_exit(tx->pds); + +@@ -2390,25 +2389,23 @@ static void tx_macro_remove(struct platform_device *pdev) + { + struct tx_macro *tx = dev_get_drvdata(&pdev->dev); + +- clk_disable_unprepare(tx->macro); +- clk_disable_unprepare(tx->dcodec); +- clk_disable_unprepare(tx->mclk); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->fsgen); +- + lpass_macro_pds_exit(tx->pds); + } + + static int tx_macro_runtime_suspend(struct device *dev) + { + struct tx_macro *tx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(tx->regmap, true); +- regcache_mark_dirty(tx->regmap); + +- clk_disable_unprepare(tx->fsgen); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(tx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(tx->regmap); + + return 0; + } +@@ -2418,34 +2415,23 @@ static int tx_macro_runtime_resume(struct device *dev) + struct tx_macro *tx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); + return ret; + } + +- ret = clk_prepare_enable(tx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare npl\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(tx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- + regcache_cache_only(tx->regmap, false); +- regcache_sync(tx->regmap); ++ ret = regcache_sync(tx->regmap); ++ if (ret) { ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops tx_macro_pm_ops = { +diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c +--- a/sound/soc/codecs/lpass-va-macro.c ++++ b/sound/soc/codecs/lpass-va-macro.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1351,34 +1352,43 @@ static int fsgen_gate_enable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; +- int ret; ++ int ret, rpm_ret; + +- if (va->has_swr_master) { +- ret = clk_prepare_enable(va->mclk); +- if (ret) +- return ret; +- } ++ ret = pm_runtime_resume_and_get(va->dev); ++ if (ret < 0) ++ return ret; + + ret = va_macro_mclk_enable(va, true); ++ if (ret) { ++ rpm_ret = pm_runtime_put_autosuspend(va->dev); ++ if (rpm_ret < 0) ++ dev_warn(va->dev, ++ "runtime PM put failed in fsgen enable unwind: %d\n", ++ rpm_ret); ++ return ret; ++ } + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, CDC_VA_SWR_CLK_ENABLE); + +- return ret; ++ return 0; + } + + static void fsgen_gate_disable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; ++ int ret; + + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, 0x0); + + va_macro_mclk_enable(va, false); +- if (va->has_swr_master) +- clk_disable_unprepare(va->mclk); ++ ++ ret = pm_runtime_put_autosuspend(va->dev); ++ if (ret < 0) ++ dev_warn(va->dev, "runtime PM put failed in fsgen disable: %d\n", ret); + } + + static int fsgen_gate_is_enabled(struct clk_hw *hw) +@@ -1539,6 +1549,7 @@ static int va_macro_probe(struct platform_device *pdev) + void __iomem *base; + u32 sample_rate = 0; + int ret; ++ int rpm_ret; + + va = devm_kzalloc(dev, sizeof(*va), GFP_KERNEL); + if (!va) +@@ -1594,7 +1605,9 @@ static int va_macro_probe(struct platform_device *pdev) + va->has_npl_clk = data->has_npl_clk; + + /* mclk rate */ +- clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + + if (va->has_npl_clk) { + va->npl = devm_clk_get(dev, "npl"); +@@ -1603,25 +1616,29 @@ static int va_macro_probe(struct platform_device *pdev) + goto err; + } + +- clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + } + +- ret = clk_prepare_enable(va->macro); ++ ret = devm_pm_clk_create(dev); + if (ret) + goto err; + +- ret = clk_prepare_enable(va->dcodec); +- if (ret) +- goto err_dcodec; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(va->mclk); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) +- goto err_npl; ++ rpm_ret = pm_runtime_resume_and_get(dev); ++ if (rpm_ret < 0) { ++ ret = rpm_ret; ++ goto err; + } + + /** +@@ -1634,7 +1651,7 @@ static int va_macro_probe(struct platform_device *pdev) + /* read version from register */ + ret = va_macro_set_lpass_codec_version(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + } + + if (va->has_swr_master) { +@@ -1664,35 +1681,28 @@ static int va_macro_probe(struct platform_device *pdev) + va_macro_dais, + ARRAY_SIZE(va_macro_dais)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = va_macro_register_fsgen_output(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + + va->fsgen = devm_clk_hw_get_clk(dev, &va->hw, "fsgen"); + if (IS_ERR(va->fsgen)) { + ret = PTR_ERR(va->fsgen); +- goto err_clkout; ++ goto err_rpm_put; + } + ++ rpm_ret = pm_runtime_put_autosuspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", rpm_ret); ++ + return 0; + +-err_clkout: +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +-err_npl: +- clk_disable_unprepare(va->mclk); +-err_mclk: +- clk_disable_unprepare(va->dcodec); +-err_dcodec: +- clk_disable_unprepare(va->macro); ++err_rpm_put: ++ rpm_ret = pm_runtime_put_sync_suspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind: %d\n", rpm_ret); + err: + lpass_macro_pds_exit(va->pds); + +@@ -1703,53 +1713,52 @@ static void va_macro_remove(struct platform_device *pdev) + { + struct va_macro *va = dev_get_drvdata(&pdev->dev); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- clk_disable_unprepare(va->dcodec); +- clk_disable_unprepare(va->macro); +- + lpass_macro_pds_exit(va->pds); + } + + static int va_macro_runtime_suspend(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(va->regmap, true); ++ ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(va->regmap, false); ++ return ret; ++ } ++ + regcache_mark_dirty(va->regmap); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- + return 0; + } + + static int va_macro_runtime_resume(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(va->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(va->dev, "unable to prepare mclk\n"); ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); + return ret; + } + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) { +- clk_disable_unprepare(va->mclk); +- dev_err(va->dev, "unable to prepare npl\n"); +- return ret; +- } +- } +- + regcache_cache_only(va->regmap, false); +- regcache_sync(va->regmap); ++ ++ ret = regcache_sync(va->regmap); ++ if (ret) { ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(va->dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2529,15 +2530,13 @@ static const struct snd_soc_dapm_route wsa_audio_map[] = { + static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + { + struct regmap *regmap = wsa->regmap; ++ int ret; + + if (enable) { +- int ret; +- +- ret = clk_prepare_enable(wsa->mclk); +- if (ret) { +- dev_err(wsa->dev, "failed to enable mclk\n"); ++ ret = pm_runtime_resume_and_get(wsa->dev); ++ if (ret < 0) + return ret; +- } ++ + wsa_macro_mclk_enable(wsa, true); + + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2548,7 +2547,10 @@ static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + CDC_WSA_SWR_CLK_EN_MASK, 0); + wsa_macro_mclk_enable(wsa, false); +- clk_disable_unprepare(wsa->mclk); ++ ++ ret = pm_runtime_put_autosuspend(wsa->dev); ++ if (ret < 0) ++ dev_warn(wsa->dev, "runtime PM put failed: %d\n", ret); + } + + return 0; +@@ -2656,7 +2658,7 @@ static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) + init.num_parents = 1; + wsa->hw.init = &init; + hw = &wsa->hw; +- ret = clk_hw_register(wsa->dev, hw); ++ ret = devm_clk_hw_register(wsa->dev, hw); + if (ret) + return ret; + +@@ -2771,28 +2773,31 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); +- clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); +- +- ret = clk_prepare_enable(wsa->macro); ++ ret = clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err; ++ return ret; + +- ret = clk_prepare_enable(wsa->dcodec); ++ ret = clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(wsa->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ return ret; + + /* reset swr ip */ + regmap_update_bits(wsa->regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2809,56 +2814,37 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa_macro_dai, + ARRAY_SIZE(wsa_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = wsa_macro_register_mclk_output(wsa); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; +- +-err_clkout: +- clk_disable_unprepare(wsa->fsgen); +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +-err_mclk: +- clk_disable_unprepare(wsa->dcodec); +-err_dcodec: +- clk_disable_unprepare(wsa->macro); +-err: ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + return ret; +- +-} +- +-static void wsa_macro_remove(struct platform_device *pdev) +-{ +- struct wsa_macro *wsa = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(wsa->macro); +- clk_disable_unprepare(wsa->dcodec); +- clk_disable_unprepare(wsa->mclk); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->fsgen); + } + + static int wsa_macro_runtime_suspend(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(wsa->regmap, true); +- regcache_mark_dirty(wsa->regmap); + +- clk_disable_unprepare(wsa->fsgen); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(wsa->regmap); + + return 0; + } +@@ -2866,36 +2852,29 @@ static int wsa_macro_runtime_suspend(struct device *dev) + static int wsa_macro_runtime_resume(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ return ret; ++ } ++ regcache_cache_only(wsa->regmap, false); ++ ++ ret = regcache_sync(wsa->regmap); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); + return ret; + } + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(wsa->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- +- regcache_cache_only(wsa->regmap, false); +- regcache_sync(wsa->regmap); +- + return 0; +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +- +- return ret; + } + + static const struct dev_pm_ops wsa_macro_pm_ops = { +@@ -2929,7 +2908,6 @@ static struct platform_driver wsa_macro_driver = { + .pm = pm_ptr(&wsa_macro_pm_ops), + }, + .probe = wsa_macro_probe, +- .remove = wsa_macro_remove, + }; + + module_platform_driver(wsa_macro_driver); +diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c +--- a/sound/soc/codecs/madera.c ++++ b/sound/soc/codecs/madera.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -513,7 +514,7 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + * We can't rely on the DAPM mutex for locking because we need a lock + * that can safely be called in hw_params + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -532,8 +533,6 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + + madera_debug_dump_domain_groups(priv); + +- mutex_unlock(&priv->rate_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(madera_domain_clk_ev); +@@ -875,9 +874,8 @@ static int madera_adsp_rate_get(struct snd_kcontrol *kcontrol, + const int adsp_num = e->shift_l; + int item; + +- mutex_lock(&priv->rate_lock); +- cached_rate = priv->adsp_rate_cache[adsp_num]; +- mutex_unlock(&priv->rate_lock); ++ scoped_guard(mutex, &priv->rate_lock) ++ cached_rate = priv->adsp_rate_cache[adsp_num]; + + item = snd_soc_enum_val_to_item(e, cached_rate); + ucontrol->value.enumerated.item[0] = item; +@@ -893,7 +891,6 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + const int adsp_num = e->shift_l; + const unsigned int item = ucontrol->value.enumerated.item[0]; +- int ret = 0; + + if (item >= e->items) + return -EINVAL; +@@ -903,22 +900,20 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + * maintain consistent behaviour that rate domains cannot be changed + * while in use since this is a hardware requirement + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, priv->adsp[adsp_num].cs_dsp.base)) { + dev_warn(priv->madera->dev, + "Cannot change '%s' while in use by active audio paths\n", + kcontrol->id.name); +- ret = -EBUSY; ++ return -EBUSY; + } else if (priv->adsp_rate_cache[adsp_num] != e->values[item]) { + /* Volatile register so defer until the codec is powered up */ + priv->adsp_rate_cache[adsp_num] = e->values[item]; +- ret = 1; ++ return 1; + } + +- mutex_unlock(&priv->rate_lock); +- +- return ret; ++ return 0; + } + + static const struct soc_enum madera_adsp_rate_enum[] = { +@@ -1061,15 +1056,13 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + * Prevent the domain powering up while we're checking whether it's + * safe to change rate domain + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + val = snd_soc_component_read(component, e->reg); + val >>= e->shift_l; + val &= e->mask; +- if (snd_soc_enum_item_to_val(e, item) == val) { +- ret = 0; +- goto out; +- } ++ if (snd_soc_enum_item_to_val(e, item) == val) ++ return 0; + + if (!madera_can_change_grp_rate(priv, e->reg)) { + dev_warn(priv->madera->dev, +@@ -1082,8 +1075,6 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + madera_spin_sysclk(priv); + } +-out: +- mutex_unlock(&priv->rate_lock); + + return ret; + } +@@ -3041,12 +3032,11 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + if ((cur & MADERA_AIF1_RATE_MASK) == (tar & MADERA_AIF1_RATE_MASK)) + return 0; + +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, base + MADERA_AIF_RATE_CTRL)) { + madera_aif_warn(dai, "Cannot change rate while active\n"); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + + /* Guard the rate change with SYSCLK cycles */ +@@ -3055,9 +3045,6 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + MADERA_AIF1_RATE_MASK, tar); + madera_spin_sysclk(priv); + +-out: +- mutex_unlock(&priv->rate_lock); +- + return ret; + } + +@@ -3345,6 +3332,16 @@ static int madera_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + return 0; + } + ++static const u64 madera_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + const struct snd_soc_dai_ops madera_dai_ops = { + .startup = &madera_startup, + .set_fmt = &madera_set_fmt, +@@ -3352,6 +3349,8 @@ const struct snd_soc_dai_ops madera_dai_ops = { + .hw_params = &madera_hw_params, + .set_sysclk = &madera_dai_set_sysclk, + .set_tristate = &madera_set_tristate, ++ .auto_selectable_formats = &madera_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_GPL(madera_dai_ops); + +diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c +--- a/sound/soc/codecs/max98090.c ++++ b/sound/soc/codecs/max98090.c +@@ -2341,6 +2341,7 @@ static irqreturn_t max98090_interrupt(int irq, void *data) + * + * @component: MAX98090 component + * @jack: jack to report detection events on ++ * @data: can be used if codec driver need extra data for configuring jack + * + * Enable microphone detection via IRQ on the MAX98090. If GPIOs are + * being used to bring out signals to the processor then only platform +@@ -2646,12 +2647,18 @@ static void max98090_i2c_remove(struct i2c_client *client) + static int max98090_runtime_resume(struct device *dev) + { + struct max98090_priv *max98090 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98090->regmap, false); + + max98090_reset(max98090); + +- regcache_sync(max98090->regmap); ++ ret = regcache_sync(max98090->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98090->regmap, true); ++ regcache_mark_dirty(max98090->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c +--- a/sound/soc/codecs/max98095.c ++++ b/sound/soc/codecs/max98095.c +@@ -5,6 +5,7 @@ + * Copyright 2011 Maxim Integrated Products + */ + ++#include + #include + #include + #include +@@ -1532,15 +1533,17 @@ static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_eq_band(component, channel, 0, coef_set->band1); +- m98095_eq_band(component, channel, 1, coef_set->band2); +- m98095_eq_band(component, channel, 2, coef_set->band3); +- m98095_eq_band(component, channel, 3, coef_set->band4); +- m98095_eq_band(component, channel, 4, coef_set->band5); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_eq_band(component, channel, 0, coef_set->band1); ++ m98095_eq_band(component, channel, 1, coef_set->band2); ++ m98095_eq_band(component, channel, 2, coef_set->band3); ++ m98095_eq_band(component, channel, 3, coef_set->band4); ++ m98095_eq_band(component, channel, 4, coef_set->band5); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +@@ -1683,12 +1686,14 @@ static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_biquad_band(component, channel, 0, coef_set->band1); +- m98095_biquad_band(component, channel, 1, coef_set->band2); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_biquad_band(component, channel, 0, coef_set->band1); ++ m98095_biquad_band(component, channel, 1, coef_set->band2); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -100,7 +100,12 @@ static int max98363_resume(struct device *dev) + return ret; + + regcache_cache_only(max98363->regmap, false); +- regcache_sync(max98363->regmap); ++ ret = regcache_sync(max98363->regmap); ++ if (ret) { ++ regcache_cache_only(max98363->regmap, true); ++ regcache_mark_dirty(max98363->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c +--- a/sound/soc/codecs/max98373-i2c.c ++++ b/sound/soc/codecs/max98373-i2c.c +@@ -488,10 +488,17 @@ static int max98373_suspend(struct device *dev) + static int max98373_resume(struct device *dev) + { + struct max98373_priv *max98373 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98373->regmap, false); + max98373_reset(max98373, dev); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/max98373-sdw.c b/sound/soc/codecs/max98373-sdw.c +--- a/sound/soc/codecs/max98373-sdw.c ++++ b/sound/soc/codecs/max98373-sdw.c +@@ -277,7 +277,12 @@ static int max98373_resume(struct device *dev) + } + + regcache_cache_only(max98373->regmap, false); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c +--- a/sound/soc/codecs/max98388.c ++++ b/sound/soc/codecs/max98388.c +@@ -863,10 +863,16 @@ static int max98388_suspend(struct device *dev) + static int max98388_resume(struct device *dev) + { + struct max98388_priv *max98388 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98388->regmap, false); + max98388_reset(max98388, dev); +- regcache_sync(max98388->regmap); ++ ret = regcache_sync(max98388->regmap); ++ if (ret) { ++ regcache_cache_only(max98388->regmap, true); ++ regcache_mark_dirty(max98388->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c +--- a/sound/soc/codecs/max98390.c ++++ b/sound/soc/codecs/max98390.c +@@ -788,7 +788,6 @@ static int max98390_dsm_init(struct snd_soc_component *component) + const char *vendor, *product; + struct max98390_priv *max98390 = + snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + char *dsm_param; + + vendor = dmi_get_system_info(DMI_SYS_VENDOR); +@@ -805,6 +804,8 @@ static int max98390_dsm_init(struct snd_soc_component *component) + snprintf(filename, sizeof(filename), "%s", + max98390->dsm_param_name); + } ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, component->dev); + if (ret) { + ret = request_firmware(&fw, "dsm_param.bin", component->dev); +@@ -812,7 +813,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + ret = request_firmware(&fw, "dsmparam.bin", + component->dev); + if (ret) +- goto err; ++ return ret; + } + } + +@@ -822,8 +823,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + if (fw->size < MAX98390_DSM_PARAM_MIN_SIZE) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + dsm_param = (char *)fw->data; + param_start_addr = (dsm_param[0] & 0xff) | (dsm_param[1] & 0xff) << 8; +@@ -833,8 +833,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + fw->size < param_size + MAX98390_DSM_PAYLOAD_OFFSET) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + regmap_write(max98390->regmap, MAX98390_R203A_AMP_EN, 0x80); + dsm_param += MAX98390_DSM_PAYLOAD_OFFSET; +@@ -842,10 +841,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + dsm_param, param_size); + regmap_write(max98390->regmap, MAX98390_R23E1_DSP_GLOBAL_EN, 0x01); + +-err_alloc: +- release_firmware(fw); +-err: +- return ret; ++ return 0; + } + + static void max98390_init_regs(struct snd_soc_component *component) +@@ -952,11 +948,17 @@ static int max98390_suspend(struct device *dev) + static int max98390_resume(struct device *dev) + { + struct max98390_priv *max98390 = dev_get_drvdata(dev); ++ int ret; + + dev_dbg(dev, "%s:Enter\n", __func__); + + regcache_cache_only(max98390->regmap, false); +- regcache_sync(max98390->regmap); ++ ret = regcache_sync(max98390->regmap); ++ if (ret) { ++ regcache_cache_only(max98390->regmap, true); ++ regcache_mark_dirty(max98390->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98396.c b/sound/soc/codecs/max98396.c +--- a/sound/soc/codecs/max98396.c ++++ b/sound/soc/codecs/max98396.c +@@ -1600,19 +1600,37 @@ static int max98396_resume(struct device *dev) + if (max98396->pvdd) { + ret = regulator_enable(max98396->pvdd); + if (ret < 0) +- return ret; ++ goto err_core_supplies; + } + + if (max98396->vbat) { + ret = regulator_enable(max98396->vbat); + if (ret < 0) +- return ret; ++ goto err_pvdd; + } + + regcache_cache_only(max98396->regmap, false); + max98396_reset(max98396, dev); +- regcache_sync(max98396->regmap); ++ ret = regcache_sync(max98396->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98396->regmap, true); ++ regcache_mark_dirty(max98396->regmap); ++ goto err_vbat; ++ } ++ + return 0; ++ ++err_vbat: ++ if (max98396->vbat) ++ regulator_disable(max98396->vbat); ++err_pvdd: ++ if (max98396->pvdd) ++ regulator_disable(max98396->pvdd); ++err_core_supplies: ++ regulator_bulk_disable(MAX98396_NUM_CORE_SUPPLIES, ++ max98396->core_supplies); ++ ++ return ret; + } + + static const struct dev_pm_ops max98396_pm = { +diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c +--- a/sound/soc/codecs/max98927.c ++++ b/sound/soc/codecs/max98927.c +@@ -742,11 +742,17 @@ static int max98927_suspend(struct device *dev) + static int max98927_resume(struct device *dev) + { + struct max98927_priv *max98927 = dev_get_drvdata(dev); ++ int ret; + + regmap_write(max98927->regmap, MAX98927_R0100_SOFT_RESET, + MAX98927_SOFT_RESET); + regcache_cache_only(max98927->regmap, false); +- regcache_sync(max98927->regmap); ++ ret = regcache_sync(max98927->regmap); ++ if (ret) { ++ regcache_cache_only(max98927->regmap, true); ++ regcache_mark_dirty(max98927->regmap); ++ return ret; ++ } + return 0; + } + +diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c +--- a/sound/soc/codecs/msm8916-wcd-analog.c ++++ b/sound/soc/codecs/msm8916-wcd-analog.c +@@ -217,9 +217,11 @@ + #define CDC_A_RX_HPH_BIAS_LDO_OCP (0xf195) + #define CDC_A_RX_HPH_BIAS_CNP (0xf196) + #define CDC_A_RX_HPH_CNP_EN (0xf197) ++#define CDC_A_RX_HPH_L_TEST (0xf19A) + #define CDC_A_RX_HPH_L_PA_DAC_CTL (0xf19B) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_MASK BIT(1) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_RESET BIT(1) ++#define CDC_A_RX_HPH_R_TEST (0xf19C) + #define CDC_A_RX_HPH_R_PA_DAC_CTL (0xf19D) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET BIT(1) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET_MASK BIT(1) +@@ -232,6 +234,8 @@ + #define RX_EAR_CTL_PA_SEL_MASK BIT(7) + #define RX_EAR_CTL_PA_SEL BIT(7) + ++#define CDC_A_RX_EAR_STATUS (0xf1A1) ++ + #define CDC_A_SPKR_DAC_CTL (0xf1B0) + #define SPKR_DAC_CTL_DAC_RESET_MASK BIT(4) + #define SPKR_DAC_CTL_DAC_RESET_NORMAL 0 +@@ -250,6 +254,7 @@ + SPKR_DRV_CAL_EN | SPKR_DRV_SETTLE_EN | \ + SPKR_DRV_FW_EN | SPKR_DRV_BOOST_SET | \ + SPKR_DRV_CMFB_SET | SPKR_DRV_GAIN_SET) ++#define CDC_A_SPKR_ANA_BIAS_SET (0xf1B3) + #define CDC_A_SPKR_OCP_CTL (0xf1B4) + #define CDC_A_SPKR_PWRSTG_CTL (0xf1B5) + #define SPKR_PWRSTG_CTL_DAC_EN_MASK BIT(0) +@@ -264,6 +269,7 @@ + + #define CDC_A_SPKR_DRV_DBG (0xf1B7) + #define CDC_A_CURRENT_LIMIT (0xf1C0) ++#define CDC_A_BYPASS_MODE (0xf1C2) + #define CDC_A_BOOST_EN_CTL (0xf1C3) + #define CDC_A_SLOPE_COMP_IP_ZERO (0xf1C4) + #define CDC_A_SEC_ACCESS (0xf1D0) +@@ -286,6 +292,11 @@ static const char * const supply_names[] = { + + #define MBHC_MAX_BUTTONS (5) + ++struct wcd_reg_seq { ++ const struct reg_default *seq; ++ int seq_size; ++}; ++ + struct pm8916_wcd_analog_priv { + u16 pmic_rev; + u16 codec_version; +@@ -696,6 +707,59 @@ static int pm8916_wcd_analog_enable_ear_pa(struct snd_soc_dapm_widget *w, + return 0; + } + ++static int pm8916_wcd_analog_enable_hphl_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ /* wait 20 ms after the digital codec has powered down */ ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ ++static int pm8916_wcd_analog_enable_hphr_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ + static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_RX_COM_OCP_CTL, 0xD1}, + {CDC_A_RX_COM_OCP_COUNT, 0xFF}, +@@ -715,9 +779,69 @@ static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_MASTER_BIAS_CTL, 0x30}, + }; + ++static const struct wcd_reg_seq pm8916_data = { ++ .seq = wcd_reg_defaults_2_0, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_2_0), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8950[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0x82}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8950_data = { ++ .seq = wcd_reg_defaults_pm8950, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8950), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8953[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0xa2}, ++ {CDC_A_BYPASS_MODE, 0x18}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_RX_EAR_STATUS, 0x10}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8953_data = { ++ .seq = wcd_reg_defaults_pm8953, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8953), ++}; ++ + static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + { + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ const struct wcd_reg_seq *wcd_reg_init_data; + int err, reg; + + err = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +@@ -738,9 +862,11 @@ static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + snd_soc_component_write(component, CDC_D_PERPH_RESET_CTL4, 0x01); + snd_soc_component_write(component, CDC_A_PERPH_RESET_CTL4, 0x01); + +- for (reg = 0; reg < ARRAY_SIZE(wcd_reg_defaults_2_0); reg++) +- snd_soc_component_write(component, wcd_reg_defaults_2_0[reg].reg, +- wcd_reg_defaults_2_0[reg].def); ++ wcd_reg_init_data = of_device_get_match_data(component->dev); ++ ++ for (reg = 0; reg < wcd_reg_init_data->seq_size; reg++) ++ snd_soc_component_write(component, wcd_reg_init_data->seq[reg].reg, ++ wcd_reg_init_data->seq[reg].def); + + priv->component = component; + +@@ -883,11 +1009,15 @@ static const struct snd_soc_dapm_widget pm8916_wcd_analog_dapm_widgets[] = { + SND_SOC_DAPM_MUX("EAR_S", SND_SOC_NOPM, 0, 0, &ear_mux), + SND_SOC_DAPM_SUPPLY("EAR CP", CDC_A_NCP_EN, 4, 0, NULL, 0), + +- SND_SOC_DAPM_PGA("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphl_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHL", SND_SOC_NOPM, 0, 0, &hphl_mux), + SND_SOC_DAPM_MIXER("HPHL DAC", CDC_A_RX_HPH_L_PA_DAC_CTL, 3, 0, NULL, + 0), +- SND_SOC_DAPM_PGA("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphr_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHR", SND_SOC_NOPM, 0, 0, &hphr_mux), + SND_SOC_DAPM_MIXER("HPHR DAC", CDC_A_RX_HPH_R_PA_DAC_CTL, 3, 0, NULL, + 0), +@@ -1259,7 +1389,9 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) + } + + static const struct of_device_id pm8916_wcd_analog_spmi_match_table[] = { +- { .compatible = "qcom,pm8916-wcd-analog-codec", }, ++ { .compatible = "qcom,pm8916-wcd-analog-codec", .data = &pm8916_data }, ++ { .compatible = "qcom,pm8950-wcd-analog-codec", .data = &pm8950_data }, ++ { .compatible = "qcom,pm8953-wcd-analog-codec", .data = &pm8953_data }, + { } + }; + +diff --git a/sound/soc/codecs/mt6357.c b/sound/soc/codecs/mt6357.c +--- a/sound/soc/codecs/mt6357.c ++++ b/sound/soc/codecs/mt6357.c +@@ -1834,8 +1834,8 @@ static int mt6357_platform_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id mt6357_platform_ids[] = { +- {"mt6357-sound", 0}, +- { /* sentinel */ }, ++ { .name = "mt6357-sound" }, ++ { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(platform, mt6357_platform_ids); + +diff --git a/sound/soc/codecs/mt6359-accdet.c b/sound/soc/codecs/mt6359-accdet.c +--- a/sound/soc/codecs/mt6359-accdet.c ++++ b/sound/soc/codecs/mt6359-accdet.c +@@ -6,6 +6,7 @@ + // Author: Argus Lin + // + ++#include + #include + #include + #include +@@ -398,14 +399,13 @@ static void mt6359_accdet_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, accdet_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + priv->pre_accdet_status = priv->accdet_status; + check_jack_btn_type(priv); + + if (priv->jack_plugged && + priv->pre_accdet_status != priv->accdet_status) + mt6359_accdet_jack_report(priv); +- mutex_unlock(&priv->res_lock); + } + + static void mt6359_accdet_jd_work(struct work_struct *work) +@@ -416,7 +416,7 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, jd_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + if (priv->jd_sts == M_PLUG_IN) { + priv->jack_plugged = true; + +@@ -450,7 +450,6 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + + if (priv->caps & ACCDET_PMIC_EINT_IRQ) + recover_eint_setting(priv); +- mutex_unlock(&priv->res_lock); + } + + static irqreturn_t mt6359_accdet_irq(int irq, void *data) +@@ -459,7 +458,7 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + unsigned int irq_val = 0, val = 0, value = 0; + int ret; + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + regmap_read(priv->regmap, ACCDET_IRQ_ADDR, &irq_val); + + if (irq_val & ACCDET_IRQ_MASK_SFT) { +@@ -474,7 +473,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + 1000); + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -498,7 +496,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -521,7 +518,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -540,7 +536,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + + queue_work(priv->jd_workqueue, &priv->jd_work); + } +- mutex_unlock(&priv->res_lock); + + return IRQ_HANDLED; + } +diff --git a/sound/soc/codecs/ntpfw.c b/sound/soc/codecs/ntpfw.c +--- a/sound/soc/codecs/ntpfw.c ++++ b/sound/soc/codecs/ntpfw.c +@@ -89,7 +89,7 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + { + struct device *dev = &i2c->dev; + const struct ntpfw_chunk *chunk; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + const u8 *data; + size_t leftover; + int ret; +@@ -101,10 +101,8 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + return ret; + } + +- if (!ntpfw_verify(dev, fw->data, fw->size, magic)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify(dev, fw->data, fw->size, magic)) ++ return -EINVAL; + + data = fw->data + sizeof(struct ntpfw_header); + leftover = fw->size - sizeof(struct ntpfw_header); +@@ -112,23 +110,18 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + while (leftover) { + chunk = (struct ntpfw_chunk *)data; + +- if (!ntpfw_verify_chunk(dev, chunk, leftover)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify_chunk(dev, chunk, leftover)) ++ return -EINVAL; + + ret = ntpfw_send_chunk(i2c, chunk); + if (ret) +- goto done; ++ return ret; + + data += be16_to_cpu(chunk->length) + sizeof(*chunk); + leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk); + } + +-done: +- release_firmware(fw); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(ntpfw_load); + +diff --git a/sound/soc/codecs/ntpfw.h b/sound/soc/codecs/ntpfw.h +--- a/sound/soc/codecs/ntpfw.h ++++ b/sound/soc/codecs/ntpfw.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0-only */ +-/** ++/* + * ntpfw.h - Firmware helper functions for Neofidelity codecs + * + * Copyright (c) 2024, SaluteDevices. All Rights Reserved. +@@ -13,10 +13,11 @@ + /** + * ntpfw_load - load firmware to amplifier over i2c interface. + * +- * @i2c Pointer to amplifier's I2C client. +- * @name Firmware file name. +- * @magic Magic number to validate firmware. +- * @return 0 or error code upon error. ++ * @i2c: Pointer to amplifier's I2C client. ++ * @name: Firmware file name. ++ * @magic: Magic number to validate firmware. ++ * ++ * Returns: 0 or error code upon error. + */ + int ntpfw_load(struct i2c_client *i2c, const char *name, const u32 magic); + +diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c +--- a/sound/soc/codecs/pcm1681.c ++++ b/sound/soc/codecs/pcm1681.c +@@ -199,10 +199,17 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, + return pcm1681_set_deemph(component); + } + ++static const u64 pcm1681_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1681_dai_ops = { + .set_fmt = pcm1681_set_dai_fmt, + .hw_params = pcm1681_hw_params, + .mute_stream = pcm1681_mute, ++ .auto_selectable_formats = &pcm1681_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm1754.c b/sound/soc/codecs/pcm1754.c +--- a/sound/soc/codecs/pcm1754.c ++++ b/sound/soc/codecs/pcm1754.c +@@ -78,10 +78,16 @@ static int pcm1754_mute_stream(struct snd_soc_dai *dai, int mute, int stream) + return 0; + } + ++static const u64 pcm1754_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm1754_dai_ops = { + .set_fmt = pcm1754_set_dai_fmt, + .hw_params = pcm1754_hw_params, + .mute_stream = pcm1754_mute_stream, ++ .auto_selectable_formats = &pcm1754_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver pcm1754_dai = { +diff --git a/sound/soc/codecs/pcm1789.c b/sound/soc/codecs/pcm1789.c +--- a/sound/soc/codecs/pcm1789.c ++++ b/sound/soc/codecs/pcm1789.c +@@ -164,11 +164,18 @@ static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd, + return ret; + } + ++static const u64 pcm1789_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1789_dai_ops = { + .set_fmt = pcm1789_set_dai_fmt, + .hw_params = pcm1789_hw_params, + .mute_stream = pcm1789_mute, + .trigger = pcm1789_trigger, ++ .auto_selectable_formats = &pcm1789_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c +--- a/sound/soc/codecs/pcm179x.c ++++ b/sound/soc/codecs/pcm179x.c +@@ -142,10 +142,16 @@ static int pcm179x_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm179x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm179x_dai_ops = { + .set_fmt = pcm179x_set_dai_fmt, + .hw_params = pcm179x_hw_params, + .mute_stream = pcm179x_mute, ++ .auto_selectable_formats = &pcm179x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm186x.c b/sound/soc/codecs/pcm186x.c +--- a/sound/soc/codecs/pcm186x.c ++++ b/sound/soc/codecs/pcm186x.c +@@ -473,11 +473,20 @@ static int pcm186x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + return 0; + } + ++static const u64 pcm186x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm186x_dai_ops = { + .set_sysclk = pcm186x_set_dai_sysclk, + .set_tdm_slot = pcm186x_set_tdm_slot, + .set_fmt = pcm186x_set_fmt, + .hw_params = pcm186x_hw_params, ++ .auto_selectable_formats = &pcm186x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver pcm1863_dai = { +diff --git a/sound/soc/codecs/pcm3060.c b/sound/soc/codecs/pcm3060.c +--- a/sound/soc/codecs/pcm3060.c ++++ b/sound/soc/codecs/pcm3060.c +@@ -164,10 +164,18 @@ static int pcm3060_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm3060_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm3060_dai_ops = { + .set_sysclk = pcm3060_set_sysclk, + .set_fmt = pcm3060_set_fmt, + .hw_params = pcm3060_hw_params, ++ .auto_selectable_formats = &pcm3060_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define PCM3060_DAI_RATES_ADC (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ +diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c +--- a/sound/soc/codecs/pcm512x.c ++++ b/sound/soc/codecs/pcm512x.c +@@ -6,7 +6,7 @@ + * Copyright 2014 Linaro Ltd + */ + +- ++#include + #include + #include + #include +@@ -399,10 +399,9 @@ static int pcm512x_digital_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + ucontrol->value.integer.value[0] = !(pcm512x->mute & 0x4); + ucontrol->value.integer.value[1] = !(pcm512x->mute & 0x2); +- mutex_unlock(&pcm512x->mutex); + + return 0; + } +@@ -414,7 +413,7 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + int ret, changed = 0; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if ((pcm512x->mute & 0x4) == (ucontrol->value.integer.value[0] << 2)) { + pcm512x->mute ^= 0x4; +@@ -430,13 +429,10 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- mutex_unlock(&pcm512x->mutex); + return ret; + } + } + +- mutex_unlock(&pcm512x->mutex); +- + return changed; + } + +@@ -1465,7 +1461,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + int ret; + unsigned int mute_det; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if (mute) { + pcm512x->mute |= 0x1; +@@ -1475,7 +1471,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to set digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1488,7 +1484,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1499,18 +1495,24 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + 200, 10000); + } + +-unlock: +- mutex_unlock(&pcm512x->mutex); +- + return ret; + } + ++static const u64 pcm512x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops pcm512x_dai_ops = { + .startup = pcm512x_dai_startup, + .hw_params = pcm512x_hw_params, + .set_fmt = pcm512x_set_fmt, + .mute_stream = pcm512x_mute, + .set_bclk_ratio = pcm512x_set_bclk_ratio, ++ .auto_selectable_formats = &pcm512x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c +--- a/sound/soc/codecs/pcm6240.c ++++ b/sound/soc/codecs/pcm6240.c +@@ -12,6 +12,7 @@ + // Author: Shenghao Ding + // + ++#include + #include + #include + #include +@@ -605,7 +606,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + unsigned int reg = mc->reg; + unsigned int val; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + if (pcm_dev->chip_id == PCM1690) { + ret = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL, +@@ -613,18 +614,18 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read mode err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + val &= PCM1690_REG_MODE_CTRL_DAMS_MSK; + /* Set to wide-range mode, before using vol ctrl. */ + if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { + ucontrol->value.integer.value[0] = -25500; +- goto out; ++ return ret; + } + /* Set to fine mode, before using fine vol ctrl. */ + if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { + ucontrol->value.integer.value[0] = -12750; +- goto out; ++ return ret; + } + } + +@@ -632,15 +633,14 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + + val = (val >> shift) & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; + ucontrol->value.integer.value[0] = val; +-out: +- mutex_unlock(&pcm_dev->codec_lock); ++ + return ret; + } + +@@ -678,7 +678,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + unsigned int val, val_mask; + unsigned int reg = mc->reg; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + val = ucontrol->value.integer.value[0] & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; +@@ -702,7 +702,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + __func__, rc); + else + rc = 1; +- mutex_unlock(&pcm_dev->codec_lock); ++ + return rc; + } + +@@ -1577,10 +1577,10 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + { + struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp); + struct i2c_adapter *adap = pcm_dev->client->adapter; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret, i, j; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + pcm_dev->component = comp; + +@@ -1588,7 +1588,7 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + for (j = 0; j < 2; j++) { + ret = pcmdev_gain_ctrl_add(pcm_dev, i, j); + if (ret < 0) +- goto out; ++ return ret; + } + } + +@@ -1621,21 +1621,17 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + if (ret) { + dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } + + ret = pcmdev_regbin_ready(fw_entry, pcm_dev); + if (ret) { + dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } +- ret = pcmdev_profile_ctrl_add(pcm_dev); +-out: +- release_firmware(fw_entry); + +- mutex_unlock(&pcm_dev->codec_lock); +- return ret; ++ return pcmdev_profile_ctrl_add(pcm_dev); + } + + +@@ -1645,9 +1641,8 @@ static void pcmdevice_comp_remove(struct snd_soc_component *codec) + + if (!pcm_dev) + return; +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_config_info_remove(pcm_dev); +- mutex_unlock(&pcm_dev->codec_lock); + } + + static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = { +@@ -1890,9 +1885,9 @@ static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream) + else + block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type); +- mutex_unlock(&pcm_dev->codec_lock); ++ + return 0; + } + +diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c +--- a/sound/soc/codecs/peb2466.c ++++ b/sound/soc/codecs/peb2466.c +@@ -6,6 +6,7 @@ + // + // Author: Herve Codina + ++#include + #include + #include + #include +@@ -1537,17 +1538,14 @@ static int peb2466_fw_parse(struct snd_soc_component *component, + + static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, component->dev); + if (ret) + return ret; + +- ret = peb2466_fw_parse(component, fw->data, fw->size); +- release_firmware(fw); +- +- return ret; ++ return peb2466_fw_parse(component, fw->data, fw->size); + } + + static int peb2466_component_probe(struct snd_soc_component *component) +@@ -1704,13 +1702,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + * So, a specific cache value is used. + */ + +- mutex_lock(&peb2466->gpio.lock); ++ guard(mutex)(&peb2466->gpio.lock); + + cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg); +- if (!cache) { +- ret = -EINVAL; +- goto end; +- } ++ if (!cache) ++ return -EINVAL; + + tmp = *cache; + tmp &= ~mask; +@@ -1718,14 +1714,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + + ret = regmap_write(peb2466->regmap, xr_reg, tmp); + if (ret) +- goto end; ++ return ret; + + *cache = tmp; +- ret = 0; + +-end: +- mutex_unlock(&peb2466->gpio.lock); +- return ret; ++ return 0; + } + + static int peb2466_chip_gpio_set(struct gpio_chip *c, unsigned int offset, +diff --git a/sound/soc/codecs/pm4125-sdw.c b/sound/soc/codecs/pm4125-sdw.c +--- a/sound/soc/codecs/pm4125-sdw.c ++++ b/sound/soc/codecs/pm4125-sdw.c +@@ -464,10 +464,16 @@ static int __maybe_unused pm4125_sdw_runtime_suspend(struct device *dev) + static int __maybe_unused pm4125_sdw_runtime_resume(struct device *dev) + { + struct pm4125_sdw_priv *priv = dev_get_drvdata(dev); ++ int ret; + + if (priv->regmap) { + regcache_cache_only(priv->regmap, false); +- regcache_sync(priv->regmap); ++ ret = regcache_sync(priv->regmap); ++ if (ret) { ++ regcache_cache_only(priv->regmap, true); ++ regcache_mark_dirty(priv->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c +--- a/sound/soc/codecs/pm4125.c ++++ b/sound/soc/codecs/pm4125.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1295,7 +1296,7 @@ static const struct irq_domain_ops pm4125_domain_ops = { + + static int pm4125_irq_init(struct pm4125_priv *pm4125, struct device *dev) + { +- pm4125->virq = irq_domain_add_linear(NULL, 1, &pm4125_domain_ops, NULL); ++ pm4125->virq = irq_domain_create_linear(NULL, 1, &pm4125_domain_ops, NULL); + if (!(pm4125->virq)) { + dev_err(dev, "%s: Failed to add IRQ domain\n", __func__); + return -EINVAL; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -784,7 +784,12 @@ static int rt1017_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1017->regmap, false); +- regcache_sync(rt1017->regmap); ++ ret = regcache_sync(rt1017->regmap); ++ if (ret) { ++ regcache_cache_only(rt1017->regmap, true); ++ regcache_mark_dirty(rt1017->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c +--- a/sound/soc/codecs/rt1316-sdw.c ++++ b/sound/soc/codecs/rt1316-sdw.c +@@ -756,7 +756,12 @@ static int rt1316_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1316->regmap, false); +- regcache_sync(rt1316->regmap); ++ ret = regcache_sync(rt1316->regmap); ++ if (ret) { ++ regcache_cache_only(rt1316->regmap, true); ++ regcache_mark_dirty(rt1316->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -830,7 +830,12 @@ static int rt1318_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1318->regmap, false); +- regcache_sync(rt1318->regmap); ++ ret = regcache_sync(rt1318->regmap); ++ if (ret) { ++ regcache_cache_only(rt1318->regmap, true); ++ regcache_mark_dirty(rt1318->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -410,6 +410,402 @@ static const struct reg_sequence rt1321_blind_write[] = { + { 0x41001988, 0x03 }, + }; + ++static const struct reg_sequence rt1321_va1_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x0000d73c, 0xb7 }, ++ { 0x0000d73d, 0xd7 }, ++ { 0x0000d73e, 0x00 }, ++ { 0x0000d73f, 0x10 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2df80, 0x00 }, ++ { 0x3fc2df81, 0x00 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x0000d541, 0x40 }, ++ { 0x0000d486, 0x43 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x20009012, 0x00 }, ++ { 0x0000dd08, 0x17 }, ++ { 0x0000dd09, 0x0e }, ++ { 0x0000dd0a, 0x17 }, ++ { 0x0000dd0b, 0x0e }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000db00, 0x00 }, ++ { 0x0000db01, 0x00 }, ++ { 0x0000db02, 0x73 }, ++ { 0x0000db03, 0x00 }, ++ { 0x0000db04, 0x00 }, ++ { 0x0000db05, 0x00 }, ++ { 0x0000db06, 0x00 }, ++ { 0x0000db07, 0x00 }, ++ { 0x0000db08, 0x7f }, ++ { 0x0000db09, 0x00 }, ++ { 0x0000db1a, 0x00 }, ++ { 0x0000db1b, 0x00 }, ++ { 0x0000db19, 0x00 }, ++}; ++ ++static const struct reg_sequence rt1321_va2_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x10008000, 0x67 }, ++ { 0x10008001, 0x80 }, ++ { 0x10008002, 0x00 }, ++ { 0x10008003, 0x00 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x0000d486, 0x43 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfa3, 0x00 }, ++ { 0x3fc2dfa2, 0x00 }, ++ { 0x3fc2dfa1, 0x00 }, ++ { 0x3fc2dfa0, 0x00 }, ++ { 0x3fc2df80, 0x10 }, ++ { 0x3fc2df81, 0x20 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x3fc2df84, 0x50 }, ++ { 0x3fc2df85, 0x19 }, ++ { 0x3fc2df86, 0x00 }, ++ { 0x3fc2df87, 0x00 }, ++ { 0x3fc2df88, 0x52 }, ++ { 0x3fc2df89, 0x23 }, ++ { 0x3fc2df8a, 0x00 }, ++ { 0x3fc2df8b, 0x00 }, ++ { 0x3fc2df8c, 0xe0 }, ++ { 0x3fc2df8d, 0x2e }, ++ { 0x3fc2df8e, 0x00 }, ++ { 0x3fc2df8f, 0x00 }, ++ { 0x3fc2df90, 0xe0 }, ++ { 0x3fc2df91, 0x2e }, ++ { 0x3fc2df92, 0x00 }, ++ { 0x3fc2df93, 0x00 }, ++ { 0x3fc2df94, 0x01 }, ++ { 0x3fc2df95, 0x08 }, ++ { 0x3fc2df96, 0x00 }, ++ { 0x3fc2df97, 0x00 }, ++ { 0x3fc2df40, 0x80 }, ++ { 0x3fc2df41, 0xbb }, ++ { 0x3fc2df42, 0x00 }, ++ { 0x3fc2df43, 0x00 }, ++ { 0x3fc2df44, 0xc0 }, ++ { 0x3fc2df45, 0x99 }, ++ { 0x3fc2df46, 0x01 }, ++ { 0x3fc2df47, 0x00 }, ++ { 0x3fc2df48, 0x00 }, ++ { 0x3fc2df49, 0x00 }, ++ { 0x3fc2df4a, 0x00 }, ++ { 0x3fc2df4b, 0x00 }, ++ { 0x3fc2df4c, 0x00 }, ++ { 0x3fc2df4d, 0x00 }, ++ { 0x3fc2df4e, 0x00 }, ++ { 0x3fc2df4f, 0x00 }, ++ { 0x3fc2df50, 0x01 }, ++ { 0x3fc2df51, 0x00 }, ++ { 0x3fc2df52, 0x00 }, ++ { 0x3fc2df53, 0x00 }, ++ { 0x3fc2df54, 0x01 }, ++ { 0x3fc2df55, 0x00 }, ++ { 0x3fc2df56, 0x00 }, ++ { 0x3fc2df57, 0x00 }, ++ { 0x3fc2df58, 0x00 }, ++ { 0x3fc2df59, 0x00 }, ++ { 0x3fc2df5a, 0x00 }, ++ { 0x3fc2df5b, 0x00 }, ++ { 0x3fc2df5c, 0x01 }, ++ { 0x3fc2df5d, 0x00 }, ++ { 0x3fc2df5e, 0x00 }, ++ { 0x3fc2df5f, 0x00 }, ++ { 0x3fc2df60, 0x00 }, ++ { 0x3fc2df61, 0x00 }, ++ { 0x3fc2df62, 0x00 }, ++ { 0x3fc2df63, 0x00 }, ++ { 0x3fc2df64, 0x00 }, ++ { 0x3fc2df65, 0x00 }, ++ { 0x3fc2df66, 0x00 }, ++ { 0x3fc2df67, 0x10 }, ++ { 0x3fc2df68, 0x01 }, ++ { 0x3fc2df69, 0x00 }, ++ { 0x3fc2df6a, 0x00 }, ++ { 0x3fc2df6b, 0x00 }, ++ { 0x3fc2df6c, 0x01 }, ++ { 0x3fc2df6d, 0x00 }, ++ { 0x3fc2df6e, 0x00 }, ++ { 0x3fc2df6f, 0x00 }, ++ { 0x3fc2df70, 0x04 }, ++ { 0x3fc2df71, 0x00 }, ++ { 0x3fc2df72, 0x00 }, ++ { 0x3fc2df73, 0x00 }, ++ { 0x3fc2df74, 0x01 }, ++ { 0x3fc2df75, 0x00 }, ++ { 0x3fc2df76, 0x00 }, ++ { 0x3fc2df77, 0x00 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000dd0b, 0x0d }, ++ { 0x0000dd0a, 0xff }, ++ { 0x0000dd09, 0x0d }, ++ { 0x0000dd08, 0xff }, ++ { 0x0000c5fb, 0x12 }, ++ { 0x0000c570, 0x08 }, ++}; ++ + static const struct reg_default rt1320_reg_defaults[] = { + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_FU113, RT1320_SDCA_CTL_FU_MUTE, CH_01), 0x01 }, +@@ -440,6 +836,7 @@ static const struct reg_default rt1320_mbq_defaults[] = { + static bool rt1320_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000 ... 0xc086: + case 0xc400 ... 0xc409: + case 0xc480 ... 0xc48f: +@@ -497,10 +894,15 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x20003000 ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x20005800 ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x20009000 ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc000c0 ... 0x3fc2dfc8: + case 0x3fe00000 ... 0x3fe36fff: + /* 0x40801508/0x40801809/0x4080180a/0x40801909/0x4080190a */ +@@ -545,6 +947,7 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000: + case 0xc003: + case 0xc081: +@@ -596,15 +999,20 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + case 0x1000c000 ... 0x1000dfff: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x2000300e ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x2000580e ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x2000900e ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc2ab80 ... 0x3fc2ac4c: + case 0x3fc2b780: + case 0x3fc2bf80 ... 0x3fc2bf83: + case 0x3fc2bfc0 ... 0x3fc2bfc8: +- case 0x3fc2d300 ... 0x3fc2d354: ++ case 0x3fc2d300 ... 0x3fc2d3cc: + case 0x3fc2dfc0 ... 0x3fc2dfc8: + case 0x3fe2e000 ... 0x3fe2e003: + case SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_ACTUAL_POWER_STATE, 0): +@@ -892,6 +1300,42 @@ static int rt1320_check_fw_ready(struct rt1320_sdw_priv *rt1320) + return 0; + } + ++static int rt1320_dspfw_status(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ unsigned int fw_status_addr, fw_ready; ++ unsigned int dspfw_run; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ fw_status_addr = RT1320_DSPFW_STATUS_ADDR; ++ break; ++ case RT1321_DEV_ID: ++ fw_status_addr = RT1321_DSPFW_STATUS_ADDR; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); ++ fw_ready &= 0x1; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_read(rt1320->regmap, 0xf01e, &dspfw_run); ++ dspfw_run &= 0x1; ++ fw_ready = (!dspfw_run && fw_ready); ++ } ++ ++ if (fw_ready) { ++ dev_dbg(dev, "%s, DSP FW was already\n", __func__); ++ return 1; ++ } ++ ++ dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); ++ return 0; ++} ++ + static int rt1320_check_power_state_ready(struct rt1320_sdw_priv *rt1320, enum rt1320_power_state ps) + { + struct device *dev = &rt1320->sdw_slave->dev; +@@ -982,6 +1426,9 @@ static int rt1320_fw_param_protocol(struct rt1320_sdw_priv *rt1320, enum rt1320_ + if (!tempbuf) + return -ENOMEM; + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) ++ paramid += 0x0bff0000; ++ + paramhr.moudleid = 1; + paramhr.commandtype = cmdid; + /* 8 is "sizeof(paramid) + sizeof(paramlength)" */ +@@ -1120,45 +1567,73 @@ static void rt1320_calc_r0(struct rt1320_sdw_priv *rt1320) + l_calir0, l_calir0_lo, r_calir0, r_calir0_lo); + } + ++static int rt1320_pilot_tone_output(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ int l_targetpostgain, r_targetpostgain; ++ unsigned long long factor = (1 << 12); ++ int l_pilotgain[9], r_pilotgain[9]; ++ const int postgain_step = 234; ++ int targetGain; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ targetGain = -320000; ++ break; ++ case RT1321_DEV_ID: ++ targetGain = -420000; ++ if (rt1320->version_id == RT1321_VA0) ++ targetGain = -320000; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 70, &l_pilotgain[0], sizeof(l_pilotgain)); ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 71, &r_pilotgain[0], sizeof(r_pilotgain)); ++ dev_dbg(dev, "%s, LR pilotgain %d, %d\n", __func__, l_pilotgain[2], r_pilotgain[2]); ++ ++ /* calculate pilot tone gain */ ++ l_pilotgain[2] = (l_pilotgain[2] * 10000) / factor; ++ r_pilotgain[2] = (r_pilotgain[2] * 10000) / factor; ++ ++ /* calculate post gain to meet target gain */ ++ l_targetpostgain = abs(targetGain - l_pilotgain[2]) / postgain_step; ++ r_targetpostgain = abs(targetGain - r_pilotgain[2]) / postgain_step; ++ l_targetpostgain = 0xfff - l_targetpostgain; ++ r_targetpostgain = 0xfff - r_targetpostgain; ++ dev_dbg(dev, "%s, LR targetpostgain=0x%x, 0x%x\n", __func__, l_targetpostgain, r_targetpostgain); ++ ++ regmap_write(rt1320->regmap, 0xdd0b, (l_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd0a, l_targetpostgain & 0xff); ++ regmap_write(rt1320->regmap, 0xdd09, (r_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd08, r_targetpostgain & 0xff); ++ ++ return 0; ++} ++ + static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = &rt1320->sdw_slave->dev; + struct rt1320_datafixpoint audfixpoint[2]; + unsigned int reg_c5fb, reg_c570, reg_cd00; +- unsigned int vol_reg[4], fw_ready; ++ unsigned int vol_reg[4]; + unsigned long long l_meanr0, r_meanr0; +- unsigned int fw_status_addr; + int l_re[5], r_re[5]; + int ret, tmp; + unsigned long long factor = (1 << 27); + unsigned short l_advancegain, r_advancegain; + unsigned int delay_s = 7; /* delay seconds for the calibration */ ++ int dspfw_status; + + if (!rt1320->component) + return; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- +- /* set volume 0dB */ + regmap_read(rt1320->regmap, 0xdd0b, &vol_reg[3]); + regmap_read(rt1320->regmap, 0xdd0a, &vol_reg[2]); + regmap_read(rt1320->regmap, 0xdd09, &vol_reg[1]); + regmap_read(rt1320->regmap, 0xdd08, &vol_reg[0]); +- regmap_write(rt1320->regmap, 0xdd0b, 0x0f); +- regmap_write(rt1320->regmap, 0xdd0a, 0xff); +- regmap_write(rt1320->regmap, 0xdd09, 0x0f); +- regmap_write(rt1320->regmap, 0xdd08, 0xff); +- + regmap_read(rt1320->regmap, 0xc5fb, ®_c5fb); + regmap_read(rt1320->regmap, 0xc570, ®_c570); + regmap_read(rt1320->regmap, 0xcd00, ®_cd00); +@@ -1171,9 +1646,8 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); + goto _finish_; + } +@@ -1184,8 +1658,19 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- if (rt1320->dev_id == RT1320_DEV_ID) +- regmap_write(rt1320->regmap, 0xc5fb, 0x00); ++ /* fine tune pilot tone output */ ++ ret = rt1320_pilot_tone_output(rt1320); ++ if (ret < 0) { ++ dev_dbg(dev, "%s, Failed to tune pilot tone output\n", __func__); ++ goto _finish_; ++ } ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x12); ++ } ++ ++ regmap_write(rt1320->regmap, 0xc5fb, 0x00); + regmap_write(rt1320->regmap, 0xc570, 0x0b); + regmap_write(rt1320->regmap, 0xcd00, 0xc5); + +@@ -1246,6 +1731,11 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x03); + ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ } ++ + /* advance gain will be set when R0 load, not here */ + regmap_write(rt1320->regmap, 0xdd0b, vol_reg[3]); + regmap_write(rt1320->regmap, 0xdd0a, vol_reg[2]); +@@ -1296,7 +1786,7 @@ static int rt1320_r0_cali_put(struct snd_kcontrol *kcontrol, + static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + { + struct sdw_slave *slave = rt1320->sdw_slave; +- const struct firmware *patch; ++ const struct firmware *patch __free(firmware) = NULL; + const char *filename; + unsigned int addr, val, min_addr, max_addr; + const unsigned char *ptr; +@@ -1312,6 +1802,9 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + max_addr = 0x10007fff; + break; + case RT1321_DEV_ID: ++ if (rt1320->version_id == RT1321_VA2) ++ return; ++ + filename = RT1321_VA_MCU_PATCH; + min_addr = 0x10008000; + max_addr = 0x10008fff; +@@ -1347,17 +1840,15 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + + if (addr > max_addr || addr < min_addr) { + dev_err(&slave->dev, "%s: the address 0x%x is wrong", __func__, addr); +- goto _exit_; ++ return; + } + if (val > 0xff) { + dev_err(&slave->dev, "%s: the value 0x%x is wrong", __func__, val); +- goto _exit_; ++ return; + } + regmap_write(rt1320->regmap, addr, val); + } + } +-_exit_: +- release_firmware(patch); + } + } + +@@ -1382,30 +1873,17 @@ static void rt1320_vab_preset(struct rt1320_sdw_priv *rt1320) + static void rt1320_t0_load(struct rt1320_sdw_priv *rt1320, unsigned int l_t0, unsigned int r_t0) + { + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int factor = (1 << 22), fw_ready; ++ unsigned int factor = (1 << 22); + int l_t0_data[38], r_t0_data[38]; +- unsigned int fw_status_addr; +- +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } ++ int dspfw_status; + + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_warn(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _exit_; + } +@@ -1444,7 +1922,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + struct device *dev = &rt1320->sdw_slave->dev; + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; +- const struct firmware *rae_fw = NULL; ++ const struct firmware *rae_fw __free(firmware) = NULL; + unsigned int fw_offset; + unsigned char *fw_data; + unsigned char *param_data; +@@ -1483,22 +1961,46 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + request_firmware(&rae_fw, rae_filename, dev); + if (rae_fw) { + +- /* RAE CRC clear */ +- regmap_write(rt1320->regmap, 0xe80b, 0x0f); +- +- /* RAE stop & CRC disable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); +- +- while (--retry) { +- regmap_read(rt1320->regmap, 0xe83f, &value); +- if (value & 0x40) +- break; +- usleep_range(1000, 1100); +- } +- if (!retry && !(value & 0x40)) { +- dev_err(dev, "%s: RAE is not ready to load\n", __func__); +- release_firmware(rae_fw); +- return -ETIMEDOUT; ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0xe80b, 0x0f); ++ /* RAE stop & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0xe83f, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0x2000300e, 0xc0); ++ regmap_write(rt1320->regmap, 0x2000300f, 0x0f); ++ /* RAE stop & Phase sync & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0xfe, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x00); ++ /* check whether write state is ready */ ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0x20003043, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; + } + + dev_dbg(dev, "%s, rae_fw size=0x%zx\n", __func__, rae_fw->size); +@@ -1551,7 +2053,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(rae_fw); + + } else { + dev_err(dev, "%s: Failed to load %s firmware\n", __func__, rae_filename); +@@ -1559,18 +2060,34 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + goto _exit_; + } + +- /* RAE CRC enable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); +- +- /* RAE update */ +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); +- +- /* RAE run */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); +- +- regmap_read(rt1320->regmap, 0xe80b, &value); +- dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); ++ regmap_read(rt1320->regmap, 0xe80b, &value); ++ dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0x30, 0x30); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); ++ /* Phase sync eanble */ ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ break; ++ } + + rt1320->rae_update_done = true; + +@@ -1598,32 +2115,21 @@ struct rt1320_dspfwheader { + + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt1320->component); + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int val, i, fw_offset, fw_ready; +- unsigned int fw_status_addr; ++ unsigned int val, i, fw_offset; + struct rt1320_dspfwheader *fwheader; + struct rt1320_imageinfo *ptr_img; + struct sdw_bpt_section sec[10]; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + unsigned char *fw_data; + bool dev_fw_match = false; + static const char hdr_sig[] = "AFX"; + unsigned int hdr_size = 0; + const char *dmi_vendor, *dmi_product, *dmi_sku; + int len_vendor, len_product, len_sku; ++ unsigned char boot_mode = 0; /* 0: from RAM; 1: from ROM */ ++ unsigned char has_0x3fc00000 = 0; + char filename[512]; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- + dmi_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + dmi_product = dmi_get_system_info(DMI_PRODUCT_NAME); + dmi_sku = dmi_get_system_info(DMI_PRODUCT_SKU); +@@ -1653,17 +2159,12 @@ struct rt1320_dspfwheader { + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (fw_ready) { ++ if (rt1320_dspfw_status(rt1320)) { + dev_dbg(dev, "%s, DSP FW was already\n", __func__); + rt1320->fw_load_done = true; + goto _exit_; + } + +- /* change to IRAM */ +- regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); +- + request_firmware(&fw, filename, dev); + if (fw) { + fwheader = (struct rt1320_dspfwheader *)fw->data; +@@ -1672,7 +2173,6 @@ struct rt1320_dspfwheader { + + if (fwheader->sync != 0x0a1c5679) { + dev_err(dev, "%s: FW sync error\n", __func__); +- release_firmware(fw); + goto _exit_; + } + +@@ -1709,9 +2209,11 @@ struct rt1320_dspfwheader { + dev_fw_match = true; + break; + case RT1321_DEV_ID: +- if (ptr_img->addr == 0x3fc00000) +- if (fw_data[9] == '1') ++ if (ptr_img->addr == 0x3fc00000) { ++ if (fw_data[7] == '1') + dev_fw_match = true; ++ has_0x3fc00000 = 1; ++ } + break; + default: + dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +@@ -1721,6 +2223,22 @@ struct rt1320_dspfwheader { + fw_offset += ptr_img->size; + } + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) { ++ /* ++ * For 1321 VA2, if the FW doesn't include the section for address 0x3fc00000, ++ * it means the FW will boot from ROM and force dev_fw_match to true to download FW by BRA. ++ */ ++ if (!has_0x3fc00000) { ++ boot_mode = 1; ++ dev_fw_match = true; ++ } ++ dev_dbg(dev, "%s: Boot from %s for VA2\n", __func__, (boot_mode ? "ROM" : "RAM")); ++ } ++ ++ /* change to IRAM */ ++ if (!boot_mode) ++ regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); ++ + if (dev_fw_match) { + dev_dbg(dev, "%s, starting BRA downloading FW..\n", __func__); + rt1320->bra_msg.dev_num = rt1320->sdw_slave->dev_num; +@@ -1732,7 +2250,6 @@ struct rt1320_dspfwheader { + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(fw); + + if (!dev_fw_match) { + dev_err(dev, "%s: FW file doesn't match to device\n", __func__); +@@ -1743,24 +2260,39 @@ struct rt1320_dspfwheader { + goto _exit_; + } + +- /* run RAM code */ +- regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); +- val |= 0x8; +- regmap_write(rt1320->regmap, 0x3fc2bfc0, val); +- +- /* clear frame counter */ + switch (rt1320->dev_id) { + case RT1320_DEV_ID: ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2bfc0, val); ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2bfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc8, 0x00); + break; + case RT1321_DEV_ID: ++ if (!boot_mode) { ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2dfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2dfc0, val); ++ } ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2dfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc8, 0x00); ++ /* enable handshake */ ++ regmap_write(rt1320->regmap, 0x3fc2dfc4, 0x00); ++ regmap_write(rt1320->regmap, 0xd470, 0xad); ++ /* minimum phase settings */ ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ regmap_write(rt1320->regmap, 0x20003003, 0x31); ++ regmap_update_bits(rt1320->regmap, 0x20003002, 0x40, 0x00); ++ regmap_write(rt1320->regmap, 0xc5b3, 0x01); ++ regmap_write(rt1320->regmap, 0xc052, 0x11); + break; + } + +@@ -1843,14 +2375,35 @@ static void rt1320_vc_preset(struct rt1320_sdw_priv *rt1320) + + static void rt1321_preset(struct rt1320_sdw_priv *rt1320) + { ++ const struct reg_sequence *blindwrite; + unsigned int i, reg, val, delay; ++ unsigned int array_size; + +- for (i = 0; i < ARRAY_SIZE(rt1321_blind_write); i++) { +- reg = rt1321_blind_write[i].reg; +- val = rt1321_blind_write[i].def; +- delay = rt1321_blind_write[i].delay_us; ++ switch (rt1320->version_id) { ++ case RT1321_VA0: ++ blindwrite = rt1321_blind_write; ++ array_size = ARRAY_SIZE(rt1321_blind_write); ++ break; ++ case RT1321_VA1: ++ blindwrite = rt1321_va1_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va1_blind_write); ++ break; ++ case RT1321_VA2: ++ blindwrite = rt1321_va2_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va2_blind_write); ++ break; ++ default: ++ dev_err(&rt1320->sdw_slave->dev, "%s: Unknown version ID %d\n", ++ __func__, rt1320->version_id); ++ return; ++ } + +- if (reg == 0x3fc2dfc3) ++ for (i = 0; i < array_size; i++) { ++ reg = blindwrite[i].reg; ++ val = blindwrite[i].def; ++ delay = blindwrite[i].delay_us; ++ ++ if (reg == 0x1000cd56) + rt1320_load_mcu_patch(rt1320); + + regmap_write(rt1320->regmap, reg, val); +@@ -1892,6 +2445,24 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) + regmap_read(rt1320->regmap, RT1320_DEV_ID_0, &val); + regmap_read(rt1320->regmap, RT1320_DEV_ID_1, &tmp); + rt1320->dev_id = (val << 8) | tmp; ++ ++ /* This is a workaround that reads the value twice to obtain the correct result. */ ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ val = (val << 8) | tmp; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ if (rt1320->version_id == 0x01) ++ rt1320->version_id = RT1321_VA2; ++ else if (val == RT1321_DEV_HV_VA0_ID) ++ rt1320->version_id = RT1321_VA0; ++ else if (val == RT1321_DEV_HV_VA1_ID) ++ rt1320->version_id = RT1321_VA1; ++ else ++ dev_err(dev, "%s: Unknown version ID 0x%x for RT1321\n", __func__, rt1320->version_id); ++ } + } + + regmap_read(rt1320->regmap, +@@ -2326,25 +2897,12 @@ static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); + static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = regmap_get_device(rt1320->regmap); +- unsigned int fw_status_addr; +- unsigned int fw_ready; ++ int dspfw_status; + int ret = 0; + + if (!rt1320->r0_l_reg || !rt1320->r0_r_reg) + return -EINVAL; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return -EINVAL; +- } +- + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + ret = rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); +@@ -2353,9 +2911,8 @@ static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + goto _timeout_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _timeout_; + } +@@ -2458,7 +3015,7 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!rt1320->hw_init) + return 0; + +- ret = pm_runtime_resume(component->dev); ++ ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + +@@ -2469,6 +3026,8 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!ucontrol->value.integer.value[0]) + rt1320->fw_load_done = false; + ++ pm_runtime_mark_last_busy(component->dev); ++ pm_runtime_put_autosuspend(component->dev); + return 0; + } + +@@ -2819,6 +3378,37 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int rt1320_bus_config(struct sdw_slave *slave, ++ struct sdw_bus_params *params) ++{ ++ struct rt1320_sdw_priv *rt1320 = dev_get_drvdata(&slave->dev); ++ unsigned int clk_base; ++ ++ regmap_read(rt1320->regmap, 0x004d, &clk_base); ++ dev_dbg(&rt1320->sdw_slave->dev, "%s clk_base=%x", __func__, clk_base); ++ ++ switch (clk_base) { ++ case RT1320_CLK_FREQ_19_2_MHZ: ++ case RT1320_CLK_FREQ_24MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x04); ++ regmap_write(rt1320->regmap, 0xc601, 0x83); ++ regmap_write(rt1320->regmap, 0xc602, 0x1f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ case RT1320_CLK_FREQ_24_576MHZ: ++ case RT1320_CLK_FREQ_22_5792MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x07); ++ regmap_write(rt1320->regmap, 0xc601, 0x80); ++ regmap_write(rt1320->regmap, 0xc602, 0x0f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + /* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now +@@ -2826,12 +3416,13 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + static const struct sdw_slave_ops rt1320_slave_ops = { + .read_prop = rt1320_read_prop, + .update_status = rt1320_update_status, ++ .bus_config = rt1320_bus_config, + }; + + static int rt1320_sdw_component_probe(struct snd_soc_component *component) + { +- int ret; + struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); ++ int ret; + + rt1320->component = component; + +@@ -3062,10 +3653,23 @@ static int rt1320_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1320->regmap, false); +- regcache_sync(rt1320->regmap); ++ ret = regcache_sync(rt1320->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt1320->mbq_regmap, false); +- regcache_sync(rt1320->mbq_regmap); ++ ret = regcache_sync(rt1320->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt1320->regmap, true); ++ regcache_cache_only(rt1320->mbq_regmap, true); ++ regcache_mark_dirty(rt1320->regmap); ++ regcache_mark_dirty(rt1320->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt1320_pm = { +diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h +--- a/sound/soc/codecs/rt1320-sdw.h ++++ b/sound/soc/codecs/rt1320-sdw.h +@@ -17,12 +17,17 @@ + + #define RT1320_DEV_ID 0x6981 + #define RT1321_DEV_ID 0x7045 ++#define RT1321_DEV_HV_VA0_ID 0x6997 ++#define RT1321_DEV_HV_VA1_ID 0x7071 + + /* imp-defined registers */ + #define RT1320_DEV_VERSION_ID_1 0xc404 + #define RT1320_DEV_ID_1 0xc405 + #define RT1320_DEV_ID_0 0xc406 + ++#define RT1320_HV_DEV_ID_0 0xf622 ++#define RT1320_HV_DEV_ID_1 0xf623 ++ + #define RT1320_POWER_STATE 0xc560 + + #define RT1321_PATCH_MAIN_VER 0x1000cffe +@@ -94,6 +99,12 @@ enum rt1320_version_id { + RT1320_VC, + }; + ++enum rt1321_version_id { ++ RT1321_VA0, ++ RT1321_VA1, ++ RT1321_VA2, ++}; ++ + #define RT1320_VER_B_ID 0x07392238 + #define RT1320_VAB_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vab.bin" + #define RT1320_VC_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vc.bin" +@@ -121,6 +132,7 @@ struct rt1320_datafixpoint { + int invrs; + }; + ++/* FW parameter id 1300 */ + typedef struct FwPara_HwSwGain { + unsigned int SwAdvGain; + unsigned int SwBasGain; +@@ -163,6 +175,13 @@ enum rt1320_rw_type { + RT1320_PARAM_READ = 3, + }; + ++enum { ++ RT1320_CLK_FREQ_19_2_MHZ = 1, ++ RT1320_CLK_FREQ_24MHZ = 2, ++ RT1320_CLK_FREQ_24_576MHZ = 3, ++ RT1320_CLK_FREQ_22_5792MHZ = 4, ++}; ++ + struct rt1320_sdw_priv { + struct snd_soc_component *component; + struct regmap *regmap; +diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c +--- a/sound/soc/codecs/rt5514-spi.c ++++ b/sound/soc/codecs/rt5514-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -79,17 +80,17 @@ static void rt5514_spi_copy_work(struct work_struct *work) + unsigned int cur_wp, remain_data; + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + if (!period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + + if (rt5514_dsp->buf_size % period_bytes) +@@ -111,7 +112,7 @@ static void rt5514_spi_copy_work(struct work_struct *work) + + if (remain_data < period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + } + +@@ -146,9 +147,6 @@ static void rt5514_spi_copy_work(struct work_struct *work) + snd_pcm_period_elapsed(rt5514_dsp->substream); + + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- +-done: +- mutex_unlock(&rt5514_dsp->dma_lock); + } + + static void rt5514_schedule_copy(struct rt5514_dsp *rt5514_dsp) +@@ -216,7 +214,7 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + snd_soc_component_get_drvdata(component); + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = substream; + rt5514_dsp->dma_offset = 0; + +@@ -225,8 +223,6 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + if (buf[0] & RT5514_IRQ_STATUS_BIT) + rt5514_schedule_copy(rt5514_dsp); + +- mutex_unlock(&rt5514_dsp->dma_lock); +- + return 0; + } + +@@ -236,9 +232,8 @@ static int rt5514_spi_hw_free(struct snd_soc_component *component, + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5514_dsp->dma_lock); +- rt5514_dsp->substream = NULL; +- mutex_unlock(&rt5514_dsp->dma_lock); ++ scoped_guard(mutex, &rt5514_dsp->dma_lock) ++ rt5514_dsp->substream = NULL; + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + +diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c +--- a/sound/soc/codecs/rt5514.c ++++ b/sound/soc/codecs/rt5514.c +@@ -1073,12 +1073,18 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { +- rt5514->dsp_enabled = 0; +- regmap_multi_reg_write(rt5514->i2c_regmap, +- rt5514_i2c_patch, +- ARRAY_SIZE(rt5514_i2c_patch)); ++ ret = regmap_multi_reg_write(rt5514->i2c_regmap, ++ rt5514_i2c_patch, ++ ARRAY_SIZE(rt5514_i2c_patch)); ++ if (ret) ++ return ret; ++ + regcache_mark_dirty(rt5514->regmap); +- regcache_sync(rt5514->regmap); ++ ret = regcache_sync(rt5514->regmap); ++ if (ret) ++ return ret; ++ ++ rt5514->dsp_enabled = 0; + } + } + break; +diff --git a/sound/soc/codecs/rt5575-spi.c b/sound/soc/codecs/rt5575-spi.c +--- a/sound/soc/codecs/rt5575-spi.c ++++ b/sound/soc/codecs/rt5575-spi.c +@@ -93,7 +93,6 @@ static void rt5575_spi_burst_write(struct spi_device *spi, u32 addr, const u8 *t + int rt5575_spi_fw_load(struct spi_device *spi) + { + struct device *dev = &spi->dev; +- const struct firmware *firmware; + int i, ret; + static const char * const fw_path[] = { + "realtek/rt5575/rt5575_fw1.bin", +@@ -104,6 +103,7 @@ int rt5575_spi_fw_load(struct spi_device *spi) + static const u32 fw_addr[] = { 0x5f400000, 0x5f600000, 0x5f7fe000, 0x5f7ff000 }; + + for (i = 0; i < ARRAY_SIZE(fw_addr); i++) { ++ const struct firmware *firmware __free(firmware) = NULL; + ret = request_firmware(&firmware, fw_path[i], dev); + if (ret) { + dev_err(dev, "Request firmware failure: %d\n", ret); +@@ -111,7 +111,6 @@ int rt5575_spi_fw_load(struct spi_device *spi) + } + + rt5575_spi_burst_write(spi, fw_addr[i], firmware->data, firmware->size); +- release_firmware(firmware); + } + + return 0; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -3323,92 +3324,89 @@ static void rt5645_jack_detect_work(struct work_struct *work) + if (!rt5645->component) + return; + +- mutex_lock(&rt5645->jd_mutex); +- +- switch (rt5645->pdata.jd_mode) { +- case 0: /* Not using rt5645 JD */ +- if (rt5645->gpiod_hp_det) { +- gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); +- if (rt5645->pdata.inv_hp_pol) +- gpio_state ^= 1; +- dev_dbg(rt5645->component->dev, "gpio_state = %d\n", +- gpio_state); +- report = rt5645_jack_detect(rt5645->component, gpio_state); +- } +- snd_soc_jack_report(rt5645->hp_jack, +- report, SND_JACK_HEADPHONE); +- snd_soc_jack_report(rt5645->mic_jack, +- report, SND_JACK_MICROPHONE); +- mutex_unlock(&rt5645->jd_mutex); +- return; +- case 4: +- val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; +- break; +- default: /* read rt5645 jd1_1 status */ +- val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; +- break; +- +- } +- +- if (!val && (rt5645->jack_type == 0)) { /* jack in */ +- report = rt5645_jack_detect(rt5645->component, 1); +- } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { +- /* for push button and jack out */ +- btn_type = 0; +- if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { +- /* button pressed */ +- report = SND_JACK_HEADSET; +- btn_type = rt5645_button_detect(rt5645->component); +- /* rt5650 can report three kinds of button behavior, +- one click, double click and hold. However, +- currently we will report button pressed/released +- event. So all the three button behaviors are +- treated as button pressed. */ +- switch (btn_type) { +- case 0x8000: +- case 0x4000: +- case 0x2000: +- report |= SND_JACK_BTN_0; +- break; +- case 0x1000: +- case 0x0800: +- case 0x0400: +- report |= SND_JACK_BTN_1; +- break; +- case 0x0200: +- case 0x0100: +- case 0x0080: +- report |= SND_JACK_BTN_2; +- break; +- case 0x0040: +- case 0x0020: +- case 0x0010: +- report |= SND_JACK_BTN_3; +- break; +- case 0x0000: /* unpressed */ +- break; +- default: +- dev_err(rt5645->component->dev, +- "Unexpected button code 0x%04x\n", +- btn_type); +- break; ++ scoped_guard(mutex, &rt5645->jd_mutex) { ++ switch (rt5645->pdata.jd_mode) { ++ case 0: /* Not using rt5645 JD */ ++ if (rt5645->gpiod_hp_det) { ++ gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); ++ if (rt5645->pdata.inv_hp_pol) ++ gpio_state ^= 1; ++ dev_dbg(rt5645->component->dev, "gpio_state = %d\n", ++ gpio_state); ++ report = rt5645_jack_detect(rt5645->component, gpio_state); + } ++ snd_soc_jack_report(rt5645->hp_jack, ++ report, SND_JACK_HEADPHONE); ++ snd_soc_jack_report(rt5645->mic_jack, ++ report, SND_JACK_MICROPHONE); ++ return; ++ case 4: ++ val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; ++ break; ++ default: /* read rt5645 jd1_1 status */ ++ val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; ++ break; + } +- if (btn_type == 0)/* button release */ +- report = rt5645->jack_type; +- else { +- mod_timer(&rt5645->btn_check_timer, +- msecs_to_jiffies(100)); +- } +- } else { +- /* jack out */ +- report = 0; +- snd_soc_component_update_bits(rt5645->component, +- RT5645_INT_IRQ_ST, 0x1, 0x0); +- rt5645_jack_detect(rt5645->component, 0); +- } + +- mutex_unlock(&rt5645->jd_mutex); ++ if (!val && rt5645->jack_type == 0) { /* jack in */ ++ report = rt5645_jack_detect(rt5645->component, 1); ++ } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { ++ /* for push button and jack out */ ++ btn_type = 0; ++ if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { ++ /* button pressed */ ++ report = SND_JACK_HEADSET; ++ btn_type = rt5645_button_detect(rt5645->component); ++ /* ++ * rt5650 can report three kinds of button behavior, ++ * one click, double click and hold. However, ++ * currently we will report button pressed/released ++ * event. So all the three button behaviors are ++ * treated as button pressed. ++ */ ++ switch (btn_type) { ++ case 0x8000: ++ case 0x4000: ++ case 0x2000: ++ report |= SND_JACK_BTN_0; ++ break; ++ case 0x1000: ++ case 0x0800: ++ case 0x0400: ++ report |= SND_JACK_BTN_1; ++ break; ++ case 0x0200: ++ case 0x0100: ++ case 0x0080: ++ report |= SND_JACK_BTN_2; ++ break; ++ case 0x0040: ++ case 0x0020: ++ case 0x0010: ++ report |= SND_JACK_BTN_3; ++ break; ++ case 0x0000: /* unpressed */ ++ break; ++ default: ++ dev_err(rt5645->component->dev, ++ "Unexpected button code 0x%04x\n", ++ btn_type); ++ break; ++ } ++ } ++ if (btn_type == 0)/* button release */ ++ report = rt5645->jack_type; ++ else ++ mod_timer(&rt5645->btn_check_timer, ++ msecs_to_jiffies(100)); ++ } else { ++ /* jack out */ ++ report = 0; ++ snd_soc_component_update_bits(rt5645->component, ++ RT5645_INT_IRQ_ST, 0x1, 0x0); ++ rt5645_jack_detect(rt5645->component, 0); ++ } ++ } + + snd_soc_jack_report(rt5645->hp_jack, report, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, report, SND_JACK_MICROPHONE); +@@ -3522,9 +3520,15 @@ static int rt5645_suspend(struct snd_soc_component *component) + static int rt5645_resume(struct snd_soc_component *component) + { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + return 0; + } +@@ -4335,9 +4339,15 @@ static int rt5645_sys_suspend(struct device *dev) + static int rt5645_sys_resume(struct device *dev) + { + struct rt5645_priv *rt5645 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + if (rt5645->hp_jack) { + rt5645->jack_type = 0; +diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c +--- a/sound/soc/codecs/rt5665.c ++++ b/sound/soc/codecs/rt5665.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -1208,7 +1209,7 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + val = snd_soc_component_read(rt5665->component, RT5665_AJD1_CTRL) & 0x0010; + if (!val) { +@@ -1274,8 +1275,6 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5665->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5665->jd_check_work); +- +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static const char * const rt5665_clk_sync[] = { +@@ -4564,7 +4563,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + { + int value, count; + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + regcache_cache_bypass(rt5665->regmap, true); + +@@ -4601,7 +4600,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("HP Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4620,7 +4620,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("MONO Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4635,9 +4636,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + regmap_write(rt5665->regmap, RT5665_BIAS_CUR_CTRL_8, 0xa602); + regmap_write(rt5665->regmap, RT5665_ASRC_8, 0x0120); + +-out_unlock: + rt5665->calibration_done = true; +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static void rt5665_calibrate_handler(struct work_struct *work) +diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c +--- a/sound/soc/codecs/rt5668.c ++++ b/sound/soc/codecs/rt5668.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -986,7 +987,7 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + return; + } + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + val = snd_soc_component_read(rt5668->component, RT5668_AJD1_CTRL) + & RT5668_JDH_RS_MASK; +@@ -1053,8 +1054,6 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5668->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5668->jd_check_work); +- +- mutex_unlock(&rt5668->calibrate_mutex); + } + + static const struct snd_kcontrol_new rt5668_snd_controls[] = { +@@ -2356,7 +2355,7 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + { + int value, count; + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + rt5668_reset(rt5668->regmap); + regmap_write(rt5668->regmap, RT5668_PWR_ANLG_1, 0xa2bf); +@@ -2400,9 +2399,6 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + /* restore settings */ + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x0000); +- +- mutex_unlock(&rt5668->calibrate_mutex); +- + } + + static int rt5668_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c +--- a/sound/soc/codecs/rt5677-spi.c ++++ b/sound/soc/codecs/rt5677-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -133,9 +134,8 @@ static int rt5677_spi_hw_params( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = substream; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -147,9 +147,8 @@ static int rt5677_spi_hw_free( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = NULL; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -311,17 +310,17 @@ static void rt5677_spi_copy_work(struct work_struct *work) + int ret = 0; + + /* Ensure runtime->dma_area buffer does not go away while copying. */ +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + if (!rt5677_dsp->substream) { + dev_err(rt5677_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5677_dsp->substream->runtime; + + if (rt5677_spi_mic_write_offset(&mic_write_offset)) { + dev_err(rt5677_dsp->dev, "No mic_write_offset\n"); +- goto done; ++ return; + } + + /* If this is the first time that we've asked for streaming data after +@@ -355,7 +354,7 @@ static void rt5677_spi_copy_work(struct work_struct *work) + ret = rt5677_spi_copy(rt5677_dsp, copy_bytes); + if (ret) { + dev_err(rt5677_dsp->dev, "Copy failed %d\n", ret); +- goto done; ++ return; + } + rt5677_dsp->avail_bytes += copy_bytes; + if (rt5677_dsp->avail_bytes >= period_bytes) { +@@ -367,8 +366,6 @@ static void rt5677_spi_copy_work(struct work_struct *work) + + delay = bytes_to_frames(runtime, period_bytes) / runtime->rate; + schedule_delayed_work(&rt5677_dsp->copy_work, secs_to_jiffies(delay)); +-done: +- mutex_unlock(&rt5677_dsp->dma_lock); + } + + static int rt5677_spi_pcm_new(struct snd_soc_component *component, +@@ -507,10 +504,8 @@ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) + header[3] = ((addr + offset) & 0x0000ff00) >> 8; + header[4] = ((addr + offset) & 0x000000ff) >> 0; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); +- ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + + /* Copy data back to caller buffer */ + rt5677_spi_reverse(cb + offset, len - offset, body, t[1].len); +@@ -564,9 +559,8 @@ int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) + offset += t.len; + t.len += RT5677_SPI_HEADER + 1; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + } + return status; + } +@@ -591,10 +585,10 @@ void rt5677_spi_hotword_detected(void) + return; + } + +- mutex_lock(&rt5677_dsp->dma_lock); +- dev_info(rt5677_dsp->dev, "Hotword detected\n"); +- rt5677_dsp->new_hotword = true; +- mutex_unlock(&rt5677_dsp->dma_lock); ++ scoped_guard(mutex, &rt5677_dsp->dma_lock) { ++ dev_info(rt5677_dsp->dev, "Hotword detected\n"); ++ rt5677_dsp->new_hotword = true; ++ } + + schedule_delayed_work(&rt5677_dsp->copy_work, 0); + } +diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c +--- a/sound/soc/codecs/rt5677.c ++++ b/sound/soc/codecs/rt5677.c +@@ -6,6 +6,8 @@ + * Author: Oder Chiou + */ + ++#include ++#include + #include + #include + #include +@@ -563,46 +565,43 @@ static int rt5677_dsp_mode_i2c_write_addr(struct rt5677_priv *rt5677, + struct snd_soc_component *component = rt5677->component; + int ret; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, + value >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set data msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, + value & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set data lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + opcode); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -622,36 +621,33 @@ static int rt5677_dsp_mode_i2c_read_addr( + int ret; + unsigned int msb, lsb; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + 0x0002); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, &msb); + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, &lsb); + *value = (msb << 16) | lsb; + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -853,11 +849,11 @@ static int rt5677_parse_and_load_dsp(struct rt5677_priv *rt5677, const u8 *buf, + + static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + { +- const struct firmware *fwp; + struct device *dev = rt5677->component->dev; +- int ret = 0; ++ int ret; + + /* Load dsp firmware from rt5677_elf_vad file */ ++ const struct firmware *fwp __free(firmware) = NULL; + ret = request_firmware(&fwp, "rt5677_elf_vad", dev); + if (ret) { + dev_err(dev, "Request rt5677_elf_vad failed %d\n", ret); +@@ -865,9 +861,7 @@ static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + } + dev_info(dev, "Requested rt5677_elf_vad (%zu)\n", fwp->size); + +- ret = rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); +- release_firmware(fwp); +- return ret; ++ return rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); + } + + static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on) +@@ -940,21 +934,20 @@ static void rt5677_dsp_work(struct work_struct *work) + activity = false; + + /* Don't turn off the DSP while handling irqs */ +- mutex_lock(&rt5677->irq_lock); +- /* Set DSP CPU to Stop */ +- regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, +- RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); ++ scoped_guard(mutex, &rt5677->irq_lock) { ++ /* Set DSP CPU to Stop */ ++ regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, ++ RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); + +- rt5677_set_dsp_mode(rt5677, false); ++ rt5677_set_dsp_mode(rt5677, false); + +- /* Disable and clear VAD interrupt */ +- regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); ++ /* Disable and clear VAD interrupt */ ++ regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); + +- /* Set GPIO1 pin back to be IRQ output for jack detect */ +- regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, +- RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); +- +- mutex_unlock(&rt5677->irq_lock); ++ /* Set GPIO1 pin back to be IRQ output for jack detect */ ++ regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, ++ RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); ++ } + } + } + +@@ -4767,6 +4760,21 @@ static int rt5677_gpio_direction_in(struct gpio_chip *chip, unsigned offset) + return rt5677_update_gpio_bits(rt5677, offset, m, v); + } + ++static int rt5677_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) ++{ ++ struct rt5677_priv *rt5677 = gpiochip_get_data(chip); ++ unsigned int shift = RT5677_GPIOx_DIR_SFT + (offset % 5) * 3; ++ unsigned int bank = offset / 5; ++ unsigned int reg = bank ? RT5677_GPIO_CTRL3 : RT5677_GPIO_CTRL2; ++ int ret; ++ ++ ret = regmap_test_bits(rt5677->regmap, reg, BIT(shift)); ++ if (ret < 0) ++ return ret; ++ ++ return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; ++} ++ + /* + * Configures the GPIO as + * 0 - floating +@@ -4834,6 +4842,7 @@ static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) + static const struct gpio_chip rt5677_template_chip = { + .label = RT5677_DRV_NAME, + .owner = THIS_MODULE, ++ .get_direction = rt5677_gpio_get_direction, + .direction_output = rt5677_gpio_direction_out, + .set = rt5677_gpio_set, + .direction_input = rt5677_gpio_direction_in, +@@ -4980,11 +4989,11 @@ static int rt5677_read(void *context, unsigned int reg, unsigned int *val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); ++ } + } else { + rt5677_dsp_mode_i2c_read(rt5677, reg, val); + } +@@ -5002,12 +5011,12 @@ static int rt5677_write(void *context, unsigned int reg, unsigned int val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, +- val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, ++ val); ++ } + } else { + rt5677_dsp_mode_i2c_write(rt5677, reg, val); + } +@@ -5399,7 +5408,7 @@ static void rt5677_resume_irq_check(struct work_struct *work) + * Without this explicit check, unplug the headset right after suspend + * starts, then after resume the headset is still shown as plugged in. + */ +- mutex_lock(&rt5677->irq_lock); ++ guard(mutex)(&rt5677->irq_lock); + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (rt5677->irq_en & rt5677_irq_descs[i].enable_mask) { + virq = irq_find_mapping(rt5677->domain, i); +@@ -5407,7 +5416,6 @@ static void rt5677_resume_irq_check(struct work_struct *work) + handle_nested_irq(virq); + } + } +- mutex_unlock(&rt5677->irq_lock); + } + + static void rt5677_irq_bus_lock(struct irq_data *data) +diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c +--- a/sound/soc/codecs/rt5682-sdw.c ++++ b/sound/soc/codecs/rt5682-sdw.c +@@ -6,6 +6,7 @@ + // Author: Oder Chiou + // + ++#include + #include + #include + #include +@@ -410,7 +411,9 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + if (rt5682->first_hw_init) { + regcache_cache_bypass(rt5682->regmap, false); + regcache_mark_dirty(rt5682->regmap); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) ++ goto err_sync; + + /* volatile registers */ + regmap_update_bits(rt5682->regmap, RT5682_CBJ_CTRL_2, +@@ -472,8 +475,16 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + /* Mark Slave initialization complete */ + rt5682->hw_init = true; + rt5682->first_hw_init = true; ++ goto out; ++ ++err_sync: ++ regcache_cache_bypass(rt5682->regmap, false); ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); + + err_nodev: ++out: + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete: %d\n", __func__, ret); +@@ -660,12 +671,11 @@ static int rt5682_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt5682->disable_irq_lock); ++ guard(mutex)(&rt5682->disable_irq_lock); + if (status->control_port & 0x4 && !rt5682->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(rt5682->irq_work_delay_time)); + } +- mutex_unlock(&rt5682->disable_irq_lock); + + return 0; + } +@@ -736,11 +746,11 @@ static int rt5682_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt5682->disable_irq_lock); +- rt5682->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt5682->disable_irq_lock); ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ rt5682->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -760,12 +770,12 @@ static int rt5682_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt5682->disable_irq_lock); +- if (rt5682->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt5682->disable_irq = false; ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ if (rt5682->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt5682->disable_irq = false; ++ } + } +- mutex_unlock(&rt5682->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT5682_PROBE_TIMEOUT); +@@ -776,7 +786,13 @@ static int rt5682_dev_resume(struct device *dev) + + regcache_cache_only(rt5682->sdw_regmap, false); + regcache_cache_only(rt5682->regmap, false); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) { ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c +--- a/sound/soc/codecs/rt5682.c ++++ b/sound/soc/codecs/rt5682.c +@@ -6,6 +6,7 @@ + // Author: Bard Liao + // + ++#include + #include + #include + #include +@@ -3125,7 +3126,7 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + { + int value, count; + +- mutex_lock(&rt5682->calibrate_mutex); ++ guard(mutex)(&rt5682->calibrate_mutex); + + rt5682_reset(rt5682); + regmap_write(rt5682->regmap, RT5682_I2C_CTRL, 0x000f); +@@ -3175,8 +3176,6 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + regmap_write(rt5682->regmap, RT5682_CALIB_ADC_CTRL, 0x2005); + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5682->regmap, RT5682_CAL_REC, 0x0c0c); +- +- mutex_unlock(&rt5682->calibrate_mutex); + } + EXPORT_SYMBOL_GPL(rt5682_calibrate); + +diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c +--- a/sound/soc/codecs/rt5682s.c ++++ b/sound/soc/codecs/rt5682s.c +@@ -6,6 +6,7 @@ + // Author: Derek Fang + // + ++#include + #include + #include + #include +@@ -648,7 +649,7 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + { + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5682s->sar_mutex); ++ guard(mutex)(&rt5682s->sar_mutex); + + switch (mode) { + case SAR_PWR_SAVING: +@@ -695,8 +696,6 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + dev_err(component->dev, "Invalid SAR Power mode: %d\n", mode); + break; + } +- +- mutex_unlock(&rt5682s->sar_mutex); + } + + static void rt5682s_enable_push_button_irq(struct snd_soc_component *component) +@@ -2534,7 +2533,7 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_FV2 | RT5682S_PWR_MB, +@@ -2556,8 +2555,6 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + + rt5682s->wclk_enabled = 1; + +- mutex_unlock(&rt5682s->wclk_mutex); +- + return 0; + } + +@@ -2570,7 +2567,7 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + if (!rt5682s->jack_type) + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, +@@ -2585,8 +2582,6 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + rt5682s_set_pllb_power(rt5682s, 0); + + rt5682s->wclk_enabled = 0; +- +- mutex_unlock(&rt5682s->wclk_mutex); + } + + static unsigned long rt5682s_wclk_recalc_rate(struct clk_hw *hw, +@@ -2997,7 +2992,7 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + { + unsigned int count, value; + +- mutex_lock(&rt5682s->calibrate_mutex); ++ guard(mutex)(&rt5682s->calibrate_mutex); + + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0xaa80); + usleep_range(15000, 20000); +@@ -3034,8 +3029,6 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + regmap_write(rt5682s->regmap, RT5682S_PWR_DIG_1, 0x00c0); + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0x0800); + regmap_write(rt5682s->regmap, RT5682S_GLB_CLK, 0x0000); +- +- mutex_unlock(&rt5682s->calibrate_mutex); + } + + static const struct regmap_config rt5682s_regmap = { +diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c +--- a/sound/soc/codecs/rt700-sdw.c ++++ b/sound/soc/codecs/rt700-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,12 +416,11 @@ static int rt700_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt700->disable_irq_lock); ++ guard(mutex)(&rt700->disable_irq_lock); + if (status->control_port & 0x4 && !rt700->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt700->disable_irq_lock); + + return 0; + } +@@ -499,11 +499,11 @@ static int rt700_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt700->disable_irq_lock); +- rt700->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt700->disable_irq_lock); ++ scoped_guard(mutex, &rt700->disable_irq_lock) { ++ rt700->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c +--- a/sound/soc/codecs/rt711-sdca-sdw.c ++++ b/sound/soc/codecs/rt711-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,13 +416,13 @@ static int rt711_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711_sdca->disable_irq_lock); +- rt711_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt711_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt711_sdca->disable_irq_lock) { ++ rt711_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -443,13 +444,15 @@ static int rt711_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711-sdca.c b/sound/soc/codecs/rt711-sdca.c +--- a/sound/soc/codecs/rt711-sdca.c ++++ b/sound/soc/codecs/rt711-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -450,7 +451,7 @@ static void rt711_sdca_btn_check_handler(struct work_struct *work) + + static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + { +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + if (rt711->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -515,8 +516,6 @@ static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + + dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt711->calibrate_mutex); + } + + static int rt711_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c +--- a/sound/soc/codecs/rt711-sdw.c ++++ b/sound/soc/codecs/rt711-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -422,12 +423,11 @@ static int rt711_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt711->disable_irq_lock); ++ guard(mutex)(&rt711->disable_irq_lock); + if (status->control_port & 0x4 && !rt711->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt711->disable_irq_lock); + + return 0; + } +@@ -509,11 +509,11 @@ static int rt711_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711->disable_irq_lock); +- rt711->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt711->disable_irq_lock); ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ rt711->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -535,12 +535,12 @@ static int rt711_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c +--- a/sound/soc/codecs/rt711.c ++++ b/sound/soc/codecs/rt711.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -89,24 +90,24 @@ static int rt711_calibration(struct rt711_priv *rt711) + struct regmap *regmap = rt711->regmap; + int ret = 0; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + dev = regmap_get_device(regmap); + + /* Calibration manual mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL, +- 0xf, 0x0); ++ 0xf, 0x0); + + /* trigger */ + rt711_index_update_bits(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, +- RT711_DAC_DC_CALI_TRIGGER); ++ RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, ++ RT711_DAC_DC_CALI_TRIGGER); + + /* wait for calibration process */ + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + + while (val & RT711_DAC_DC_CALI_TRIGGER) { + if (loop >= 500) { +@@ -119,16 +120,15 @@ static int rt711_calibration(struct rt711_priv *rt711) + + usleep_range(10000, 11000); + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + } + + /* depop mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, +- RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); ++ RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); + + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret); + return ret; +@@ -362,24 +362,24 @@ static void rt711_jack_init(struct rt711_priv *rt711) + { + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt711->component); + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + /* power on */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + if (rt711->hs_jack) { + /* unsolicited response & IRQ control */ + regmap_write(rt711->regmap, +- RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); ++ RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); + regmap_write(rt711->regmap, +- RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); ++ RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); + regmap_write(rt711->regmap, +- RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); ++ RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x10, 0x2420); ++ 0x10, 0x2420); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x19, 0x2e11); ++ 0x19, 0x2e11); + + switch (rt711->jd_src) { + case RT711_JD1: +@@ -449,8 +449,7 @@ static void rt711_jack_init(struct rt711_priv *rt711) + /* power off */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + } + + static int rt711_set_jack_detect(struct snd_soc_component *component, +@@ -511,7 +510,7 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + unsigned int read_ll, read_rl; + int i; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; +@@ -599,7 +598,6 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + +- mutex_unlock(&rt711->calibrate_mutex); + return 0; + } + +@@ -908,11 +906,11 @@ static int rt711_set_bias_level(struct snd_soc_component *component, + break; + + case SND_SOC_BIAS_STANDBY: +- mutex_lock(&rt711->calibrate_mutex); +- regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, +- AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ scoped_guard(mutex, &rt711->calibrate_mutex) { ++ regmap_write(rt711->regmap, ++ RT711_SET_AUDIO_POWER_STATE, ++ AC_PWRST_D3); ++ } + break; + + default: +diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c +--- a/sound/soc/codecs/rt712-sdca-dmic.c ++++ b/sound/soc/codecs/rt712-sdca-dmic.c +@@ -912,10 +912,23 @@ static int rt712_sdca_dmic_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt712->regmap, true); ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_mark_dirty(rt712->regmap); ++ regcache_mark_dirty(rt712->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt712_sdca_dmic_pm = { +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -427,13 +428,13 @@ static int rt712_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt712_sdca->disable_irq_lock); +- rt712_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt712_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt712_sdca->disable_irq_lock) { ++ rt712_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -455,14 +456,15 @@ static int rt712_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt712->disable_irq_lock); +- if (rt712->disable_irq == true) { +- +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt712->disable_irq = false; ++ scoped_guard(mutex, &rt712->disable_irq_lock) { ++ if (rt712->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt712->disable_irq = false; ++ } + } +- mutex_unlock(&rt712->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT712_PROBE_TIMEOUT); +@@ -472,9 +474,20 @@ static int rt712_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) { ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -7,6 +7,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -403,7 +404,7 @@ static void rt712_sdca_btn_check_handler(struct work_struct *work) + + static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + { +- mutex_lock(&rt712->calibrate_mutex); ++ guard(mutex)(&rt712->calibrate_mutex); + + if (rt712->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -450,8 +451,6 @@ static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + + dev_dbg(&rt712->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt712->calibrate_mutex); + } + + static int rt712_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -466,13 +467,13 @@ static int rt721_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt721_sdca->disable_irq_lock); +- rt721_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt721_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt721_sdca->disable_irq_lock) { ++ rt721_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -494,13 +495,15 @@ static int rt721_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt721->disable_irq_lock); +- if (rt721->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt721->disable_irq = false; ++ scoped_guard(mutex, &rt721->disable_irq_lock) { ++ if (rt721->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt721->disable_irq = false; ++ } + } +- mutex_unlock(&rt721->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT721_PROBE_TIMEOUT); +@@ -510,9 +513,20 @@ static int rt721_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt721->regmap, false); +- regcache_sync(rt721->regmap); ++ ret = regcache_sync(rt721->regmap); ++ if (ret) { ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt721->mbq_regmap, false); +- regcache_sync(rt721->mbq_regmap); ++ ret = regcache_sync(rt721->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt721->mbq_regmap, true); ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -5,7 +5,7 @@ + // Copyright(c) 2024 Realtek Semiconductor Corp. + // + // +- ++#include + #include + #include + #include +@@ -289,7 +289,7 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) + + static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + { +- mutex_lock(&rt721->calibrate_mutex); ++ guard(mutex)(&rt721->calibrate_mutex); + if (rt721->hs_jack) { + sdw_write_no_pm(rt721->slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0); +@@ -309,7 +309,6 @@ static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x4000, 0x4000); + } +- mutex_unlock(&rt721->calibrate_mutex); + } + + static int rt721_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c +--- a/sound/soc/codecs/rt722-sdca-sdw.c ++++ b/sound/soc/codecs/rt722-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -517,13 +518,13 @@ static int rt722_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt722_sdca->disable_irq_lock); +- rt722_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt722_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt722_sdca->disable_irq_lock) { ++ rt722_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -545,13 +546,15 @@ static int rt722_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt722->disable_irq_lock); +- if (rt722->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt722->disable_irq = false; ++ scoped_guard(mutex, &rt722->disable_irq_lock) { ++ if (rt722->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt722->disable_irq = false; ++ } + } +- mutex_unlock(&rt722->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT722_PROBE_TIMEOUT); +@@ -561,7 +564,13 @@ static int rt722_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt722->regmap, false); +- regcache_sync(rt722->regmap); ++ ret = regcache_sync(rt722->regmap); ++ if (ret) { ++ regcache_cache_only(rt722->regmap, true); ++ regcache_mark_dirty(rt722->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -294,7 +295,7 @@ static void rt722_sdca_btn_check_handler(struct work_struct *work) + + static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + { +- mutex_lock(&rt722->calibrate_mutex); ++ guard(mutex)(&rt722->calibrate_mutex); + if (rt722->hs_jack) { + /* set SCP_SDCA_IntMask1[0]=1 */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK1, +@@ -317,7 +318,6 @@ static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_HDA_CTL, + RT722_GE_RELATED_CTL2, 0x4000, 0x4000); + } +- mutex_unlock(&rt722->calibrate_mutex); + } + + static int rt722_sdca_set_jack_detect(struct snd_soc_component *component, +@@ -352,8 +352,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; + const char *dmi_vendor, *dmi_product, *dmi_sku; +- char *cae_filename; +- const struct firmware *cae_fw = NULL; + unsigned int cae_st_spk, cae_st_hp, cae_st_mic; + unsigned int func, value; + unsigned int combined_val; +@@ -385,7 +383,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + space = strchr(dmi_sku, ' '); + s_len = space ? space - dmi_sku : strlen(dmi_sku); + +- cae_filename = kasprintf(GFP_KERNEL, ++ char *cae_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, + "realtek/rt722/rt722_RAE_%.*s_%.*s_%.*s.dat", + v_len, dmi_vendor, + p_len, dmi_product, +@@ -399,8 +398,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regmap_write(rt722->regmap, RT722_MIC_CAE_PARAM39, 0x5f); + usleep_range(50000, 60000); + ++ const struct firmware *cae_fw __free(firmware) = NULL; + request_firmware(&cae_fw, cae_filename, dev); +- kfree(cae_filename); + if (!cae_fw) { + dev_err(dev, "%s: Failed to load CAE firmware\n", __func__); + return -ENOENT; +@@ -555,7 +554,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regcache_cache_bypass(rt722->regmap, false); + rt722->cae_update_done = 1; + dev_dbg(dev, "%s: CAE FW update done.\n", __func__); +- release_firmware(cae_fw); + return 0; + + verify_abort: +@@ -565,7 +563,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + out_release: + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG, + RT722_MISC_CTRL1, 0x8000, 0x0000); +- release_firmware(cae_fw); + dev_err(dev, "%s: CAE FW update aborted (ret=%d).\n", __func__, ret); + return ret; + } +@@ -1906,6 +1903,9 @@ int rt722_sdca_io_init(struct device *dev, struct sdw_slave *slave) + rt722_sdca_amp_preset(rt722); + rt722_sdca_jack_preset(rt722); + ++ if (rt722->hs_jack && (!rt722->first_hw_init)) ++ rt722_sdca_jack_init(rt722); ++ + if (rt722->first_hw_init) { + regcache_cache_bypass(rt722->regmap, false); + regcache_mark_dirty(rt722->regmap); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -0,0 +1,339 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca-sdw.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt766-sdca-sdw.h" ++ ++#define RT766_PROBE_TIMEOUT 5000 ++ ++static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_MUTE_REG(UAJ, USER_FU41, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_PDE_REQ_REG(UAJ, PDE47): ++ case RT766_PDE_REQ_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_FUNC_STATUS_REG(UAJ): /* 0x40480000 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): /* 0x40481400 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): /* 0x40481480 */ ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): /* 0x40600490 */ ++ case RT766_PDE_REQ_REG(MIC, PDE11): ++ case RT766_MUTE_REG(MIC, USER_FU113, 1): ++ case RT766_MUTE_REG(MIC, USER_FU113, 2): ++ case RT766_MUTE_REG(MIC, USER_FU113, 3): ++ case RT766_MUTE_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_FUNC_STATUS_REG(MIC): /* 0x40880000 */ ++ case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): /* 0x40881500 */ ++ case RT766_FUNC_STATUS_REG(HID): /* 0x40c80000 */ ++ /* 0x40c80080 - 0x40c80098 */ ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_MUTE_REG(AMP, USER_FU21, 1): ++ case RT766_MUTE_REG(AMP, USER_FU21, 2): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ case RT766_PDE_REQ_REG(AMP, PDE23): ++ case RT766_FUNC_STATUS_REG(AMP): /* 0x41080000 */ ++ case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER): ++ case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): /* 0x41081980 */ ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool rt766_sdca_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_FUNC_STATUS_REG(UAJ): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): ++ case RT766_FUNC_STATUS_REG(MIC): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): ++ case RT766_FUNC_STATUS_REG(HID): ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_FUNC_STATUS_REG(AMP): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static int rt766_sdca_mbq_size(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ return 2; ++ default: ++ return 1; ++ } ++} ++ ++static const struct regmap_sdw_mbq_cfg rt766_sdca_mbq_cfg = { ++ .mbq_size = rt766_sdca_mbq_size, ++}; ++ ++static const struct regmap_config rt766_sdca_regmap = { ++ .reg_bits = 32, ++ .val_bits = 16, ++ .readable_reg = rt766_sdca_readable_register, ++ .volatile_reg = rt766_sdca_volatile_register, ++ .reg_defaults = rt766_sdca_defaults, ++ .num_reg_defaults = ARRAY_SIZE(rt766_sdca_defaults), ++ .max_register = SDW_SDCA_MAX_REGISTER, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int rt766_sdca_update_status(struct sdw_slave *slave, ++ enum sdw_slave_status status) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(&slave->dev); ++ ++ if (status == SDW_SLAVE_UNATTACHED) ++ rt766->hw_init = false; ++ ++ if (status == SDW_SLAVE_ATTACHED) { ++ if (rt766->hs_jack) { ++ /* ++ * Due to the SCP_SDCA_INTMASK will be cleared by any reset, and then ++ * if the device attached again, we will need to set the setting back. ++ * It could avoid losing the jack detection interrupt. ++ * This also could sync with the cache value as the rt766_sdca_jack_init set. ++ */ ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24); ++ } ++ } ++ ++ /* ++ * Perform initialization only if slave status is present and ++ * hw_init flag is false ++ */ ++ if (rt766->hw_init || status != SDW_SLAVE_ATTACHED) ++ return 0; ++ ++ /* perform I/O transfers required for Slave initialization */ ++ return rt766_sdca_io_init(&slave->dev, slave); ++} ++ ++static int rt766_sdca_read_prop(struct sdw_slave *slave) ++{ ++ struct sdw_slave_prop *prop = &slave->prop; ++ int ret; ++ ++ ret = sdw_slave_read_prop(slave); ++ if (ret < 0) ++ return ret; ++ ++ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; ++ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; ++ /* ++ * SDCA interrupts are routed through SoundWire domain IRQ. ++ */ ++ prop->use_domain_irq = true; ++ ++ return 0; ++} ++ ++static const struct sdw_slave_ops rt766_sdca_slave_ops = { ++ .read_prop = rt766_sdca_read_prop, ++ .update_status = rt766_sdca_update_status, ++}; ++ ++static int rt766_sdca_sdw_probe(struct sdw_slave *slave, ++ const struct sdw_device_id *id) ++{ ++ struct regmap *regmap; ++ ++ /* Regmap Initialization */ ++ regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, ++ &rt766_sdca_regmap, &rt766_sdca_mbq_cfg); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ return rt766_sdca_init(&slave->dev, regmap, slave); ++} ++ ++static void rt766_sdca_sdw_remove(struct sdw_slave *slave) ++{ ++ pm_runtime_disable(&slave->dev); ++} ++ ++static const struct sdw_device_id rt766_sdca_id[] = { ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ {}, ++}; ++MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); ++ ++static int rt766_sdca_dev_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, true); ++ return 0; ++} ++ ++static int rt766_sdca_dev_system_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ int ret1, ret2; ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ /* ++ * prevent new interrupts from being handled after the ++ * deferred work completes and before the parent disables ++ * interrupts on the link ++ */ ++ mutex_lock(&rt766->disable_irq_lock); ++ rt766->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24, 0); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ if (ret1 < 0 || ret2 < 0) { ++ /* log but don't prevent suspend from happening */ ++ dev_dbg(&slave->dev, "%s: could not disable SDCA interrupts\n:", __func__); ++ } ++ ++ return rt766_sdca_dev_suspend(dev); ++} ++ ++static int rt766_sdca_dev_resume(struct device *dev) ++{ ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ if (!slave->unattach_request) { ++ mutex_lock(&rt766->disable_irq_lock); ++ if (rt766->disable_irq == true) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK3, SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK4, SDW_SCP_SDCA_INTMASK_SDCA_24); ++ rt766->disable_irq = false; ++ } ++ mutex_unlock(&rt766->disable_irq_lock); ++ goto regmap_sync; ++ } ++ ++ ret = sdw_slave_wait_for_init(slave, RT766_PROBE_TIMEOUT); ++ if (ret) { ++ sdw_show_ping_status(slave->bus, true); ++ return ret; ++ } ++ ++regmap_sync: ++ regcache_cache_only(rt766->regmap, false); ++ ret = regcache_sync(rt766->regmap); ++ if (ret) { ++ regcache_cache_only(rt766->regmap, true); ++ regcache_mark_dirty(rt766->regmap); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rt766_sdca_pm = { ++ SYSTEM_SLEEP_PM_OPS(rt766_sdca_dev_system_suspend, rt766_sdca_dev_resume) ++ RUNTIME_PM_OPS(rt766_sdca_dev_suspend, rt766_sdca_dev_resume, NULL) ++}; ++ ++static struct sdw_driver rt766_sdca_sdw_driver = { ++ .driver = { ++ .name = "rt766-sdca", ++ .pm = pm_ptr(&rt766_sdca_pm), ++ }, ++ .probe = rt766_sdca_sdw_probe, ++ .remove = rt766_sdca_sdw_remove, ++ .ops = &rt766_sdca_slave_ops, ++ .id_table = rt766_sdca_id, ++}; ++module_sdw_driver(rt766_sdca_sdw_driver); ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.h b/sound/soc/codecs/rt766-sdca-sdw.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca-sdw.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_SDW_H__ ++#define __RT766_SDW_H__ ++ ++#include ++#include ++ ++static const struct reg_default rt766_sdca_defaults[] = { ++ /* 0x40400289 - 0x4040028a */ ++ { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 }, ++ /* 0x40400291 - 0x40400292 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 }, ++ /* 0x40400789 - 0x4040078a */ ++ { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 }, ++ /* 0x40400791 - 0x40400792 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 }, /* 0x40401408 */ ++ { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 }, /* 0x40401488 */ ++ { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480080 */ ++ { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480880 */ ++ /* 0x40600259 - 0x4060025a */ ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 }, ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 }, ++ { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 }, /* 0x40600488 */ ++ ++ { RT766_PDE_REQ_REG(MIC, PDE11), 0x03 }, /* 0x40801508 */ ++ /* 0x40801809 - 0x4080180c */ ++ { RT766_MUTE_REG(MIC, USER_FU113, 1), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 2), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 3), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 4), 0x01 }, ++ /* 0x40801811 - 0x40801814 */ ++ { RT766_VOLUME_REG(MIC, USER_FU113, 1), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 2), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 3), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 4), 0x0000 }, ++ { RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40880900 */ ++ ++ /* 0x41000189 - 0x4100018a */ ++ { RT766_MUTE_REG(AMP, USER_FU21, 1), 0x01 }, ++ { RT766_MUTE_REG(AMP, USER_FU21, 2), 0x01 }, ++ /* 0x41000191 - 0x41000192 */ ++ { RT766_VOLUME_REG(AMP, USER_FU21, 1), 0x0000 }, ++ { RT766_VOLUME_REG(AMP, USER_FU21, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(AMP, PDE23), 0x03 }, /* 0x41001988 */ ++ { RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0x00 }, /* 0x41080200 */ ++ { RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x41081080 */ ++ ++}; ++ ++#endif /* __RT766_SDW_H__ */ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -0,0 +1,1378 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt-sdw-common.h" ++ ++static int rt766_sdca_btn_detect(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ unsigned char *buf = NULL; ++ unsigned int offset, owner, length; ++ unsigned int det_mode, idx, val; ++ int ret; ++ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ /* get current UMP message owner */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), ++ &owner); ++ if (ret < 0) ++ goto io_error; ++ ++ /* if owner is device then there is no button event from device */ ++ if (owner == 1) ++ return 0; ++ ++ if (det_mode) { ++ /* read UMP message length */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH), ++ &length); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ /* read UMP message offset */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGEOFFSET), ++ &offset); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ buf = devm_kzalloc(&rt766->slave->dev, length, GFP_KERNEL); ++ if (!buf) { ++ dev_err(&rt766->slave->dev, "%s: alloc buf failed\n", __func__); ++ goto _end_btn_det_; ++ } ++ ++ for (idx = 0; idx < length; idx++) { ++ ret = regmap_read(rt766->regmap, ++ RT766_BUF_ADDR_HID1 + offset + idx, &val); ++ if (ret < 0) ++ goto _end_btn_det_; ++ buf[idx] = val & 0xff; ++ } ++ ++ if (rt766->hid) ++ hid_input_report(rt766->hid, HID_INPUT_REPORT, ++ buf, length, 1); ++ } ++ ++_end_btn_det_: ++ if (buf) ++ devm_kfree(&rt766->slave->dev, buf); ++ ++ /* Host is owner, so set back to device */ ++ if (owner == 0) { ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), 0x01); ++ } ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_btn_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_btn_detect(interrupt); ++ mutex_unlock(&rt766->disable_irq_lock); ++ return IRQ_HANDLED; ++} ++ ++static int rt766_sdca_headset_detect(struct rt766_sdca_priv *rt766) ++{ ++ unsigned int det_mode; ++ int ret; ++ ++ /* get detected_mode */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ switch (det_mode) { ++ case 0x00: ++ rt766->jack_type = 0; ++ break; ++ case 0x03: ++ rt766->jack_type = SND_JACK_HEADPHONE; ++ break; ++ case 0x05: ++ rt766->jack_type = SND_JACK_HEADSET; ++ break; ++ } ++ ++ /* write selected_mode */ ++ if (det_mode) { ++ ret = regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), ++ det_mode); ++ if (ret < 0) ++ goto io_error; ++ } ++ ++ dev_dbg(&rt766->slave->dev, ++ "%s, detected_mode=0x%x\n", __func__, det_mode); ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_headset_detect(rt766); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ dev_dbg(&rt766->slave->dev, ++ "in %s, jack_type=%d\n", __func__, rt766->jack_type); ++ ++ snd_soc_jack_report(rt766->hs_jack, rt766->jack_type, SND_JACK_HEADSET); ++ return IRQ_HANDLED; ++} ++ ++static void rt766_sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ hid_destroy_device(rt766->hid); ++} ++ ++static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766, ++ struct sdca_function_data *function, ++ struct snd_soc_component *component, ++ struct sdca_interrupt_info *info, ++ bool enabled) ++{ ++ struct device *dev = &rt766->slave->dev; ++ struct sdca_interrupt *interrupt; ++ struct sdca_control *control; ++ struct sdca_entity *entity; ++ irq_handler_t handler; ++ int i, j, irq, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ for (j = 0; j < entity->num_controls; j++) { ++ control = &entity->controls[j]; ++ irq = control->interrupt_position; ++ ++ switch (SDCA_CTL_TYPE(entity->type, control->sel)) { ++ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): ++ handler = rt766_sdca_irq_jd_handler; ++ break; ++ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ handler = rt766_sdca_irq_btn_handler; ++ break; ++ default: ++ continue; ++ } ++ ++ interrupt = &info->irqs[irq]; ++ ++ if (enabled) { ++ ret = sdca_irq_data_populate(dev, rt766->regmap, component, ++ function, entity, control, ++ interrupt); ++ if (ret) ++ return ret; ++ ++ if (handler == rt766_sdca_irq_btn_handler) { ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ ++ interrupt->free_priv = rt766_sdca_destroy_hid_device; ++ rt766->hid = interrupt->priv; ++ } ++ ++ interrupt->priv = rt766; ++ ret = sdca_irq_request(dev, info, irq, interrupt->name, ++ handler, interrupt); ++ if (ret) { ++ dev_err(dev, "failed to request irq %s: %d\n", ++ interrupt->name, ret); ++ sdca_irq_cleanup_late(dev, function, info); ++ return ret; ++ } ++ dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name); ++ } else { ++ sdca_irq_cleanup_late(dev, function, info); ++ dev_dbg(dev, "Freeing IRQ %d\n", irq); ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, ++ struct snd_soc_jack *hs_jack, void *data) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ if (!rt766->uaj_func_data) { ++ dev_err(&rt766->slave->dev, "The SDCA UAJ function is not supported.\n"); ++ return -EINVAL; ++ } ++ ++ rt766->hs_jack = hs_jack; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ ret = pm_runtime_resume_and_get(component->dev); ++ if (ret < 0) { ++ if (ret != -EACCES) { ++ dev_err(component->dev, "%s: failed to resume %d\n", __func__, ret); ++ return ret; ++ } ++ ++ /* pm_runtime not enabled yet */ ++ dev_dbg(component->dev, "%s: skipping jack init for now\n", __func__); ++ return 0; ++ } ++ ++ /* disable interrupts if hs_jack is not set */ ++ if (!rt766->hs_jack) { ++ if (rt766->uaj_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ rt766->component, rt766->irq_info, false); ++ ++ if (rt766->hid_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ rt766->component, rt766->irq_info, false); ++ } ++ ++ pm_runtime_put_autosuspend(component->dev); ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_fu_ctl(struct rt766_sdca_priv *rt766, int func_num, int fu_num) ++{ ++ unsigned int fu01_reg, fu02_reg; ++ unsigned int ch_01, ch_02; ++ unsigned int ch_mute; ++ unsigned int fu_reg; ++ int err, i; ++ ++ switch (fu_num) { ++ case RT766_SDCA_ENT_USER_FU41: ++ ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU36: ++ ch_01 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU21: ++ ch_01 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU113: ++ for (i = 0; i < ARRAY_SIZE(rt766->fu113_mixer_mute); i++) { ++ ch_mute = (rt766->fu113_dapm_mute || rt766->fu113_mixer_mute[i]) ? 0x01 : 0x00; ++ fu_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1) + i; ++ err = regmap_write(rt766->regmap, fu_reg, ch_mute); ++ if (err < 0) ++ return err; ++ } ++ return 0; ++ } ++ ++ fu01_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1); ++ fu02_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 2); ++ err = regmap_write(rt766->regmap, fu01_reg, ch_01); ++ if (err < 0) ++ return err; ++ err = regmap_write(rt766->regmap, fu02_reg, ch_02); ++ if (err < 0) ++ return err; ++ ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu41_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu41_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu41_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu41_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu41_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu41_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu36_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu36_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu36_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu36_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu36_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu36_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu36_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu36_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu21_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu21_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu21_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu21_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu21_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu21_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu21_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu21_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu113_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu113_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu113_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ const unsigned int interval_offset = 0xc0; ++ unsigned int regvalue, ctl, i; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value); ++ ctl = p->max - (((0x1e00 - regvalue) & 0xffff) / interval_offset); ++ ++ ucontrol->value.integer.value[i] = ctl; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ const unsigned int interval_offset = 0xc0; ++ unsigned int gain_val[4]; ++ unsigned int i, changed = 0; ++ unsigned int regvalue[4]; ++ int err; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value[i]); ++ ++ gain_val[i] = ucontrol->value.integer.value[i]; ++ if (gain_val[i] > p->max) ++ gain_val[i] = p->max; ++ ++ gain_val[i] = 0x1e00 - ((p->max - gain_val[i]) * interval_offset); ++ gain_val[i] &= 0xffff; ++ ++ if (regvalue[i] != gain_val[i]) ++ changed = 1; ++ } ++ ++ if (!changed) ++ return 0; ++ ++ for (i = 0; i < p->count; i++) { ++ err = regmap_write(rt766->regmap, p->reg_base + i, gain_val[i]); ++ if (err < 0) ++ dev_err(&rt766->slave->dev, "0x%08x can't be set\n", p->reg_base + i); ++ } ++ ++ return changed; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ unsigned int i; ++ ++ for (i = 0; i < 4; i++) ++ ucontrol->value.integer.value[i] = !rt766->fu113_mixer_mute[i]; ++ return 0; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err, changed = 0, i; ++ ++ for (i = 0; i < 4; i++) { ++ if (rt766->fu113_mixer_mute[i] != !ucontrol->value.integer.value[i]) ++ changed = 1; ++ rt766->fu113_mixer_mute[i] = !ucontrol->value.integer.value[i]; ++ } ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ if (err < 0) ++ return err; ++ return changed; ++} ++ ++static int rt766_sdca_fu41_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu41_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu41_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event, int func_num, int pde_num, const char *pde_ent) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdca_function_data *func_data; ++ unsigned char ps0 = 0x0, ps3 = 0x3; ++ const struct sdca_entity *entity; ++ unsigned int pde_req_reg; ++ int from_ps, to_ps; ++ int ret; ++ ++ pde_req_reg = SDW_SDCA_CTL(func_num, pde_num, SDCA_CTL_PDE_REQUESTED_PS, 0); ++ ++ switch (func_num) { ++ case RT766_FUNC_NUM_UAJ: ++ func_data = rt766->uaj_func_data; ++ break; ++ case RT766_FUNC_NUM_AMP: ++ func_data = rt766->sa_func_data; ++ break; ++ case RT766_FUNC_NUM_MIC: ++ func_data = rt766->sm_func_data; ++ break; ++ default: ++ dev_err(component->dev, "%s: unsupported func_num %d\n", ++ __func__, func_num); ++ return -EINVAL; ++ } ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ regmap_write(rt766->regmap, pde_req_reg, ps0); ++ from_ps = ps3; ++ to_ps = ps0; ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ regmap_write(rt766->regmap, pde_req_reg, ps3); ++ from_ps = ps0; ++ to_ps = ps3; ++ break; ++ } ++ ++ entity = sdca_find_entity_by_label(func_data, pde_ent); ++ if (!entity) { ++ dev_err(component->dev, "%s: failed to find entity %s\n", ++ __func__, pde_ent); ++ return -EINVAL; ++ } ++ ++ ret = sdca_asoc_pde_poll_actual_ps(rt766->regmap, ++ func_num, ++ pde_num, ++ from_ps, to_ps, ++ entity->pde.max_delay, ++ entity->pde.num_max_delay); ++ if (ret) ++ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ __func__, from_ps, to_ps, ret); ++ ++ return ret; ++} ++ ++static int rt766_sdca_pde47_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE47, "PDE 47"); ++} ++ ++static int rt766_sdca_fu36_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu36_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu36_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde34_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE34, "PDE 34"); ++} ++ ++static int rt766_sdca_fu21_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu21_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu21_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde23_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_PDE23, "PDE 23"); ++} ++ ++static int rt766_sdca_pde11_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_PDE11, "PDE 11"); ++} ++ ++static int rt766_dmic_fu_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ ++ if (p->max == 1) ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; ++ else ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; ++ uinfo->count = p->count; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = p->max; ++ return 0; ++} ++ ++static const char * const rt766_rx_data_ch_select[] = { ++ "L,R", ++ "R,L", ++ "L,L", ++ "R,R", ++ "L,L+R", ++ "R,L+R", ++ "L+R,L", ++ "L+R,R", ++ "L+R,L+R", ++}; ++ ++static SOC_ENUM_SINGLE_DECL(rt766_rx_data_ch_enum, ++ RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0, ++ rt766_rx_data_ch_select); ++ ++static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -9525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(spk_vol_tlv, -6525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); ++static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, -200, 200, 0); ++ ++#define RT766_P75DB_STEP 0xC0 /* 0.75 dB in Q7.8 format */ ++#define RT766_2DB_STEP 0x200 /* 2 dB in Q7.8 format */ ++#define RT766_HP_VOL_MIN (-127) /* -95.25 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MIN (-23) /* -17.25 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MIN (-1) /* -2 dB / 2 dB step */ ++#define RT766_SPK_VOL_MIN (-87) /* -65.25 dB / 0.75 dB step */ ++#define RT766_P_VOL_MAX 0 /* 0 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MAX 40 /* 30 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MAX 20 /* 40 dB / 2 dB step */ ++ ++static const struct snd_kcontrol_new rt766_sdca_controls[] = { ++ SOC_DOUBLE_EXT("FU41 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu41_playback_get, rt766_sdca_fu41_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU41 Playback Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU41, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU41, 2), ++ RT766_HP_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, hp_vol_tlv), ++ SOC_DOUBLE_EXT("FU36 Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu36_capture_get, rt766_sdca_fu36_capture_put), ++ SDCA_DOUBLE_Q78_TLV("FU36 Capture Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU36, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU36, 2), ++ RT766_HS_VOL_MIN, RT766_HS_VOL_MAX, RT766_P75DB_STEP, mic_vol_tlv), ++ SDCA_DOUBLE_Q78_TLV("FU33 Boost Volume", ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), ++ RT766_HS_BOOST_VOL_MIN, RT766_HS_BOOST_VOL_MAX, RT766_2DB_STEP, boost_vol_tlv), ++ ++ SOC_DOUBLE_EXT("FU21 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu21_playback_get, rt766_sdca_fu21_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU21 Playback Volume", ++ RT766_VOLUME_REG(AMP, USER_FU21, 1), ++ RT766_VOLUME_REG(AMP, USER_FU21, 2), ++ RT766_SPK_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, spk_vol_tlv), ++ SOC_ENUM("RX Channel Select", rt766_rx_data_ch_enum), ++ ++ RT_SDCA_FU_CTRL("FU113 Capture Switch", ++ RT766_MUTE_REG(MIC, USER_FU113, 1), 1, 1, 4, rt766_dmic_fu_info, ++ rt766_sdca_dmic_fu113_capture_get, rt766_sdca_dmic_fu113_capture_put), ++ RT_SDCA_EXT_TLV("FU113 Capture Volume", ++ RT766_VOLUME_REG(MIC, USER_FU113, 1), ++ rt766_sdca_dmic_set_gain_get, rt766_sdca_dmic_set_gain_put, ++ 4, 0x3f, mic_vol_tlv, rt766_dmic_fu_info), ++}; ++ ++static const struct snd_soc_dapm_widget rt766_sdca_dapm_widgets[] = { ++ /* UAJ */ ++ SND_SOC_DAPM_OUTPUT("HP"), ++ SND_SOC_DAPM_INPUT("MIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 47", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde47_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 34", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde34_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_DAC_E("FU 41", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu41_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 36", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu36_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("DP12TX", "DP12 Capture", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* AMP */ ++ SND_SOC_DAPM_OUTPUT("SPOL"), ++ SND_SOC_DAPM_OUTPUT("SPOR"), ++ SND_SOC_DAPM_DAC_E("FU 21", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu21_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 23", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde23_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* DMIC */ ++ SND_SOC_DAPM_INPUT("DMIC1"), ++ SND_SOC_DAPM_INPUT("DMIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde11_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 113", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu113_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_OUT("DP8TX", "DP8 Capture", 0, SND_SOC_NOPM, 0, 0), ++}; ++ ++static const struct snd_soc_dapm_route rt766_sdca_audio_map[] = { ++ { "FU 41", NULL, "DP3RX" }, ++ { "DP12TX", NULL, "FU 36" }, ++ { "FU 36", NULL, "PDE 34" }, ++ { "FU 36", NULL, "MIC2" }, ++ { "HP", NULL, "PDE 47" }, ++ { "HP", NULL, "FU 41" }, ++ ++ { "FU 21", NULL, "DP1RX" }, ++ { "FU 21", NULL, "PDE 23" }, ++ { "SPOL", NULL, "FU 21" }, ++ { "SPOR", NULL, "FU 21" }, ++ ++ {"DP8TX", NULL, "FU 113"}, ++ {"FU 113", NULL, "PDE 11"}, ++ {"FU 113", NULL, "DMIC1"}, ++ {"FU 113", NULL, "DMIC2"}, ++}; ++ ++static int rt766_sdca_probe(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct device *dev = &rt766->slave->dev; ++ int ret; ++ ++ rt766->component = component; ++ ++ ret = pm_runtime_resume(component->dev); ++ if (ret < 0 && ret != -EACCES) ++ return ret; ++ ++ if (rt766->uaj_func_data) { ++ dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq); ++ ++ rt766->irq_info = devm_sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq); ++ if (IS_ERR(rt766->irq_info)) ++ return PTR_ERR(rt766->irq_info); ++ ++ ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ ++ if (rt766->hid_func_data) { ++ ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static void rt766_sdca_remove(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ sdca_irq_cleanup_late(component->dev, rt766->uaj_func_data, rt766->irq_info); ++ sdca_irq_cleanup_late(component->dev, rt766->hid_func_data, rt766->irq_info); ++} ++ ++static const struct snd_soc_component_driver soc_sdca_dev_rt766 = { ++ .probe = rt766_sdca_probe, ++ .remove = rt766_sdca_remove, ++ .controls = rt766_sdca_controls, ++ .num_controls = ARRAY_SIZE(rt766_sdca_controls), ++ .dapm_widgets = rt766_sdca_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(rt766_sdca_dapm_widgets), ++ .dapm_routes = rt766_sdca_audio_map, ++ .num_dapm_routes = ARRAY_SIZE(rt766_sdca_audio_map), ++ .set_jack = rt766_sdca_set_jack_detect, ++ .endianness = 1, ++}; ++ ++static int rt766_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, ++ int direction) ++{ ++ snd_soc_dai_dma_data_set(dai, direction, sdw_stream); ++ ++ return 0; ++} ++ ++static void rt766_sdca_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ snd_soc_dai_set_dma_data(dai, substream, NULL); ++} ++ ++static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_config stream_config; ++ struct sdw_port_config port_config; ++ enum sdw_data_direction direction; ++ struct sdw_stream_runtime *sdw_stream; ++ unsigned int sampling_rate; ++ int retval, port; ++ ++ dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); ++ sdw_stream = snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!sdw_stream) ++ return -EINVAL; ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); ++ ++ /* SoundWire specific configuration */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ direction = SDW_DATA_DIR_RX; ++ if (dai->id == RT766_AIF1) ++ port = 3; ++ else if (dai->id == RT766_AIF2) ++ port = 1; ++ else ++ return -EINVAL; ++ } else { ++ direction = SDW_DATA_DIR_TX; ++ if (dai->id == RT766_AIF1) ++ port = 12; ++ else if (dai->id == RT766_AIF3) ++ port = 8; ++ else ++ return -EINVAL; ++ } ++ ++ port_config.num = port; ++ retval = sdw_stream_add_slave(rt766->slave, &stream_config, ++ &port_config, 1, sdw_stream); ++ if (retval) { ++ dev_err(dai->dev, "%s: Unable to configure port\n", __func__); ++ return retval; ++ } ++ ++ if (params_channels(params) > 16) { ++ dev_err(component->dev, "%s: Unsupported channels %d\n", ++ __func__, params_channels(params)); ++ return -EINVAL; ++ } ++ ++ /* sampling rate configuration */ ++ switch (params_rate(params)) { ++ case 44100: ++ sampling_rate = RT766_SDCA_RATE_44100HZ; ++ break; ++ case 48000: ++ sampling_rate = RT766_SDCA_RATE_48000HZ; ++ break; ++ case 96000: ++ sampling_rate = RT766_SDCA_RATE_96000HZ; ++ break; ++ case 192000: ++ sampling_rate = RT766_SDCA_RATE_192000HZ; ++ break; ++ default: ++ dev_err(component->dev, "%s: Rate %d is not supported\n", ++ __func__, params_rate(params)); ++ return -EINVAL; ++ } ++ ++ /* set sampling frequency */ ++ switch (dai->id) { ++ case RT766_AIF1: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF2: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF3: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ default: ++ dev_err(component->dev, "%s: Wrong DAI id\n", __func__); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_runtime *sdw_stream = ++ snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ sdw_stream_remove_slave(rt766->slave, sdw_stream); ++ return 0; ++} ++ ++#define RT766_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ ++ SNDRV_PCM_RATE_192000) ++#define RT766_DAC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) ++#define RT766_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++static const struct snd_soc_dai_ops rt766_sdca_ops = { ++ .hw_params = rt766_sdca_pcm_hw_params, ++ .hw_free = rt766_sdca_pcm_hw_free, ++ .set_stream = rt766_sdca_set_sdw_stream, ++ .shutdown = rt766_sdca_shutdown, ++}; ++ ++static struct snd_soc_dai_driver rt766_sdca_dai[] = { ++ { ++ .name = "rt766-sdca-aif1", ++ .id = RT766_AIF1, ++ .playback = { ++ .stream_name = "DP3 Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .capture = { ++ .stream_name = "DP12 Capture", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ .symmetric_rate = 1, ++ }, ++ { ++ .name = "rt766-sdca-aif2", ++ .id = RT766_AIF2, ++ .playback = { ++ .stream_name = "DP1 Playback", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ }, ++ { ++ .name = "rt766-sdca-aif3", ++ .id = RT766_AIF3, ++ .capture = { ++ .stream_name = "DP8 Capture", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ } ++}; ++ ++static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function, ++ const char *label) ++{ ++ struct snd_soc_pcm_stream stream; ++ struct sdca_entity *entity; ++ int i, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ if (strcmp(entity->label, label)) ++ continue; ++ ++ /* Can't check earlier as only terminals have an iot member. */ ++ if (!entity->iot.is_dataport) ++ continue; ++ ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, &stream); ++ if (ret < 0) { ++ dev_dbg(dev, "%s: failed to parse rates for entity %s\n", ++ __func__, entity->label); ++ return 0; ++ } ++ ++ dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, stream.rates); ++ } ++ ++ return stream.rates; ++} ++ ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave) ++{ ++ struct sdca_function_data *func_data_ptr; ++ struct snd_soc_dai_driver *dai_drv; ++ struct rt766_sdca_priv *rt766; ++ unsigned int rates; ++ int ret; ++ int i; ++ ++ rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL); ++ if (!rt766) ++ return -ENOMEM; ++ ++ dev_set_drvdata(dev, rt766); ++ rt766->slave = slave; ++ rt766->regmap = regmap; ++ ++ regcache_cache_only(rt766->regmap, true); ++ ++ ret = devm_mutex_init(dev, &rt766->disable_irq_lock); ++ if (ret < 0) { ++ dev_err(dev, "Failed to initialize mutex\n"); ++ return ret; ++ } ++ ++ /* ++ * Mark hw_init to false ++ * HW init will be performed when device reports present ++ */ ++ rt766->hw_init = false; ++ rt766->first_hw_init = false; ++ rt766->fu41_dapm_mute = true; ++ rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false; ++ rt766->fu36_dapm_mute = true; ++ rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true; ++ rt766->fu21_dapm_mute = true; ++ rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false; ++ rt766->fu113_dapm_mute = true; ++ rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] = ++ rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true; ++ ++ dai_drv = devm_kzalloc(dev, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai), GFP_KERNEL); ++ if (!dai_drv) { ++ dev_err(dev, "Failed to allocate memory for DAI driver\n"); ++ ret = -ENOMEM; ++ goto _sdw_init_err_; ++ } ++ memcpy(dai_drv, rt766_sdca_dai, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai)); ++ ++ /* get SDCA function data */ ++ dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions); ++ for (i = 0; i < slave->sdca_data.num_functions; i++) { ++ func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL); ++ if (!func_data_ptr) { ++ dev_err(dev, "Failed to allocate memory for function data\n"); ++ ret = -ENOMEM; ++ goto _free_dai_drv_; ++ } ++ ++ func_data_ptr->desc = &slave->sdca_data.function[i]; ++ ret = sdca_parse_function(dev, func_data_ptr); ++ if (ret) { ++ devm_kfree(dev, func_data_ptr); ++ goto _free_dai_drv_; ++ } ++ dev_dbg(dev, "Function type=%d, num_entities=%d", ++ slave->sdca_data.function[i].type, func_data_ptr->num_entities); ++ ++ switch (slave->sdca_data.function[i].type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ rt766->uaj_func_data = func_data_ptr; ++ /* ++ * Some machines may only support a subset of the sample rates supported by the codec. ++ * Therefore, we need to parse the supported sample rates from the DisCo table and ++ * configure them in the DAI. If the DisCo table does not provide sample rate information, ++ * we will fall back to the default supported rates defined in the codec driver. ++ */ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].playback.rates = rates; ++ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ rt766->sa_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21"); ++ if (rates) ++ dai_drv[RT766_DAI_AMP].playback.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ rt766->sm_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113"); ++ if (rates) ++ dai_drv[RT766_DAI_MIC].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ rt766->hid_func_data = func_data_ptr; ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ slave->sdca_data.function[i].type); ++ } ++ } ++ ++ ret = devm_snd_soc_register_component(dev, ++ &soc_sdca_dev_rt766, dai_drv, ARRAY_SIZE(rt766_sdca_dai)); ++ if (ret < 0) ++ goto _free_dai_drv_; ++ ++ /* set autosuspend parameters */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ ++ /* make sure the device does not suspend immediately */ ++ pm_runtime_mark_last_busy(dev); ++ ++ pm_runtime_enable(dev); ++ dev_dbg(dev, "%s\n", __func__); ++ return 0; ++ ++_free_dai_drv_: ++ if (dai_drv) ++ devm_kfree(dev, dai_drv); ++ ++_sdw_init_err_: ++ return ret; ++} ++ ++static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_function_data *func_data) ++{ ++ struct device *dev = &rt766->slave->dev; ++ unsigned int func_status_reg; ++ unsigned int func_status; ++ int ret; ++ ++ switch (func_data->desc->type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ func_status_reg = RT766_FUNC_STATUS_REG(UAJ); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ func_status_reg = RT766_FUNC_STATUS_REG(AMP); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ func_status_reg = RT766_FUNC_STATUS_REG(MIC); ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ func_status_reg = RT766_FUNC_STATUS_REG(HID); ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ func_data->desc->type); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt766->regmap, func_status_reg, &func_status); ++ dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); ++ ++ if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { ++ ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); ++ if (ret) { ++ dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); ++ goto _func_init_err_; ++ } ++ ++ regmap_write(rt766->regmap, func_status_reg, ++ SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION); ++ } ++ ++ return 0; ++ ++_func_init_err_: ++ dev_err(dev, "%s: %s init writes failed, err=%d", __func__, func_data->desc->name, ret); ++ return ret; ++} ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ rt766->disable_irq = false; ++ ++ if (rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, false); ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, true); ++ } else { ++ /* ++ * PM runtime status is marked as 'active' only when a Slave reports as Attached ++ */ ++ ++ /* update count of parent 'active' children */ ++ pm_runtime_set_active(&slave->dev); ++ } ++ ++ pm_runtime_get_noresume(&slave->dev); ++ ++ regmap_read(rt766->regmap, RT766_BOND_LATCH_ID, &val); ++ dev_dbg(&slave->dev, "%s bond ID=0x%x (%s)\n", __func__, val, (val == 0x1) ? "RT767" : "RT766"); ++ ++ /* check function status and initialize if needed */ ++ if (rt766->uaj_func_data) ++ rt766_func_initialize(rt766, rt766->uaj_func_data); ++ if (rt766->sa_func_data) ++ rt766_func_initialize(rt766, rt766->sa_func_data); ++ if (rt766->sm_func_data) ++ rt766_func_initialize(rt766, rt766->sm_func_data); ++ if (rt766->hid_func_data) ++ rt766_func_initialize(rt766, rt766->hid_func_data); ++ ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, false); ++ regcache_mark_dirty(rt766->regmap); ++ } else { ++ rt766->first_hw_init = true; ++ } ++ ++ /* Mark Slave initialization complete */ ++ rt766->hw_init = true; ++ ++ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); ++ ++ pm_runtime_put_autosuspend(&slave->dev); ++ ++ return 0; ++} ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.h +@@ -0,0 +1,140 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_H__ ++#define __RT766_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct rt766_sdca_priv { ++ struct regmap *regmap; ++ struct snd_soc_component *component; ++ struct sdw_slave *slave; ++ bool hw_init; ++ bool first_hw_init; ++ struct snd_soc_jack *hs_jack; ++ struct mutex disable_irq_lock; /* SDCA irq lock protection */ ++ bool disable_irq; ++ int jack_type; ++ bool fu41_dapm_mute; ++ bool fu41_mixer_l_mute; ++ bool fu41_mixer_r_mute; ++ bool fu113_dapm_mute; ++ bool fu113_mixer_mute[4]; ++ bool fu21_dapm_mute; ++ bool fu21_mixer_l_mute; ++ bool fu21_mixer_r_mute; ++ bool fu36_dapm_mute; ++ bool fu36_mixer_l_mute; ++ bool fu36_mixer_r_mute; ++ struct sdca_function_data *uaj_func_data; ++ struct sdca_function_data *sm_func_data; ++ struct sdca_function_data *sa_func_data; ++ struct sdca_function_data *hid_func_data; ++ struct sdca_interrupt_info *irq_info; ++ struct hid_device *hid; ++}; ++ ++/* vendor registers */ ++#define RT766_VERSION_ID 0xc404 ++#define RT766_DEV_ID1 0xc405 ++#define RT766_DEV_ID0 0xc406 ++#define RT766_BOND_LATCH_ID 0xc407 ++ ++#define RT766_HP_POWER_STATE 0x1000004 ++#define RT766_HP_FSM_CTL2_1 0x100000d ++ ++/* MCU Patch address */ ++#define RT766_MCU_PATCH_ADDR1_START 0x10010000 ++#define RT766_MCU_PATCH_ADDR1_END 0x10011fff ++#define RT766_MCU_PATCH_ADDR2_START 0x10020000 ++#define RT766_MCU_PATCH_ADDR2_END 0x10023fff ++ ++/* Buffer address for HID */ ++#define RT766_BUF_ADDR_HID1 0x44030000 ++#define RT766_BUF_ADDR_HID2 0x44030020 ++ ++/* SDCA (Channel) */ ++#define RT766_CH_1 0x01 ++#define RT766_CH_2 0x02 ++#define RT766_CH_3 0x03 ++#define RT766_CH_4 0x04 ++ ++/* RT766 SDCA Control - function number */ ++#define RT766_FUNC_NUM_UAJ 0x01 ++#define RT766_FUNC_NUM_MIC 0x02 ++#define RT766_FUNC_NUM_HID 0x03 ++#define RT766_FUNC_NUM_AMP 0x04 ++ ++/* RT766 SDCA entity */ ++#define RT766_SDCA_ENT_0 0x00 ++#define RT766_SDCA_ENT_HID101 0x01 ++#define RT766_SDCA_ENT_GE49 0x49 ++#define RT766_SDCA_ENT_USER_FU41 0x05 ++#define RT766_SDCA_ENT_USER_FU36 0x0f ++#define RT766_SDCA_ENT_USER_FU21 0x03 ++#define RT766_SDCA_ENT_USER_FU113 0x30 ++#define RT766_SDCA_ENT_PDE23 0x33 ++#define RT766_SDCA_ENT_PDE47 0x28 ++#define RT766_SDCA_ENT_PDE11 0x2a ++#define RT766_SDCA_ENT_PDE34 0x29 ++#define RT766_SDCA_ENT_CS41 0x01 ++#define RT766_SDCA_ENT_CS36 0x11 ++#define RT766_SDCA_ENT_CS113 0x12 ++#define RT766_SDCA_ENT_CS21 0x21 ++#define RT766_SDCA_ENT_PLATFORM_FU33 0x44 ++#define RT766_SDCA_ENT_PPU21 0x04 ++ ++/* sample frequency index */ ++#define RT766_SDCA_RATE_44100HZ 0x08 ++#define RT766_SDCA_RATE_48000HZ 0x09 ++#define RT766_SDCA_RATE_96000HZ 0x0b ++#define RT766_SDCA_RATE_192000HZ 0x0d ++ ++/* SDCA Register macros */ ++#define RT766_MUTE_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_MUTE, RT766_CH_##ch) ++ ++#define RT766_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_CHANNEL_VOLUME, RT766_CH_##ch) ++ ++#define RT766_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_GAIN, RT766_CH_##ch) ++ ++#define RT766_PDE_REQ_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_REQUESTED_PS, 0) ++ ++#define RT766_PDE_ACTUAL_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_ACTUAL_PS, 0) ++ ++#define RT766_FUNC_STATUS_REG(func) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0) ++ ++#define RT766_SDCA_CTL(func, ent, ctl) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##ent, ctl, 0) ++ ++enum { ++ RT766_AIF1, ++ RT766_AIF2, ++ RT766_AIF3, ++}; ++ ++enum { ++ RT766_DAI_UAJ, ++ RT766_DAI_AMP, ++ RT766_DAI_MIC, ++}; ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave); ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave); ++#endif /* __RT766_H__ */ +diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c +--- a/sound/soc/codecs/rt9120.c ++++ b/sound/soc/codecs/rt9120.c +@@ -606,12 +606,19 @@ static int rt9120_runtime_suspend(struct device *dev) + static int rt9120_runtime_resume(struct device *dev) + { + struct rt9120_data *data = dev_get_drvdata(dev); ++ int ret; + + if (data->pwdnn_gpio) { + gpiod_set_value(data->pwdnn_gpio, 1); + msleep(RT9120_CHIPON_WAITMS); + regcache_cache_only(data->regmap, false); +- regcache_sync(data->regmap); ++ ret = regcache_sync(data->regmap); ++ if (ret) { ++ regcache_cache_only(data->regmap, true); ++ regcache_mark_dirty(data->regmap); ++ gpiod_set_value(data->pwdnn_gpio, 0); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c +--- a/sound/soc/codecs/sigmadsp.c ++++ b/sound/soc/codecs/sigmadsp.c +@@ -5,6 +5,7 @@ + * Copyright 2009-2014 Analog Devices Inc. + */ + ++#include + #include + #include + #include +@@ -135,7 +136,7 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + uint8_t *data; + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + data = ucontrol->value.bytes.data; + +@@ -148,8 +149,6 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + ctrl->cached = true; + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -160,7 +159,7 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + if (!ctrl->cached) { + ret = sigmadsp_read(sigmadsp, ctrl->addr, ctrl->cache, +@@ -174,8 +173,6 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + ctrl->num_bytes); + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -487,7 +484,7 @@ static void devm_sigmadsp_release(struct device *dev, void *res) + static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + { + const struct sigma_firmware_header *ssfw_head; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + u32 crc; + +@@ -495,7 +492,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + ret = request_firmware(&fw, name, sigmadsp->dev); + if (ret) { + pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); +- goto done; ++ return ret; + } + + /* then verify the header */ +@@ -509,13 +506,13 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + */ + if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); +- goto done; ++ return -EINVAL; + } + + ssfw_head = (void *)fw->data; + if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); +- goto done; ++ return -EINVAL; + } + + crc = crc32(0, fw->data + sizeof(*ssfw_head), +@@ -524,7 +521,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (crc != le32_to_cpu(ssfw_head->crc)) { + dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", + le32_to_cpu(ssfw_head->crc), crc); +- goto done; ++ return -EINVAL; + } + + switch (ssfw_head->version) { +@@ -545,9 +542,6 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (ret) + sigmadsp_firmware_release(sigmadsp); + +-done: +- release_firmware(fw); +- + return ret; + } + +@@ -677,10 +671,10 @@ static void sigmadsp_activate_ctrl(struct sigmadsp *sigmadsp, + return; + changed = snd_ctl_activate_id(card, &ctrl->kcontrol->id, active); + if (active && changed > 0) { +- mutex_lock(&sigmadsp->lock); +- if (ctrl->cached) +- sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); +- mutex_unlock(&sigmadsp->lock); ++ scoped_guard(mutex, &sigmadsp->lock) { ++ if (ctrl->cached) ++ sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); ++ } + } + } + +diff --git a/sound/soc/codecs/sma1303.c b/sound/soc/codecs/sma1303.c +--- a/sound/soc/codecs/sma1303.c ++++ b/sound/soc/codecs/sma1303.c +@@ -1777,6 +1777,8 @@ static void sma1303_i2c_remove(struct i2c_client *client) + struct sma1303_priv *sma1303 = + (struct sma1303_priv *) i2c_get_clientdata(client); + ++ if (sma1303->attr_grp) ++ sysfs_remove_group(sma1303->kobj, sma1303->attr_grp); + cancel_delayed_work_sync(&sma1303->check_fault_work); + } + +diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c +--- a/sound/soc/codecs/sma1307.c ++++ b/sound/soc/codecs/sma1307.c +@@ -1690,7 +1690,7 @@ static void sma1307_check_fault_worker(struct work_struct *work) + + static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *file) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int size, offset, num_mode; + int ret; + +@@ -1703,22 +1703,18 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil + return; + } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { + dev_err(sma1307->dev, "%s: Invalid file\n", __func__); +- release_firmware(fw); + sma1307->set.status = false; + return; + } + + int *data __free(kfree) = kzalloc(fw->size, GFP_KERNEL); + if (!data) { +- release_firmware(fw); + sma1307->set.status = false; + return; + } + size = fw->size >> 2; + memcpy(data, fw->data, fw->size); + +- release_firmware(fw); +- + /* HEADER */ + sma1307->set.header_size = SMA1307_SETTING_HEADER_SIZE; + sma1307->set.checksum = data[sma1307->set.header_size - 2]; +diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c +--- a/sound/soc/codecs/sta32x.c ++++ b/sound/soc/codecs/sta32x.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -269,9 +270,9 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta32x->coeff_lock); ++ guard(mutex)(&sta32x->coeff_lock); + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); +@@ -283,24 +284,20 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + + regmap_write(sta32x->regmap, STA32X_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; ++ + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta32x->regmap, STA32X_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta32x->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c +--- a/sound/soc/codecs/sta350.c ++++ b/sound/soc/codecs/sta350.c +@@ -16,6 +16,7 @@ + + #define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + ++#include + #include + #include + #include +@@ -306,9 +307,9 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta350->coeff_lock); ++ guard(mutex)(&sta350->coeff_lock); + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); +@@ -320,24 +321,19 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta350->regmap, STA350_CFUD, cfud); + + regmap_write(sta350->regmap, STA350_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta350->regmap, STA350_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta350->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta350_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -88,6 +88,9 @@ + #define TAC_FW_FILE_HDR 20 + #define TAC_MAX_FW_CHUNKS 512 + ++#define TAC_UAJ_PREP_CONNECTED 0xff ++#define TAC_UAJ_PREP_DISCONNECTED 0xdf ++ + struct tac_fw_hdr { + u32 size; + u32 version_offset; +@@ -138,6 +141,7 @@ struct tac5xx2_prv { + bool hw_init; + bool first_hw_init_done; + u32 part_id; ++ u32 rev_id; + struct snd_soc_jack *hs_jack; + int jack_type; + /* Custom fw binary. UMP File Download is not used. */ +@@ -261,7 +265,6 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 0, 0x78), 0x0}, + {TAC_REG_SDW(0, 0, 0x7b), 0x0}, + {TAC_REG_SDW(0, 0, 0x7c), 0xd0}, +- {TAC_REG_SDW(0, 0, 0x7d), 0x0}, + {TAC_REG_SDW(0, 0, 0x7e), 0x0}, + {TAC_REG_SDW(0, 1, 0x1), 0x0}, + {TAC_REG_SDW(0, 1, 0x2), 0x0}, +@@ -507,18 +510,6 @@ static const struct regmap_config tac_regmap = { + .use_single_write = true, + }; + +-/* Check if device has DSP algo that needs status monitoring */ +-static bool tac_has_dsp_algo(struct tac5xx2_prv *tac_dev) +-{ +- switch (tac_dev->part_id) { +- case 0x5682: +- case 0x2883: +- return true; +- default: +- return false; +- } +-} +- + /* Check if device has UAJ (Universal Audio Jack) support */ + static bool tac_has_uaj_support(struct tac5xx2_prv *tac_dev) + { +@@ -800,7 +791,7 @@ static int tac_sdw_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, pde_entity, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, pde_entity, + SDCA_PDE_PS3, SDCA_PDE_PS0, NULL, 0); + if (ret) + dev_err(tac_dev->dev, "failed to transition func %d, pde %d from PS3 -> PS0, err=%d\n", +@@ -847,7 +838,7 @@ static int tac_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, + pde_entity, SDCA_PDE_PS0, SDCA_PDE_PS3, + NULL, 0); + if (ret) +@@ -942,6 +933,7 @@ static int tac5xx2_sdca_button_detect(struct tac5xx2_prv *tac_dev) + static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + { + int val, ret; ++ u8 jack_prep; + + ret = regmap_read(tac_dev->regmap, + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35, +@@ -951,6 +943,8 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + return ret; + } + ++ jack_prep = TAC_UAJ_PREP_CONNECTED; ++ + switch (val) { + case 4: + tac_dev->jack_type = SND_JACK_MICROPHONE; +@@ -964,6 +958,7 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + case 0: + default: + tac_dev->jack_type = 0; ++ jack_prep = TAC_UAJ_PREP_DISCONNECTED; + break; + } + +@@ -973,26 +968,44 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + if (ret) + dev_err(tac_dev->dev, "Failed to update the jack type to device"); + ++ /* ++ * When uaj is uplugged and booted, we end up with the channel prepare ++ * timeout error for the uaj ports. This writes allows the channel prepare ++ * to succeed when the uaj is not plugged in. ++ */ ++ if (tac_dev->rev_id >= 0x30) { ++ ret = regmap_write(tac_dev->regmap, TAC_REG_SDW(0, 3, 127), jack_prep); ++ if (ret) ++ dev_warn(tac_dev->dev, "Failed to write jack_prep register: %d\n", ret); ++ } ++ + return 0; + } + + static int tac5xx2_jack_init(struct tac5xx2_prv *tac_dev) + { ++ u32 jd_int_mask, hid_int_mask; + int ret = 0; + ++ if (tac_dev->rev_id >= 0x30) { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_12; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_17; ++ } else { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_11; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_16; ++ } ++ + if (!tac_dev->hs_jack) + goto disable_interrupts; + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_11); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, jd_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register jack detection interrupt: %d\n", ret); + goto disable_interrupts; + } + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, +- SDW_SCP_SDCA_INTMASK_SDCA_16); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, hid_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register for button detect interrupt: %d\n", ret); +@@ -1051,6 +1064,7 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + unsigned int sdca_int2, sdca_int3, jack_report_mask = 0; + struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev); + struct device *dev = &slave->dev; ++ u32 headset_detect_chk, hid_detect_chk; + int btn_type = 0; + int ret = 0; + +@@ -1081,14 +1095,22 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + dev_dbg(dev, "SDCA_INT2: 0x%02x, SDCA_INT3: 0x%02x\n", + sdca_int2, sdca_int3); + +- if (sdca_int2 & SDW_SCP_SDCA_INT_SDCA_11) { ++ if (tac_dev->rev_id >= 0x30) { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_12; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_17; ++ } else { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_11; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_16; ++ } ++ ++ if (sdca_int2 & headset_detect_chk) { + ret = tac5xx2_sdca_headset_detect(tac_dev); + if (ret < 0) + goto clear; + jack_report_mask |= SND_JACK_HEADSET; + } + +- if (sdca_int3 & SDW_SCP_SDCA_INT_SDCA_16) { ++ if (sdca_int3 & hid_detect_chk) { + btn_type = tac5xx2_sdca_button_detect(tac_dev); + if (btn_type < 0) + btn_type = 0; +@@ -1695,6 +1717,15 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + ++ if (!tac_dev->rev_id) { ++ ret = regmap_read(tac_dev->regmap, TAC_REV_ID, &tac_dev->rev_id); ++ if (ret) { ++ dev_err(tac_dev->dev, "failed to rev id, err=%d\n", ret); ++ goto io_init_err; ++ } ++ dev_dbg(tac_dev->dev, "detected rev_id 0x%x", tac_dev->rev_id); ++ } ++ + if (tac_dev->fw_files && tac_dev->fw_file_cnt > 0) { + ret = tac_download_fw_to_hw(tac_dev); + if (ret) { +@@ -1940,7 +1971,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + "failed to allocate %s function data", + func_name); + function_data->desc = &peripheral->sdca_data.function[i]; +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + *func_ptr = function_data; + else +@@ -1959,6 +1990,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->hw_init = false; + tac_dev->first_hw_init_done = false; + tac_dev->part_id = id->part_id; ++ tac_dev->rev_id = 0x0; + dev_set_drvdata(dev, tac_dev); + + regmap = devm_regmap_init_sdw_mbq_cfg(&peripheral->dev, peripheral, +@@ -1972,17 +2004,13 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->jack_type = 0; + init_completion(&tac_dev->fw_caching_complete); + +- if (tac_has_dsp_algo(tac_dev)) { +- tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, +- sizeof(tac_dev->fw_binaryname)); ++ tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, ++ sizeof(tac_dev->fw_binaryname)); + +- ret = tac_load_and_cache_firmware_async(tac_dev); +- if (ret) { +- complete_all(&tac_dev->fw_caching_complete); +- dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); +- } +- } else { ++ ret = tac_load_and_cache_firmware_async(tac_dev); ++ if (ret) { + complete_all(&tac_dev->fw_caching_complete); ++ dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); + } + + ret = tac_init(tac_dev); +diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c +--- a/sound/soc/codecs/tas2552.c ++++ b/sound/soc/codecs/tas2552.c +@@ -495,13 +495,21 @@ static int tas2552_runtime_suspend(struct device *dev) + static int tas2552_runtime_resume(struct device *dev) + { + struct tas2552_data *tas2552 = dev_get_drvdata(dev); ++ int ret; + + gpiod_set_value_cansleep(tas2552->enable_gpio, 1); + + tas2552_sw_shutdown(tas2552, 0); + + regcache_cache_only(tas2552->regmap, false); +- regcache_sync(tas2552->regmap); ++ ret = regcache_sync(tas2552->regmap); ++ if (ret) { ++ regcache_cache_only(tas2552->regmap, true); ++ regcache_mark_dirty(tas2552->regmap); ++ tas2552_sw_shutdown(tas2552, 1); ++ gpiod_set_value_cansleep(tas2552->enable_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c +--- a/sound/soc/codecs/tas2781-comlib-i2c.c ++++ b/sound/soc/codecs/tas2781-comlib-i2c.c +@@ -6,6 +6,7 @@ + // + // Author: Shenghao Ding + ++#include + #include + #include + #include +@@ -342,7 +343,7 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + /* Codec Lock Hold to ensure that codec_probe and firmware parsing and + * loading do not simultaneously execute. + */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + + if (tas_priv->name_prefix) + scnprintf(tas_priv->rca_binaryname, 64, "%s-%sRCA%d.bin", +@@ -360,8 +361,6 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n", + ret); + +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); + return ret; + } + EXPORT_SYMBOL_GPL(tascodec_init); +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -2253,7 +2253,7 @@ int tas2781_load_calibration(void *context, char *file_name, + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + struct tasdevice_fw *tas_fmw; + struct firmware fmw; + int offset = 0; +@@ -2263,60 +2263,50 @@ int tas2781_load_calibration(void *context, char *file_name, + if (ret) { + dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", + __func__, file_name); +- goto out; ++ return ret; + } + + if (!fw_entry->size) { + dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", + __func__, (unsigned long)fw_entry->size); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + fmw.size = fw_entry->size; + fmw.data = fw_entry->data; + + tas_fmw = tasdev->cali_data_fmw = kzalloc_obj(struct tasdevice_fw); +- if (!tasdev->cali_data_fmw) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!tasdev->cali_data_fmw) ++ return -ENOMEM; ++ + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, "fw_parse_header EXIT!\n"); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_variable_hdr_cal(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, + "%s: fw_parse_variable_header_cal EXIT!\n", __func__); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_program_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_program_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_configuration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_configuration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_calibration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_calibration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + +-out: +- release_firmware(fw_entry); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2409,7 +2399,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, + int tasdevice_dsp_parser(void *context) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; +- const struct firmware *fw_entry; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, +@@ -2417,15 +2407,10 @@ int tasdevice_dsp_parser(void *context) + if (ret) { + dev_err(tas_priv->dev, "%s: load %s error\n", __func__, + tas_priv->coef_binaryname); +- goto out; ++ return ret; + } + +- ret = tasdevice_dspfw_ready(fw_entry, tas_priv); +- release_firmware(fw_entry); +- fw_entry = NULL; +- +-out: +- return ret; ++ return tasdevice_dspfw_ready(fw_entry, tas_priv); + } + EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2807,11 +2792,12 @@ int tasdevice_prmg_load(void *context, int prm_no) + } + EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_load, "SND_SOC_TAS2781_FMWLIB"); + +-void tasdevice_tuning_switch(void *context, int state) ++void tasdevice_tuning_switch(void *context, int state, bool is_cap) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; +- int profile_cfg_id = tas_priv->rcabin.profile_cfg_id; ++ int profile_cfg_id = is_cap ? tas_priv->rcabin.capture_profile_id : ++ tas_priv->rcabin.profile_cfg_id; + + /* + * Only RCA-based Playback can still work with no dsp program running +@@ -2828,7 +2814,6 @@ void tasdevice_tuning_switch(void *context, int state) + if (state == 0) { + if (tas_fmw && tas_priv->cur_prog < tas_fmw->nr_programs) { + /* dsp mode or tuning mode */ +- profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + tasdevice_select_tuningprm_cfg(tas_priv, + tas_priv->cur_prog, tas_priv->cur_conf, + profile_cfg_id); +diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c +--- a/sound/soc/codecs/tas2781-i2c.c ++++ b/sound/soc/codecs/tas2781-i2c.c +@@ -13,6 +13,7 @@ + // Author: Kevin Lu + // + ++#include + #include + #include + #include +@@ -617,6 +618,7 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); ++ int i; + + guard(mutex)(&priv->codec_lock); + if (priv->chip_id == TAS2563) +@@ -624,6 +626,14 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + else + tas2781_calib_stop_put(priv); + ++ /* ++ * Set reloading-firmware flag after calibration, the flag will work ++ * during next playback, then set to the program id after reloading ++ * firmware. ++ */ ++ for (i = 0; i < priv->ndev; i++) ++ priv->tasdevice[i].cur_prog = -1; ++ + return 1; + } + +@@ -843,12 +853,12 @@ static int tasdevice_digital_gain_get( + unsigned char data[4]; + int ret; + +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- goto out; ++ return ret; + } + + target = get_unaligned_be32(&data[0]); +@@ -868,8 +878,7 @@ static int tasdevice_digital_gain_get( + /* find out the member same as or closer to the current volume */ + ucontrol->value.integer.value[0] = + abs(target - ar_l) <= abs(target - ar_r) ? l : r; +-out: +- mutex_unlock(&tas_dev->codec_lock); ++ + return 0; + } + +@@ -882,29 +891,26 @@ static int tasdevice_digital_gain_put( + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_dev = snd_soc_component_get_drvdata(codec); + int vol = ucontrol->value.integer.value[0]; +- int status = 0, max = mc->max, rc = 1; ++ int status = 0, max = mc->max; + int i, ret; + unsigned int reg = mc->reg; + unsigned int volrd, volwr; + unsigned char data[4]; + + vol = clamp(vol, 0, max); +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- rc = -1; +- goto out; ++ return -1; + } + + volrd = get_unaligned_be32(&data[0]); + volwr = get_unaligned_be32(tas_dev->dvc_tlv_table[vol]); + +- if (volrd == volwr) { +- rc = 0; +- goto out; +- } ++ if (volrd == volwr) ++ return 0; + + for (i = 0; i < tas_dev->ndev; i++) { + ret = tasdevice_dev_bulk_write(tas_dev, i, reg, +@@ -918,10 +924,9 @@ static int tasdevice_digital_gain_put( + } + + if (status) +- rc = -1; +-out: +- mutex_unlock(&tas_dev->codec_lock); +- return rc; ++ return -1; ++ ++ return 1; + } + + static const struct snd_kcontrol_new tasdevice_cali_controls[] = { +@@ -995,6 +1000,57 @@ static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + return ret; + } + ++/** ++ * tasdevice_set_capture_profile_id - Set runtime capture profile index via ++ * ALSA control ++ * @kcontrol: ALSA kcontrol handle that triggers this operation ++ * @ucontrol: User space control value carrying the new profile index ++ * ++ * This mixer control handler validates the user-provided capture profile ID ++ * against the maximum valid index parsed from the loaded DSP firmware, ++ * then updates the runtime stored capture profile ID only if the new value ++ * differs from the current active one. It will immediately return -EINVAL ++ * if the submitted profile ID falls outside the valid range, including the ++ * edge case that no valid configuration blocks are detected in firmware. ++ * ++ * No actual DSP register write is performed in this handler. The updated ++ * profile ID will be applied to the hardware when the next ALSA capture ++ * stream starts up. Caller does not need to take extra codec lock here, ++ * as the ALSA control core already guarantees serialized execution. ++ * ++ * Return: 1 if profile ID value was changed, 0 if no modification needed, ++ * -EINVAL if the input profile ID is out of valid range ++ */ ++static int tasdevice_set_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int user_prof_id = ucontrol->value.integer.value[0]; ++ unsigned int max_valid_id; ++ int ret = 0; ++ ++ /* ++ * Align valid range with the bound defined in ++ * tasdevice_info_profile() ++ */ ++ max_valid_id = tas_priv->rcabin.ncfgs - 1; ++ ++ /* ++ * Reject invalid input including zero total configuration edge ++ * case ++ */ ++ if (tas_priv->rcabin.ncfgs == 0 || user_prof_id > max_valid_id) ++ return -EINVAL; ++ ++ if (tas_priv->rcabin.capture_profile_id != user_prof_id) { ++ tas_priv->rcabin.capture_profile_id = user_prof_id; ++ ret = 1; ++ } ++ ++ return ret; ++} ++ + static int tasdevice_info_active_num(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +@@ -1075,6 +1131,41 @@ static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + return 0; + } + ++/** ++ * tasdevice_get_capture_profile_id - Report current active capture profile ++ * ID to user space ++ * @kcontrol: ALSA kcontrol structure passed from ALSA core ++ * @ucontrol: User-space control element value buffer to write the result back ++ * ++ * This function ensures the returned profile ID is always clamped inside the ++ * valid range advertised by the info callback, preventing accidental invalid ++ * values from being exposed to applications even if internal driver state is ++ * temporarily inconsistent. ++ * ++ * Returns 0 on successful fill of the control value, no error conditions ++ * are defined for this getter callback. ++ */ ++static int tasdevice_get_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int max_valid_id, current_prof_id; ++ ++ max_valid_id = tas_priv->rcabin.ncfgs > 0 ? ++ (tas_priv->rcabin.ncfgs - 1U) : 0; ++ ++ /* ++ * Cast current profile id to unsigned to match type with max_valid_id, ++ * avoid signedness mismatch; ++ */ ++ current_prof_id = (unsigned int)tas_priv->rcabin.capture_profile_id; ++ /* Prevent underflow when there are no loaded capture profiles. */ ++ ucontrol->value.integer.value[0] = min(current_prof_id, max_valid_id); ++ ++ return 0; ++} ++ + static int tasdevice_get_chip_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -1117,6 +1208,41 @@ static int tasdevice_create_control(struct tasdevice_priv *tas_priv) + ret = snd_soc_add_component_controls(tas_priv->codec, + prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); + ++ mix_index = 0; ++ switch (tas_priv->chip_id) { ++ case TAS2563: ++ case TAS2568: ++ case TAS2570: ++ case TAS2572: ++ case TAS2573: ++ case TAS2574: ++ case TAS2781: ++ prof_ctrls = devm_kcalloc(tas_priv->dev, nr_controls, ++ sizeof(prof_ctrls[0]), GFP_KERNEL); ++ if (!prof_ctrls) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* Create a mixer item for selecting the capture profile */ ++ name = devm_kstrdup(tas_priv->dev, "Speaker Capture Profile Id", ++ GFP_KERNEL); ++ if (!name) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ prof_ctrls[mix_index].name = name; ++ prof_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ prof_ctrls[mix_index].info = tasdevice_info_profile; ++ prof_ctrls[mix_index].get = tasdevice_get_capture_profile_id; ++ prof_ctrls[mix_index].put = tasdevice_set_capture_profile_id; ++ mix_index++; ++ ++ ret = snd_soc_add_component_controls(tas_priv->codec, ++ prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); ++ break; ++ } ++ + out: + return ret; + } +@@ -1330,6 +1456,43 @@ static void alpa_cali_update(struct bulk_reg_val *p, + p->val_len = 4; + } + ++static int create_tas2781_cali_start_ktrl(struct tasdevice_priv ++ *priv, struct snd_kcontrol_new *cali_ctrl) ++{ ++ struct soc_bytes_ext *ext_cali_start; ++ char *cali_start_name; ++ ++ ext_cali_start = devm_kzalloc(priv->dev, ++ sizeof(*ext_cali_start), GFP_KERNEL); ++ if (!ext_cali_start) ++ return -ENOMEM; ++ ++ cali_start_name = devm_kstrdup(priv->dev, ++ "Calibration Start", GFP_KERNEL); ++ if (!cali_start_name) ++ return -ENOMEM; ++ /* ++ * package structure for tas2781 ftc start: ++ * Pkg len (1 byte) ++ * Reg id (1 byte, constant 'r') ++ * book, page, register for pilot threshold, pilot tone ++ * and sine gain (12 bytes) ++ * for (i = 0; i < Device-Sum; i++) { ++ * Device #i index_info (1 byte) ++ * Sine gain for Device #i (8 bytes) ++ * } ++ */ ++ ext_cali_start->max = 14 + priv->ndev * 9; ++ cali_ctrl->name = cali_start_name; ++ cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ cali_ctrl->info = snd_soc_bytes_info_ext; ++ cali_ctrl->put = tas2781_calib_start_put; ++ cali_ctrl->get = tasdev_nop_get; ++ cali_ctrl->private_value = (unsigned long)ext_cali_start; ++ ++ return 0; ++} ++ + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + { + struct calidata *cali_data = &priv->cali_data; +@@ -1448,37 +1611,11 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + */ + cali_data->data[0] = 0xff; + if (priv->chip_id == TAS2781) { +- struct soc_bytes_ext *ext_cali_start; +- char *cali_start_name; +- +- ext_cali_start = devm_kzalloc(priv->dev, +- sizeof(*ext_cali_start), GFP_KERNEL); +- if (!ext_cali_start) +- return -ENOMEM; +- +- cali_start_name = devm_kstrdup(priv->dev, +- "Calibration Start", GFP_KERNEL); +- if (!cali_start_name) +- return -ENOMEM; +- /* +- * package structure for tas2781 ftc start: +- * Pkg len (1 byte) +- * Reg id (1 byte, constant 'r') +- * book, page, register for pilot threshold, pilot tone +- * and sine gain (12 bytes) +- * for (i = 0; i < Device-Sum; i++) { +- * Device #i index_info (1 byte) +- * Sine gain for Device #i (8 bytes) +- * } +- */ +- ext_cali_start->max = 14 + priv->ndev * 9; +- cali_ctrls[i].name = cali_start_name; +- cali_ctrls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; +- cali_ctrls[i].info = snd_soc_bytes_info_ext; +- cali_ctrls[i].put = tas2781_calib_start_put; +- cali_ctrls[i].get = tasdev_nop_get; +- cali_ctrls[i].private_value = (unsigned long)ext_cali_start; ++ rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); ++ if (rc != 0) ++ return rc; + i++; ++ + } + + return snd_soc_add_component_controls(priv->codec, cali_ctrls, +@@ -1765,13 +1902,25 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int state = 0; + +- /* Codec Lock Hold */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + if (event == SND_SOC_DAPM_PRE_PMD) + state = 1; +- tasdevice_tuning_switch(tas_priv, state); +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); ++ tasdevice_tuning_switch(tas_priv, state, false); ++ ++ return 0; ++} ++ ++static int tasdevice_capture_dapm_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ int state = 0; ++ ++ guard(mutex)(&tas_priv->codec_lock); ++ if (event == SND_SOC_DAPM_PRE_PMD) ++ state = 1; ++ tasdevice_tuning_switch(tas_priv, state, true); + + return 0; + } +@@ -1779,7 +1928,7 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT_E("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM, +- 0, 0, tasdevice_dapm_event, ++ 0, 0, tasdevice_capture_dapm_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SPK("SPK", tasdevice_dapm_event), + SND_SOC_DAPM_OUTPUT("OUT"), +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -12,6 +12,7 @@ + // Author: Baojun Xu + // Author: Kevin Lu + ++#include + #include + #include + #include +@@ -494,6 +495,56 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) + return tas2783_sdca_mbq_size(dev, reg) > 0; + } + ++static bool tas2783_writeable_register(struct device *dev, unsigned int reg) ++{ ++ /* ++ * The Latency Control of every Entity, together with the Power Domain ++ * actual state and the protection status, is read-only. They are ++ * listed in tas2783_reg_default[] with a placeholder value, so without ++ * this a regcache_sync() would try to write them back and the ++ * peripheral would reject the transaction, aborting the sync. ++ */ ++ switch (reg) { ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): ++ return false; ++ ++ default: ++ return tas2783_sdca_mbq_size(dev, reg) > 0; ++ } ++} ++ + static bool tas2783_volatile_register(struct device *dev, u32 reg) + { + switch (reg) { +@@ -515,6 +566,7 @@ static const struct regmap_config tas_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = tas2783_readable_register, ++ .writeable_reg = tas2783_writeable_register, + .volatile_reg = tas2783_volatile_register, + .reg_defaults = tas2783_reg_default, + .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), +@@ -692,12 +744,12 @@ static s32 tas2783_update_calibdata(struct tas2783_prv *tas_dev) + return 0; + } + +- mutex_lock(&tas_dev->calib_lock); +- ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, +- tas_dev->cali_data.read_sz); +- if (!ret) +- tas2783_set_calib_params_to_device(tas_dev, tmp_val); +- mutex_unlock(&tas_dev->calib_lock); ++ scoped_guard(mutex, &tas_dev->calib_lock) { ++ ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, ++ tas_dev->cali_data.read_sz); ++ if (!ret) ++ tas2783_set_calib_params_to_device(tas_dev, tmp_val); ++ } + + return ret; + } +@@ -926,22 +978,23 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + dev_err(tas_dev->dev, + "clear latch failed, err=%d", ret); + +- mutex_lock(&tas_dev->pde_lock); +- /* +- * Sometimes, there is error returned during power on. +- * So added retry logic to ensure power on so that +- * port prepare succeeds +- */ +- do { +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_ON); +- if (!ret) +- break; +- usleep_range(2000, 2200); +- } while (retry--); +- mutex_unlock(&tas_dev->pde_lock); ++ scoped_guard(mutex, &tas_dev->pde_lock) { ++ /* ++ * Sometimes, there is error returned during power on. ++ * So added retry logic to ensure power on so that ++ * port prepare succeeds ++ */ ++ do { ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (!ret) ++ break; ++ usleep_range(2000, 2200); ++ } while (retry--); ++ } ++ + if (ret) + return ret; + +@@ -965,7 +1018,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +- s32 ret; + struct snd_soc_component *component = dai->component; + struct tas2783_prv *tas_dev = + snd_soc_component_get_drvdata(component); +@@ -974,14 +1026,11 @@ static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + + sdw_stream_remove_slave(tas_dev->sdw_peripheral, sdw_stream); + +- mutex_lock(&tas_dev->pde_lock); +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_OFF); +- mutex_unlock(&tas_dev->pde_lock); +- +- return ret; ++ guard(mutex)(&tas_dev->pde_lock); ++ return regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_OFF); + } + + static const struct snd_soc_dai_ops tas_dai_ops = { +@@ -1349,7 +1398,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, + function_data->desc = &peripheral->sdca_data.function[i]; + + /* Parse the function */ +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + tas_dev->sa_func_data = function_data; + else +diff --git a/sound/soc/codecs/tas5805m.c b/sound/soc/codecs/tas5805m.c +--- a/sound/soc/codecs/tas5805m.c ++++ b/sound/soc/codecs/tas5805m.c +@@ -12,6 +12,7 @@ + // + // It has been simplified a little and reworked for the 5.x ALSA SoC API. + ++#include + #include + #include + #include +@@ -230,10 +231,9 @@ static int tas5805m_vol_get(struct snd_kcontrol *kcontrol, + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + ucontrol->value.integer.value[0] = tas5805m->vol[0]; + ucontrol->value.integer.value[1] = tas5805m->vol[1]; +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -249,13 +249,12 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); +- int ret = 0; + + if (!(volume_is_valid(ucontrol->value.integer.value[0]) && + volume_is_valid(ucontrol->value.integer.value[1]))) + return -EINVAL; + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->vol[0] != ucontrol->value.integer.value[0] || + tas5805m->vol[1] != ucontrol->value.integer.value[1]) { + tas5805m->vol[0] = ucontrol->value.integer.value[0]; +@@ -265,11 +264,10 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + tas5805m->is_powered); + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- ret = 1; ++ return 1; + } +- mutex_unlock(&tas5805m->lock); + +- return ret; ++ return 0; + } + + static const struct snd_kcontrol_new tas5805m_snd_controls[] = { +@@ -332,7 +330,7 @@ static void do_work(struct work_struct *work) + + dev_dbg(&tas5805m->i2c->dev, "DSP startup\n"); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + /* We mustn't issue any I2C transactions until the I2S + * clock is stable. Furthermore, we must allow a 5ms + * delay after the first set of register writes to +@@ -345,7 +343,6 @@ static void do_work(struct work_struct *work) + + tas5805m->is_powered = true; + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + } + + static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, +@@ -362,7 +359,7 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + dev_dbg(component->dev, "DSP shutdown\n"); + cancel_work_sync(&tas5805m->work); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->is_powered) { + tas5805m->is_powered = false; + +@@ -379,7 +376,6 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + + regmap_write(rm, REG_DEVICE_CTRL_2, DCTRL2_MODE_HIZ); + } +- mutex_unlock(&tas5805m->lock); + } + + return 0; +@@ -414,14 +410,13 @@ static int tas5805m_mute(struct snd_soc_dai *dai, int mute, int direction) + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + dev_dbg(component->dev, "set mute=%d (is_powered=%d)\n", + mute, tas5805m->is_powered); + + tas5805m->is_muted = mute; + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -462,7 +457,6 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + struct tas5805m_priv *tas5805m; + char filename[128]; + const char *config_name; +- const struct firmware *fw; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &tas5805m_regmap); +@@ -507,24 +501,20 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + + snprintf(filename, sizeof(filename), "tas5805m_dsp_%s.bin", + config_name); ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, dev); + if (ret) + return ret; + + if ((fw->size < 2) || (fw->size & 1)) { + dev_err(dev, "firmware is invalid\n"); +- release_firmware(fw); + return -EINVAL; + } + + tas5805m->dsp_cfg_len = fw->size; + tas5805m->dsp_cfg_data = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL); +- if (!tas5805m->dsp_cfg_data) { +- release_firmware(fw); ++ if (!tas5805m->dsp_cfg_data) + return -ENOMEM; +- } +- +- release_firmware(fw); + + /* Do the first part of the power-on here, while we can expect + * the I2S interface to be quiet. We must raise PDN# and then +diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c +--- a/sound/soc/codecs/tlv320aic26.c ++++ b/sound/soc/codecs/tlv320aic26.c +@@ -320,8 +320,14 @@ static int aic26_probe(struct snd_soc_component *component) + return 0; + } + ++static void aic26_remove(struct snd_soc_component *component) ++{ ++ device_remove_file(component->dev, &dev_attr_keyclick); ++} ++ + static const struct snd_soc_component_driver aic26_soc_component_dev = { + .probe = aic26_probe, ++ .remove = aic26_remove, + .controls = aic26_snd_controls, + .num_controls = ARRAY_SIZE(aic26_snd_controls), + .dapm_widgets = tlv320aic26_dapm_widgets, +diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c +--- a/sound/soc/codecs/tlv320aic31xx.c ++++ b/sound/soc/codecs/tlv320aic31xx.c +@@ -1720,18 +1720,14 @@ static int tlv320dac3100_fw_load(struct aic31xx_priv *aic31xx, + static int tlv320dac3100_load_coeffs(struct aic31xx_priv *aic31xx, + const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, aic31xx->dev); + if (ret) + return ret; + +- ret = tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); +- +- release_firmware(fw); +- +- return ret; ++ return tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); + } + + static int aic31xx_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c +--- a/sound/soc/codecs/tlv320aic32x4-clk.c ++++ b/sound/soc/codecs/tlv320aic32x4-clk.c +@@ -1,5 +1,5 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Clock Tree for the Texas Instruments TLV320AIC32x4 + * + * Copyright 2019 Annaliese McDermond +@@ -9,6 +9,7 @@ + + #include + #include ++#include + #include + #include + +@@ -57,7 +58,7 @@ static void clk_aic32x4_pll_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLLEN, 0); ++ AIC32X4_PLLEN, 0); + } + + static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) +@@ -75,7 +76,7 @@ static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) + } + + static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + /* Change to use regmap_bulk_read? */ + unsigned int val; +@@ -106,19 +107,19 @@ static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, + } + + static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + int ret; + /* Change to use regmap_bulk_write for some if not all? */ + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_R_MASK, settings->r); ++ AIC32X4_PLL_R_MASK, settings->r); + if (ret < 0) + return ret; + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_P_MASK, +- settings->p << AIC32X4_PLL_P_SHIFT); ++ AIC32X4_PLL_P_MASK, ++ settings->p << AIC32X4_PLL_P_SHIFT); + if (ret < 0) + return ret; + +@@ -136,23 +137,21 @@ static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, + return 0; + } + +-static unsigned long clk_aic32x4_pll_calc_rate( +- struct clk_aic32x4_pll_muldiv *settings, +- unsigned long parent_rate) ++static unsigned long clk_aic32x4_pll_calc_rate(struct clk_aic32x4_pll_muldiv *settings, ++ unsigned long parent_rate) + { + u64 rate; + /* + * We scale j by 10000 to account for the decimal part of P and divide + * it back out later. + */ +- rate = (u64) parent_rate * settings->r * +- ((settings->j * 10000) + settings->d); ++ rate = (u64)parent_rate * settings->r * ((settings->j * 10000) + settings->d); + +- return (unsigned long) DIV_ROUND_UP_ULL(rate, settings->p * 10000); ++ return (unsigned long)DIV_ROUND_UP_ULL(rate, settings->p * 10000); + } + + static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, +- unsigned long rate, unsigned long parent_rate) ++ unsigned long rate, unsigned long parent_rate) + { + u64 multiplier; + +@@ -165,14 +164,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + * of the multiplier. This is because we can't do floating point + * math in the kernel. + */ +- multiplier = (u64) rate * settings->p * 10000; ++ multiplier = (u64)rate * settings->p * 10000; + do_div(multiplier, parent_rate); + + /* + * J can't be over 64, so R can scale this. + * R can't be greater than 4. + */ +- settings->r = ((u32) multiplier / 640000) + 1; ++ settings->r = ((u32)multiplier / 640000) + 1; + if (settings->r > 4) + return -1; + do_div(multiplier, settings->r); +@@ -184,14 +183,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + return -1; + + /* Figure out the integer part, J, and the fractional part, D. */ +- settings->j = (u32) multiplier / 10000; +- settings->d = (u32) multiplier % 10000; ++ settings->j = (u32)multiplier / 10000; ++ settings->d = (u32)multiplier % 10000; + + return 0; + } + + static unsigned long clk_aic32x4_pll_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -220,8 +219,8 @@ static int clk_aic32x4_pll_determine_rate(struct clk_hw *hw, + } + + static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, +- unsigned long rate, +- unsigned long parent_rate) ++ unsigned long rate, ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -236,7 +235,7 @@ static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, + return ret; + + /* 10ms is the delay to wait before the clocks are stable */ +- msleep(10); ++ usleep_range(10000, 20000); + + return 0; + } +@@ -261,7 +260,6 @@ static u8 clk_aic32x4_pll_get_parent(struct clk_hw *hw) + return (val & AIC32X4_PLL_CLKIN_MASK) >> AIC32X4_PLL_CLKIN_SHIFT; + } + +- + static const struct clk_ops aic32x4_pll_ops = { + .prepare = clk_aic32x4_pll_prepare, + .unprepare = clk_aic32x4_pll_unprepare, +@@ -311,11 +309,11 @@ static void clk_aic32x4_div_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + regmap_update_bits(div->regmap, div->reg, +- AIC32X4_DIVEN, 0); ++ AIC32X4_DIVEN, 0); + } + + static int clk_aic32x4_div_set_rate(struct clk_hw *hw, unsigned long rate, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + u8 divisor; +@@ -342,7 +340,7 @@ static int clk_aic32x4_div_determine_rate(struct clk_hw *hw, + } + + static unsigned long clk_aic32x4_div_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + unsigned int val; +@@ -399,7 +397,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + { + .name = "pll", + .parent_names = +- (const char* []) { "mclk", "bclk", "gpio", "din" }, ++ (const char *[]) { "mclk", "bclk", "gpio", "din" }, + .num_parents = 4, + .ops = &aic32x4_pll_ops, + .reg = 0, +@@ -414,28 +412,28 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }, + { + .name = "ndac", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NDAC, + }, + { + .name = "mdac", +- .parent_names = (const char * []) { "ndac" }, ++ .parent_names = (const char *[]) { "ndac" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MDAC, + }, + { + .name = "nadc", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NADC, + }, + { + .name = "madc", +- .parent_names = (const char * []) { "nadc" }, ++ .parent_names = (const char *[]) { "nadc" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MADC, +@@ -451,7 +449,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }; + + static struct clk *aic32x4_register_clk(struct device *dev, +- struct aic32x4_clkdesc *desc) ++ struct aic32x4_clkdesc *desc) + { + struct clk_init_data init; + struct clk_aic32x4 *priv; +@@ -464,8 +462,8 @@ static struct clk *aic32x4_register_clk(struct device *dev, + init.flags = 0; + + priv = devm_kzalloc(dev, sizeof(struct clk_aic32x4), GFP_KERNEL); +- if (priv == NULL) +- return (struct clk *) -ENOMEM; ++ if (!priv) ++ return ERR_PTR(-ENOMEM); + + priv->dev = dev; + priv->hw.init = &init; +diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c +--- a/sound/soc/codecs/tlv320aic32x4-i2c.c ++++ b/sound/soc/codecs/tlv320aic32x4-i2c.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,17 +15,23 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_i2c_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 8; +- config.val_bits = 8; ++ regmap = devm_regmap_init_i2c(i2c, &aic32x4_i2c_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_i2c(i2c, &config); + type = (uintptr_t)i2c_get_match_data(i2c); + + return aic32x4_probe(&i2c->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c +--- a/sound/soc/codecs/tlv320aic32x4-spi.c ++++ b/sound/soc/codecs/tlv320aic32x4-spi.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,19 +15,25 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_spi_regmap_config = { ++ .reg_bits = 7, ++ .pad_bits = 1, ++ .val_bits = 8, ++ .read_flag_mask = 0x01, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_spi_probe(struct spi_device *spi) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 7; +- config.pad_bits = 1; +- config.val_bits = 8; +- config.read_flag_mask = 0x01; ++ regmap = devm_regmap_init_spi(spi, &aic32x4_spi_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_spi(spi, &config); + type = (uintptr_t)spi_get_device_match_data(spi); + + return aic32x4_probe(&spi->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c +--- a/sound/soc/codecs/tlv320aic32x4.c ++++ b/sound/soc/codecs/tlv320aic32x4.c +@@ -1,7 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + /* +- * linux/sound/soc/codecs/tlv320aic32x4.c +- * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin +@@ -28,7 +26,6 @@ + #include + #include + #include +-#include + + #include "tlv320aic32x4.h" + +@@ -45,7 +42,8 @@ struct aic32x4_priv { + struct regulator *supply_dv; + struct regulator *supply_av; + +- struct aic32x4_setup_data *setup; ++ unsigned int gpio_func[5]; ++ + struct device *dev; + enum aic32x4_type type; + +@@ -75,7 +73,7 @@ static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, + }; + + static int mic_bias_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *kcontrol, int event) ++ struct snd_kcontrol *kcontrol, int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + +@@ -83,25 +81,23 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, + case SND_SOC_DAPM_POST_PMU: + /* Change Mic Bias Registor */ + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, +- AIC32X4_MICBIAS_LDOIN | +- AIC32X4_MICBIAS_2075V); +- printk(KERN_DEBUG "%s: Mic Bias will be turned ON\n", __func__); ++ AIC32x4_MICBIAS_MASK, ++ AIC32X4_MICBIAS_LDOIN | ++ AIC32X4_MICBIAS_2075V); ++ dev_dbg(component->dev, "Mic Bias will be turned ON\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, 0); +- printk(KERN_DEBUG "%s: Mic Bias will be turned OFF\n", +- __func__); ++ AIC32x4_MICBIAS_MASK, 0); ++ dev_dbg(component->dev, "Mic Bias will be turned OFF\n"); + break; + } + + return 0; + } + +- + static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -114,7 +110,7 @@ static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -123,8 +119,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_DOUTCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP2 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP2 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -142,7 +137,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -155,7 +150,7 @@ static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -164,8 +159,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_MISOCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP4 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP4 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -183,7 +177,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -195,7 +189,7 @@ static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -204,8 +198,7 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_GPIOCTL); + gpio_check = (val & AIC32X4_MFP5_GPIO_OUTPUT); + if (gpio_check != AIC32X4_MFP5_GPIO_OUTPUT) { +- printk(KERN_ERR "%s: MFP5 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP5 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -222,25 +215,31 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + return 0; + }; + +-static const struct snd_kcontrol_new aic32x4_mfp1[] = { +- SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp2[] = { +- SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp3[] = { +- SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp4[] = { +- SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp5[] = { +- SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, +- aic32x4_set_mfp5_gpio), ++static const struct { ++ unsigned int reg; ++ struct snd_kcontrol_new ctrl; ++} aic32x4_mfp_cfg[] = { ++ { ++ .reg = AIC32X4_DINCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_DOUTCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), ++ }, ++ { ++ .reg = AIC32X4_SCLKCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_MISOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), ++ }, ++ { ++ .reg = AIC32X4_GPIOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, ++ aic32x4_set_mfp5_gpio), ++ }, + }; + + /* 0dB min, 0.5dB steps */ +@@ -270,53 +269,42 @@ static SOC_ENUM_SINGLE_DECL(r_ptm_enum, AIC32X4_RPLAYBACK, 2, ptm_text); + + static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("PCM Playback Volume", AIC32X4_LDACVOL, +- AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), ++ AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), + SOC_ENUM("DAC Left Playback PowerTune Switch", l_ptm_enum), + SOC_ENUM("DAC Right Playback PowerTune Switch", r_ptm_enum), + SOC_DOUBLE_R_S_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R_S_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 6, 0x01, 1), ++ AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 6, 0x01, 1), ++ AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_ENUM("LO Playback Common Mode Switch", lo_cm_enum), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 7, 0x01, 1), ++ AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_S_TLV("ADC Level Volume", AIC32X4_LADCVOL, +- AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), ++ AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), ++ AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), +- SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 4, 0x07, 0), +- SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 0, 0x03, 0), +- SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 6, 0x03, 0), +- SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 1, 0x1F, 0), +- SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, +- 0, 0x7F, 0), +- SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, +- 0, 0x1F, 0), +- SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, +- 0, 0x0F, 0), ++ SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, 4, 0x07, 0), ++ SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, 0, 0x03, 0), ++ SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, 6, 0x03, 0), ++ SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, 1, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, 0, 0x7F, 0), ++ SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, 0, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, 0, 0x0F, 0), + }; + + static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { +@@ -354,21 +342,27 @@ static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), + }; +@@ -385,21 +379,27 @@ static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), + }; +@@ -428,38 +428,38 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_rmixer_controls), ++ in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_rmixer_controls), ++ in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_rmixer_controls), ++ in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_rmixer_controls), ++ in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cmr_to_rmixer_controls), ++ cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_rmixer_controls), ++ in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_rmixer_controls), ++ in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_lmixer_controls), ++ in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_lmixer_controls), ++ in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_lmixer_controls), ++ in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_lmixer_controls), ++ in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cml_to_lmixer_controls), ++ cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_lmixer_controls), ++ in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_lmixer_controls), ++ in3r_to_lmixer_controls), + + SND_SOC_DAPM_SUPPLY("Mic Bias", AIC32X4_MICBIAS, 6, 0, mic_bias_event, +- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_POST("ADC Reset", aic32x4_reset_adc), + +@@ -575,7 +575,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, + }; + +-static const struct regmap_range_cfg aic32x4_regmap_pages[] = { ++const struct regmap_range_cfg aic32x4_regmap_pages[] = { + { + .selector_reg = 0, + .selector_mask = 0xff, +@@ -585,13 +585,7 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { + .range_max = AIC32X4_REFPOWERUP, + }, + }; +- +-const struct regmap_config aic32x4_regmap_config = { +- .max_register = AIC32X4_REFPOWERUP, +- .ranges = aic32x4_regmap_pages, +- .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +-}; +-EXPORT_SYMBOL(aic32x4_regmap_config); ++EXPORT_SYMBOL_GPL(aic32x4_regmap_pages); + + static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +@@ -624,7 +618,7 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: +- printk(KERN_ERR "aic32x4: invalid clock provider\n"); ++ dev_err(component->dev, "invalid clock provider\n"); + return -EINVAL; + } + +@@ -651,19 +645,20 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + AIC32X4_IFACE1_DATATYPE_SHIFT); + break; + default: +- printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); ++ dev_err(component->dev, "invalid DAI interface format\n"); + return -EINVAL; + } + + aic32x4->fmt = fmt; + + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATATYPE_MASK | +- AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); ++ AIC32X4_IFACE1_DATATYPE_MASK | ++ AIC32X4_IFACE1_MASTER_MASK, ++ iface_reg_1); + snd_soc_component_update_bits(component, AIC32X4_IFACE2, +- AIC32X4_DATA_OFFSET_MASK, iface_reg_2); ++ AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + snd_soc_component_update_bits(component, AIC32X4_IFACE3, +- AIC32X4_BCLKINV_MASK, iface_reg_3); ++ AIC32X4_BCLKINV_MASK, iface_reg_3); + + return 0; + } +@@ -676,14 +671,13 @@ static int aic32x4_set_aosr(struct snd_soc_component *component, u8 aosr) + static int aic32x4_set_dosr(struct snd_soc_component *component, u16 dosr) + { + snd_soc_component_write(component, AIC32X4_DOSRMSB, dosr >> 8); +- snd_soc_component_write(component, AIC32X4_DOSRLSB, +- (dosr & 0xff)); ++ snd_soc_component_write(component, AIC32X4_DOSRLSB, dosr & 0xff); + + return 0; + } + + static int aic32x4_set_processing_blocks(struct snd_soc_component *component, +- u8 r_block, u8 p_block) ++ u8 r_block, u8 p_block) + { + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + +@@ -703,6 +697,45 @@ static int aic32x4_set_processing_blocks(struct snd_soc_component *component, + return 0; + } + ++static int aic32x4_configure_rate(struct snd_soc_component *component, ++ unsigned int rate, u8 *aosr, u8 *adc_rc, ++ u8 *dac_rc, u8 *dosr_inc) ++{ ++ struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); ++ u8 prb_rx, prb_tx; ++ ++ if (rate <= 48000) { ++ *aosr = 128; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 8; ++ prb_rx = 1; ++ prb_tx = 1; ++ } else if (rate <= 96000) { ++ *aosr = 64; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 4; ++ prb_rx = 1; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 9; ++ } else if (rate == 192000) { ++ *aosr = 32; ++ *adc_rc = 3; ++ *dac_rc = 4; ++ *dosr_inc = 2; ++ prb_rx = 13; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 19; ++ } else { ++ dev_err(component->dev, "Sampling rate %u not supported\n", rate); ++ return -EINVAL; ++ } ++ ++ if (aic32x4->type == AIC32X4_TYPE_TAS2505) ++ prb_rx = 0; ++ ++ return aic32x4_set_processing_blocks(component, prb_rx, prb_tx); ++} ++ + static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned int sample_rate, unsigned int channels, + unsigned int bit_depth) +@@ -717,7 +750,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned long adc_clock_rate, dac_clock_rate; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "pll" }, + { .id = "nadc" }, + { .id = "madc" }, +@@ -729,37 +762,11 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + if (ret) + return ret; + +- if (sample_rate <= 48000) { +- aosr = 128; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 8; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 1); +- } else if (sample_rate <= 96000) { +- aosr = 64; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 4; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 9); +- } else if (sample_rate == 192000) { +- aosr = 32; +- adc_resource_class = 3; +- dac_resource_class = 4; +- dosr_increment = 2; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 13, 19); +- } else { +- dev_err(component->dev, "Sampling rate not supported\n"); +- return -EINVAL; +- } ++ ret = aic32x4_configure_rate(component, sample_rate, &aosr, ++ &adc_resource_class, &dac_resource_class, ++ &dosr_increment); ++ if (ret) ++ return ret; + + /* PCM over I2S is always 2-channel */ + if ((aic32x4->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) +@@ -781,50 +788,39 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + (min_mdac * dosr * sample_rate); + for (mdac = min_mdac; mdac <= 128; ++mdac) { + for (ndac = max_ndac; ndac > 0; --ndac) { +- dac_clock_rate = ndac * mdac * dosr * +- sample_rate; +- if (dac_clock_rate == adc_clock_rate) { +- if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) +- continue; ++ dac_clock_rate = ndac * mdac * dosr * sample_rate; ++ if (dac_clock_rate != adc_clock_rate) ++ continue; + +- clk_set_rate(clocks[0].clk, +- dac_clock_rate); ++ if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) ++ continue; + +- clk_set_rate(clocks[1].clk, +- sample_rate * aosr * +- madc); +- clk_set_rate(clocks[2].clk, +- sample_rate * aosr); +- aic32x4_set_aosr(component, +- aosr); ++ clk_set_rate(clocks[0].clk, dac_clock_rate); + +- clk_set_rate(clocks[3].clk, +- sample_rate * dosr * +- mdac); +- clk_set_rate(clocks[4].clk, +- sample_rate * dosr); +- aic32x4_set_dosr(component, +- dosr); ++ clk_set_rate(clocks[1].clk, sample_rate * aosr * madc); ++ clk_set_rate(clocks[2].clk, sample_rate * aosr); ++ aic32x4_set_aosr(component, aosr); + +- clk_set_rate(clocks[5].clk, +- sample_rate * channels * +- bit_depth); ++ clk_set_rate(clocks[3].clk, sample_rate * dosr * mdac); ++ clk_set_rate(clocks[4].clk, sample_rate * dosr); ++ aic32x4_set_dosr(component, dosr); + +- return 0; +- } ++ clk_set_rate(clocks[5].clk, ++ sample_rate * channels * bit_depth); ++ ++ return 0; + } + } + } + } + +- dev_err(component->dev, +- "Could not set clocks to support sample rate.\n"); ++ dev_err(component->dev, "Could not set clocks to support sample rate.\n"); + return -EINVAL; + } + + static int aic32x4_hw_params(struct snd_pcm_substream *substream, +- struct snd_pcm_hw_params *params, +- struct snd_soc_dai *dai) ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); +@@ -837,24 +833,20 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + + switch (params_physical_width(params)) { + case 16: +- iface1_reg |= (AIC32X4_WORD_LEN_16BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_16BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 20: +- iface1_reg |= (AIC32X4_WORD_LEN_20BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 24: +- iface1_reg |= (AIC32X4_WORD_LEN_24BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 32: +- iface1_reg |= (AIC32X4_WORD_LEN_32BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + } + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); ++ AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + + if (params_channels(params) == 1) { + dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; +@@ -865,7 +857,7 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; + } + snd_soc_component_update_bits(component, AIC32X4_DACSETUP, +- AIC32X4_DAC_CHAN_MASK, dacsetup_reg); ++ AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + + return 0; + } +@@ -875,7 +867,7 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute, int direction) + struct snd_soc_component *component = dai->component; + + snd_soc_component_update_bits(component, AIC32X4_DACMUTE, +- AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); ++ AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + + return 0; + } +@@ -886,7 +878,7 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "madc" }, + { .id = "mdac" }, + { .id = "bdiv" }, +@@ -955,44 +947,14 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + + /* setup GPIO functions */ +- /* MFP1 */ +- if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DINCTL, +- aic32x4->setup->gpio_func[0]); +- snd_soc_add_component_controls(component, aic32x4_mfp1, +- ARRAY_SIZE(aic32x4_mfp1)); +- } ++ BUILD_BUG_ON(ARRAY_SIZE(aic32x4->gpio_func) != ARRAY_SIZE(aic32x4_mfp_cfg)); ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) { ++ if (aic32x4->gpio_func[i] == AIC32X4_MFPX_DEFAULT_VALUE) ++ continue; + +- /* MFP2 */ +- if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DOUTCTL, +- aic32x4->setup->gpio_func[1]); +- snd_soc_add_component_controls(component, aic32x4_mfp2, +- ARRAY_SIZE(aic32x4_mfp2)); +- } +- +- /* MFP3 */ +- if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_SCLKCTL, +- aic32x4->setup->gpio_func[2]); +- snd_soc_add_component_controls(component, aic32x4_mfp3, +- ARRAY_SIZE(aic32x4_mfp3)); +- } +- +- /* MFP4 */ +- if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_MISOCTL, +- aic32x4->setup->gpio_func[3]); +- snd_soc_add_component_controls(component, aic32x4_mfp4, +- ARRAY_SIZE(aic32x4_mfp4)); +- } +- +- /* MFP5 */ +- if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_GPIOCTL, +- aic32x4->setup->gpio_func[4]); +- snd_soc_add_component_controls(component, aic32x4_mfp5, +- ARRAY_SIZE(aic32x4_mfp5)); ++ snd_soc_component_write(component, aic32x4_mfp_cfg[i].reg, ++ aic32x4->gpio_func[i]); ++ snd_soc_add_component_controls(component, &aic32x4_mfp_cfg[i].ctrl, 1); + } + } + +@@ -1002,7 +964,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1013,8 +975,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1022,7 +983,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_component_write(component, AIC32X4_MICBIAS, +- AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); ++ AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) + snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); +@@ -1041,16 +1002,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Mic PGA routing */ + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_IN2R_10K); ++ AIC32X4_LMICPGANIN_IN2R_10K); + else + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_CM1L_10K); ++ AIC32X4_LMICPGANIN_CM1L_10K); + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_IN1L_10K); ++ AIC32X4_RMICPGANIN_IN1L_10K); + else + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_CM1R_10K); ++ AIC32X4_RMICPGANIN_CM1R_10K); + + /* + * Workaround: for an unknown reason, the ADC needs to be powered up +@@ -1058,8 +1019,8 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + * a HW BUG or some kind of behavior not documented in the datasheet. + */ + tmp_reg = snd_soc_component_read(component, AIC32X4_ADCSETUP); +- snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg | +- AIC32X4_LADC_EN | AIC32X4_RADC_EN); ++ snd_soc_component_write(component, AIC32X4_ADCSETUP, ++ tmp_reg | AIC32X4_LADC_EN | AIC32X4_RADC_EN); + snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg); + + /* +@@ -1102,13 +1063,13 @@ static const struct snd_kcontrol_new aic32x4_tas2505_snd_controls[] = { + SOC_ENUM("DAC Playback PowerTune Switch", l_ptm_enum), + + SOC_SINGLE_TLV("HP Driver Gain Volume", +- AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), ++ AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE("HP DAC Playback Switch", AIC32X4_HPLGAIN, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Driver Playback Volume", +- TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), ++ TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE_TLV("Speaker Amplifier Playback Volume", +- TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), ++ TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + }; +@@ -1144,11 +1105,12 @@ static const struct snd_soc_dapm_route aic32x4_tas2505_dapm_routes[] = { + static struct snd_soc_dai_driver aic32x4_tas2505_dai = { + .name = "tas2505-hifi", + .playback = { +- .stream_name = "Playback", +- .channels_min = 1, +- .channels_max = 2, +- .rates = SNDRV_PCM_RATE_8000_96000, +- .formats = AIC32X4_FORMATS,}, ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = AIC32X4_FORMATS, ++ }, + .ops = &aic32x4_ops, + .symmetric_rate = 1, + }; +@@ -1159,7 +1121,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1170,8 +1132,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1192,7 +1153,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AIC32X4_CMMODE, tmp_reg); + + /* +- * Enable the fast charging feature and ensure the needed 40ms ellapsed ++ * Enable the fast charging feature and ensure the needed 40ms elapsed + * before using the analog circuits. + */ + snd_soc_component_write(component, TAS2505_REFPOWERUP, +@@ -1218,17 +1179,10 @@ static const struct snd_soc_component_driver soc_component_dev_aic32x4_tas2505 = + .endianness = 1, + }; + +-static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, +- struct device_node *np) ++static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, struct device_node *np) + { +- struct aic32x4_setup_data *aic32x4_setup; + int ret; + +- aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), +- GFP_KERNEL); +- if (!aic32x4_setup) +- return -ENOMEM; +- + ret = of_property_match_string(np, "clock-names", "mclk"); + if (ret < 0) + return -EINVAL; +@@ -1245,9 +1199,11 @@ static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, + gpiod_set_consumer_name(aic32x4->rstn_gpio, "tlv320aic32x4_rstn"); + } + +- if (of_property_read_u32_array(np, "aic32x4-gpio-func", +- aic32x4_setup->gpio_func, 5) >= 0) +- aic32x4->setup = aic32x4_setup; ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) ++ aic32x4->gpio_func[i] = AIC32X4_MFPX_DEFAULT_VALUE; ++ of_property_read_u32_array(np, "aic32x4-gpio-func", ++ aic32x4->gpio_func, ARRAY_SIZE(aic32x4->gpio_func)); ++ + return 0; + } + +@@ -1266,7 +1222,7 @@ static void aic32x4_disable_regulators(struct aic32x4_priv *aic32x4) + } + + static int aic32x4_setup_regulators(struct device *dev, +- struct aic32x4_priv *aic32x4) ++ struct aic32x4_priv *aic32x4) + { + int ret = 0; + +@@ -1356,12 +1312,8 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + struct device_node *np = dev->of_node; + int ret; + +- if (IS_ERR(regmap)) +- return PTR_ERR(regmap); +- +- aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), +- GFP_KERNEL); +- if (aic32x4 == NULL) ++ aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), GFP_KERNEL); ++ if (!aic32x4) + return -ENOMEM; + + aic32x4->dev = dev; +@@ -1405,11 +1357,13 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + switch (aic32x4->type) { + case AIC32X4_TYPE_TAS2505: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4_tas2505, &aic32x4_tas2505_dai, 1); ++ &soc_component_dev_aic32x4_tas2505, ++ &aic32x4_tas2505_dai, 1); + break; + default: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4, &aic32x4_dai, 1); ++ &soc_component_dev_aic32x4, ++ &aic32x4_dai, 1); + } + + if (ret) { +diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h +--- a/sound/soc/codecs/tlv320aic32x4.h ++++ b/sound/soc/codecs/tlv320aic32x4.h +@@ -3,7 +3,6 @@ + * tlv320aic32x4.h + */ + +- + #ifndef _TLV320AIC32X4_H + #define _TLV320AIC32X4_H + +@@ -16,7 +15,7 @@ enum aic32x4_type { + AIC32X4_TYPE_TAS2505, + }; + +-extern const struct regmap_config aic32x4_regmap_config; ++extern const struct regmap_range_cfg aic32x4_regmap_pages[]; + int aic32x4_probe(struct device *dev, struct regmap *regmap, + enum aic32x4_type type); + void aic32x4_remove(struct device *dev); +@@ -24,7 +23,7 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + + /* tlv320aic32x4 register space (in decimal to match datasheet) */ + +-#define AIC32X4_REG(page, reg) ((page * 128) + reg) ++#define AIC32X4_REG(page, reg) (((page) * 128) + (reg)) + + #define AIC32X4_PSEL AIC32X4_REG(0, 0) + +@@ -234,4 +233,31 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + #define AIC32X4_MAX_CODEC_CLKIN_FREQ 110000000 + #define AIC32X4_MAX_PLL_CLKIN 20000000 + ++#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 ++#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 ++#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 ++#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 ++#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 ++ ++#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 ++#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 ++ ++/* GPIO API */ ++#define AIC32X4_MFPX_DEFAULT_VALUE 0xff ++ ++#define AIC32X4_MFP1_DIN_DISABLED 0 ++#define AIC32X4_MFP1_DIN_ENABLED 0x2 ++#define AIC32X4_MFP1_GPIO_IN 0x4 ++ ++#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 ++ ++#define AIC32X4_MFP_GPIO_ENABLED 0x4 ++ ++#define AIC32X4_MFP5_GPIO_DISABLED 0x0 ++#define AIC32X4_MFP5_GPIO_INPUT 0x8 ++#define AIC32X4_MFP5_GPIO_OUTPUT 0xc ++#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 ++ + #endif /* _TLV320AIC32X4_H */ +diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c +--- a/sound/soc/codecs/tlv320dac33.c ++++ b/sound/soc/codecs/tlv320dac33.c +@@ -7,6 +7,7 @@ + * Copyright: (C) 2009 Nokia Corporation + */ + ++#include + #include + #include + #include +@@ -236,13 +237,10 @@ static int dac33_write_locked(struct snd_soc_component *component, unsigned int + unsigned int value) + { + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&dac33->mutex); +- ret = dac33_write(component, reg, value); +- mutex_unlock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + +- return ret; ++ return dac33_write(component, reg, value); + } + + #define DAC33_I2C_ADDR_AUTOINC 0x80 +@@ -365,13 +363,13 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_dbg(component->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); +- goto exit; ++ return ret; + } + + if (power) { +@@ -380,7 +378,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + if (dac33->reset_gpiod) { +@@ -388,7 +386,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -400,7 +398,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -409,14 +407,12 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to disable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + dac33->chip_power = 0; + } + +-exit: +- mutex_unlock(&dac33->mutex); + return ret; + } + +@@ -659,7 +655,6 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; + unsigned int delay; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: +@@ -667,10 +662,10 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + DAC33_THRREG(dac33->nsample)); + + /* Take the timestamps */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- dac33->t_stamp1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); ++ dac33->t_stamp1 = dac33->t_stamp2; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(dac33->alarm_threshold)); +@@ -682,11 +677,11 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + break; + case DAC33_FIFO_MODE7: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- /* Move back the timestamp with drain time */ +- dac33->t_stamp1 -= dac33->mode7_us_to_lthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); ++ /* Move back the timestamp with drain time */ ++ dac33->t_stamp1 -= dac33->mode7_us_to_lthr; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(DAC33_MODE7_MARGIN)); +@@ -704,14 +699,12 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); + + dac33_write16(component, DAC33_NSAMPLE_MSB, + DAC33_THRREG(dac33->nsample)); +@@ -735,7 +728,7 @@ static void dac33_work(struct work_struct *work) + dac33 = container_of(work, struct tlv320dac33_priv, work); + component = dac33->component; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + switch (dac33->state) { + case DAC33_PREFILL: + dac33->state = DAC33_PLAYBACK; +@@ -757,18 +750,15 @@ static void dac33_work(struct work_struct *work) + dac33_write(component, DAC33_FIFO_CTRL_A, reg); + break; + } +- mutex_unlock(&dac33->mutex); + } + + static irqreturn_t dac33_interrupt_handler(int irq, void *dev) + { + struct snd_soc_component *component = dev; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); + + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) +@@ -902,14 +892,13 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ +- mutex_unlock(&dac33->mutex); + return 0; + } + +@@ -1053,8 +1042,6 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + break; + } + +- mutex_unlock(&dac33->mutex); +- + return 0; + } + +@@ -1156,21 +1143,20 @@ static snd_pcm_sframes_t dac33_dai_delay( + unsigned int time_delta, uthr; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- t1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ t1 = dac33->t_stamp2; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) +- goto out; ++ return 0; + + if (t0 > t1) { + /* +@@ -1230,23 +1216,22 @@ static snd_pcm_sframes_t dac33_dai_delay( + } + break; + case DAC33_FIFO_MODE7: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- uthr = dac33->uthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ uthr = dac33->uthr; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) +- goto out; ++ return 0; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ +- delay = uthr; +- goto out; ++ return uthr; + } + + time_delta = t_now - t0; +@@ -1287,7 +1272,7 @@ static snd_pcm_sframes_t dac33_dai_delay( + dac33->fifo_mode); + break; + } +-out: ++ + return delay; + } + +diff --git a/sound/soc/codecs/tscs42xx.c b/sound/soc/codecs/tscs42xx.c +--- a/sound/soc/codecs/tscs42xx.c ++++ b/sound/soc/codecs/tscs42xx.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -210,25 +211,21 @@ static int power_up_audio_plls(struct snd_soc_component *component) + return ret; + } + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, mask, val); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL on (%d)\n", ret); +- goto exit; ++ return ret; + } + + if (!plls_locked(component)) { + dev_err(component->dev, "Failed to lock plls\n"); + ret = -ENOMSG; +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int power_down_audio_plls(struct snd_soc_component *component) +@@ -236,28 +233,24 @@ static int power_down_audio_plls(struct snd_soc_component *component) + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL1, + RV_PLLCTL1C_PDB_PLL1_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL2, + RV_PLLCTL1C_PDB_PLL2_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int coeff_ram_get(struct snd_kcontrol *kcontrol, +@@ -269,13 +262,11 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + memcpy(ucontrol->value.bytes.data, + &tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], params->max); + +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- + return 0; + } + +@@ -290,14 +281,14 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + unsigned int coeff_cnt = params->max / COEFF_SIZE; + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + tscs42xx->coeff_ram_synced = false; + + memcpy(&tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + if (plls_locked(component)) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, +@@ -305,18 +296,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + /* Input L Capture Route */ +@@ -385,21 +370,17 @@ static int dac_event(struct snd_soc_dapm_widget *w, + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + if (!tscs42xx->coeff_ram_synced) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, 0x00, + COEFF_RAM_COEFF_COUNT); + if (ret < 0) +- goto exit; ++ return ret; + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static const struct snd_soc_dapm_widget tscs42xx_dapm_widgets[] = { +@@ -926,12 +907,10 @@ static int setup_sample_rate(struct snd_soc_component *component, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->samplerate = rate; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +@@ -1253,12 +1232,10 @@ static int tscs42xx_set_dai_bclk_ratio(struct snd_soc_dai *codec_dai, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->bclk_ratio = ratio; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c +--- a/sound/soc/codecs/tscs454.c ++++ b/sound/soc/codecs/tscs454.c +@@ -4,6 +4,7 @@ + // Author: Steven Eckhoff + + #include ++#include + #include + #include + #include +@@ -329,12 +330,10 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); +- +- memcpy(ucontrol->value.bytes.data, +- &coeff_ram[ctl->addr * COEFF_SIZE], params->max); +- +- mutex_unlock(coeff_ram_lock); ++ scoped_guard(mutex, coeff_ram_lock) { ++ memcpy(ucontrol->value.bytes.data, ++ &coeff_ram[ctl->addr * COEFF_SIZE], params->max); ++ } + + return 0; + } +@@ -428,15 +427,15 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); ++ guard(mutex)(coeff_ram_lock); + + *coeff_ram_synced = false; + + memcpy(&coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs454->pll1.lock); +- mutex_lock(&tscs454->pll2.lock); ++ guard(mutex)(&tscs454->pll1.lock); ++ guard(mutex)(&tscs454->pll2.lock); + + val = snd_soc_component_read(component, R_PLLSTAT); + if (val) { /* PLLs locked */ +@@ -446,18 +445,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + *coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->pll2.lock); +- mutex_unlock(&tscs454->pll1.lock); +- mutex_unlock(coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static inline int coeff_ram_sync(struct snd_soc_component *component, +@@ -465,41 +458,35 @@ static inline int coeff_ram_sync(struct snd_soc_component *component, + { + int ret; + +- mutex_lock(&tscs454->dac_ram.lock); +- if (!tscs454->dac_ram.synced) { +- ret = write_coeff_ram(component, tscs454->dac_ram.cache, +- R_DACCRS, R_DACCRADD, R_DACCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->dac_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->dac_ram.lock) { ++ if (!tscs454->dac_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->dac_ram.cache, ++ R_DACCRS, R_DACCRADD, R_DACCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->dac_ram.lock); + +- mutex_lock(&tscs454->spk_ram.lock); +- if (!tscs454->spk_ram.synced) { +- ret = write_coeff_ram(component, tscs454->spk_ram.cache, +- R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->spk_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->spk_ram.lock) { ++ if (!tscs454->spk_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->spk_ram.cache, ++ R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->spk_ram.lock); + +- mutex_lock(&tscs454->sub_ram.lock); +- if (!tscs454->sub_ram.synced) { +- ret = write_coeff_ram(component, tscs454->sub_ram.cache, +- R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->sub_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->sub_ram.lock) { ++ if (!tscs454->sub_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->sub_ram.cache, ++ R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->sub_ram.lock); + + return 0; + } +@@ -658,16 +645,14 @@ static int set_sysclk(struct snd_soc_component *component) + + static inline void reserve_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users++; +- mutex_unlock(&pll->lock); + } + + static inline void free_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users--; +- mutex_unlock(&pll->lock); + } + + static int pll_connected(struct snd_soc_dapm_widget *source, +@@ -679,15 +664,13 @@ static int pll_connected(struct snd_soc_dapm_widget *source, + int users; + + if (strstr(source->name, "PLL 1")) { +- mutex_lock(&tscs454->pll1.lock); +- users = tscs454->pll1.users; +- mutex_unlock(&tscs454->pll1.lock); ++ scoped_guard(mutex, &tscs454->pll1.lock) ++ users = tscs454->pll1.users; + dev_dbg(component->dev, "%s(): PLL 1 users = %d\n", __func__, + users); + } else { +- mutex_lock(&tscs454->pll2.lock); +- users = tscs454->pll2.users; +- mutex_unlock(&tscs454->pll2.lock); ++ scoped_guard(mutex, &tscs454->pll2.lock) ++ users = tscs454->pll2.users; + dev_dbg(component->dev, "%s(): PLL 2 users = %d\n", __func__, + users); + } +@@ -806,7 +789,7 @@ static inline int aif_free(struct snd_soc_component *component, + { + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d\n", __func__, aif->id); + +@@ -829,8 +812,6 @@ static inline int aif_free(struct snd_soc_component *component, + free_pll(tscs454->internal_rate.pll); + } + +- mutex_unlock(&tscs454->aifs_status_lock); +- + return 0; + } + +@@ -3174,7 +3155,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + unsigned int val; + int ret; + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d fs = %u\n", __func__, + aif->id, fs); +@@ -3207,14 +3188,14 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + ret = set_aif_fs(component, aif->id, fs); + if (ret < 0) { + dev_err(component->dev, "Failed to set aif fs (%d)\n", ret); +- goto exit; ++ return ret; + } + + ret = set_aif_sample_format(component, params_format(params), aif->id); + if (ret < 0) { + dev_err(component->dev, + "Failed to set aif sample format (%d)\n", ret); +- goto exit; ++ return ret; + } + + set_aif_status_active(&tscs454->aifs_status, aif->id, +@@ -3223,11 +3204,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + dev_dbg(component->dev, "Set aif %d active. Streams status is 0x%x\n", + aif->id, tscs454->aifs_status.streams); + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->aifs_status_lock); +- +- return ret; ++ return 0; + } + + static int tscs454_hw_free(struct snd_pcm_substream *substream, +diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c +--- a/sound/soc/codecs/twl6040.c ++++ b/sound/soc/codecs/twl6040.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int status; + +- mutex_lock(&priv->mutex); ++ guard(mutex)(&priv->mutex); + + /* Sync status */ + status = twl6040_read(component, TWL6040_REG_STATUS); +@@ -281,8 +282,6 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + snd_soc_jack_report(jack, report, report); + else + snd_soc_jack_report(jack, 0, report); +- +- mutex_unlock(&priv->mutex); + } + + void twl6040_hs_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/uda1342.c b/sound/soc/codecs/uda1342.c +--- a/sound/soc/codecs/uda1342.c ++++ b/sound/soc/codecs/uda1342.c +@@ -308,9 +308,16 @@ static int uda1342_suspend(struct device *dev) + static int uda1342_resume(struct device *dev) + { + struct uda1342_priv *uda1342 = dev_get_drvdata(dev); ++ int ret; + ++ regcache_cache_only(uda1342->regmap, false); + regcache_mark_dirty(uda1342->regmap); +- regcache_sync(uda1342->regmap); ++ ret = regcache_sync(uda1342->regmap); ++ if (ret) { ++ regcache_cache_only(uda1342->regmap, true); ++ regcache_mark_dirty(uda1342->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c +--- a/sound/soc/codecs/wcd9335.c ++++ b/sound/soc/codecs/wcd9335.c +@@ -5010,9 +5010,9 @@ static int wcd9335_power_on_reset(struct wcd9335_codec *wcd) + */ + usleep_range(600, 650); + +- gpiod_set_value(wcd->reset_gpio, 1); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 1); + msleep(20); +- gpiod_set_value(wcd->reset_gpio, 0); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 0); + msleep(20); + + return 0; +diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c +--- a/sound/soc/codecs/wcd934x.c ++++ b/sound/soc/codecs/wcd934x.c +@@ -1265,13 +1265,10 @@ static int wcd934x_set_sido_input_src(struct wcd934x_codec *wcd, int sido_src) + + static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- +- if (++wcd->sysclk_users != 1) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (++wcd->sysclk_users != 1) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_ANA_BIAS, + WCD934X_ANA_BIAS_EN_MASK, +@@ -1328,12 +1325,10 @@ static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + + static int wcd934x_disable_ana_bias_and_syclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- if (--wcd->sysclk_users != 0) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (--wcd->sysclk_users != 0) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_CLK_SYS_MCLK_PRG, + WCD934X_EXT_CLK_BUF_EN_MASK | +@@ -2384,7 +2379,7 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + __func__, micb_num); + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + + switch (req) { + case MICB_PULLUP_ENABLE: +@@ -2446,8 +2441,6 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&wcd934x->micb_lock); +- + return 0; + } + +@@ -2488,7 +2481,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -2506,7 +2499,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2521,15 +2514,11 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD934X_MICB_VAL_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD934X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2550,9 +2539,8 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd934x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd934x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +@@ -5899,10 +5887,8 @@ static int wcd934x_codec_probe(struct platform_device *pdev) + } + + static const struct platform_device_id wcd934x_driver_id[] = { +- { +- .name = "wcd934x-codec", +- }, +- {}, ++ { .name = "wcd934x-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, wcd934x_driver_id); + +diff --git a/sound/soc/codecs/wcd937x-sdw.c b/sound/soc/codecs/wcd937x-sdw.c +--- a/sound/soc/codecs/wcd937x-sdw.c ++++ b/sound/soc/codecs/wcd937x-sdw.c +@@ -1084,10 +1084,16 @@ static int wcd937x_sdw_runtime_suspend(struct device *dev) + static int wcd937x_sdw_runtime_resume(struct device *dev) + { + struct wcd937x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c +--- a/sound/soc/codecs/wcd937x.c ++++ b/sound/soc/codecs/wcd937x.c +@@ -2,6 +2,7 @@ + // Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + + #include ++#include + #include + #include + #include +@@ -1056,7 +1057,7 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + return -EINVAL; + } + +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + switch (req) { + case MICB_PULLUP_ENABLE: + wcd937x->pullup_ref[micb_index]++; +@@ -1136,7 +1137,6 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + } + break; + } +- mutex_unlock(&wcd937x->micb_lock); + + return 0; + } +@@ -1460,7 +1460,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd937x_priv *wcd937x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1475,7 +1475,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -1490,15 +1490,11 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD937X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD937X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -1519,9 +1515,8 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd937x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd937x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd938x-sdw.c b/sound/soc/codecs/wcd938x-sdw.c +--- a/sound/soc/codecs/wcd938x-sdw.c ++++ b/sound/soc/codecs/wcd938x-sdw.c +@@ -1245,10 +1245,16 @@ static int wcd938x_sdw_runtime_suspend(struct device *dev) + static int wcd938x_sdw_runtime_resume(struct device *dev) + { + struct wcd938x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + pm_runtime_mark_last_busy(dev); +diff --git a/sound/soc/codecs/wcd938x.c b/sound/soc/codecs/wcd938x.c +--- a/sound/soc/codecs/wcd938x.c ++++ b/sound/soc/codecs/wcd938x.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -1976,7 +1977,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd938x_priv *wcd938x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1994,7 +1995,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd938x->micb_lock); ++ guard(mutex)(&wcd938x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2009,15 +2010,11 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD938X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD938X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2038,9 +2035,8 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd938x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd938x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd939x-sdw.c b/sound/soc/codecs/wcd939x-sdw.c +--- a/sound/soc/codecs/wcd939x-sdw.c ++++ b/sound/soc/codecs/wcd939x-sdw.c +@@ -1431,10 +1431,16 @@ static int wcd939x_sdw_runtime_suspend(struct device *dev) + static int wcd939x_sdw_runtime_resume(struct device *dev) + { + struct wcd939x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c +--- a/sound/soc/codecs/wcd939x.c ++++ b/sound/soc/codecs/wcd939x.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1923,7 +1924,6 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(component); + unsigned int micb_reg, cur_vout_ctl, micb_en; + int req_vout_ctl; +- int ret = 0; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1941,7 +1941,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd939x->micb_lock); ++ guard(mutex)(&wcd939x->micb_lock); + + /* + * If requested micbias voltage is same as current micbias +@@ -1957,15 +1957,11 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD939X_MICB_VOUT_CTL); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = req_vout_ctl; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return req_vout_ctl; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + dev_dbg(component->dev, "%s: micb_num: %d, cur_mv: %d, req_mv: %d, micb_en: %d\n", + __func__, micb_num, WCD_VOUT_CTL_TO_MICB(cur_vout_ctl), +@@ -1990,9 +1986,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + usleep_range(2000, 2100); + } + +-exit: +- mutex_unlock(&wcd939x->micb_lock); +- return ret; ++ return 0; + } + + static int wcd939x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c +--- a/sound/soc/codecs/wm0010.c ++++ b/sound/soc/codecs/wm0010.c +@@ -9,6 +9,7 @@ + * Scott Ling + */ + ++#include + #include + #include + #include +@@ -148,13 +149,11 @@ static const char *wm0010_state_to_str(enum wm0010_state state) + static void wm0010_halt(struct snd_soc_component *component) + { + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + enum wm0010_state state; + + /* Fetch the wm0010 state */ +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + switch (state) { + case WM0010_POWER_OFF: +@@ -173,9 +172,8 @@ static void wm0010_halt(struct snd_soc_component *component) + break; + } + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- wm0010->state = WM0010_POWER_OFF; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ wm0010->state = WM0010_POWER_OFF; + } + + struct wm0010_boot_xfer { +@@ -190,11 +188,9 @@ struct wm0010_boot_xfer { + static void wm0010_mark_boot_failure(struct wm0010_priv *wm0010) + { + enum wm0010_state state; +- unsigned long flags; + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + dev_err(wm0010->dev, "Failed to transition from `%s' state to `%s' state\n", + wm0010_state_to_str(state), wm0010_state_to_str(state + 1)); +@@ -337,7 +333,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + struct wm0010_boot_xfer *xfer; + int ret; + DECLARE_COMPLETION_ONSTACK(done); +- const struct firmware *fw; + const struct dfw_binrec *rec; + const struct dfw_inforec *inforec; + u64 *img; +@@ -346,6 +341,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + INIT_LIST_HEAD(&xfer_list); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, name, component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request application(%s): %d\n", +@@ -364,16 +360,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* First record should be INFO */ + if (rec->command != DFW_CMD_INFO) { + dev_err(component->dev, "First record not INFO\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + if (inforec->info_version != INFO_VERSION) { + dev_err(component->dev, + "Unsupported version (%02d) of INFO record\r\n", + inforec->info_version); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + dev_dbg(component->dev, "Version v%02d INFO record found\r\n", +@@ -382,8 +376,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* Check it's a DSP file */ + if (dsp != DEVICE_ID_WM0010) { + dev_err(component->dev, "Not a WM0010 firmware file.\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + /* Skip the info record as we don't need to send it */ +@@ -408,14 +401,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.rx_buf = out; + + img = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.tx_buf = img; + +@@ -453,13 +446,13 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + ret = spi_async(spi, &xfer->m); + if (ret != 0) { + dev_err(component->dev, "Write failed: %d\n", ret); +- goto abort1; ++ goto abort; + } + + if (wm0010->boot_failed) { + dev_dbg(component->dev, "Boot fail!\n"); + ret = -EINVAL; +- goto abort1; ++ goto abort; + } + } + +@@ -467,7 +460,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + ret = 0; + +-abort1: ++abort: + while (!list_empty(&xfer_list)) { + xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer, + list); +@@ -477,8 +470,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + kfree(xfer); + } + +-abort: +- release_firmware(fw); + return ret; + } + +@@ -486,14 +477,12 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + { + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + struct spi_message m; + struct spi_transfer t; +- u32 *img; +- u8 *out; + int i; + int ret = 0; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request stage2 loader: %d\n", +@@ -504,17 +493,15 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); + + /* Copy to local buffer first as vmalloc causes problems for dma */ +- img = kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); +- if (!img) { +- ret = -ENOMEM; +- goto abort2; +- } ++ u32 *img __free(kfree) = ++ kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); ++ if (!img) ++ return -ENOMEM; + +- out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); +- if (!out) { +- ret = -ENOMEM; +- goto abort1; +- } ++ u8 *out __free(kfree) = ++ kzalloc(fw->size, GFP_KERNEL | GFP_DMA); ++ if (!out) ++ return -ENOMEM; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); +@@ -531,7 +518,7 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + ret = spi_sync(spi, &m); + if (ret != 0) { + dev_err(component->dev, "Initial download failed: %d\n", ret); +- goto abort; ++ return ret; + } + + /* Look for errors from the boot ROM */ +@@ -540,18 +527,11 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_err(component->dev, "Boot ROM error: %x in %d\n", + out[i], i); + wm0010_mark_boot_failure(wm0010); +- ret = -EBUSY; +- goto abort; ++ return -EBUSY; + } + } +-abort: +- kfree(out); +-abort1: +- kfree(img); +-abort2: +- release_firmware(fw); + +- return ret; ++ return 0; + } + + static int wm0010_boot(struct snd_soc_component *component) +@@ -734,9 +714,8 @@ static int wm0010_set_bias_level(struct snd_soc_component *component, + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_PREPARE) { +- mutex_lock(&wm0010->lock); +- wm0010_halt(component); +- mutex_unlock(&wm0010->lock); ++ scoped_guard(mutex, &wm0010->lock) ++ wm0010_halt(component); + } + break; + case SND_SOC_BIAS_OFF: +@@ -832,9 +811,8 @@ static irqreturn_t wm0010_irq(int irq, void *data) + case WM0010_OUT_OF_RESET: + case WM0010_BOOTROM: + case WM0010_STAGE2: +- spin_lock(&wm0010->irq_lock); +- complete(&wm0010->boot_completion); +- spin_unlock(&wm0010->irq_lock); ++ scoped_guard(spinlock, &wm0010->irq_lock) ++ complete(&wm0010->boot_completion); + return IRQ_HANDLED; + default: + return IRQ_NONE; +diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c +--- a/sound/soc/codecs/wm2000.c ++++ b/sound/soc/codecs/wm2000.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -612,20 +613,15 @@ static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int anc_active = ucontrol->value.integer.value[0]; +- int ret; + + if (anc_active > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->anc_active = anc_active; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, +@@ -645,20 +641,15 @@ static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int val = ucontrol->value.integer.value[0]; +- int ret; + + if (val > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->spk_ena = val; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_kcontrol_new wm2000_controls[] = { +@@ -676,9 +667,8 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); +- int ret; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + wm2000->anc_eng_ena = 1; +@@ -686,11 +676,7 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + if (SND_SOC_DAPM_EVENT_OFF(event)) + wm2000->anc_eng_ena = 0; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { +@@ -810,7 +796,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + struct wm2000_priv *wm2000; + struct wm2000_platform_data *pdata; + const char *filename; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + int ret, i; + unsigned int reg; + u16 id; +@@ -828,7 +814,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + ret = PTR_ERR(wm2000->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); +- goto out; ++ return ret; + } + + for (i = 0; i < WM2000_NUM_SUPPLIES; i++) +@@ -922,9 +908,6 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + + err_supplies: + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); +- +-out: +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c +--- a/sound/soc/codecs/wm2200.c ++++ b/sound/soc/codecs/wm2200.c +@@ -2461,7 +2461,15 @@ static int wm2200_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm2200->regmap, false); +- regcache_sync(wm2200->regmap); ++ ret = regcache_sync(wm2200->regmap); ++ if (ret) { ++ regcache_cache_only(wm2200->regmap, true); ++ regcache_mark_dirty(wm2200->regmap); ++ gpiod_set_value_cansleep(wm2200->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), ++ wm2200->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c +--- a/sound/soc/codecs/wm5100.c ++++ b/sound/soc/codecs/wm5100.c +@@ -2659,7 +2659,15 @@ static int wm5100_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm5100->regmap, false); +- regcache_sync(wm5100->regmap); ++ ret = regcache_sync(wm5100->regmap); ++ if (ret) { ++ regcache_cache_only(wm5100->regmap, true); ++ regcache_mark_dirty(wm5100->regmap); ++ gpiod_set_value_cansleep(wm5100->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), ++ wm5100->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c +--- a/sound/soc/codecs/wm5102.c ++++ b/sound/soc/codecs/wm5102.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -667,10 +668,9 @@ static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + put_unaligned_be16(arizona->dac_comp_coeff, + ucontrol->value.bytes.data); +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -681,16 +681,14 @@ static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + uint16_t dac_comp_coeff = get_unaligned_be16(ucontrol->value.bytes.data); +- int ret = 0; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_coeff != dac_comp_coeff) { + arizona->dac_comp_coeff = dac_comp_coeff; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, +@@ -699,9 +697,8 @@ static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -712,19 +709,17 @@ static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; +- int ret = 0; + + if (ucontrol->value.integer.value[0] > mc->max) + return -EINVAL; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_enabled != ucontrol->value.integer.value[0]) { + arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static const char * const wm5102_osr_text[] = { +diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c +--- a/sound/soc/codecs/wm8731.c ++++ b/sound/soc/codecs/wm8731.c +@@ -10,6 +10,7 @@ + * Based on wm8753.c by Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -110,22 +111,20 @@ static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8731->lock); ++ guard(mutex)(&wm8731->lock); + if (wm8731->deemph != deemph) { + wm8731->deemph = deemph; + + wm8731_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8731->lock); + +- return ret; ++ return 0; + } + + static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -458,22 +459,20 @@ static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8903->lock); ++ guard(mutex)(&wm8903->lock); + if (wm8903->deemph != deemph) { + wm8903->deemph = deemph; + + wm8903_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8903->lock); + +- return ret; ++ return 0; + } + + /* ALSA can only do steps of .01dB */ +diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c +--- a/sound/soc/codecs/wm8940.c ++++ b/sound/soc/codecs/wm8940.c +@@ -333,7 +333,7 @@ static const struct snd_soc_dapm_route wm8940_dapm_routes[] = { + {"ADC", NULL, "Boost Mixer"}, + }; + +-#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0); ++#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0) + + static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c +--- a/sound/soc/codecs/wm8958-dsp2.c ++++ b/sound/soc/codecs/wm8958-dsp2.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -864,9 +865,8 @@ static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "ENH_EQ", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->enh_eq = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -876,9 +876,8 @@ static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC+VSS", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc_vss = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -888,9 +887,8 @@ static void wm8958_mbc_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c +--- a/sound/soc/codecs/wm8962.c ++++ b/sound/soc/codecs/wm8962.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1564,11 +1565,10 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int old = wm8962->dsp2_ena; +- int ret = 0; + int dsp2_running = snd_soc_component_read(component, WM8962_DSP2_POWER_MANAGEMENT) & + WM8962_DSP2_ENA; + +- mutex_lock(&wm8962->dsp2_ena_lock); ++ guard(mutex)(&wm8962->dsp2_ena_lock); + + if (ucontrol->value.integer.value[0]) + wm8962->dsp2_ena |= 1 << shift; +@@ -1576,9 +1576,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962->dsp2_ena &= ~(1 << shift); + + if (wm8962->dsp2_ena == old) +- goto out; +- +- ret = 1; ++ return 0; + + if (dsp2_running) { + if (wm8962->dsp2_ena) +@@ -1587,10 +1585,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962_dsp2_stop(component); + } + +-out: +- mutex_unlock(&wm8962->dsp2_ena_lock); +- +- return ret; ++ return 1; + } + + /* The VU bits for the headphones are in a different register to the mute +@@ -3937,7 +3932,9 @@ static int wm8962_runtime_resume(struct device *dev) + WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, + 0); + +- regcache_sync(wm8962->regmap); ++ ret = regcache_sync(wm8962->regmap); ++ if (ret) ++ goto cache_sync_err; + + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, + WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, +@@ -3955,6 +3952,11 @@ static int wm8962_runtime_resume(struct device *dev) + + return 0; + ++cache_sync_err: ++ regcache_cache_only(wm8962->regmap, true); ++ regcache_mark_dirty(wm8962->regmap); ++ regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), ++ wm8962->supplies); + disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; +diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c +--- a/sound/soc/codecs/wm8994.c ++++ b/sound/soc/codecs/wm8994.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -766,7 +767,7 @@ static void active_reference(struct snd_soc_component *component) + { + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount++; + +@@ -775,8 +776,6 @@ static void active_reference(struct snd_soc_component *component) + + /* If we're using jack detection go into audio mode */ + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_AUDIO); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void active_dereference(struct snd_soc_component *component) +@@ -784,7 +783,7 @@ static void active_dereference(struct snd_soc_component *component) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + u16 mode; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount--; + +@@ -800,8 +799,6 @@ static void active_dereference(struct snd_soc_component *component) + + wm1811_jackdet_set_mode(component, mode); + } +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static int clk_sys_event(struct snd_soc_dapm_widget *w, +@@ -3704,7 +3701,7 @@ static void wm8958_open_circuit_work(struct work_struct *work) + open_circuit_work.work); + struct device *dev = wm8994->wm8994->dev; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm1811_micd_stop(wm8994->hubs.component); + +@@ -3718,8 +3715,6 @@ static void wm8958_open_circuit_work(struct work_struct *work) + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void wm8958_mic_id(void *data, u16 status) +@@ -3777,7 +3772,7 @@ static void wm1811_mic_work(struct work_struct *work) + struct snd_soc_component *component = wm8994->hubs.component; + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + +- pm_runtime_get_sync(component->dev); ++ guard(pm_runtime_active)(component->dev); + + /* If required for an external cap force MICBIAS on */ + if (control->pdata.jd_ext_cap) { +@@ -3785,7 +3780,7 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_dapm_sync(dapm); + } + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + dev_dbg(component->dev, "Starting mic detection\n"); + +@@ -3803,10 +3798,6 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm1811_jackdet_irq(int irq, void *data) +@@ -4026,15 +4017,10 @@ static void wm8958_mic_work(struct work_struct *work) + mic_complete_work.work); + struct snd_soc_component *component = wm8994->hubs.component; + +- pm_runtime_get_sync(component->dev); +- +- mutex_lock(&wm8994->accdet_lock); ++ guard(pm_runtime_active)(component->dev); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->mic_id_cb(wm8994->mic_id_cb_data, wm8994->mic_status); +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm8958_mic_irq(int irq, void *data) +diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c +--- a/sound/soc/codecs/wm9712.c ++++ b/sound/soc/codecs/wm9712.c +@@ -6,6 +6,7 @@ + * Author: Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -229,7 +230,7 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = 1 << shift; + +- mutex_lock(&wm9712->lock); ++ guard(mutex)(&wm9712->lock); + old = wm9712->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9712->hp_mixer[mixer] |= mask; +@@ -251,8 +252,6 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9712->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c +--- a/sound/soc/codecs/wm9713.c ++++ b/sound/soc/codecs/wm9713.c +@@ -11,6 +11,7 @@ + * o Support for DAPM + */ + ++#include + #include + #include + #include +@@ -238,7 +239,7 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = (1 << shift); + +- mutex_lock(&wm9713->lock); ++ guard(mutex)(&wm9713->lock); + old = wm9713->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9713->hp_mixer[mixer] |= mask; +@@ -260,8 +261,6 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9713->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -348,7 +348,6 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); +- int ret = 1; + + if (ucontrol->value.enumerated.item[0] == dsp[e->shift_l].fw) + return 0; +@@ -356,16 +355,14 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + if (ucontrol->value.enumerated.item[0] >= WM_ADSP_NUM_FW) + return -EINVAL; + +- mutex_lock(&dsp[e->shift_l].cs_dsp.pwr_lock); ++ guard(mutex)(&dsp[e->shift_l].cs_dsp.pwr_lock); + + if (dsp[e->shift_l].cs_dsp.booted || !list_empty(&dsp[e->shift_l].compr_list)) +- ret = -EBUSY; ++ return -EBUSY; + else + dsp[e->shift_l].fw = ucontrol->value.enumerated.item[0]; + +- mutex_unlock(&dsp[e->shift_l].cs_dsp.pwr_lock); +- +- return ret; ++ return 1; + } + EXPORT_SYMBOL_GPL(wm_adsp_fw_put); + +@@ -450,15 +447,13 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, + if (val == 0) + return 0; /* 0 means no event */ + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + if (cs_ctl->enabled) + ret = cs_dsp_coeff_write_acked_control(cs_ctl, val); + else + ret = -EPERM; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + if (ret < 0) + return ret; + +@@ -486,15 +481,13 @@ static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + int ret = 0; + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size); + + if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) + ret = -EFAULT; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + return ret; + } + +@@ -694,10 +687,9 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, + struct cs_dsp_coeff_ctl *cs_ctl; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); + + if (ret < 0) + return ret; +@@ -709,14 +701,10 @@ EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); + int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) + { +- int ret; ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); +- ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), ++ return cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), + 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); + +@@ -1270,38 +1258,32 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + { + struct wm_adsp_compr *compr, *tmp; + struct snd_soc_pcm_runtime *rtd = stream->private_data; +- int ret = 0; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (wm_adsp_fw[dsp->fw].num_caps == 0) { + adsp_err(dsp, "%s: Firmware does not support compressed API\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -ENXIO; +- goto out; ++ return -ENXIO; + } + + if (wm_adsp_fw[dsp->fw].compr_direction != stream->direction) { + adsp_err(dsp, "%s: Firmware does not support stream direction\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + list_for_each_entry(tmp, &dsp->compr_list, list) { + if (!strcmp(tmp->name, snd_soc_rtd_to_codec(rtd, 0)->name)) { + adsp_err(dsp, "%s: Only a single stream supported per dai\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + } + + compr = kzalloc_obj(*compr); +- if (!compr) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!compr) ++ return -ENOMEM; + + compr->dsp = dsp; + compr->stream = stream; +@@ -1311,10 +1293,7 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + + stream->runtime->private_data = compr; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_open); + +@@ -1324,7 +1303,7 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + wm_adsp_compr_detach(compr); + list_del(&compr->list); +@@ -1332,8 +1311,6 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + kfree(compr->raw_buf); + kfree(compr); + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_free); +@@ -1741,7 +1718,7 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + + compr_dbg(compr, "Trigger: %d\n", cmd); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -1777,8 +1754,6 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_trigger); +@@ -1907,21 +1882,20 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + + compr_dbg(compr, "Pointer request\n"); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + buf = compr->buf; + + if (dsp->fatal_error || !buf || buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); +- ret = -EIO; +- goto out; ++ return -EIO; + } + + if (buf->avail < wm_adsp_compr_frag_words(compr)) { + ret = wm_adsp_buffer_update_avail(buf); + if (ret < 0) { + compr_err(compr, "Error reading avail: %d\n", ret); +- goto out; ++ return ret; + } + + /* +@@ -1934,14 +1908,14 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + if (buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); +- goto out; ++ return ret; + } + + ret = wm_adsp_buffer_reenable_irq(buf); + if (ret < 0) { + compr_err(compr, "Failed to re-enable buffer IRQ: %d\n", + ret); +- goto out; ++ return ret; + } + } + } +@@ -1950,9 +1924,6 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + tstamp->copied_total += buf->avail * CS_DSP_DATA_WORD_SIZE; + tstamp->sampling_rate = compr->sample_rate; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_pointer); +@@ -2063,15 +2034,13 @@ int wm_adsp_compr_copy(struct snd_soc_component *component, + struct wm_adsp *dsp = compr->dsp; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (stream->direction == SND_COMPRESS_CAPTURE) + ret = wm_adsp_compr_read(compr, buf, count); + else + ret = -ENOTSUPP; + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); +diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c +--- a/sound/soc/codecs/wsa881x.c ++++ b/sound/soc/codecs/wsa881x.c +@@ -1178,7 +1178,13 @@ static int wsa881x_runtime_resume(struct device *dev) + } + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ gpiod_direction_output(wsa881x->sd_n, 1); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c +--- a/sound/soc/codecs/wsa883x.c ++++ b/sound/soc/codecs/wsa883x.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -1237,9 +1238,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = true; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = true; + + switch (wsa883x->dev_mode) { + case RECEIVER: +@@ -1290,9 +1290,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + WSA883X_GLOBAL_PA_EN_MASK, 0); + snd_soc_component_write_field(component, WSA883X_PDM_WD_CTL, + WSA883X_PDM_EN_MASK, 0); +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = false; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = false; + break; + } + return 0; +@@ -1696,9 +1695,15 @@ static int wsa883x_runtime_suspend(struct device *dev) + static int wsa883x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c +--- a/sound/soc/codecs/wsa884x.c ++++ b/sound/soc/codecs/wsa884x.c +@@ -1701,9 +1701,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = true; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = true; + + wsa884x_spkr_post_pmu(component, wsa884x); + +@@ -1717,9 +1716,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, + 0x0); + +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = false; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = false; + break; + } + +@@ -2147,9 +2145,15 @@ static int wsa884x_runtime_suspend(struct device *dev) + static int wsa884x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/wsa885x.c +@@ -0,0 +1,1533 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. ++ */ ++ ++/* WSA885X codec driver */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Control Registers - Audio Processing */ ++#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S 0x0000 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK 0x0004 ++#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX 0x0140 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID 0x0208 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX 0x0240 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER 0x0340 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0 0x4405 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1 0x4406 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB 0x4409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB 0x6409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB 0x440a ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB 0x640a ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS 0x0a04 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS 0x0a40 ++#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE 0x0b10 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX 0x0e40 ++ ++/* Analog Top Registers - Power and Clock Control */ ++#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d ++#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG 0x8024 ++#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG 0x8025 ++#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL 0x8034 ++ ++/* Analog PLL Registers */ ++#define WSA885X_ANA_PLL_DIV_CTL_0 0x8090 ++#define WSA885X_ANA_PLL_DIV_CTL_1 0x8091 ++#define WSA885X_ANA_TOP_PLL_VCO_CTL 0x8092 ++#define WSA885X_ANA_TOP_PLL_LOOPFILT_0 0x8093 ++#define WSA885X_ANA_TOP_PLL_OVRD_CTL 0x8098 ++#define WSA885X_ANA_TOP_PLL_STATUS_0 0x809a ++#define WSA885X_ANA_TOP_PLL_STATUS_1 0x809b ++ ++/* Analog Boost Control Registers */ ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL2 0x805b ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL3 0x805c ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2 0x805e ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3 0x805f ++#define WSA885X_ANA_TOP_BOOST_MISC 0x8063 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2 0x8065 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4 0x8067 ++ ++/* Analog IV Sense ADC Registers */ ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2 0x80ca ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3 0x80cb ++#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL 0x80cc ++#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2 0x80d0 ++ ++/* Analog Speaker Power Stage Registers */ ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3 0x8108 ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3 0x810b ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3 0x810e ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3 0x8111 ++#define WSA885X_ANA_TOP_SPK_TOP_SPARE3 0x813c ++#define WSA885X_SPK_TOP_LF_CH1_CTRL11 0x811c ++#define WSA885X_SPK_TOP_LF_CH1_TUNE1 0x811d ++#define WSA885X_SPK_TOP_LF_CH2_TUNE1 0x8129 ++#define WSA885X_SPK_TOP_LF_CH1_CTRL9 0x811a ++#define WSA885X_SPK_TOP_LF_CH2_CTRL9 0x8126 ++#define WSA885X_SPK_TOP_LF_CH2_CTRL11 0x8128 ++#define WSA885X_SPK_TOP_COMMON_CTRL2 0x8102 ++#define WSA885X_SPK_TOP_COMMON_TUNE1 0x8103 ++#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1 0x80ba ++#define WSA885X_DIG_CTRL0_TOP_CLK_CFG 0x8418 ++#define WSA885X_DIG_CTRL0_SDCA_COMMIT 0x8419 ++#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE 0x841a ++#define WSA885X_DIG_CTRL0_SYS_CLK_SEL 0x841b ++#define WSA885X_DIG_CTRL0_CDC_CLK_CTL 0x841c ++#define WSA885X_DIG_CTRL0_PA_FSM_CTL 0x8420 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0 0x8423 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1 0x8424 ++#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1 0x842b ++#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1 0x8435 ++#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL 0x8458 ++#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL 0x8470 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1 0x84b4 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1 0x84b7 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1 0x84A0 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1 0x84A4 ++ ++/* Digital Control 1 Registers - I2S/TDM Interface */ ++#define WSA885X_DIG_CTRL1_I2S_CTL0 0x85A0 ++#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX 0x85A2 ++#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX 0x85A3 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX 0x85AA ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX 0x85AB ++#define WSA885X_DIG_CTRL1_I2S_RESET_CTL 0x85AE ++ ++/* CDC RX Path Registers - Audio Data Path */ ++#define WSA885X_CDC_RX0_RX_PATH_CFG0 0x8601 ++#define WSA885X_CDC_RX0_RX_PATH_CFG1 0x8602 ++#define WSA885X_CDC_RX0_RX_PATH_CTL 0x8606 ++#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613 ++#define WSA885X_CDC_RX1_RX_PATH_CFG0 0x8621 ++#define WSA885X_CDC_RX1_RX_PATH_CFG1 0x8622 ++#define WSA885X_CDC_RX1_RX_PATH_CTL 0x8626 ++#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633 ++ ++/* CDC Compander Registers - Dynamic Range Control */ ++#define WSA885X_CDC_COMPANDER0_CTL0 0x8640 ++#define WSA885X_CDC_COMPANDER0_CTL7 0x8647 ++#define WSA885X_CDC_COMPANDER1_CTL0 0x8660 ++#define WSA885X_CDC_COMPANDER1_CTL7 0x8667 ++ ++/* CDC Speaker Protection Registers - IV Sense */ ++#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1 ++#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1 ++#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9 ++#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9 ++ ++/* CDC Class-H Registers - Headroom Control */ ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL1 0x86CD ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL0 0x86CC ++#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0 0x86C7 ++#define WSA885X_CDC_CLSH_CLSH_V_HD_PA 0x86C3 ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL2 0x86CE ++ ++/* Driver Constants */ ++#define WSA885X_CLK_RATE_FIXED 73728000 ++#define WSA885X_NUM_REGS 0x03 ++ ++/* Interrupt Registers */ ++#define WSA885X_INTR_STATUS0 0x8584 ++#define WSA885X_INTR_MASK0 0x8581 ++#define WSA885X_INTR_CLEAR0 0x8587 ++ ++/* Power and PA FSM Control Registers */ ++#define WSA885X_PA0_FSM_CTL0 0x842A ++#define WSA885X_PA1_FSM_CTL0 0x8434 ++ ++/* Digital Control GPIO and Interrupt Registers */ ++#define WSA885X_DIG_CTRL1_PIN_CT 0x8510 ++#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL 0x8518 ++#define WSA885X_DIG_CTRL1_INTR_MODE 0x8580 ++ ++#define WSA885X_I2S_CTL0_PCM_RATE_MASK GENMASK(4, 1) ++#define WSA885X_I2S_CTL0_ENABLE_MASK BIT(0) ++#define WSA885X_I2S_CTL0_PCM_RATE(v) \ ++ FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v)) ++#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ 0x0 ++#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ 0x1 ++#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ 0x2 ++#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ 0x3 ++#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ 0x4 ++#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5 ++#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6 ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v)) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v)) ++#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK BIT(0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3) ++#define WSA885X_I2S_TDM_CH_TX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_TX_CH1_EN BIT(1) ++#define WSA885X_I2S_TDM_CH_TX_CH2_EN BIT(2) ++#define WSA885X_I2S_TDM_CH_TX_CH3_EN BIT(3) ++#define WSA885X_I2S_TDM_CH_RX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_RX_CH3_EN BIT(3) ++#define WSA885X_I2S_RESET_CTL_RESET_MASK BIT(0) ++#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2) ++#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3) ++#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK BIT(2) ++ ++#define WSA885X_I2S_TX_SLOT_ISENSE0 0x1 ++#define WSA885X_I2S_TX_SLOT_ISENSE1 0x2 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE0 0x5 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE1 0x6 ++ ++/* RX Sample Rate Index Values - Audio Playback Path */ ++#define WSA885X_RX_RATE_8000HZ 0x00 ++#define WSA885X_RX_RATE_16000HZ 0x01 ++#define WSA885X_RX_RATE_32000HZ 0x02 ++#define WSA885X_RX_RATE_44100HZ 0x03 ++#define WSA885X_RX_RATE_48000HZ 0x04 ++#define WSA885X_RX_RATE_96000HZ 0x05 ++#define WSA885X_RX_RATE_192000HZ 0x06 ++#define WSA885X_RX_RATE_384000HZ 0x07 ++ ++/* VI Sample Rate Index Values - Voltage/Current Sensing Path */ ++#define WSA885X_VI_RATE_8000HZ 0x00 ++#define WSA885X_VI_RATE_16000HZ 0x01 ++#define WSA885X_VI_RATE_44100HZ 0x02 ++#define WSA885X_VI_RATE_48000HZ 0x03 ++#define WSA885X_VI_RATE_96000HZ 0x04 ++#define WSA885X_VI_RATE_22050HZ 0x05 ++#define WSA885X_VI_RATE_24000HZ 0x06 ++#define WSA885X_VI_RATE_192000HZ 0x07 ++#define WSA885X_VI_RATE_384000HZ 0x08 ++ ++/* Channel Configuration Masks */ ++#define WSA885X_CHANNEL_STEREO 0x03 ++#define WSA885X_CHANNEL_MONO_LEFT 0x01 ++#define WSA885X_CHANNEL_MONO_RIGHT 0x02 ++ ++#define WSA885X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 | \ ++ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) ++ ++#define WSA885X_PLL_LOCK_BIT BIT(0) ++ ++#define WSA885X_FU21_VOL_STEPS 124 ++#define WSA885X_USAGE_MODE_MAX 8 ++static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0); ++ ++static bool wsa885x_is_valid_rx_slot_mask(u32 mask) ++{ ++ return mask == WSA885X_CHANNEL_MONO_LEFT || ++ mask == WSA885X_CHANNEL_MONO_RIGHT || ++ mask == WSA885X_CHANNEL_STEREO; ++} ++ ++static const char *const wsa885x_supply_name[] = { ++ "vdd-1p8", ++ "vdd-io", ++}; ++ ++enum { ++ WSA885X_BATT_1S = 1, ++ WSA885X_BATT_2S, ++}; ++ ++enum { ++ WSA885X_IRQ_INT_SAF2WAR = 0, ++ WSA885X_IRQ_INT_WAR2SAF, ++ WSA885X_IRQ_INT_DISABLE, ++ WSA885X_IRQ_INT_PA0_OCP, ++ WSA885X_IRQ_INT_PA1_OCP, ++ WSA885X_IRQ_INT_CLIP0, ++ WSA885X_IRQ_INT_CLIP1, ++ WSA885X_IRQ_INT_CLK_WD, ++ WSA885X_IRQ_INT_INTR_GPIO1_PIN, ++ WSA885X_IRQ_INT_INTR_GPIO2_PIN, ++ WSA885X_IRQ_INT_UVLO, ++ WSA885X_IRQ_INT_BOP, ++ WSA885X_IRQ_INT_PA0_FSM_ERR, ++ WSA885X_IRQ_INT_PA1_FSM_ERR, ++ WSA885X_IRQ_INT_MAIN_FSM_ERR, ++ WSA885X_IRQ_INT_PCM_DATA0_WD, ++ WSA885X_IRQ_INT_PCM_DATA1_WD, ++ WSA885X_IRQ_INT_PCM_DATA0_DC, ++ WSA885X_IRQ_INT_PCM_DATA1_DC, ++ WSA885X_IRQ_INT_PLL_UNLOCKED, ++ WSA885X_IRQ_INT_PROT_MODE_CHANGE, ++ WSA885X_IRQ_INT_PB_CLOCK_VALID, ++ WSA885X_IRQ_INT_SENSE_CLOCK_VALID, ++ WSA885X_IRQ_MAX, ++}; ++ ++struct wsa885x_priv { ++ struct i2c_client *client; ++ struct regmap *regmap; ++ struct device *dev; ++ struct snd_soc_component *component; ++ struct gpio_desc *sd_n; ++ struct reset_control *sd_reset; ++ u32 usage_mode; ++ u32 rx_slot_mask; ++ u32 batt_conf; ++ int stereo_vol_db; ++ struct mutex state_lock; /* protects mutable control state */ ++}; ++ ++struct wsa885x_reg_update { ++ unsigned int reg; ++ unsigned int mask; ++ unsigned int val; ++}; ++ ++static const struct regmap_range_cfg wsa885x_regmap_ranges[] = { ++ { ++ .range_min = 0, ++ .range_max = 0x88ff, ++ .selector_reg = 0x0, ++ .selector_mask = 0xFF, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 0x100, ++ }, ++}; ++ ++static const struct reg_default wsa885x_codec_reg_defaults[] = { ++ {WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03}, ++ {WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22}, ++ {WSA885X_ANA_PLL_DIV_CTL_0, 0x0c}, ++ {WSA885X_ANA_PLL_DIV_CTL_1, 0x50}, ++ {WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00}, ++ {WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4}, ++ {WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13}, ++ {WSA885X_ANA_TOP_BOOST_MISC, 0x79}, ++ {WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00}, ++ {WSA885X_SPK_TOP_COMMON_CTRL2, 0x08}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_COMMON_TUNE1, 0x03}, ++ {WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00}, ++ {WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff}, ++ {WSA885X_ANA_TOP_PLL_STATUS_0, 0x00}, ++ {WSA885X_ANA_TOP_PLL_STATUS_1, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9}, ++ {WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00}, ++ {WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00}, ++ {WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00}, ++ {WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00}, ++ {WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00}, ++ {WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f}, ++ {WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL1_I2S_CTL0, 0x06}, ++ {WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, WSA885X_I2S_TDM_CH_RX_CH3_EN}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX0_RX_PATH_CTL, 0x24}, ++ {WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX1_RX_PATH_CTL, 0x04}, ++ {WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL7, 0x2a}, ++ {WSA885X_CDC_COMPANDER1_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER1_CTL7, 0x2a}, ++ {WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c}, ++ {WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d}, ++ {WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05}, ++}; ++ ++static void wsa885x_multi_update_bits(struct regmap *regmap, ++ const struct wsa885x_reg_update *updates, ++ size_t num_updates) ++{ ++ size_t i; ++ ++ for (i = 0; i < num_updates; i++) ++ regmap_update_bits(regmap, updates[i].reg, ++ updates[i].mask, updates[i].val); ++} ++ ++static void wsa885x_toggle_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++} ++ ++static void wsa885x_pulse_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++} ++ ++static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val) ++{ ++ if (!slot_num_val) ++ return -EINVAL; ++ ++ switch (slots) { ++ case 2: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2; ++ return 0; ++ case 4: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4; ++ return 0; ++ case 8: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 }, ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 }, ++ }; ++ unsigned int slot_num_val; ++ int ret; ++ ++ if (!regmap) ++ return -EINVAL; ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) ++ return ret; ++ ++ regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs)); ++ ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, ++ slot_num_val); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK); ++ regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH0_EN); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN); ++ ++ return 0; ++} ++ ++static int wsa885x_wait_for_pll_lock(struct wsa885x_priv *wsa885x) ++{ ++ unsigned int status = 0; ++ int cnt = 0, ret = 0; ++ ++ do { ++ usleep_range(1000, 1100); ++ ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (status & WSA885X_PLL_LOCK_BIT) ++ return 0; ++ } while (++cnt < 20); ++ ++ dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status); ++ return -ETIMEDOUT; ++} ++ ++static int wsa885x_2s_conf(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 }, ++ { WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d }, ++ { WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static const struct reg_sequence wsa885x_reg_init[] = { ++ { WSA885X_CDC_RX0_RX_PATH_CTL, 0x24 }, ++ { WSA885X_CDC_RX1_RX_PATH_CTL, 0x24 }, ++ { WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_CDC_COMPANDER0_CTL0, 0x01 }, ++ { WSA885X_CDC_COMPANDER1_CTL0, 0x01 }, ++ { WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0f }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x4f }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x02 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x85 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0c }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0e }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x0c }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01 }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x82 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x34 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x41 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0x7f }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c }, ++ { WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d }, ++ { WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03 }, ++ { WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05 }, ++ { WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x20 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0x45 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0x45 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x35 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x21 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc7 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x11 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0x80 }, ++ { WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x08 }, ++ { WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x47 }, ++ { WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x47 }, ++ { WSA885X_CDC_COMPANDER0_CTL7, 0x34 }, ++ { WSA885X_CDC_COMPANDER1_CTL7, 0x34 }, ++ { WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x79 }, ++}; ++ ++static int wsa885x_hw_init(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e }, ++ { WSA885X_DIG_CTRL1_INTR_MODE, 0x01 }, ++ { WSA885X_DIG_CTRL1_PIN_CT, 0x04 }, ++ }; ++ int ret; ++ ++ ret = regmap_multi_reg_write(wsa885x->regmap, wsa885x_reg_init, ++ ARRAY_SIZE(wsa885x_reg_init)); ++ if (ret) ++ return ret; ++ ++ if (wsa885x->batt_conf == WSA885X_BATT_2S) { ++ ret = wsa885x_2s_conf(wsa885x); ++ if (ret) ++ return ret; ++ } ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_unmask_interrupts(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_INTR_MASK0, 0x00 }, ++ { WSA885X_INTR_MASK0 + 1, 0x00 }, ++ { WSA885X_INTR_MASK0 + 2, 0xf8 }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_wait_for_pde_state(struct wsa885x_priv *wsa885x, int ps) ++{ ++ unsigned int act_ps = 0, clock_valid = 0; ++ int rc = 0, cnt = 0; ++ ++ if (ps < 0 || ps > 3) ++ return -EINVAL; ++ ++ do { ++ usleep_range(1000, 1500); ++ rc = regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, ++ &act_ps); ++ if (rc) { ++ dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc); ++ return rc; ++ } ++ if (act_ps == ps) ++ return 0; ++ } while (++cnt < 5); ++ ++ if (regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, ++ &clock_valid)) ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid read failed\n", ++ ps, act_ps); ++ else ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid:%d\n", ++ ps, act_ps, clock_valid); ++ ++ return -ETIMEDOUT; ++} ++ ++static void wsa885x_program_stereo_volume(struct wsa885x_priv *wsa885x, ++ int stereo_vol_db, bool commit) ++{ ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00); ++ ++ if (commit) ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++} ++ ++static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct wsa885x_priv *wsa885x; ++ u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ switch (params_rate(params)) { ++ case 8000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ; ++ break; ++ case 16000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ; ++ break; ++ case 32000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 44100: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ; ++ break; ++ case 48000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 88200: ++ case 96000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ; ++ break; ++ case 176400: ++ case 192000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ; ++ break; ++ case 352800: ++ case 384000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ; ++ break; ++ default: ++ dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params)); ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_PCM_RATE_MASK | ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) | ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, ++ cs21_sample_rate_idx); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, ++ cs24_sample_rate_idx); ++ ++ mutex_lock(&wsa885x->state_lock); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_slot_mask, ++ unsigned int rx_slot_mask, int slots, ++ int slot_width) ++{ ++ static const struct wsa885x_reg_update stereo_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ static const struct wsa885x_reg_update mono_left_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ }; ++ static const struct wsa885x_reg_update mono_right_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ unsigned int slot_num_val; ++ u32 mask; ++ int ret; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) { ++ dev_err(wsa885x->dev, "%s: unsupported slot count %d\n", ++ __func__, slots); ++ return ret; ++ } ++ ++ if (rx_slot_mask && !wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) { ++ dev_err(wsa885x->dev, ++ "%s: unsupported rx_slot_mask 0x%x\n", ++ __func__, rx_slot_mask); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (rx_slot_mask) ++ wsa885x->rx_slot_mask = rx_slot_mask; ++ else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mask = wsa885x->rx_slot_mask; ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, ++ WSA885X_I2S_RESET_CTL_RESET_MASK); ++ ++ if (mask == WSA885X_CHANNEL_STEREO) { ++ wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates, ++ ARRAY_SIZE(stereo_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN); ++ } else if (mask == WSA885X_CHANNEL_MONO_LEFT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates, ++ ARRAY_SIZE(mono_left_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } else if (mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates, ++ ARRAY_SIZE(mono_right_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, 0); ++ ++ ret = 0; ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, ++ unsigned int freq, int dir) ++{ ++ static const struct reg_sequence pll_prep[] = { ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 }, ++ { WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 }, ++ { WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 }, ++ { WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence pll_cleanup[] = { ++ { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 }, ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ u32 pll_div; ++ int ret = 0; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (!freq) ++ return -EINVAL; ++ if (WSA885X_CLK_RATE_FIXED % freq) ++ return -EINVAL; ++ pll_div = WSA885X_CLK_RATE_FIXED / freq; ++ if (pll_div > 0xff) ++ return -EINVAL; ++ ++ regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep)); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02); ++ ++ ret = wsa885x_wait_for_pll_lock(wsa885x); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n"); ++ regmap_multi_reg_write(wsa885x->regmap, pll_cleanup, ++ ARRAY_SIZE(pll_cleanup)); ++ return ret; ++ } ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream) ++{ ++ static const struct reg_sequence mute_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 }, ++ }; ++ static const struct reg_sequence mute_commit_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_prep_head_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_prep_tail_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_volume_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_commit_regs[] = { ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_finish_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ int ret = 0, ps0 = 0, ps3 = 3; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ ret = -EINVAL; ++ goto exit_unlock; ++ } ++ ++ if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ ++ if (mute) { ++ regmap_multi_reg_write(wsa885x->regmap, mute_regs, ++ ARRAY_SIZE(mute_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps3); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "PS3 transition failed: %d\n", ret); ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs, ++ ARRAY_SIZE(mute_commit_regs)); ++ } ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs, ++ ARRAY_SIZE(unmute_prep_head_regs)); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, ++ wsa885x->usage_mode); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs, ++ ARRAY_SIZE(unmute_prep_tail_regs)); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs, ++ ARRAY_SIZE(unmute_volume_regs)); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs, ++ ARRAY_SIZE(unmute_commit_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps0); ++ if (ret) ++ goto exit_unlock; ++ ++ if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN | ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02); ++ } ++ ++ regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs, ++ ARRAY_SIZE(unmute_finish_regs)); ++ } ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops wsa885x_dai_ops = { ++ .hw_params = wsa885x_codec_hw_params, ++ .set_tdm_slot = wsa885x_codec_set_tdm_slot, ++ .set_sysclk = wsa885x_codec_set_sysclk, ++ .mute_stream = wsa885x_codec_mute_stream, ++ .hw_free = wsa885x_codec_hw_free, ++}; ++ ++static struct snd_soc_dai_driver wsa885x_dai[] = { ++ { ++ .name = "wsa885x_dai_drv", ++ .playback = { ++ .stream_name = "WSA885X TDM Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = WSA885X_RATES, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .ops = &wsa885x_dai_ops, ++ }, ++}; ++ ++static void wsa885x_reset_assert(void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (wsa885x->sd_reset) ++ reset_control_assert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 1); ++} ++ ++static void wsa885x_reset_deassert(struct wsa885x_priv *wsa885x) ++{ ++ if (wsa885x->sd_reset) ++ reset_control_deassert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 0); ++} ++ ++static int wsa885x_get_reset(struct device *dev, struct wsa885x_priv *wsa885x) ++{ ++ wsa885x->sd_reset = devm_reset_control_get_optional_shared(dev, NULL); ++ if (IS_ERR(wsa885x->sd_reset)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_reset), ++ "Failed to get reset\n"); ++ else if (wsa885x->sd_reset) ++ return 0; ++ ++ wsa885x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); ++ if (IS_ERR(wsa885x->sd_n)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n), ++ "Shutdown Control GPIO not found\n"); ++ ++ return 0; ++} ++ ++static bool wsa885x_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_DIG_CTRL0_SDCA_COMMIT: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_INTR_CLEAR0: ++ case WSA885X_INTR_CLEAR0 + 1: ++ case WSA885X_INTR_CLEAR0 + 2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool wsa885x_readable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg == WSA885X_INTR_CLEAR0 || ++ reg == WSA885X_INTR_CLEAR0 + 1 || ++ reg == WSA885X_INTR_CLEAR0 + 2) ++ return false; ++ return reg <= 0x88ff; ++} ++ ++static bool wsa885x_writeable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg > 0x88ff) ++ return false; ++ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++static const struct regmap_config wsa885x_regmap_cfg = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x88FF, ++ .ranges = wsa885x_regmap_ranges, ++ .num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges), ++ .reg_defaults = wsa885x_codec_reg_defaults, ++ .num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults), ++ .volatile_reg = wsa885x_volatile_register, ++ .writeable_reg = wsa885x_writeable_register, ++ .readable_reg = wsa885x_readable_register, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int wsa885x_component_probe(struct snd_soc_component *component) ++{ ++ struct wsa885x_priv *wsa885x = ++ snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ wsa885x->component = component; ++ snd_soc_component_init_regmap(component, wsa885x->regmap); ++ ++ ret = wsa885x_hw_init(wsa885x); ++ if (ret) ++ return ret; ++ ++ return wsa885x_unmask_interrupts(wsa885x); ++} ++ ++static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ int val; ++ ++ mutex_lock(&wsa885x->state_lock); ++ val = wsa885x->stereo_vol_db + 84; ++ mutex_unlock(&wsa885x->state_lock); ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = val; ++ return 0; ++} ++ ++static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ long val; ++ int stereo_vol_db; ++ ++ val = ucontrol->value.integer.value[0]; ++ ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) { ++ dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val); ++ return -EINVAL; ++ } ++ ++ stereo_vol_db = (int)val - 84; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->stereo_vol_db == stereo_vol_db) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x_program_stereo_volume(wsa885x, stereo_vol_db, true); ++ wsa885x->stereo_vol_db = stereo_vol_db; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_usage_modes_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ mutex_unlock(&wsa885x->state_lock); ++ return -ERANGE; ++ } ++ ++ ucontrol->value.integer.value[0] = wsa885x->usage_mode; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static int wsa885x_usage_modes_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 val = ucontrol->value.integer.value[0]; ++ ++ if (val > WSA885X_USAGE_MODE_MAX) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode == val) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->usage_mode = val; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_rx_slot_mask_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask; ++ ++ mutex_lock(&wsa885x->state_lock); ++ mask = wsa885x->rx_slot_mask; ++ mutex_unlock(&wsa885x->state_lock); ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = mask; ++ ++ return 0; ++} ++ ++static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask = ucontrol->value.integer.value[0]; ++ ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->rx_slot_mask == mask) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->rx_slot_mask = mask; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static const struct snd_kcontrol_new wsa885x_snd_controls[] = { ++ SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0, ++ wsa885x_usage_modes_get, ++ wsa885x_usage_modes_put), ++ ++ SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM, ++ 0, WSA885X_FU21_VOL_STEPS, 0, ++ wsa885x_stereo_gain_offset_get, ++ wsa885x_stereo_gain_offset_put, ++ wsa885x_fu21_digital_gain), ++ ++ SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0, ++ wsa885x_rx_slot_mask_get, ++ wsa885x_rx_slot_mask_put), ++}; ++ ++static const struct snd_soc_component_driver wsa885x_component = { ++ .name = "wsa885x", ++ .probe = wsa885x_component_probe, ++ .controls = wsa885x_snd_controls, ++ .num_controls = ARRAY_SIZE(wsa885x_snd_controls), ++}; ++ ++static irqreturn_t wsa885x_handle_irq(int irq_idx, void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX) ++ return IRQ_NONE; ++ ++ switch (irq_idx) { ++ case WSA885X_IRQ_INT_SAF2WAR: ++ case WSA885X_IRQ_INT_WAR2SAF: ++ case WSA885X_IRQ_INT_DISABLE: ++ case WSA885X_IRQ_INT_INTR_GPIO1_PIN: ++ case WSA885X_IRQ_INT_INTR_GPIO2_PIN: ++ case WSA885X_IRQ_INT_PA0_OCP: ++ case WSA885X_IRQ_INT_PA1_OCP: ++ case WSA885X_IRQ_INT_CLIP0: ++ case WSA885X_IRQ_INT_CLIP1: ++ case WSA885X_IRQ_INT_CLK_WD: ++ case WSA885X_IRQ_INT_BOP: ++ case WSA885X_IRQ_INT_UVLO: ++ case WSA885X_IRQ_INT_PCM_DATA0_DC: ++ case WSA885X_IRQ_INT_PCM_DATA1_DC: ++ case WSA885X_IRQ_INT_PLL_UNLOCKED: ++ case WSA885X_IRQ_INT_PROT_MODE_CHANGE: ++ case WSA885X_IRQ_INT_PB_CLOCK_VALID: ++ case WSA885X_IRQ_INT_SENSE_CLOCK_VALID: ++ break; ++ case WSA885X_IRQ_INT_PCM_DATA0_WD: ++ case WSA885X_IRQ_INT_PCM_DATA1_WD: ++ if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD) ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ else ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ break; ++ case WSA885X_IRQ_INT_PA0_FSM_ERR: ++ case WSA885X_IRQ_INT_PA1_FSM_ERR: ++ case WSA885X_IRQ_INT_MAIN_FSM_ERR: ++ if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0, ++ WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t wsa885x_interrupt_handler(int irq, void *data) ++{ ++ static const unsigned int status_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_STATUS0, ++ WSA885X_INTR_STATUS0 + 1, ++ WSA885X_INTR_STATUS0 + 2, ++ }; ++ static const unsigned int clear_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_CLEAR0, ++ WSA885X_INTR_CLEAR0 + 1, ++ WSA885X_INTR_CLEAR0 + 2, ++ }; ++ unsigned int status[WSA885X_NUM_REGS] = { 0 }; ++ struct wsa885x_priv *wsa885x = data; ++ irqreturn_t handled = IRQ_NONE; ++ irqreturn_t irq_ret; ++ int i, bit, ret, irq_num; ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "Failed to read status_reg[%d] (0x%x): %d\n", ++ i, status_reg[i], ret); ++ status[i] = 0; ++ continue; ++ } ++ } ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ for (bit = 0; bit < 8; bit++) { ++ if (status[i] & BIT(bit)) { ++ irq_num = i * 8 + bit; ++ regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit)); ++ regmap_write(wsa885x->regmap, clear_reg[i], 0); ++ if (irq_num >= WSA885X_IRQ_MAX) { ++ dev_warn_ratelimited(wsa885x->dev, ++ "Unexpected IRQ bit %d (reg %d)\n", ++ bit, i); ++ handled = IRQ_HANDLED; ++ continue; ++ } ++ irq_ret = wsa885x_handle_irq(irq_num, wsa885x); ++ if (irq_ret == IRQ_HANDLED) ++ handled = IRQ_HANDLED; ++ } ++ } ++ } ++ return handled; ++} ++ ++static int wsa885x_register_irq(struct wsa885x_priv *wsa885x) ++{ ++ if (!wsa885x->client->irq) ++ return dev_err_probe(wsa885x->dev, -EINVAL, ++ "IRQ is not configured\n"); ++ ++ return devm_request_threaded_irq(wsa885x->dev, wsa885x->client->irq, NULL, ++ wsa885x_interrupt_handler, ++ IRQF_ONESHOT, ++ dev_name(wsa885x->dev), wsa885x); ++} ++ ++static int wsa885x_probe(struct i2c_client *client) ++{ ++ struct wsa885x_priv *wsa885x; ++ const struct snd_soc_component_driver *component_driver = &wsa885x_component; ++ const char *battery_config; ++ unsigned int i; ++ int ret; ++ struct device *dev = &client->dev; ++ ++ wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL); ++ if (!wsa885x) ++ return -ENOMEM; ++ ++ wsa885x->client = client; ++ wsa885x->dev = dev; ++ wsa885x->stereo_vol_db = -84; ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mutex_init(&wsa885x->state_lock); ++ wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg); ++ ++ if (IS_ERR(wsa885x->regmap)) ++ return PTR_ERR(wsa885x->regmap); ++ ++ ret = device_property_read_string(dev, "qcom,battery-config", ++ &battery_config); ++ if (ret) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "1s")) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "2s")) { ++ wsa885x->batt_conf = WSA885X_BATT_2S; ++ } else { ++ return dev_err_probe(dev, -EINVAL, ++ "Invalid battery config %s (expected 1s or 2s)\n", ++ battery_config); ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(wsa885x_supply_name); i++) { ++ ret = devm_regulator_get_enable(dev, wsa885x_supply_name[i]); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "Failed to enable regulator %s\n", ++ wsa885x_supply_name[i]); ++ } ++ ++ ret = wsa885x_get_reset(dev, wsa885x); ++ if (ret) ++ return ret; ++ ++ wsa885x_reset_deassert(wsa885x); ++ usleep_range(5000, 5500); ++ ++ ret = devm_add_action_or_reset(dev, wsa885x_reset_assert, wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n"); ++ ++ i2c_set_clientdata(client, wsa885x); ++ ++ ret = wsa885x_register_irq(wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "wsa885x irq registration failed\n"); ++ ++ ret = devm_snd_soc_register_component(dev, component_driver, ++ wsa885x_dai, ++ ARRAY_SIZE(wsa885x_dai)); ++ if (ret) ++ return dev_err_probe(dev, ret, "Codec component registration failed\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id wsa885x_dt_match[] = { ++ { ++ .compatible = "qcom,wsa8855", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, wsa885x_dt_match); ++ ++static const struct i2c_device_id wsa885x_id[] = { ++ { ++ .name = "wsa885x", ++ .driver_data = 0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, wsa885x_id); ++ ++static struct i2c_driver wsa885x_driver = { ++ .driver = { ++ .name = "wsa885x", ++ .of_match_table = wsa885x_dt_match, ++ }, ++ .probe = wsa885x_probe, ++ .id_table = wsa885x_id, ++}; ++ ++module_i2c_driver(wsa885x_driver); ++ ++MODULE_DESCRIPTION("ASoC WSA885X Stereo Smart PA Codec Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c +--- a/sound/soc/codecs/zl38060.c ++++ b/sound/soc/codecs/zl38060.c +@@ -162,7 +162,7 @@ static int zl38_fw_send_xaddr(struct regmap *regmap, const void *data) + static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + { + const struct ihex_binrec *rec; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + u32 addr; + u16 len; + int err; +@@ -180,7 +180,7 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + return err; + err = zl38_fw_enter_boot_mode(regmap); + if (err) +- goto out; ++ return err; + rec = (const struct ihex_binrec *)fw->data; + while (rec) { + addr = be32_to_cpu(rec->addr); +@@ -195,15 +195,12 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + err = -EINVAL; + } + if (err) +- goto out; ++ return err; + /* next ! */ + rec = ihex_next_binrec(rec); + } +- err = zl38_fw_go(regmap); + +-out: +- release_firmware(fw); +- return err; ++ return zl38_fw_go(regmap); + } + + +diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c +--- a/sound/soc/dwc/dwc-i2s.c ++++ b/sound/soc/dwc/dwc-i2s.c +@@ -955,7 +955,9 @@ static int dw_i2s_probe(struct platform_device *pdev) + } + + irq = platform_get_irq_optional(pdev, 0); +- if (irq >= 0) { ++ if (irq == -EPROBE_DEFER) ++ return irq; ++ if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -69,7 +69,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + + /** + * struct codec_priv - CODEC private data +- * @mclk: Main clock of the CODEC + * @mclk_freq: Clock rate of MCLK + * @free_freq: Clock rate of MCLK for hw_free() + * @mclk_id: MCLK (or main clock) id for set_sysclk() +@@ -80,7 +79,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + * to stay within PLL frequency limits + */ + struct codec_priv { +- struct clk *mclk; + unsigned long mclk_freq; + unsigned long free_freq; + u32 mclk_id; +@@ -109,12 +107,58 @@ struct cpu_priv { + u32 slot_num; + }; + ++struct fsl_asoc_card_priv; ++ ++/* ++ * struct fsl_asoc_card_pdata - per-compatible static card description ++ * @sysclk_dir: initial CPU SYSCLK direction override (0 = leave default IN) ++ * @sysclk_ratio: SYSCLK ratio on sample rate (0 = not used) ++ * @slot_width: TDM slot width (0 = not TDM) ++ * @codec_dai_name: name of the codec DAI ++ * @codec_mclk_id: MCLK id passed to set_sysclk() for the codec ++ * @codec_fll_id: FLL id; only valid when has_pll is true ++ * @codec_pll_id: PLL id; only valid when has_pll is true ++ * @codec_pll_ratio_s24: PLL output ratio for S24_LE ++ * @has_pll: codec uses PLL/FLL; codec_fll_id and codec_pll_id are valid ++ * @dai_fmt: DAI format flags ++ * @playback_only: restrict card to playback direction ++ * @capture_only: restrict card to capture direction ++ * @dapm_routes: DAPM route table override ++ * @num_dapm_routes: number of entries in dapm_routes ++ * @exclude_format: PCM format bitmask excluded (for SAI + WM8960/WM8962) ++ * @codec_init: codec-specific init run after mclk_freq is populated ++ * @probe_init: optional DT-driven init run at end of probe() (e.g. SPDIF codec discovery) ++ */ ++struct fsl_asoc_card_pdata { ++ u32 sysclk_dir[2]; ++ u32 sysclk_ratio[2]; ++ u32 slot_width; ++ const char *codec_dai_name; ++ u32 codec_mclk_id; ++ int codec_fll_id; ++ int codec_pll_id; ++ int codec_pll_ratio_s24; ++ bool has_pll; ++ bool playback_only; ++ bool capture_only; ++ u32 dai_fmt; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ u64 exclude_format; ++ int (*codec_init)(struct fsl_asoc_card_priv *priv); ++ int (*probe_init)(struct device_node *codec_np[], ++ struct device_node *cpu_np, ++ const char *codec_dai_name[], ++ struct fsl_asoc_card_priv *priv); ++}; ++ + /** + * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data + * @dai_link: DAI link structure including normal one and DPCM link + * @hp_jack: Headphone Jack structure + * @mic_jack: Microphone Jack structure + * @pdev: platform device pointer ++ * @pdata: pointer to the per-compatible card platform data + * @codec_priv: CODEC private data + * @cpu_priv: CPU private data + * @card: ASoC card structure +@@ -135,6 +179,7 @@ struct fsl_asoc_card_priv { + struct simple_util_jack hp_jack; + struct simple_util_jack mic_jack; + struct platform_device *pdev; ++ const struct fsl_asoc_card_pdata *pdata; + struct codec_priv codec_priv[2]; + struct cpu_priv cpu_priv; + struct snd_soc_card card; +@@ -609,6 +654,192 @@ static int fsl_asoc_card_spdif_init(struct device_node *codec_np[], + return 0; + } + ++static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ unsigned long mclk_freq = priv->codec_priv[0].mclk_freq; ++ ++ /* ++ * Set CPU sysclk frequency from codec MCLK only if not already ++ * set by the CPU DAI init (e.g. ESAI extal clock takes precedence). ++ */ ++ if (!priv->cpu_priv.sysclk_freq[TX]) ++ priv->cpu_priv.sysclk_freq[TX] = mclk_freq; ++ if (!priv->cpu_priv.sysclk_freq[RX]) ++ priv->cpu_priv.sysclk_freq[RX] = mclk_freq; ++ ++ priv->constraint_channels = &cs42888_channel_constraints; ++ if (mclk_freq % 12288000 == 0) ++ priv->constraint_rates = &cs42888_rate_48k_constraints; ++ else if (mclk_freq % 11289600 == 0) ++ priv->constraint_rates = &cs42888_rate_44k_constraints; ++ else ++ dev_warn(&priv->pdev->dev, ++ "Unknown MCLK frequency %lu, no rate constraints\n", ++ mclk_freq); ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_wm8958_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; ++ return 0; ++} ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs42888_pdata = { ++ .codec_dai_name = "cs42888", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .slot_width = 32, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++ .codec_init = fsl_asoc_card_cs42888_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs427x_pdata = { ++ .codec_dai_name = "cs4271-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = CS427x_SYSCLK_MCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_sgtl5000_pdata = { ++ .codec_dai_name = "sgtl5000", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = SGTL5000_SYSCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic32x4_pdata = { ++ .codec_dai_name = "tlv320aic32x4-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic31xx_pdata = { ++ .codec_dai_name = "tlv320dac31xx-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8962_pdata = { ++ .codec_dai_name = "wm8962", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8962_SYSCLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8962_SYSCLK_FLL, ++ .codec_pll_id = WM8962_FLL, ++ /* ++ * WM8962 has same BCLK generation limitations as WM8960. ++ * See WM8960 section for detailed explanation. ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8960_pdata = { ++ .codec_dai_name = "wm8960-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .has_pll = true, ++ .codec_fll_id = WM8960_SYSCLK_AUTO, ++ .codec_pll_id = WM8960_SYSCLK_AUTO, ++ /* ++ * WM8960 in master mode cannot generate exact 1.92 MHz BCLK ++ * required for S20_3LE (48kHz x 2ch x 20bit). Closest available ++ * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. ++ * ++ * In SAI master mode, SAI derive BCLK from MCLK using integer ++ * dividers only. S20_3LE requires non-integer divider ratios ++ * with standard MCLK frequencies. For example, 48kHz stereo ++ * needs 1.920 MHz BCLK, which requires a divider of 6.4 from ++ * 12.288 MHz MCLK (not an integer). ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_ac97_pdata = { ++ .codec_dai_name = "ac97-hifi", ++ .dai_fmt = SND_SOC_DAIFMT_AC97, ++ .dapm_routes = audio_map_ac97, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_ac97), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_mqs_pdata = { ++ .codec_dai_name = "fsl-mqs-dai", ++ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC | ++ SND_SOC_DAIFMT_NB_NF, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8524_pdata = { ++ .codec_dai_name = "wm8524-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ /* RX=0, TX=1: set TX (index 1) to CLOCK_OUT, RX stays at default IN */ ++ .sysclk_dir = { 0, SND_SOC_CLOCK_OUT }, ++ .sysclk_ratio = { 0, 256 }, ++ .slot_width = 32, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_si476x_pdata = { ++ .codec_dai_name = "si476x-codec", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .dapm_routes = audio_map_rx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_rx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8958_pdata = { ++ .codec_dai_name = "wm8994-aif1", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8994_FLL_SRC_MCLK1, ++ .has_pll = true, ++ .codec_fll_id = WM8994_SYSCLK_FLL1, ++ .codec_pll_id = WM8994_FLL1, ++ .codec_init = fsl_asoc_card_wm8958_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_nau8822_pdata = { ++ .codec_dai_name = "nau8822-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = NAU8822_CLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = NAU8822_CLK_PLL, ++ .codec_pll_id = NAU8822_CLK_PLL, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8904_pdata = { ++ .codec_dai_name = "wm8904-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8904_FLL_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8904_CLK_FLL, ++ .codec_pll_id = WM8904_FLL_MCLK, ++ .codec_pll_ratio_s24 = 192, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_spdif_pdata = { ++ .codec_dai_name = "spdif", ++ .dai_fmt = DAI_FMT_BASE, ++ .probe_init = fsl_asoc_card_spdif_init, ++}; ++ + static int hp_jack_event(struct notifier_block *nb, unsigned long event, + void *data) + { +@@ -643,17 +874,173 @@ static struct notifier_block mic_jack_nb = { + .notifier_call = mic_jack_event, + }; + +-static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++/* ++ * fsl_asoc_card_init_cpu - configure CPU DAI-specific settings. ++ * ++ * Called from late_probe() when the CPU DAI component is guaranteed bound. ++ */ ++static int fsl_asoc_card_init_cpu(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) + { + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); +- struct snd_soc_pcm_runtime *rtd = list_first_entry( +- &card->rtd_list, struct snd_soc_pcm_runtime, list); ++ struct device_node *np = priv->pdev->dev.of_node; ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ const char *comp_drv_name = cpu_dai->component->driver->name; ++ struct device *dev = card->dev; ++ int ret; ++ ++ if (!strcmp(comp_drv_name, "fsl-ssi")) { ++ /* Only SSI needs to configure AUDMUX */ ++ ret = fsl_asoc_card_audmux_init(np, priv); ++ if (ret) { ++ dev_err(dev, "failed to init audmux\n"); ++ return ret; ++ } ++ } else if (!strcmp(comp_drv_name, "fsl-esai")) { ++ struct clk *esai_clk = clk_get(cpu_dai->dev, "extal"); ++ ++ if (!IS_ERR(esai_clk)) { ++ priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); ++ priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); ++ clk_put(esai_clk); ++ } else { ++ dev_warn(dev, "failed to get ESAI extal clock: %ld\n", PTR_ERR(esai_clk)); ++ } ++ ++ priv->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL; ++ priv->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL; ++ } else if (!strcmp(comp_drv_name, "fsl-sai")) { ++ priv->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1; ++ priv->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1; ++ ++ if (priv->pdata->exclude_format) ++ priv->exclude_format = priv->pdata->exclude_format; ++ } ++ ++ return 0; ++} ++ ++/* ++ * fsl_asoc_card_init_codecs - read codec MCLK rates and set codec sysclk. ++ * ++ * Called from late_probe() after all components are bound. ++ */ ++static int fsl_asoc_card_init_codecs(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ const struct fsl_asoc_card_pdata *pdata = priv->pdata; + struct snd_soc_dai *codec_dai; + struct codec_priv *codec_priv; + struct device *dev = card->dev; + int codec_idx; + int ret; + ++ /* Read MCLK rate from each bound codec component */ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ struct clk *codec_clk = clk_get(codec_dai->component->dev, NULL); ++ ++ codec_priv = &priv->codec_priv[codec_idx]; ++ if (!IS_ERR(codec_clk)) { ++ codec_priv->mclk_freq = clk_get_rate(codec_clk); ++ clk_put(codec_clk); ++ } ++ } ++ ++ if (pdata->codec_init) { ++ ret = pdata->codec_init(priv); ++ if (ret) ++ return ret; ++ } ++ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ codec_priv = &priv->codec_priv[codec_idx]; ++ ++ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, ++ codec_priv->mclk_freq, SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) { ++ dev_err(dev, "failed to set sysclk in %s\n", __func__); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static void fsl_asoc_card_free_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ ++ if (priv->hp_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->hp_jack.jack, &hp_jack_nb); ++ snd_soc_jack_free_gpios(&priv->hp_jack.jack, 1, &priv->hp_jack.gpio); ++ priv->hp_jack.gpio.desc = NULL; ++ } ++ ++ if (priv->mic_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->mic_jack.jack, &mic_jack_nb); ++ snd_soc_jack_free_gpios(&priv->mic_jack.jack, 1, &priv->mic_jack.gpio); ++ priv->mic_jack.gpio.desc = NULL; ++ } ++} ++ ++/* ++ * fsl_asoc_card_init_jack - register optional headphone and mic jacks. ++ * ++ * Called from late_probe() once per card bind cycle. ++ */ ++static int fsl_asoc_card_init_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct device_node *np = priv->pdev->dev.of_node; ++ int ret; ++ ++ /* ++ * Properties "hp-det-gpios" and "mic-det-gpios" are optional. ++ * simple_util_init_jack() checks for the GPIO property and ++ * does nothing if it is absent. ++ */ ++ if (of_property_present(np, "hp-det-gpios") || ++ of_property_present(np, "hp-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->hp_jack, ++ 1, NULL, "Headphone Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); ++ } ++ ++ if (of_property_present(np, "mic-det-gpios") || ++ of_property_present(np, "mic-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->mic_jack, ++ 0, NULL, "Mic Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); ++ } ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct snd_soc_pcm_runtime *rtd; ++ int ret; ++ ++ /* Use the first rtd which carries the CPU+codec DAIs */ ++ rtd = list_first_entry(&card->rtd_list, ++ struct snd_soc_pcm_runtime, list); ++ ++ ret = fsl_asoc_card_init_jack(card); ++ if (ret) ++ goto jack_fail; ++ ++ ret = fsl_asoc_card_init_cpu(card, rtd); ++ if (ret) ++ goto jack_fail; ++ + if (fsl_asoc_card_is_ac97(priv)) { + #if IS_ENABLED(CONFIG_SND_AC97_CODEC) + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; +@@ -671,19 +1058,20 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) + return 0; + } + +- for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { +- codec_priv = &priv->codec_priv[codec_idx]; ++ ret = fsl_asoc_card_init_codecs(card, rtd); ++ if (ret) ++ goto jack_fail; + +- ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, +- codec_priv->mclk_freq, SND_SOC_CLOCK_IN); +- if (ret && ret != -ENOTSUPP) { +- dev_err(dev, "failed to set sysclk in %s\n", __func__); +- return ret; +- } ++ return 0; + +- if (!IS_ERR_OR_NULL(codec_priv->mclk)) +- clk_prepare_enable(codec_priv->mclk); +- } ++jack_fail: ++ fsl_asoc_card_free_jack(card); ++ return ret; ++} ++ ++static int fsl_asoc_card_card_remove(struct snd_soc_card *card) ++{ ++ fsl_asoc_card_free_jack(card); + + return 0; + } +@@ -697,12 +1085,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + struct platform_device *asrc_pdev = NULL; + struct device_node *bitclkprovider = NULL; + struct device_node *frameprovider = NULL; +- struct platform_device *cpu_pdev; + struct fsl_asoc_card_priv *priv; +- struct device *codec_dev[2] = { NULL, NULL }; ++ const struct fsl_asoc_card_pdata *pdata; + struct snd_soc_dai_link_component *dlc; +- const char *codec_dai_name[2]; +- const char *codec_dev_name[2]; ++ const char *codec_dai_name[2] = { NULL, NULL }; + u32 asrc_fmt = 0; + int codec_idx; + u32 width; +@@ -714,6 +1100,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + + priv->pdev = pdev; + ++ pdata = of_device_get_match_data(&pdev->dev); ++ if (!pdata) { ++ dev_err(&pdev->dev, "unknown Device Tree compatible\n"); ++ return -EINVAL; ++ } ++ priv->pdata = pdata; ++ + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + /* Give a chance to old DT bindings */ + if (!cpu_np) +@@ -726,52 +1119,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto fail; + } + +- cpu_pdev = of_find_device_by_node(cpu_np); +- if (!cpu_pdev) { +- ret = dev_err_probe(&pdev->dev, -EPROBE_DEFER, +- "failed to find CPU DAI device\n"); +- goto fail; +- } +- + codec_np[0] = of_parse_phandle(np, "audio-codec", 0); + codec_np[1] = of_parse_phandle(np, "audio-codec", 1); + +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_np[codec_idx]) { +- struct platform_device *codec_pdev; +- struct i2c_client *codec_i2c; +- +- codec_i2c = of_find_i2c_device_by_node(codec_np[codec_idx]); +- if (codec_i2c) { +- codec_dev[codec_idx] = &codec_i2c->dev; +- codec_dev_name[codec_idx] = codec_i2c->name; +- } +- if (!codec_dev[codec_idx]) { +- codec_pdev = of_find_device_by_node(codec_np[codec_idx]); +- if (codec_pdev) { +- codec_dev[codec_idx] = &codec_pdev->dev; +- codec_dev_name[codec_idx] = codec_pdev->name; +- } +- } +- } +- } +- + asrc_np = of_parse_phandle(np, "audio-asrc", 0); + if (asrc_np) + asrc_pdev = of_find_device_by_node(asrc_np); + +- /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_dev[codec_idx]) { +- struct clk *codec_clk = clk_get(codec_dev[codec_idx], NULL); +- +- if (!IS_ERR(codec_clk)) { +- priv->codec_priv[codec_idx].mclk_freq = clk_get_rate(codec_clk); +- clk_put(codec_clk); +- } +- } +- } +- + /* Default sample rate and format, will be updated in hw_params() */ + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; +@@ -823,133 +1177,40 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + /* Diversify the card configurations */ +- if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { +- codec_dai_name[0] = "cs42888"; +- priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.slot_width = 32; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->constraint_channels = &cs42888_channel_constraints; +- if (priv->codec_priv[0].mclk_freq % 12288000 == 0) +- priv->constraint_rates = &cs42888_rate_48k_constraints; +- else if (priv->codec_priv[0].mclk_freq % 11289600 == 0) +- priv->constraint_rates = &cs42888_rate_44k_constraints; +- else +- dev_warn(&pdev->dev, "Unknown MCLK frequency %lu, no rate constraints\n", +- priv->codec_priv[0].mclk_freq); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { +- codec_dai_name[0] = "cs4271-hifi"; +- priv->codec_priv[0].mclk_id = CS427x_SYSCLK_MCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { +- codec_dai_name[0] = "sgtl5000"; +- priv->codec_priv[0].mclk_id = SGTL5000_SYSCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) { +- codec_dai_name[0] = "tlv320aic32x4-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic31xx")) { +- codec_dai_name[0] = "tlv320dac31xx-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; ++ priv->cpu_priv.sysclk_dir[TX] = pdata->sysclk_dir[TX]; ++ priv->cpu_priv.sysclk_dir[RX] = pdata->sysclk_dir[RX]; ++ priv->cpu_priv.sysclk_ratio[TX] = pdata->sysclk_ratio[TX]; ++ priv->cpu_priv.sysclk_ratio[RX] = pdata->sysclk_ratio[RX]; ++ priv->cpu_priv.slot_width = pdata->slot_width; ++ ++ codec_dai_name[0] = pdata->codec_dai_name; ++ priv->codec_priv[0].mclk_id = pdata->codec_mclk_id; ++ if (pdata->has_pll) { ++ priv->codec_priv[0].fll_id = pdata->codec_fll_id; ++ priv->codec_priv[0].pll_id = pdata->codec_pll_id; ++ } ++ if (pdata->codec_pll_ratio_s24) ++ priv->codec_priv[0].pll_ratio_s24 = pdata->codec_pll_ratio_s24; ++ ++ if (pdata->playback_only) { + priv->dai_link[1].playback_only = 1; + priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { +- codec_dai_name[0] = "wm8962"; +- priv->codec_priv[0].mclk_id = WM8962_SYSCLK_MCLK; +- priv->codec_priv[0].fll_id = WM8962_SYSCLK_FLL; +- priv->codec_priv[0].pll_id = WM8962_FLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8962 has same BCLK generation limitations as WM8960. +- * See WM8960 section for detailed explanation. +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { +- codec_dai_name[0] = "wm8960-hifi"; +- priv->codec_priv[0].fll_id = WM8960_SYSCLK_AUTO; +- priv->codec_priv[0].pll_id = WM8960_SYSCLK_AUTO; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8960 in master mode cannot generate exact 1.92 MHz BCLK +- * required for S20_3LE (48kHz × 2ch × 20bit). Closest available +- * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. +- * +- * In SAI master mode, SAI derive BCLK from MCLK using integer +- * dividers only. S20_3LE requires non-integer divider ratios +- * with standard MCLK frequencies. For example, 48kHz stereo +- * needs 1.920 MHz BCLK, which requires a divider of 6.4 from +- * 12.288 MHz MCLK (not an integer). +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { +- codec_dai_name[0] = "ac97-hifi"; +- priv->dai_fmt = SND_SOC_DAIFMT_AC97; +- priv->card.dapm_routes = audio_map_ac97; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) { +- codec_dai_name[0] = "fsl-mqs-dai"; +- priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | +- SND_SOC_DAIFMT_CBC_CFC | +- SND_SOC_DAIFMT_NB_NF; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) { +- codec_dai_name[0] = "wm8524-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.slot_width = 32; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_ratio[TX] = 256; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) { +- codec_dai_name[0] = "si476x-codec"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->card.dapm_routes = audio_map_rx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) { +- codec_dai_name[0] = "wm8994-aif1"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- priv->codec_priv[0].mclk_id = WM8994_FLL_SRC_MCLK1; +- priv->codec_priv[0].fll_id = WM8994_SYSCLK_FLL1; +- priv->codec_priv[0].pll_id = WM8994_FLL1; +- priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; +- priv->card.dapm_routes = NULL; +- priv->card.num_dapm_routes = 0; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-nau8822")) { +- codec_dai_name[0] = "nau8822-hifi"; +- priv->codec_priv[0].mclk_id = NAU8822_CLK_MCLK; +- priv->codec_priv[0].fll_id = NAU8822_CLK_PLL; +- priv->codec_priv[0].pll_id = NAU8822_CLK_PLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- if (codec_dev[0]) +- priv->codec_priv[0].mclk = devm_clk_get(codec_dev[0], NULL); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8904")) { +- codec_dai_name[0] = "wm8904-hifi"; +- priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].fll_id = WM8904_CLK_FLL; +- priv->codec_priv[0].pll_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].pll_ratio_s24 = 192; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-spdif")) { +- ret = fsl_asoc_card_spdif_init(codec_np, cpu_np, codec_dai_name, priv); ++ } ++ if (pdata->capture_only) { ++ priv->dai_link[1].capture_only = 1; ++ priv->dai_link[2].capture_only = 1; ++ } ++ ++ priv->dai_fmt = pdata->dai_fmt; ++ ++ priv->card.dapm_routes = pdata->dapm_routes; ++ priv->card.num_dapm_routes = pdata->num_dapm_routes; ++ ++ if (pdata->probe_init) { ++ ret = pdata->probe_init(codec_np, cpu_np, ++ codec_dai_name, priv); + if (ret) + goto asrc_fail; +- } else { +- dev_err(&pdev->dev, "unknown Device Tree compatible\n"); +- ret = -EINVAL; +- goto asrc_fail; + } + + /* +@@ -995,51 +1256,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + of_node_put(bitclkprovider); + of_node_put(frameprovider); + +- if (!fsl_asoc_card_is_ac97(priv) && !codec_dev[0] +- && codec_dai_name[0] != snd_soc_dummy_dlc.dai_name) { +- dev_dbg(&pdev->dev, "failed to find codec device\n"); +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- /* Common settings for corresponding Freescale CPU DAI driver */ +- if (of_node_name_eq(cpu_np, "ssi")) { +- /* Only SSI needs to configure AUDMUX */ +- ret = fsl_asoc_card_audmux_init(np, priv); +- if (ret) { +- dev_err(&pdev->dev, "failed to init audmux\n"); +- goto asrc_fail; +- } +- } else if (of_node_name_eq(cpu_np, "esai")) { +- struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal"); +- +- if (!IS_ERR(esai_clk)) { +- priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); +- priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); +- clk_put(esai_clk); +- } else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) { +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; +- priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; +- } else if (of_node_name_eq(cpu_np, "sai")) { +- priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; +- priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; +- } +- + /* Initialize sound card */ + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) { +- snprintf(priv->name, sizeof(priv->name), "%s-audio", +- fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name[0]); +- priv->card.name = priv->name; ++ /* ++ * "model" is required by the DT binding. Enforce it here so ++ * the driver fails with a clear message. ++ */ ++ dev_err(&pdev->dev, "Error parsing card name: %d\n", ret); ++ goto asrc_fail; + } + priv->card.dai_link = priv->dai_link; + priv->card.late_probe = fsl_asoc_card_late_probe; ++ priv->card.remove = fsl_asoc_card_card_remove; + priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); + +@@ -1146,39 +1377,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto asrc_fail; + } + +- /* +- * Properties "hp-det-gpios" and "mic-det-gpios" are optional, and +- * simple_util_init_jack() uses these properties for creating +- * Headphone Jack and Microphone Jack. +- * +- * The notifier is initialized in snd_soc_card_jack_new(), then +- * snd_soc_jack_notifier_register can be called. +- */ +- if (of_property_present(np, "hp-det-gpios") || +- of_property_present(np, "hp-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->hp_jack, +- 1, NULL, "Headphone Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); +- } +- +- if (of_property_present(np, "mic-det-gpios") || +- of_property_present(np, "mic-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->mic_jack, +- 0, NULL, "Mic Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); +- } +- + asrc_fail: + of_node_put(asrc_np); + of_node_put(codec_np[0]); + of_node_put(codec_np[1]); +- put_device(&cpu_pdev->dev); + fail: + of_node_put(cpu_np); + +@@ -1186,21 +1388,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id fsl_asoc_card_dt_ids[] = { +- { .compatible = "fsl,imx-audio-ac97", }, +- { .compatible = "fsl,imx-audio-cs42888", }, +- { .compatible = "fsl,imx-audio-cs427x", }, +- { .compatible = "fsl,imx-audio-tlv320aic32x4", }, +- { .compatible = "fsl,imx-audio-tlv320aic31xx", }, +- { .compatible = "fsl,imx-audio-sgtl5000", }, +- { .compatible = "fsl,imx-audio-wm8962", }, +- { .compatible = "fsl,imx-audio-wm8960", }, +- { .compatible = "fsl,imx-audio-mqs", }, +- { .compatible = "fsl,imx-audio-wm8524", }, +- { .compatible = "fsl,imx-audio-si476x", }, +- { .compatible = "fsl,imx-audio-wm8958", }, +- { .compatible = "fsl,imx-audio-nau8822", }, +- { .compatible = "fsl,imx-audio-wm8904", }, +- { .compatible = "fsl,imx-audio-spdif", }, ++ { .compatible = "fsl,imx-audio-ac97", .data = &fsl_asoc_ac97_pdata }, ++ { .compatible = "fsl,imx-audio-cs42888", .data = &fsl_asoc_cs42888_pdata }, ++ { .compatible = "fsl,imx-audio-cs427x", .data = &fsl_asoc_cs427x_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic32x4", .data = &fsl_asoc_tlv320aic32x4_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic31xx", .data = &fsl_asoc_tlv320aic31xx_pdata }, ++ { .compatible = "fsl,imx-audio-sgtl5000", .data = &fsl_asoc_sgtl5000_pdata }, ++ { .compatible = "fsl,imx-audio-wm8962", .data = &fsl_asoc_wm8962_pdata }, ++ { .compatible = "fsl,imx-audio-wm8960", .data = &fsl_asoc_wm8960_pdata }, ++ { .compatible = "fsl,imx-audio-mqs", .data = &fsl_asoc_mqs_pdata }, ++ { .compatible = "fsl,imx-audio-wm8524", .data = &fsl_asoc_wm8524_pdata }, ++ { .compatible = "fsl,imx-audio-si476x", .data = &fsl_asoc_si476x_pdata }, ++ { .compatible = "fsl,imx-audio-wm8958", .data = &fsl_asoc_wm8958_pdata }, ++ { .compatible = "fsl,imx-audio-nau8822", .data = &fsl_asoc_nau8822_pdata }, ++ { .compatible = "fsl,imx-audio-wm8904", .data = &fsl_asoc_wm8904_pdata }, ++ { .compatible = "fsl,imx-audio-spdif", .data = &fsl_asoc_spdif_pdata }, + {} + }; + MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -222,10 +222,9 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc = pair->asrc; + struct device *dev = &asrc->pdev->dev; +- unsigned long lock_flags; + int i, ret = 0; + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc->pair[i] != NULL) +@@ -250,8 +249,6 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + pair->index = index; + } + +- spin_unlock_irqrestore(&asrc->lock, lock_flags); +- + return ret; + } + +@@ -265,19 +262,16 @@ static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) + { + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; +- unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + asrc->channel_avail += pair->channels; + asrc->pair[index] = NULL; + pair->error = 0; +- +- spin_unlock_irqrestore(&asrc->lock, lock_flags); + } + + /** +diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h +--- a/sound/soc/fsl/fsl_asrc.h ++++ b/sound/soc/fsl/fsl_asrc.h +@@ -444,7 +444,7 @@ struct dma_block { + }; + + /** +- * fsl_asrc_soc_data: soc specific data ++ * struct fsl_asrc_soc_data - soc specific data + * + * @use_edma: using edma as dma device or not + * @channel_bits: width of ASRCNCR register for each pair +@@ -457,7 +457,7 @@ struct fsl_asrc_soc_data { + }; + + /** +- * fsl_asrc_pair_priv: ASRC Pair private data ++ * struct fsl_asrc_pair_priv - ASRC Pair private data + * + * @config: configuration profile + */ +@@ -466,7 +466,7 @@ struct fsl_asrc_pair_priv { + }; + + /** +- * fsl_asrc_priv: ASRC private data ++ * struct fsl_asrc_priv - ASRC private data + * + * @asrck_clk: clock sources to driver ASRC internal logic + * @soc: soc specific data +diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c +--- a/sound/soc/fsl/fsl_asrc_dma.c ++++ b/sound/soc/fsl/fsl_asrc_dma.c +@@ -219,7 +219,9 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, + */ + component_be = snd_soc_lookup_component_nolocked(dev_be, SND_DMAENGINE_PCM_DRV_NAME); + if (component_be) { +- be_chan = soc_component_to_pcm(component_be)->chan[substream->stream]; ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component_be); ++ ++ be_chan = pcm->chan[substream->stream]; + tmp_chan = be_chan; + } + if (!tmp_chan) { +diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c +--- a/sound/soc/fsl/fsl_audmix.c ++++ b/sound/soc/fsl/fsl_audmix.c +@@ -286,7 +286,6 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) + { + struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); +- unsigned long lock_flags; + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) +@@ -296,16 +295,14 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms |= BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms &= ~BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c +--- a/sound/soc/fsl/fsl_dma.c ++++ b/sound/soc/fsl/fsl_dma.c +@@ -18,8 +18,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + +@@ -824,9 +822,34 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np; + struct resource res; ++ void __iomem *channel; + const uint32_t *iprop; ++ int irq; + int ret; + ++ channel = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(channel)) ++ return PTR_ERR(channel); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ dma = devm_kzalloc(&pdev->dev, sizeof(*dma), GFP_KERNEL); ++ if (!dma) ++ return -ENOMEM; ++ ++ dma->dai.name = DRV_NAME; ++ dma->dai.open = fsl_dma_open; ++ dma->dai.close = fsl_dma_close; ++ dma->dai.hw_params = fsl_dma_hw_params; ++ dma->dai.hw_free = fsl_dma_hw_free; ++ dma->dai.pointer = fsl_dma_pointer; ++ dma->dai.pcm_new = fsl_dma_new; ++ ++ dma->channel = channel; ++ dma->irq = irq; ++ + /* Find the SSI node that points to us. */ + ssi_np = find_ssi_node(np); + if (!ssi_np) { +@@ -842,55 +865,19 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + return ret; + } + +- dma = kzalloc_obj(*dma); +- if (!dma) { +- of_node_put(ssi_np); +- return -ENOMEM; +- } +- +- dma->dai.name = DRV_NAME; +- dma->dai.open = fsl_dma_open; +- dma->dai.close = fsl_dma_close; +- dma->dai.hw_params = fsl_dma_hw_params; +- dma->dai.hw_free = fsl_dma_hw_free; +- dma->dai.pointer = fsl_dma_pointer; +- dma->dai.pcm_new = fsl_dma_new; +- + /* Store the SSI-specific information that we need */ + dma->ssi_stx_phys = res.start + REG_SSI_STX0; + dma->ssi_srx_phys = res.start + REG_SSI_SRX0; + + iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); ++ of_node_put(ssi_np); + if (iprop) + dma->ssi_fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + dma->ssi_fifo_depth = 8; + +- of_node_put(ssi_np); +- +- ret = devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register platform\n"); +- kfree(dma); +- return ret; +- } +- +- dma->channel = of_iomap(np, 0); +- dma->irq = irq_of_parse_and_map(np, 0); +- +- dev_set_drvdata(&pdev->dev, dma); +- +- return 0; +-} +- +-static void fsl_soc_dma_remove(struct platform_device *pdev) +-{ +- struct dma_object *dma = dev_get_drvdata(&pdev->dev); +- +- iounmap(dma->channel); +- irq_dispose_mapping(dma->irq); +- kfree(dma); ++ return devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); + } + + static const struct of_device_id fsl_soc_dma_ids[] = { +@@ -905,7 +892,6 @@ static struct platform_driver fsl_soc_dma_driver = { + .of_match_table = fsl_soc_dma_ids, + }, + .probe = fsl_soc_dma_probe, +- .remove = fsl_soc_dma_remove, + }; + + module_platform_driver(fsl_soc_dma_driver); +diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h +--- a/sound/soc/fsl/fsl_dma.h ++++ b/sound/soc/fsl/fsl_dma.h +@@ -92,7 +92,7 @@ static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) + #define CCSR_DMA_ATR_SNOOP 0x00050000 + #define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +-/** ++/* + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List +@@ -107,7 +107,7 @@ struct fsl_dma_list_descriptor { + u8 res[8]; /* Reserved */ + } __attribute__ ((aligned(32), packed)); + +-/** ++/* + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -1025,7 +1025,6 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; + struct device *dev; +- unsigned long lock_flags; + int ret; + + if (!easrc) +@@ -1053,9 +1052,8 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + if (ret) + return ret; + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- ret = fsl_easrc_config_slot(easrc, ctx->index); +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ ret = fsl_easrc_config_slot(easrc, ctx->index); + if (ret) + return ret; + +@@ -1301,13 +1299,12 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *easrc = ctx->asrc; + struct device *dev; +- unsigned long lock_flags; + int ret = 0; + int i; + + dev = &easrc->pdev->dev; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->pair[i]) +@@ -1331,8 +1328,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + easrc->channel_avail -= channels; + } + +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- + return ret; + } + +@@ -1343,7 +1338,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + */ + static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + { +- unsigned long lock_flags; + struct fsl_asrc *easrc; + + if (!ctx) +@@ -1351,14 +1345,12 @@ static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + + easrc = ctx->asrc; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + fsl_easrc_release_slot(easrc, ctx->index); + + easrc->channel_avail += ctx->channels; + easrc->pair[ctx->index] = NULL; +- +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + } + + /* +@@ -2292,15 +2284,13 @@ static int fsl_easrc_runtime_suspend(struct device *dev) + { + struct fsl_asrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_priv *easrc_priv = easrc->private; +- unsigned long lock_flags; + + regcache_cache_only(easrc->regmap, true); + + clk_disable_unprepare(easrc->mem_clk); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- easrc_priv->firmware_loaded = 0; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ easrc_priv->firmware_loaded = 0; + + return 0; + } +@@ -2311,7 +2301,6 @@ static int fsl_easrc_runtime_resume(struct device *dev) + struct fsl_easrc_priv *easrc_priv = easrc->private; + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; +- unsigned long lock_flags; + int ret; + int i; + +@@ -2323,13 +2312,11 @@ static int fsl_easrc_runtime_resume(struct device *dev) + regcache_mark_dirty(easrc->regmap); + regcache_sync(easrc->regmap); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- if (easrc_priv->firmware_loaded) { +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- goto skip_load; ++ scoped_guard(spinlock_irqsave, &easrc->lock) { ++ if (easrc_priv->firmware_loaded) ++ return 0; ++ easrc_priv->firmware_loaded = 1; + } +- easrc_priv->firmware_loaded = 1; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + + ret = fsl_easrc_get_firmware(easrc); + if (ret) { +@@ -2377,9 +2364,7 @@ static int fsl_easrc_runtime_resume(struct device *dev) + goto disable_mem_clk; + } + +-skip_load: + return 0; +- + disable_mem_clk: + clk_disable_unprepare(easrc->mem_clk); + return ret; +diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h +--- a/sound/soc/fsl/fsl_easrc.h ++++ b/sound/soc/fsl/fsl_easrc.h +@@ -584,7 +584,7 @@ struct fsl_easrc_slot { + }; + + /** +- * fsl_easrc_ctx_priv: EASRC context private data ++ * struct fsl_easrc_ctx_priv - EASRC context private data + * + * @in_params: input parameter + * @out_params: output parameter +@@ -625,7 +625,7 @@ struct fsl_easrc_ctx_priv { + }; + + /** +- * fsl_easrc_priv: EASRC private data ++ * struct fsl_easrc_priv - EASRC private data + * + * @slot: slot setting + * @firmware_hdr: the header of firmware +diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c +--- a/sound/soc/fsl/fsl_esai.c ++++ b/sound/soc/fsl/fsl_esai.c +@@ -709,10 +709,9 @@ static void fsl_esai_hw_reset(struct work_struct *work) + { + struct fsl_esai *esai_priv = container_of(work, struct fsl_esai, work); + bool tx = true, rx = false, enabled[2]; +- unsigned long lock_flags; + u32 tfcr, rfcr; + +- spin_lock_irqsave(&esai_priv->lock, lock_flags); ++ guard(spinlock_irqsave)(&esai_priv->lock); + /* Save the registers */ + regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr); + regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr); +@@ -750,8 +749,6 @@ static void fsl_esai_hw_reset(struct work_struct *work) + fsl_esai_trigger_start(esai_priv, tx); + if (enabled[rx]) + fsl_esai_trigger_start(esai_priv, rx); +- +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); + } + + static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, +@@ -759,7 +756,6 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + + esai_priv->channels[tx] = substream->runtime->channels; + +@@ -767,16 +763,14 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_start(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_start(esai_priv, tx); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_stop(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_stop(esai_priv, tx); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c +--- a/sound/soc/fsl/fsl_spdif.c ++++ b/sound/soc/fsl/fsl_spdif.c +@@ -853,17 +853,15 @@ static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +@@ -885,17 +883,15 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c +--- a/sound/soc/fsl/fsl_ssi.c ++++ b/sound/soc/fsl/fsl_ssi.c +@@ -1218,13 +1218,13 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + if (reg > 0x7f) + return; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 write clk_prepare_enable failed: %d\n", + ret); +- goto ret_unlock; ++ return; + } + + lreg = reg << 12; +@@ -1238,9 +1238,6 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + udelay(100); + + clk_disable_unprepare(fsl_ac97_data->clk); +- +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + } + + static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, +@@ -1252,12 +1249,12 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned int lreg; + int ret; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 read clk_prepare_enable failed: %d\n", ret); +- goto ret_unlock; ++ return val; + } + + lreg = (reg & 0x7f) << 12; +@@ -1272,8 +1269,6 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + + clk_disable_unprepare(fsl_ac97_data->clk); + +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + return val; + } + +diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c +--- a/sound/soc/fsl/fsl_xcvr.c ++++ b/sound/soc/fsl/fsl_xcvr.c +@@ -797,10 +797,9 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + int ret = 0; + +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -812,7 +811,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DPTH_RESET(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to set DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -824,7 +823,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, "err updating isr %d\n", ret); +- goto release_lock; ++ return ret; + } + fallthrough; + case FSL_XCVR_MODE_SPDIF: +@@ -833,7 +832,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -844,14 +843,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL); + if (ret < 0) { + dev_err(dai->dev, "Error while setting IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + /* clear DPATH RESET */ +@@ -860,7 +859,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + break; +@@ -873,14 +872,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to disable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -891,7 +890,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + if (xcvr->soc_data->spdif_only) + break; +@@ -905,7 +904,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + if (ret < 0) { + dev_err(dai->dev, + "Err updating ISR %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -916,18 +915,16 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + break; + } + +-release_lock: +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + return ret; + } + + static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + { + struct device *dev = &xcvr->pdev->dev; +- const struct firmware *fw; + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; + u32 mask, val; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to request firmware.\n"); +@@ -939,7 +936,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ + if (rem > 16384) { + dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); +- release_firmware(fw); + return -ENOMEM; + } + +@@ -950,7 +946,7 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + if (ret < 0) { + dev_err(dev, "FW: failed to set page %d, err=%d\n", + page, ret); +- goto err_firmware; ++ return ret; + } + + off = page * size; +@@ -971,11 +967,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + } + } + +-err_firmware: +- release_firmware(fw); +- if (ret < 0) +- return ret; +- + /* configure watermarks */ + mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; + val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); +@@ -1448,11 +1439,10 @@ static void reset_rx_work(struct work_struct *work) + { + struct fsl_xcvr *xcvr = container_of(work, struct fsl_xcvr, work_rst); + struct device *dev = &xcvr->pdev->dev; +- unsigned long lock_flags; + u32 ext_ctrl; + + dev_dbg(dev, "reset rx path\n"); +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + regmap_read(xcvr->regmap, FSL_XCVR_EXT_CTRL, &ext_ctrl); + + if (!(ext_ctrl & FSL_XCVR_EXT_CTRL_DMA_RD_DIS)) { +@@ -1469,7 +1459,6 @@ static void reset_rx_work(struct work_struct *work) + FSL_XCVR_EXT_CTRL_RX_DPTH_RESET, + 0); + } +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + } + + static irqreturn_t irq0_isr(int irq, void *devid) +diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c +--- a/sound/soc/fsl/imx-audio-rpmsg.c ++++ b/sound/soc/fsl/imx-audio-rpmsg.c +@@ -22,7 +22,6 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data; + struct rpmsg_info *info; + struct rpmsg_msg *msg; +- unsigned long flags; + + if (!rpmsg->rpmsg_pdev) + return 0; +@@ -37,21 +36,21 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + /* TYPE C is notification from M core */ + switch (r_msg->header.cmd) { + case TX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[TX], flags); +- msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[TX]; +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[TX]; ++ } + info->callback[TX](info->callback_param[TX]); + break; + case RX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[RX], flags); +- msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[1]; +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[1]; ++ } + info->callback[RX](info->callback_param[RX]); + break; + default: +diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c +--- a/sound/soc/fsl/imx-pcm-rpmsg.c ++++ b/sound/soc/fsl/imx-pcm-rpmsg.c +@@ -39,10 +39,9 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + struct rpmsg_device *rpdev = info->rpdev; + int ret = 0; + +- mutex_lock(&info->msg_lock); ++ guard(mutex)(&info->msg_lock); + if (!rpdev) { + dev_err(info->dev, "rpmsg channel not ready\n"); +- mutex_unlock(&info->msg_lock); + return -EINVAL; + } + +@@ -55,15 +54,12 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + sizeof(struct rpmsg_s_msg)); + if (ret) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); +- mutex_unlock(&info->msg_lock); + return ret; + } + + /* No receive msg for TYPE_C command */ +- if (msg->s_msg.header.type == MSG_TYPE_C) { +- mutex_unlock(&info->msg_lock); ++ if (msg->s_msg.header.type == MSG_TYPE_C) + return 0; +- } + + /* wait response from rpmsg */ + ret = wait_for_completion_timeout(&info->cmd_complete, +@@ -71,7 +67,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + if (!ret) { + dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n", + msg->s_msg.header.cmd); +- mutex_unlock(&info->msg_lock); + return -ETIMEDOUT; + } + +@@ -100,8 +95,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd, + info->r_msg.param.resp); + +- mutex_unlock(&info->msg_lock); +- + return 0; + } + +@@ -109,14 +102,13 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + struct rpmsg_msg *msg, + struct rpmsg_info *info) + { +- unsigned long flags; + int ret = 0; + + /* + * Queue the work to workqueue. + * If the queue is full, drop the message. + */ +- spin_lock_irqsave(&info->wq_lock, flags); ++ guard(spinlock_irqsave)(&info->wq_lock); + if (info->work_write_index != info->work_read_index) { + int index = info->work_write_index; + +@@ -130,7 +122,6 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + info->msg_drop_count[substream->stream]++; + ret = -EPIPE; + } +- spin_unlock_irqrestore(&info->wq_lock, flags); + + return ret; + } +@@ -523,7 +514,6 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + snd_pcm_sframes_t avail; + struct timer_list *timer; + struct rpmsg_msg *msg; +- unsigned long flags; + int buffer_tail = 0; + int written_num; + +@@ -553,11 +543,11 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + msg->s_msg.param.buffer_tail = buffer_tail; + + /* The notification message is updated to latest */ +- spin_lock_irqsave(&info->lock[substream->stream], flags); +- memcpy(&info->notify[substream->stream], msg, +- sizeof(struct rpmsg_s_msg)); +- info->notify_updated[substream->stream] = true; +- spin_unlock_irqrestore(&info->lock[substream->stream], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[substream->stream]) { ++ memcpy(&info->notify[substream->stream], msg, ++ sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[substream->stream] = true; ++ } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_hw_avail(runtime); +@@ -641,7 +631,7 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + bool is_notification = false; + struct rpmsg_info *info; + struct rpmsg_msg msg; +- unsigned long flags; ++ bool updated; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + info = work_of_rpmsg->info; +@@ -652,25 +642,26 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + * enough data in M core side, need to let M core know + * data is updated immediately. + */ +- spin_lock_irqsave(&info->lock[TX], flags); +- if (info->notify_updated[TX]) { +- memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[TX] = false; +- spin_unlock_irqrestore(&info->lock[TX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ updated = info->notify_updated[TX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[TX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); + +- spin_lock_irqsave(&info->lock[RX], flags); +- if (info->notify_updated[RX]) { +- memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[RX] = false; +- spin_unlock_irqrestore(&info->lock[RX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ updated = info->notify_updated[RX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[RX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); ++ + + /* Skip the notification message for it has been processed above */ + if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C && +@@ -682,10 +673,10 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + info->send_message(&work_of_rpmsg->msg, info); + + /* update read index */ +- spin_lock_irqsave(&info->wq_lock, flags); +- info->work_read_index++; +- info->work_read_index %= WORK_MAX_NUM; +- spin_unlock_irqrestore(&info->wq_lock, flags); ++ scoped_guard(spinlock_irqsave, &info->wq_lock) { ++ info->work_read_index++; ++ info->work_read_index %= WORK_MAX_NUM; ++ } + } + + static int imx_rpmsg_pcm_probe(struct platform_device *pdev) +@@ -819,9 +810,9 @@ static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = { + }; + + static const struct platform_device_id imx_rpmsg_pcm_id_table[] = { +- { .name = "rpmsg-audio-channel" }, +- { .name = "rpmsg-micfil-channel" }, +- { }, ++ { .name = "rpmsg-audio-channel" }, ++ { .name = "rpmsg-micfil-channel" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, imx_rpmsg_pcm_id_table); + +diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c +--- a/sound/soc/fsl/mpc5200_dma.c ++++ b/sound/soc/fsl/mpc5200_dma.c +@@ -77,18 +77,20 @@ static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) + { + struct psc_dma_stream *s = _psc_dma_stream; + +- spin_lock(&s->psc_dma->lock); +- /* For each finished period, dequeue the completed period buffer +- * and enqueue a new one in it's place. */ +- while (bcom_buffer_done(s->bcom_task)) { +- bcom_retrieve_buffer(s->bcom_task, NULL, NULL); ++ scoped_guard(spinlock, &s->psc_dma->lock) { ++ /* ++ * For each finished period, dequeue the completed period buffer ++ * and enqueue a new one in its place ++ */ ++ while (bcom_buffer_done(s->bcom_task)) { ++ bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + +- s->period_current = (s->period_current+1) % s->runtime->periods; +- s->period_count++; ++ s->period_current = (s->period_current+1) % s->runtime->periods; ++ s->period_count++; + +- psc_dma_bcom_enqueue_next_buffer(s); ++ psc_dma_bcom_enqueue_next_buffer(s); ++ } + } +- spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ +@@ -116,7 +118,6 @@ static int psc_dma_trigger(struct snd_soc_component *component, + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; +- unsigned long flags; + int i; + + switch (cmd) { +@@ -135,19 +136,18 @@ static int psc_dma_trigger(struct snd_soc_component *component, + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ +- spin_lock_irqsave(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); + +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); ++ for (i = 0; i < runtime->periods; i++) ++ if (!bcom_queue_full(s->bcom_task)) ++ psc_dma_bcom_enqueue_next_buffer(s); + +- for (i = 0; i < runtime->periods; i++) +- if (!bcom_queue_full(s->bcom_task)) +- psc_dma_bcom_enqueue_next_buffer(s); +- +- bcom_enable(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ bcom_enable(s->bcom_task); ++ } + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + +@@ -158,13 +158,13 @@ static int psc_dma_trigger(struct snd_soc_component *component, + substream->pstr->stream, s->period_count); + s->active = 0; + +- spin_lock_irqsave(&psc_dma->lock, flags); +- bcom_disable(s->bcom_task); +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ bcom_disable(s->bcom_task); ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); ++ } + + break; + +@@ -314,35 +314,29 @@ int mpc5200_audio_dma_create(struct platform_device *op) + { + phys_addr_t fifo; + struct psc_dma *psc_dma; +- struct resource res; ++ struct resource *res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; +- int ret; ++ ++ regs = devm_platform_get_and_ioremap_resource(op, 0, &res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + /* Fetch the registers and IRQ of the PSC */ +- irq = irq_of_parse_and_map(op->dev.of_node, 0); +- if (of_address_to_resource(op->dev.of_node, 0, &res)) { +- dev_err(&op->dev, "Missing reg property\n"); +- return -ENODEV; +- } +- regs = devm_ioremap(&op->dev, res.start, resource_size(&res)); +- if (!regs) { +- dev_err(&op->dev, "Could not map registers\n"); +- return -ENODEV; +- } ++ irq = platform_get_irq(op, 0); ++ if (irq < 0) ++ return irq; + + /* Allocate and initialize the driver private data */ +- psc_dma = kzalloc_obj(*psc_dma); ++ psc_dma = devm_kzalloc(&op->dev, sizeof(*psc_dma), GFP_KERNEL); + if (!psc_dma) + return -ENOMEM; + + /* Get the PSC ID */ + prop = of_get_property(op->dev.of_node, "cell-index", &size); +- if (!prop || size < sizeof *prop) { +- ret = -ENODEV; +- goto out_free; +- } ++ if (!prop || size < sizeof *prop) ++ return -ENODEV; + + spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); +@@ -357,7 +351,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ +- fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); ++ fifo = res->start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = +@@ -365,8 +359,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); +- ret = -ENODEV; +- goto out_free; ++ return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ +@@ -399,16 +392,14 @@ int mpc5200_audio_dma_create(struct platform_device *op) + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + +- rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, ++ rc = devm_request_irq(&op->dev, psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); +- rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); +- rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); +- if (rc) { +- ret = -ENODEV; +- goto out_irq; +- } ++ if (rc) ++ return -ENODEV; + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); +@@ -416,13 +407,6 @@ int mpc5200_audio_dma_create(struct platform_device *op) + /* Tell the ASoC OF helpers about it */ + return devm_snd_soc_register_component(&op->dev, + &mpc5200_audio_dma_component, NULL, 0); +-out_irq: +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +-out_free: +- kfree(psc_dma); +- return ret; + } + EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +@@ -435,12 +419,6 @@ int mpc5200_audio_dma_destroy(struct platform_device *op) + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + +- /* Release irqs */ +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +- +- kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c +--- a/sound/soc/fsl/mpc5200_psc_ac97.c ++++ b/sound/soc/fsl/mpc5200_psc_ac97.c +@@ -30,14 +30,13 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + int status; + unsigned int val; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + +@@ -53,19 +52,16 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + +- mutex_unlock(&psc_dma->mutex); + return (unsigned short) val; + } + +@@ -74,52 +70,46 @@ static void psc_ac97_write(struct snd_ac97 *ac97, + { + int status; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); +- goto out; ++ return; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); +- +- out: +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_warm_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); +- +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_cold_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); +- dev_dbg(psc_dma->dev, "cold reset\n"); ++ scoped_guard(mutex, &psc_dma->mutex) { ++ dev_dbg(psc_dma->dev, "cold reset\n"); + +- mpc5200_psc_ac97_gpio_reset(psc_dma->id); ++ mpc5200_psc_ac97_gpio_reset(psc_dma->id); + +- /* Notify the PSC that a reset has occurred */ +- out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); ++ /* Notify the PSC that a reset has occurred */ ++ out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); + +- /* Re-enable RX and TX */ +- out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); +- +- mutex_unlock(&psc_dma->mutex); ++ /* Re-enable RX and TX */ ++ out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); ++ } + + usleep_range(1000, 2000); + psc_ac97_warm_reset(ac97); +@@ -286,7 +276,7 @@ static int psc_ac97_of_probe(struct platform_device *op) + return rc; + } + +- rc = snd_soc_register_component(&op->dev, &psc_ac97_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_ac97_component, + psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); +@@ -312,8 +302,6 @@ static int psc_ac97_of_probe(struct platform_device *op) + static void psc_ac97_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); +- snd_soc_set_ac97_ops(NULL); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c +--- a/sound/soc/fsl/mpc5200_psc_i2s.c ++++ b/sound/soc/fsl/mpc5200_psc_i2s.c +@@ -79,9 +79,12 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * ++ * @cpu_dai: DAI runtime data pointer + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +@@ -101,7 +104,10 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * ++ * @cpu_dai: DAI runtime data pointer + * @format: one of SND_SOC_DAIFMT_xxx ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + { +@@ -119,7 +125,7 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + */ + + /** +- * psc_i2s_dai_template: template CPU Digital Audio Interface ++ * var psc_i2s_dai_ops - template CPU Digital Audio Interface + */ + static const struct snd_soc_dai_ops psc_i2s_dai_ops = { + .hw_params = psc_i2s_hw_params, +@@ -166,7 +172,7 @@ static int psc_i2s_of_probe(struct platform_device *op) + if (rc != 0) + return rc; + +- rc = snd_soc_register_component(&op->dev, &psc_i2s_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_i2s_component, + psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); +@@ -213,7 +219,6 @@ static int psc_i2s_of_probe(struct platform_device *op) + static void psc_i2s_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c +--- a/sound/soc/fsl/p1022_ds.c ++++ b/sound/soc/fsl/p1022_ds.c +@@ -73,6 +73,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_ds_machine_probe: initialize the board + * +@@ -82,8 +84,7 @@ struct machine_data { + */ + static int p1022_ds_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -122,8 +123,7 @@ static int p1022_ds_machine_probe(struct snd_soc_card *card) + static int p1022_ds_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -156,8 +156,7 @@ static int p1022_ds_startup(struct snd_pcm_substream *substream) + */ + static int p1022_ds_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -399,8 +398,7 @@ static int p1022_ds_probe(struct platform_device *pdev) + static void p1022_ds_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c +--- a/sound/soc/fsl/p1022_rdk.c ++++ b/sound/soc/fsl/p1022_rdk.c +@@ -79,6 +79,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_rdk_machine_probe - initialize the board + * @card: ASoC card instance +@@ -91,8 +93,7 @@ struct machine_data { + */ + static int p1022_rdk_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -134,8 +135,7 @@ static int p1022_rdk_machine_probe(struct snd_soc_card *card) + static int p1022_rdk_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -169,8 +169,7 @@ static int p1022_rdk_startup(struct snd_pcm_substream *substream) + */ + static int p1022_rdk_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -361,8 +360,7 @@ static int p1022_rdk_probe(struct platform_device *pdev) + static void p1022_rdk_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c +--- a/sound/soc/generic/audio-graph-card.c ++++ b/sound/soc/generic/audio-graph-card.c +@@ -579,11 +579,11 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) + goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); + if (ret < 0) + goto end; + +@@ -594,6 +594,7 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -603,14 +604,13 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph_parse_of); + +diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c +--- a/sound/soc/generic/audio-graph-card2.c ++++ b/sound/soc/generic/audio-graph-card2.c +@@ -1303,11 +1303,11 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + struct graph2_custom_hooks *hooks) + { + struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; ++ int ret = -ENOMEM; + + struct link_info *li __free(kfree) = kzalloc_obj(*li); + if (!li) +- return -ENOMEM; ++ goto end; + + card->probe = graph_util_card_probe; + card->owner = THIS_MODULE; +@@ -1316,31 +1316,35 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if ((hooks) && (hooks)->hook_pre) { + ret = (hooks)->hook_pre(priv); + if (ret < 0) +- goto err; ++ goto end; + } + + ret = graph_for_each_link(priv, hooks, li, graph_count); + if (!li->link) + ret = -EINVAL; + if (ret < 0) +- goto err; ++ goto end; + + ret = simple_util_init_priv(priv, li); + if (ret < 0) +- goto err; ++ goto end; + + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); + if (IS_ERR(priv->pa_gpio)) { + ret = PTR_ERR(priv->pa_gpio); + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); +- goto err; ++ goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) +- goto err; ++ goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, NULL); + if (ret < 0) + goto err; + +@@ -1349,6 +1353,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -1363,15 +1368,13 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + + simple_util_debug_info(priv); + +- ret = snd_soc_of_parse_aux_devs(card, "aux-devs"); +- if (ret < 0) +- goto err; +- + ret = devm_snd_soc_register_card(dev, card); + err: +- if (ret < 0) +- dev_err_probe(dev, ret, "parse error\n"); +- ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } ++end: + return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph2_parse_of); +diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c +--- a/sound/soc/generic/simple-card-utils.c ++++ b/sound/soc/generic/simple-card-utils.c +@@ -216,6 +216,27 @@ int simple_util_set_dailink_name(struct simple_util_priv *priv, + } + EXPORT_SYMBOL_GPL(simple_util_set_dailink_name); + ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property) ++{ ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device_node *node = card->dev->of_node; ++ char prop[128]; ++ ++ if (!prefix) ++ prefix = ""; ++ ++ snprintf(prop, sizeof(prop), "%s%s", prefix, property); ++ ++ /* no property is not error */ ++ if (!of_property_present(node, prop)) ++ return 0; ++ ++ return func(card, prop); ++} ++EXPORT_SYMBOL_GPL(simple_util_parse_property); ++ + int simple_util_parse_card_name(struct simple_util_priv *priv, + char *prefix) + { +@@ -731,11 +752,12 @@ void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + } + EXPORT_SYMBOL_GPL(simple_util_canonicalize_cpu); + +-void simple_util_clean_reference(struct snd_soc_card *card) ++void simple_util_clean_reference(struct simple_util_priv *priv) + { + struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link_component *cpu; + struct snd_soc_dai_link_component *codec; ++ struct snd_soc_card *card = simple_priv_to_card(priv); + int i, j; + + for_each_card_prelinks(card, i, dai_link) { +@@ -747,57 +769,6 @@ void simple_util_clean_reference(struct snd_soc_card *card) + } + EXPORT_SYMBOL_GPL(simple_util_clean_reference); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); +- +- if (!of_property_present(node, prop)) +- return 0; +- +- return snd_soc_of_parse_audio_routing(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_routing); +- +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); +- +- if (of_property_present(node, prop)) +- return snd_soc_of_parse_audio_simple_widgets(card, prop); +- +- /* no widgets is not error */ +- return 0; +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_widgets); +- +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix) +-{ +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches"); +- +- return snd_soc_of_parse_pin_switches(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_pin_switches); +- + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, +@@ -854,9 +825,9 @@ int simple_util_init_jack(struct snd_soc_card *card, + } + EXPORT_SYMBOL_GPL(simple_util_init_jack); + +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, char *prefix) ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) + { +- struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + int found_jack_index = 0; + int type = 0; +@@ -1026,8 +997,9 @@ EXPORT_SYMBOL_GPL(simple_util_init_priv); + void simple_util_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + +- simple_util_clean_reference(card); ++ simple_util_clean_reference(priv); + } + EXPORT_SYMBOL_GPL(simple_util_remove); + +diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c +--- a/sound/soc/generic/simple-card.c ++++ b/sound/soc/generic/simple-card.c +@@ -517,44 +517,6 @@ static int simple_populate_aux(struct simple_util_priv *priv) + return simple_ret(priv, ret); + } + +-static int simple_parse_of(struct simple_util_priv *priv, struct link_info *li) +-{ +- struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; +- +- ret = simple_util_parse_widgets(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_routing(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_pin_switches(card, PREFIX); +- if (ret < 0) +- goto end; +- +- /* Single/Muti DAI link(s) & New style of DT node */ +- memset(li, 0, sizeof(*li)); +- ret = simple_for_each_link(priv, li, +- simple_dai_link_of, +- simple_dai_link_of_dpcm); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_card_name(priv, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_populate_aux(priv); +- if (ret < 0) +- goto end; +- +- ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs"); +-end: +- return simple_ret(priv, ret); +-} +- + static int simple_count_noml(struct simple_util_priv *priv, + struct device_node *np, + struct device_node *codec, +@@ -699,28 +661,19 @@ static int simple_soc_probe(struct snd_soc_card *card) + if (ret < 0) + goto end; + +- ret = simple_util_init_aux_jacks(priv, PREFIX); ++ ret = simple_util_init_aux_jacks(card, PREFIX); + end: + return simple_ret(priv, ret); + } + +-static int simple_probe(struct platform_device *pdev) ++static int simple_parse_of(struct simple_util_priv *priv) + { +- struct simple_util_priv *priv; +- struct device *dev = &pdev->dev; +- struct snd_soc_card *card; +- int ret; ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device *dev = card->dev; ++ int ret = -EINVAL; + +- /* Allocate the private data and the DAI link array */ +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- card = simple_priv_to_card(priv); +- card->owner = THIS_MODULE; +- card->dev = dev; +- card->probe = simple_soc_probe; +- card->driver_name = "simple-card"; ++ if (!dev) ++ return simple_ret(priv, ret); + + ret = -ENOMEM; + struct link_info *li __free(kfree) = kzalloc_obj(*li); +@@ -739,25 +692,71 @@ static int simple_probe(struct platform_device *pdev) + if (ret < 0) + goto end; + +- ret = simple_parse_of(priv, li); +- if (ret < 0) { +- dev_err_probe(dev, ret, "parse error\n"); ++ ret = simple_util_parse_widgets(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_routing(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_pin_switches(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ /* Single/Muti DAI link(s) & New style of DT node */ ++ memset(li, 0, sizeof(*li)); ++ ret = simple_for_each_link(priv, li, ++ simple_dai_link_of, ++ simple_dai_link_of_dpcm); ++ if (ret < 0) ++ goto err; ++ ++ /* Card name should be set after simple_for_each_link() */ ++ ret = simple_util_parse_card_name(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ ret = simple_populate_aux(priv); ++ if (ret < 0) + goto err; +- } + + snd_soc_card_set_drvdata(card, priv); + + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return simple_ret(priv, ret); ++} ++ ++static int simple_probe(struct platform_device *pdev) ++{ ++ struct simple_util_priv *priv; ++ struct device *dev = &pdev->dev; ++ struct snd_soc_card *card; ++ ++ /* Allocate the private data and the DAI link array */ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ card = simple_priv_to_card(priv); ++ card->owner = THIS_MODULE; ++ card->dev = dev; ++ card->probe = simple_soc_probe; ++ card->driver_name = "simple-card"; ++ ++ return simple_parse_of(priv); + } + + static const struct of_device_id simple_of_match[] = { +diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c +--- a/sound/soc/intel/atom/sst-atom-controls.c ++++ b/sound/soc/intel/atom/sst-atom-controls.c +@@ -14,6 +14,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -73,14 +74,10 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) + { +- int ret; ++ guard(mutex)(&drv->lock); + +- mutex_lock(&drv->lock); +- ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, +- task_id, pipe_id, cmd_data, len); +- mutex_unlock(&drv->lock); +- +- return ret; ++ return sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, ++ task_id, pipe_id, cmd_data, len); + } + + /* +@@ -167,7 +164,7 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) +@@ -175,7 +172,6 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + break; + + ucontrol->value.enumerated.item[0] = mux; +- mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", +@@ -235,7 +231,7 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + if (mux > e->max - 1) + return -EINVAL; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; +@@ -244,7 +240,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -258,7 +253,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -354,13 +348,12 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(bc->params, ucontrol->value.bytes.data, bc->max); + break; + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; +@@ -368,7 +361,6 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + /*if pipe is enabled, need to send the algo params from here*/ + if (bc->w && bc->w->power) + ret = sst_send_algo_cmd(drv, bc); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -475,7 +467,7 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: +@@ -497,7 +489,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + break; + + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; +@@ -506,7 +497,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -521,10 +511,9 @@ static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); +- mutex_unlock(&drv->lock); + + return 0; + } +@@ -761,27 +750,29 @@ int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) + return ret; + } + +- mutex_lock(&drv->lock); +- if (enable) +- timer_usage++; +- else +- timer_usage--; +- +- /* +- * Send the command only if this call is the first enable or last +- * disable +- */ +- if ((enable && (timer_usage == 1)) || +- (!enable && (timer_usage == 0))) { +- ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, +- SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, +- sizeof(cmd.header) + cmd.header.length); +- if (ret && enable) { ++ scoped_guard(mutex, &drv->lock) { ++ if (enable) ++ timer_usage++; ++ else + timer_usage--; +- enable = false; ++ ++ /* ++ * Send the command only if this call is the first enable or last ++ * disable ++ */ ++ if ((enable && timer_usage == 1) || ++ (!enable && timer_usage == 0)) { ++ ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, ++ SST_FLAG_BLOCKED, ++ SST_TASK_SBA, 0, &cmd, ++ sizeof(cmd.header) + ++ cmd.header.length); ++ if (ret && enable) { ++ timer_usage--; ++ enable = false; ++ } + } + } +- mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); +diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +--- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c ++++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +@@ -11,6 +11,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -32,7 +33,7 @@ int sst_register_dsp(struct sst_device *dev) + return -EINVAL; + if (!try_module_get(dev->dev->driver->owner)) + return -ENODEV; +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + if (sst) { + dev_err(dev->dev, "we already have a device %s\n", sst->name); + module_put(dev->dev->driver->owner); +@@ -41,7 +42,7 @@ int sst_register_dsp(struct sst_device *dev) + } + dev_dbg(dev->dev, "registering device %s\n", dev->name); + sst = dev; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_register_dsp); +@@ -53,17 +54,15 @@ int sst_unregister_dsp(struct sst_device *dev) + if (dev != sst) + return -EINVAL; + +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + +- if (!sst) { +- mutex_unlock(&sst_lock); ++ if (!sst) + return -EIO; +- } + + module_put(sst->dev->driver->owner); + dev_dbg(dev->dev, "unreg %s\n", sst->name); + sst = NULL; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_unregister_dsp); +@@ -103,21 +102,14 @@ static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) + void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) + { +- unsigned long flags; +- spin_lock_irqsave(&stream->status_lock, flags); ++ guard(spinlock_irqsave)(&stream->status_lock); + stream->stream_status = state; +- spin_unlock_irqrestore(&stream->status_lock, flags); + } + + static inline int sst_get_stream_status(struct sst_runtime_stream *stream) + { +- int state; +- unsigned long flags; +- +- spin_lock_irqsave(&stream->status_lock, flags); +- state = stream->stream_status; +- spin_unlock_irqrestore(&stream->status_lock, flags); +- return state; ++ guard(spinlock_irqsave)(&stream->status_lock); ++ return stream->stream_status; + } + + static void sst_fill_alloc_params(struct snd_pcm_substream *substream, +@@ -244,12 +236,7 @@ static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + + stream->stream_info.str_id = str_params.stream_id; + +- ret_val = stream->ops->open(sst->dev, &str_params); +- if (ret_val <= 0) +- return ret_val; +- +- +- return ret_val; ++ return stream->ops->open(sst->dev, &str_params); + } + + static void sst_period_elapsed(void *arg) +@@ -304,7 +291,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + { + int ret_val = 0; + struct snd_pcm_runtime *runtime = substream->runtime; +- struct sst_runtime_stream *stream; ++ struct sst_runtime_stream *stream __free(kfree) = NULL; + + stream = kzalloc_obj(*stream); + if (!stream) +@@ -312,15 +299,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ +- mutex_lock(&sst_lock); +- if (!sst || +- !try_module_get(sst->dev->driver->owner)) { +- dev_err(dai->dev, "no device available to run\n"); +- ret_val = -ENODEV; +- goto out_ops; ++ scoped_guard(mutex, &sst_lock) { ++ if (!sst || ++ !try_module_get(sst->dev->driver->owner)) { ++ dev_err(dai->dev, "no device available to run\n"); ++ return -ENODEV; ++ } ++ stream->ops = sst->ops; + } +- stream->ops = sst->ops; +- mutex_unlock(&sst_lock); + + stream->stream_info.str_id = 0; + +@@ -330,7 +316,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + + ret_val = power_up_sst(stream); + if (ret_val < 0) +- goto out_power_up; ++ return ret_val; + + /* + * Make sure the period to be multiple of 1ms to align the +@@ -347,12 +333,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + +- return snd_pcm_hw_constraint_integer(runtime, +- SNDRV_PCM_HW_PARAM_PERIODS); +-out_ops: +- mutex_unlock(&sst_lock); +-out_power_up: +- kfree(stream); ++ ret_val = snd_pcm_hw_constraint_integer(runtime, ++ SNDRV_PCM_HW_PARAM_PERIODS); ++ ++ if (ret_val < 0) ++ return ret_val; ++ ++ stream = NULL; ++ + return ret_val; + } + +diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c +--- a/sound/soc/intel/atom/sst/sst_ipc.c ++++ b/sound/soc/intel/atom/sst/sst_ipc.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -180,9 +181,8 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; +- unsigned long irq_flags; + +- spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); ++ guard(spinlock_irqsave)(&sst_drv_ctx->ipc_spin_lock); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + +@@ -200,7 +200,6 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); +- spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + } + + +diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c +--- a/sound/soc/intel/atom/sst/sst_pvt.c ++++ b/sound/soc/intel/atom/sst/sst_pvt.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -64,9 +65,8 @@ u64 sst_shim_read64(void __iomem *addr, int offset) + void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) + { +- mutex_lock(&sst_drv_ctx->sst_lock); ++ guard(mutex)(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; +- mutex_unlock(&sst_drv_ctx->sst_lock); + } + + /* +@@ -302,18 +302,17 @@ int sst_assign_pvt_id(struct intel_sst_drv *drv) + { + int local; + +- spin_lock(&drv->block_lock); ++ guard(spinlock)(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ +- spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); +- spin_unlock(&drv->block_lock); ++ + return local; + } + +diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c +--- a/sound/soc/intel/avs/apl.c ++++ b/sound/soc/intel/avs/apl.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -190,7 +191,7 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + { + struct avs_path *path; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Any gateway without buffer allocated in LP area disqualifies D0IX. */ + list_for_each_entry(path, &adev->path_list, node) { + struct avs_path_pipeline *ppl; +@@ -210,14 +211,11 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + if (cfg->copier.dma_type == INVALID_OBJECT_ID) + continue; + +- if (!mod->gtw_attrs.lp_buffer_alloc) { +- spin_unlock(&adev->path_list_lock); ++ if (!mod->gtw_attrs.lp_buffer_alloc) + return false; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return true; + } +diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h +--- a/sound/soc/intel/avs/avs.h ++++ b/sound/soc/intel/avs/avs.h +@@ -336,14 +336,13 @@ int avs_icl_load_basefw(struct avs_dev *adev, struct firmware *fw); + /* Soc component members */ + + struct avs_soc_component { +- struct snd_soc_component base; ++ struct snd_soc_component *base; + struct avs_tplg *tplg; + + struct list_head node; + }; + +-#define to_avs_soc_component(comp) \ +- container_of(comp, struct avs_soc_component, base) ++#define to_avs_soc_component(comp) snd_soc_component_to_priv(comp) + + extern const struct snd_soc_dai_ops avs_dai_fe_ops; + +diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c +--- a/sound/soc/intel/avs/boards/da7219.c ++++ b/sound/soc/intel/avs/boards/da7219.c +@@ -175,7 +175,6 @@ static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + if (!dl || !platform) + return -ENOMEM; + +- dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +@@ -259,10 +258,8 @@ static int avs_da7219_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_da7219_driver_ids[] = { +- { +- .name = "avs_da7219", +- }, +- {}, ++ { .name = "avs_da7219" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_da7219_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c +--- a/sound/soc/intel/avs/boards/dmic.c ++++ b/sound/soc/intel/avs/boards/dmic.c +@@ -104,10 +104,8 @@ static int avs_dmic_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_dmic_driver_ids[] = { +- { +- .name = "avs_dmic", +- }, +- {}, ++ { .name = "avs_dmic" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_dmic_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/es8336.c b/sound/soc/intel/avs/boards/es8336.c +--- a/sound/soc/intel/avs/boards/es8336.c ++++ b/sound/soc/intel/avs/boards/es8336.c +@@ -309,10 +309,8 @@ static int avs_es8336_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_es8336_driver_ids[] = { +- { +- .name = "avs_es8336", +- }, +- {}, ++ { .name = "avs_es8336" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_es8336_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -231,10 +231,8 @@ static int avs_hdaudio_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_hdaudio_driver_ids[] = { +- { +- .name = "avs_hdaudio", +- }, +- {}, ++ { .name = "avs_hdaudio" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_hdaudio_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/i2s_test.c b/sound/soc/intel/avs/boards/i2s_test.c +--- a/sound/soc/intel/avs/boards/i2s_test.c ++++ b/sound/soc/intel/avs/boards/i2s_test.c +@@ -107,10 +107,8 @@ static int avs_i2s_test_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_i2s_test_driver_ids[] = { +- { +- .name = "avs_i2s_test", +- }, +- {}, ++ { .name = "avs_i2s_test" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_i2s_test_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c +--- a/sound/soc/intel/avs/boards/max98357a.c ++++ b/sound/soc/intel/avs/boards/max98357a.c +@@ -136,10 +136,8 @@ static int avs_max98357a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98357a_driver_ids[] = { +- { +- .name = "avs_max98357a", +- }, +- {}, ++ { .name = "avs_max98357a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98357a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c +--- a/sound/soc/intel/avs/boards/max98373.c ++++ b/sound/soc/intel/avs/boards/max98373.c +@@ -191,10 +191,8 @@ static int avs_max98373_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98373_driver_ids[] = { +- { +- .name = "avs_max98373", +- }, +- {}, ++ { .name = "avs_max98373" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98373_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98927.c b/sound/soc/intel/avs/boards/max98927.c +--- a/sound/soc/intel/avs/boards/max98927.c ++++ b/sound/soc/intel/avs/boards/max98927.c +@@ -188,10 +188,8 @@ static int avs_max98927_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98927_driver_ids[] = { +- { +- .name = "avs_max98927", +- }, +- {}, ++ { .name = "avs_max98927" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98927_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c +--- a/sound/soc/intel/avs/boards/nau8825.c ++++ b/sound/soc/intel/avs/boards/nau8825.c +@@ -293,10 +293,8 @@ static int avs_nau8825_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_nau8825_driver_ids[] = { +- { +- .name = "avs_nau8825", +- }, +- {}, ++ { .name = "avs_nau8825" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_nau8825_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/pcm3168a.c b/sound/soc/intel/avs/boards/pcm3168a.c +--- a/sound/soc/intel/avs/boards/pcm3168a.c ++++ b/sound/soc/intel/avs/boards/pcm3168a.c +@@ -132,10 +132,8 @@ static int avs_pcm3168a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_pcm3168a_driver_ids[] = { +- { +- .name = "avs_pcm3168a", +- }, +- {}, ++ { .name = "avs_pcm3168a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_pcm3168a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/probe.c b/sound/soc/intel/avs/boards/probe.c +--- a/sound/soc/intel/avs/boards/probe.c ++++ b/sound/soc/intel/avs/boards/probe.c +@@ -64,7 +64,7 @@ static const struct platform_device_id avs_probe_mb_driver_ids[] = { + { + .name = "avs_probe_mb", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_probe_mb_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c +--- a/sound/soc/intel/avs/boards/rt274.c ++++ b/sound/soc/intel/avs/boards/rt274.c +@@ -261,7 +261,7 @@ static const struct platform_device_id avs_rt274_driver_ids[] = { + { + .name = "avs_rt274", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt274_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c +--- a/sound/soc/intel/avs/boards/rt286.c ++++ b/sound/soc/intel/avs/boards/rt286.c +@@ -231,7 +231,7 @@ static const struct platform_device_id avs_rt286_driver_ids[] = { + { + .name = "avs_rt286", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt286_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c +--- a/sound/soc/intel/avs/boards/rt298.c ++++ b/sound/soc/intel/avs/boards/rt298.c +@@ -250,7 +250,7 @@ static const struct platform_device_id avs_rt298_driver_ids[] = { + { + .name = "avs_rt298", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt298_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5514.c b/sound/soc/intel/avs/boards/rt5514.c +--- a/sound/soc/intel/avs/boards/rt5514.c ++++ b/sound/soc/intel/avs/boards/rt5514.c +@@ -178,7 +178,7 @@ static const struct platform_device_id avs_rt5514_driver_ids[] = { + { + .name = "avs_rt5514", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5514_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5640.c b/sound/soc/intel/avs/boards/rt5640.c +--- a/sound/soc/intel/avs/boards/rt5640.c ++++ b/sound/soc/intel/avs/boards/rt5640.c +@@ -252,7 +252,7 @@ static const struct platform_device_id avs_rt5640_driver_ids[] = { + { + .name = "avs_rt5640", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5640_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5663.c b/sound/soc/intel/avs/boards/rt5663.c +--- a/sound/soc/intel/avs/boards/rt5663.c ++++ b/sound/soc/intel/avs/boards/rt5663.c +@@ -249,7 +249,7 @@ static const struct platform_device_id avs_rt5663_driver_ids[] = { + { + .name = "avs_rt5663", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5663_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c +--- a/sound/soc/intel/avs/boards/rt5682.c ++++ b/sound/soc/intel/avs/boards/rt5682.c +@@ -325,7 +325,7 @@ static const struct platform_device_id avs_rt5682_driver_ids[] = { + { + .name = "avs_rt5682", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5682_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c +--- a/sound/soc/intel/avs/boards/ssm4567.c ++++ b/sound/soc/intel/avs/boards/ssm4567.c +@@ -180,7 +180,7 @@ static const struct platform_device_id avs_ssm4567_driver_ids[] = { + { + .name = "avs_ssm4567", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_ssm4567_driver_ids); + +diff --git a/sound/soc/intel/avs/control.c b/sound/soc/intel/avs/control.c +--- a/sound/soc/intel/avs/control.c ++++ b/sound/soc/intel/avs/control.c +@@ -27,7 +27,7 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + struct avs_path_pipeline *ppl; + struct avs_path_module *mod; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + list_for_each_entry(path, &adev->path_list, node) { + list_for_each_entry(ppl, &path->ppl_list, node) { + list_for_each_entry(mod, &ppl->mod_list, node) { +@@ -35,14 +35,11 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + + if ((guid_equal(type, &AVS_PEAKVOL_MOD_UUID) || + guid_equal(type, &AVS_GAIN_MOD_UUID)) && +- mod->template->ctl_id == id) { +- spin_unlock(&adev->path_list_lock); ++ mod->template->ctl_id == id) + return mod; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return NULL; + } +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -14,6 +14,7 @@ + // foundation of this driver + // + ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + if (snd_hdac_bus_handle_stream_irq(bus, status, hdac_update_stream)) + ret = IRQ_HANDLED; + +- spin_lock_irq(&bus->reg_lock); ++ guard(spinlock_irq)(&bus->reg_lock); + /* Clear RIRB interrupt. */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { +@@ -283,7 +284,6 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + ret = IRQ_HANDLED; + } + +- spin_unlock_irq(&bus->reg_lock); + return ret; + } + +diff --git a/sound/soc/intel/avs/debug.h b/sound/soc/intel/avs/debug.h +--- a/sound/soc/intel/avs/debug.h ++++ b/sound/soc/intel/avs/debug.h +@@ -9,6 +9,7 @@ + #ifndef __SOUND_SOC_INTEL_AVS_DEBUG_H + #define __SOUND_SOC_INTEL_AVS_DEBUG_H + ++#include + #include "messages.h" + #include "registers.h" + +@@ -26,14 +27,9 @@ struct avs_dev; + + static inline int avs_log_buffer_status_locked(struct avs_dev *adev, union avs_notify_msg *msg) + { +- unsigned long flags; +- int ret; ++ guard(spinlock_irqsave)(&adev->trace_lock); + +- spin_lock_irqsave(&adev->trace_lock, flags); +- ret = avs_dsp_op(adev, log_buffer_status, msg); +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- +- return ret; ++ return avs_dsp_op(adev, log_buffer_status, msg); + } + + struct avs_apl_log_buffer_layout { +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -6,9 +6,9 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include +-#include + #include + #include + #include +@@ -236,20 +236,15 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) { +- ret = -EBUSY; +- goto err; +- } ++ if (kfifo_initialized(&adev->trace_fifo)) ++ return -EBUSY; + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- goto err; ++ return ret; + + file->private_data = adev; + return 0; +-err: +- module_put(adev->dev->driver->owner); +- return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +@@ -257,24 +252,22 @@ static int strace_release(struct inode *inode, struct file *file) + union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); + struct avs_dev *adev = file->private_data; + unsigned long resource_mask; +- unsigned long flags, i; ++ unsigned long i; + u32 num_cores; + + resource_mask = adev->logged_resources; + num_cores = adev->hw_cfg.dsp_cores; + +- spin_lock_irqsave(&adev->trace_lock, flags); ++ scoped_guard(spinlock_irqsave, &adev->trace_lock) { ++ /* Gather any remaining logs. */ ++ for_each_set_bit(i, &resource_mask, num_cores) { ++ msg.log.core = i; ++ avs_dsp_op(adev, log_buffer_status, &msg); ++ } + +- /* Gather any remaining logs. */ +- for_each_set_bit(i, &resource_mask, num_cores) { +- msg.log.core = i; +- avs_dsp_op(adev, log_buffer_status, &msg); ++ kfifo_free(&adev->trace_fifo); + } + +- kfifo_free(&adev->trace_fifo); +- +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- + module_put(adev->dev->driver->owner); + return 0; + } +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -99,39 +100,39 @@ static void avs_dsp_recovery(struct avs_dev *adev) + unsigned int core_mask; + int ret; + +- mutex_lock(&adev->comp_list_mutex); +- /* disconnect all running streams */ +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct snd_soc_pcm_runtime *rtd; +- struct snd_soc_card *card; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ /* disconnect all running streams */ ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct snd_soc_pcm_runtime *rtd; ++ struct snd_soc_card *card; + +- card = acomp->base.card; +- if (!card) +- continue; +- +- for_each_card_rtds(card, rtd) { +- struct snd_pcm *pcm; +- int dir; +- +- pcm = rtd->pcm; +- if (!pcm || rtd->dai_link->no_pcm) ++ card = acomp->base->card; ++ if (!card) + continue; + +- for_each_pcm_streams(dir) { +- struct snd_pcm_substream *substream; ++ for_each_card_rtds(card, rtd) { ++ struct snd_pcm *pcm; ++ int dir; + +- substream = pcm->streams[dir].substream; +- if (!substream || !substream->runtime) ++ pcm = rtd->pcm; ++ if (!pcm || rtd->dai_link->no_pcm) + continue; + +- /* No need for _irq() as we are in nonatomic context. */ +- snd_pcm_stream_lock(substream); +- snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); +- snd_pcm_stream_unlock(substream); ++ for_each_pcm_streams(dir) { ++ struct snd_pcm_substream *substream; ++ ++ substream = pcm->streams[dir].substream; ++ if (!substream || !substream->runtime) ++ continue; ++ ++ /* No need for _irq() as we are in nonatomic context. */ ++ snd_pcm_stream_lock(substream); ++ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); ++ snd_pcm_stream_unlock(substream); ++ } + } + } + } +- mutex_unlock(&adev->comp_list_mutex); + + /* forcibly shutdown all cores */ + core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); +@@ -397,7 +398,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + if (!ipc->ready) + return -EPERM; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); +@@ -412,7 +413,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + /* Same treatment as on exception, just stack_dump=0. */ + avs_dsp_exception_caught(adev, &msg); + } +- goto exit; ++ return ret; + } + + ret = ipc->rx.rsp.status; +@@ -436,8 +437,6 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + memcpy(reply->data, ipc->rx.data, reply->size); + } + +-exit: +- mutex_unlock(&ipc->msg_mutex); + return ret; + } + +@@ -501,7 +500,7 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + struct avs_ipc *ipc = adev->ipc; + int ret; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, NULL); +@@ -522,8 +521,6 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + dev_err(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + +- mutex_unlock(&ipc->msg_mutex); +- + return ret; + } + +diff --git a/sound/soc/intel/avs/loader.c b/sound/soc/intel/avs/loader.c +--- a/sound/soc/intel/avs/loader.c ++++ b/sound/soc/intel/avs/loader.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -630,15 +631,15 @@ static int avs_load_firmware(struct avs_dev *adev, bool purge) + if (ret) + goto reenable_gating; + +- mutex_lock(&adev->comp_list_mutex); +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct avs_tplg *tplg = acomp->tplg; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct avs_tplg *tplg = acomp->tplg; + +- ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); +- if (ret < 0) +- break; ++ ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); ++ if (ret < 0) ++ break; ++ } + } +- mutex_unlock(&adev->comp_list_mutex); + + reenable_gating: + avs_hda_l1sen_enable(adev, true); +diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c +--- a/sound/soc/intel/avs/path.c ++++ b/sound/soc/intel/avs/path.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -69,16 +70,13 @@ avs_path_find_path(struct avs_dev *adev, const char *name, u32 template_id) + if (!template) + return NULL; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Only one variant of given path template may be instantiated at a time. */ + list_for_each_entry(path, &adev->path_list, node) { +- if (path->template->owner == template) { +- spin_unlock(&adev->path_list_lock); ++ if (path->template->owner == template) + return path; +- } + } + +- spin_unlock(&adev->path_list_lock); + return NULL; + } + +@@ -1300,7 +1298,7 @@ void avs_path_free(struct avs_path *path) + struct avs_path *cpath, *csave; + struct avs_dev *adev = path->owner; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Free all condpaths this path spawned. */ + list_for_each_entry_safe(cpath, csave, &path->source_list, source_node) +@@ -1309,8 +1307,6 @@ void avs_path_free(struct avs_path *path) + avs_condpath_free(path->owner, cpath); + + avs_path_free_unlocked(path); +- +- mutex_unlock(&adev->path_mutex); + } + + struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, +@@ -1329,13 +1325,13 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + } + + /* Serialize path and its components creation. */ +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + /* Satisfy needs of avs_path_find_tplg(). */ +- mutex_lock(&adev->comp_list_mutex); ++ guard(mutex)(&adev->comp_list_mutex); + + path = avs_path_create_unlocked(adev, dma_id, variant); + if (IS_ERR(path)) +- goto exit; ++ return path; + + ret = avs_condpaths_walk_all(adev, path); + if (ret) { +@@ -1343,10 +1339,6 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + path = ERR_PTR(ret); + } + +-exit: +- mutex_unlock(&adev->comp_list_mutex); +- mutex_unlock(&adev->path_mutex); +- + return path; + } + +@@ -1491,15 +1483,13 @@ static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* If either source or sink stops, so do the attached conditional paths. */ + list_for_each_entry(cpath, &path->source_list, source_node) + avs_condpath_pause(adev, cpath); + list_for_each_entry(cpath, &path->sink_list, sink_node) + avs_condpath_pause(adev, cpath); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_pause(struct avs_path *path) +@@ -1555,7 +1545,7 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Run conditional paths only if source and sink are both running. */ + list_for_each_entry(cpath, &path->source_list, source_node) +@@ -1567,8 +1557,6 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + if (cpath->source->state == AVS_PPL_STATE_RUNNING && + cpath->sink->state == AVS_PPL_STATE_RUNNING) + avs_condpath_run(adev, cpath, trigger); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_run(struct avs_path *path, int trigger) +diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c +--- a/sound/soc/intel/avs/pcm.c ++++ b/sound/soc/intel/avs/pcm.c +@@ -959,7 +959,7 @@ static const struct file_operations topology_name_fops = { + static int avs_component_load_libraries(struct avs_soc_component *acomp) + { + struct avs_tplg *tplg = acomp->tplg; +- struct avs_dev *adev = to_avs_dev(acomp->base.dev); ++ struct avs_dev *adev = to_avs_dev(acomp->base->dev); + int ret; + + if (!tplg->num_libs) +@@ -1393,25 +1393,28 @@ int avs_register_component(struct device *dev, const char *name, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) + { + struct avs_soc_component *acomp; +- int ret; ++ const char *comp_name; + + acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL); + if (!acomp) + return -ENOMEM; + +- acomp->base.name = devm_kstrdup(dev, name, GFP_KERNEL); +- if (!acomp->base.name) ++ acomp->base = snd_soc_component_alloc(dev); ++ if (!acomp->base) ++ return -ENOMEM; ++ ++ comp_name = devm_kstrdup(dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + + INIT_LIST_HEAD(&acomp->node); + + drv->use_dai_pcm_id = !obsolete_card_names; + +- ret = snd_soc_component_initialize(&acomp->base, drv, dev); +- if (ret < 0) +- return ret; ++ snd_soc_component_set_name(acomp->base, comp_name); ++ snd_soc_component_set_priv(acomp->base, acomp); + +- return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais); ++ return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + } + + static struct snd_soc_dai_driver dmic_cpu_dais[] = { +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -296,19 +296,19 @@ static const struct snd_soc_component_driver avs_probe_component_driver = { + int avs_register_probe_component(struct avs_dev *adev, const char *name) + { + struct snd_soc_component *component; +- int ret; ++ const char *comp_name; + +- component = devm_kzalloc(adev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(adev->dev); + if (!component) + return -ENOMEM; + +- component->name = devm_kstrdup(adev->dev, name, GFP_KERNEL); +- if (!component->name) ++ comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &avs_probe_component_driver, adev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, comp_name); + +- return snd_soc_add_component(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); ++ return snd_soc_register_component(component, ++ &avs_probe_component_driver, ++ probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + } +diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c +--- a/sound/soc/intel/avs/topology.c ++++ b/sound/soc/intel/avs/topology.c +@@ -2229,7 +2229,7 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) + + int avs_load_topology(struct snd_soc_component *comp, const char *filename) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, filename, comp->dev); +@@ -2242,7 +2242,6 @@ int avs_load_topology(struct snd_soc_component *comp, const char *filename) + if (ret < 0) + dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); + +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c +--- a/sound/soc/intel/avs/utils.c ++++ b/sound/soc/intel/avs/utils.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -48,13 +49,12 @@ int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_mo + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_entry_index(adev, uuid); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -62,13 +62,12 @@ int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_modu + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -86,13 +85,12 @@ bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) + bool ret = false; + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + ret = ida_is_empty(adev->mod_idas[idx]); + +- mutex_unlock(&adev->modres_mutex); + return ret; + } + +@@ -163,68 +161,57 @@ int avs_module_info_init(struct avs_dev *adev, bool purge) + if (ret) + return AVS_IPC_RET(ret); + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + ret = avs_module_ida_alloc(adev, info, purge); + if (ret < 0) { + dev_err(adev->dev, "initialize module idas failed: %d\n", ret); +- goto exit; ++ return ret; + } + + /* Refresh current information with newly received table. */ + kfree(adev->mods_info); + adev->mods_info = info; + +-exit: +- mutex_unlock(&adev->modres_mutex); + return ret; + } + + void avs_module_info_free(struct avs_dev *adev) + { +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + avs_module_ida_destroy(adev); + kfree(adev->mods_info); + adev->mods_info = NULL; +- +- mutex_unlock(&adev->modres_mutex); + } + + int avs_module_id_alloc(struct avs_dev *adev, u16 module_id) + { +- int ret, idx, max_id; ++ int idx, max_id; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); +- ret = -EINVAL; +- goto exit; ++ return -EINVAL; + } + max_id = adev->mods_info->entries[idx].instance_max_count - 1; +- ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); +-exit: +- mutex_unlock(&adev->modres_mutex); +- return ret; ++ ++ return ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); + } + + void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); +- if (idx == -ENOENT) { ++ if (idx == -ENOENT) + dev_err(adev->dev, "invalid module id: %d", module_id); +- goto exit; +- } +- +- ida_free(adev->mod_idas[idx], instance_id); +-exit: +- mutex_unlock(&adev->modres_mutex); ++ else ++ ida_free(adev->mod_idas[idx], instance_id); + } + + /* +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -1483,12 +1483,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -1507,13 +1507,13 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + if (mach->mach_params.dmic_num) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:dmic cfg-mics:%d", + card->components, + mach->mach_params.dmic_num); +@@ -1524,7 +1524,7 @@ static int mc_probe(struct platform_device *pdev) + /* Register the card */ + ret = devm_snd_soc_register_card(card->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -1542,8 +1542,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "sof_sdw", }, +- {} ++ { .name = "sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h +--- a/sound/soc/intel/catpt/core.h ++++ b/sound/soc/intel/catpt/core.h +@@ -139,9 +139,7 @@ int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, + + int catpt_first_boot_firmware(struct catpt_dev *cdev); + int catpt_boot_firmware(struct catpt_dev *cdev, bool restore); +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan); ++int catpt_store_firmware_context(struct catpt_dev *cdev); + int catpt_coredump(struct catpt_dev *cdev); + + #include +diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c +--- a/sound/soc/intel/catpt/device.c ++++ b/sound/soc/intel/catpt/device.c +@@ -28,44 +28,17 @@ + static int catpt_do_suspend(struct device *dev) + { + struct catpt_dev *cdev = dev_get_drvdata(dev); +- struct dma_chan *chan; + int ret; + +- chan = catpt_dma_request_config_chan(cdev); +- if (IS_ERR(chan)) +- return PTR_ERR(chan); +- + memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx)); + ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx); +- if (ret) { +- ret = CATPT_IPC_RET(ret); +- goto release_dma_chan; +- } +- +- ret = catpt_dsp_stall(cdev, true); + if (ret) +- goto release_dma_chan; ++ return CATPT_IPC_RET(ret); + +- ret = catpt_store_memdumps(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store memdumps failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_module_states(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store module states failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_streams_context(cdev, chan); +- if (ret) +- dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); +- +-release_dma_chan: +- dma_release_channel(chan); ++ ret = catpt_store_firmware_context(cdev); + if (ret) + return ret; ++ + return catpt_dsp_power_down(cdev); + } + +diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c +--- a/sound/soc/intel/catpt/loader.c ++++ b/sound/soc/intel/catpt/loader.c +@@ -26,7 +26,7 @@ struct catpt_fw_hdr { + u32 reserved[4]; + } __packed; + +-struct catpt_fw_mod_hdr { ++struct catpt_fw_module_hdr { + char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; +@@ -81,7 +81,7 @@ catpt_request_region(struct resource *root, resource_size_t size) + return __request_region(root, addr, size, NULL, 0); + } + +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { + struct catpt_stream_runtime *stream; + +@@ -108,7 +108,7 @@ int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -138,7 +138,7 @@ int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -171,6 +171,39 @@ int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + ++int catpt_store_firmware_context(struct catpt_dev *cdev) ++{ ++ struct dma_chan *chan; ++ int ret; ++ ++ chan = catpt_dma_request_config_chan(cdev); ++ if (IS_ERR(chan)) ++ return PTR_ERR(chan); ++ ++ ret = catpt_dsp_stall(cdev, true); ++ if (ret) ++ goto exit; ++ ++ ret = catpt_store_dram_data(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store memdumps failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_module_states(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store module states failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_streams_context(cdev, chan); ++ if (ret) ++ dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); ++exit: ++ dma_release_channel(chan); ++ return ret; ++} ++ + static int + catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { +@@ -199,7 +232,7 @@ catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_restore_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -234,9 +267,9 @@ static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_fwimage(struct catpt_dev *cdev, +- struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_block_hdr *blk) ++static int catpt_restore_dram_rodata(struct catpt_dev *cdev, ++ struct dma_chan *chan, dma_addr_t paddr, ++ struct catpt_fw_block_hdr *blk) + { + struct resource r1 = {}; + int i; +@@ -324,28 +357,26 @@ static int catpt_load_block(struct catpt_dev *cdev, + + static int catpt_restore_basefw(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *basefw) ++ struct catpt_fw_module_hdr *basefw) + { +- u32 offset = sizeof(*basefw); ++ u32 off = sizeof(*basefw); + int ret, i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + basefw, sizeof(*basefw), false); + +- /* restore basefw image */ ++ /* Restore IRAM and .rodata for DRAM based on the firmware image. */ + for (i = 0; i < basefw->blocks; i++) { + struct catpt_fw_block_hdr *blk; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_IRAM: +- ret = catpt_load_block(cdev, chan, paddr + offset, +- blk, false); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, false); + break; + default: +- ret = catpt_restore_fwimage(cdev, chan, paddr + offset, +- blk); ++ ret = catpt_restore_dram_rodata(cdev, chan, paddr + off, blk); + break; + } + +@@ -354,11 +385,11 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + +- /* then proceed with memory dumps */ +- ret = catpt_restore_memdumps(cdev, chan); ++ /* Then proceed with DRAM .data saved before D3. */ ++ ret = catpt_restore_dram_data(cdev, chan); + if (ret) + dev_err(cdev->dev, "restore memdumps failed: %d\n", ret); + +@@ -367,9 +398,9 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + + static int catpt_restore_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -379,7 +410,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_INSTANCE: +@@ -390,7 +421,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + ALIGN(blk->size, 4)); + break; + default: +- ret = catpt_load_block(cdev, chan, paddr + offset, ++ ret = catpt_load_block(cdev, chan, paddr + off, + blk, false); + break; + } +@@ -400,7 +431,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + return 0; +@@ -408,10 +439,10 @@ static int catpt_restore_module(struct catpt_dev *cdev, + + static int catpt_load_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { + struct catpt_module_type *type; +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -423,9 +454,9 @@ static int catpt_load_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + +- ret = catpt_load_block(cdev, chan, paddr + offset, blk, true); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, true); + if (ret) { + dev_err(cdev->dev, "load block failed: %d\n", ret); + return ret; +@@ -440,7 +471,7 @@ static int catpt_load_module(struct catpt_dev *cdev, + type->state_size = blk->size; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + /* init module type static info */ +@@ -457,17 +488,17 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -479,12 +510,10 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + + switch (mod->module_id) { + case CATPT_MODID_BASE_FW: +- ret = catpt_restore_basefw(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_basefw(cdev, chan, paddr + off, mod); + break; + default: +- ret = catpt_restore_module(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_module(cdev, chan, paddr + off, mod); + break; + } + +@@ -493,7 +522,7 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; +@@ -503,17 +532,17 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -523,44 +552,40 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + if (mod->module_id > CATPT_MODID_LAST) + return -EINVAL; + +- ret = catpt_load_module(cdev, chan, paddr + offset, mod); ++ ret = catpt_load_module(cdev, chan, paddr + off, mod); + if (ret) { + dev_err(cdev->dev, "load module failed: %d\n", ret); + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; + } + +-static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, +- const char *name, const char *signature, +- bool restore) ++static int catpt_request_load_firmware(struct catpt_dev *cdev, struct dma_chan *chan, ++ const char *name, bool restore) + { + struct catpt_fw_hdr *fw; +- struct firmware *img; + dma_addr_t paddr; + void *vaddr; + int ret; + +- ret = request_firmware((const struct firmware **)&img, name, cdev->dev); ++ const struct firmware *img __free(firmware) = NULL; ++ ret = request_firmware(&img, name, cdev->dev); + if (ret) + return ret; + + fw = (struct catpt_fw_hdr *)img->data; +- if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) { ++ if (strncmp(fw->signature, FW_SIGNATURE, FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "firmware signature mismatch\n"); +- ret = -EINVAL; +- goto release_fw; ++ return -EINVAL; + } + + vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); +- if (!vaddr) { +- ret = -ENOMEM; +- goto release_fw; +- } ++ if (!vaddr) ++ return -ENOMEM; + + memcpy(vaddr, img->data, img->size); + fw = (struct catpt_fw_hdr *)vaddr; +@@ -570,12 +595,10 @@ static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, + ret = catpt_load_firmware(cdev, chan, paddr, fw); + + dma_free_coherent(cdev->dev, img->size, vaddr, paddr); +-release_fw: +- release_firmware(img); + return ret; + } + +-static int catpt_load_images(struct catpt_dev *cdev, bool restore) ++static int catpt_request_dma_load_firmware(struct catpt_dev *cdev, bool restore) + { + struct dma_chan *chan; + int ret; +@@ -584,8 +607,7 @@ static int catpt_load_images(struct catpt_dev *cdev, bool restore) + if (IS_ERR(chan)) + return PTR_ERR(chan); + +- ret = catpt_load_image(cdev, chan, cdev->spec->fw_name, +- FW_SIGNATURE, restore); ++ ret = catpt_request_load_firmware(cdev, chan, cdev->spec->fw_name, restore); + if (ret) + goto release_dma_chan; + +@@ -605,7 +627,7 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + + catpt_dsp_stall(cdev, true); + +- ret = catpt_load_images(cdev, restore); ++ ret = catpt_request_dma_load_firmware(cdev, restore); + if (ret) { + dev_err(cdev->dev, "load binaries failed: %d\n", ret); + return ret; +@@ -619,10 +641,10 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + if (!ret) { + dev_err(cdev->dev, "firmware ready timeout\n"); + return -ETIMEDOUT; +- /* Wake up does not mean FW is ready, an exception could occur. */ +- } else if (!cdev->ipc.ready) { +- return -EREMOTEIO; + } ++ /* Wake up does not mean FW is ready, an exception could occur. */ ++ if (!cdev->ipc.ready) ++ return -EREMOTEIO; + + /* update sram pg & clock once done booting */ + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); +diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c +--- a/sound/soc/intel/catpt/pcm.c ++++ b/sound/soc/intel/catpt/pcm.c +@@ -75,8 +75,8 @@ static struct catpt_stream_template *catpt_topology[] = { + static struct catpt_stream_template * + catpt_get_stream_template(struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + enum catpt_stream_type type; + + type = cpu_dai->driver->id; +@@ -159,11 +159,11 @@ static void catpt_stream_read_position(struct catpt_dev *cdev, + static void catpt_arrange_page_table(struct snd_pcm_substream *substream, + struct snd_dma_buffer *pgtbl) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream); + int i, pages; + +- pages = snd_sgbuf_aligned_pages(rtm->dma_bytes); ++ pages = snd_sgbuf_aligned_pages(runtime->dma_bytes); + + for (i = 0; i < pages; i++) { + u32 pfn, offset; +@@ -386,7 +386,7 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dmab; + struct catpt_stream_runtime *stream; + struct catpt_audio_format afmt; +@@ -412,8 +412,8 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.page_table_addr = stream->pgtbl.addr; +- rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE); +- rinfo.size = rtm->dma_bytes; ++ rinfo.num_pages = DIV_ROUND_UP(runtime->dma_bytes, PAGE_SIZE); ++ rinfo.size = runtime->dma_bytes; + rinfo.offset = 0; + rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0)); + +@@ -544,11 +544,11 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + struct catpt_notify_position *pos) + { + struct snd_pcm_substream *substream = stream->substream; +- struct snd_pcm_runtime *r = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t dsppos, newpos; + int ret; + +- dsppos = bytes_to_frames(r, pos->stream_position); ++ dsppos = bytes_to_frames(runtime, pos->stream_position); + + if (!stream->prepared) + goto exit; +@@ -556,8 +556,8 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + if (stream->template->type != CATPT_STRM_TYPE_RENDER) + goto exit; + +- if (dsppos >= r->buffer_size / 2) +- newpos = r->buffer_size / 2; ++ if (dsppos >= runtime->buffer_size / 2) ++ newpos = runtime->buffer_size / 2; + else + newpos = 0; + /* +@@ -565,7 +565,7 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + * (buffer half consumed) update wp to allow stream progression. + */ + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id, +- frames_to_bytes(r, newpos), ++ frames_to_bytes(runtime, newpos), + false, false); + if (ret) { + dev_err(cdev->dev, "update position for stream %d failed: %d\n", +@@ -600,11 +600,11 @@ static const struct snd_pcm_hardware catpt_pcm_hardware = { + }; + + static int catpt_component_pcm_new(struct snd_soc_component *component, +- struct snd_soc_pcm_runtime *rtm) ++ struct snd_soc_pcm_runtime *rtd) + { + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + +- snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG, ++ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV_SG, + cdev->dev, + catpt_pcm_hardware.buffer_bytes_max, + catpt_pcm_hardware.buffer_bytes_max); +@@ -615,9 +615,9 @@ static int catpt_component_pcm_new(struct snd_soc_component *component, + static int catpt_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + +- if (!rtm->dai_link->no_pcm) ++ if (!rtd->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware); + return 0; + } +@@ -626,13 +626,13 @@ static snd_pcm_uframes_t + catpt_component_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + u32 pos; + +- if (rtm->dai_link->no_pcm) ++ if (rtd->dai_link->no_pcm) + return 0; + + stream = snd_soc_dai_get_dma_data(cpu_dai, substream); +@@ -650,10 +650,10 @@ static const struct snd_soc_dai_ops catpt_fe_dai_ops = { + .trigger = catpt_dai_trigger, + }; + +-static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm, ++static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) + { +- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtm, 0); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct catpt_ssp_device_format devfmt; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; +@@ -871,8 +871,7 @@ static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol) + return CATPT_IPC_RET(ret); + } + +-static int catpt_volume_info(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_info *uinfo) ++static int catpt_volume_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) + { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = CATPT_CHANNELS_MAX; +@@ -1073,18 +1072,14 @@ int catpt_arm_stream_templates(struct catpt_dev *cdev) + int catpt_register_plat_component(struct catpt_dev *cdev) + { + struct snd_soc_component *component; +- int ret; + +- component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(cdev->dev); + if (!component) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &catpt_comp_driver, +- cdev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, catpt_comp_driver.name); + +- component->name = catpt_comp_driver.name; +- return snd_soc_add_component(component, dai_drivers, +- ARRAY_SIZE(dai_drivers)); ++ return snd_soc_register_component(component, ++ &catpt_comp_driver, ++ dai_drivers, ARRAY_SIZE(dai_drivers)); + } +diff --git a/sound/soc/intel/common/soc-acpi-intel-arl-match.c b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-arl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +@@ -572,6 +572,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), +@@ -600,12 +601,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x1, /* link0 required */ + .links = arl_sdca_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -717,24 +717,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l23.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l2_cs35l56x6_l13, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l2-cs35l56x6-l13.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -748,12 +752,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2) | BIT(3), + .links = lnl_712_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt712-l2-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -766,19 +772,22 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1320_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1), + .links = lnl_sdw_rt713_l0_rt1318_l1, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg" ++ .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +@@ -1321,60 +1321,70 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = tac5572_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5572.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5672_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5672.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5682_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5682.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2783_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2783.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2883_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2883.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1316_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1318_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = mtl_rt713_l0_rt1318_l1_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l1-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = mtl_rt713_l0_rt1316_l12, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3) | BIT(0), + .links = mtl_712_l0_1712_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt712-l0-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1395,7 +1405,8 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = mtl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), +@@ -1455,12 +1466,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x9, /* 2 active links required */ + .links = mtl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1474,18 +1487,21 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = rt5682_link2_max98373_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-rt5682-l2-max98373-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = cs42l42_link0_max98363_link2, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +@@ -492,7 +492,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rt722_l0_rt1320_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt722-l0-rt1320-l23.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, ++ .machine_check = snd_soc_acpi_intel_no_function_topology, + }, + { + .link_mask = BIT(1) | BIT(2), +@@ -522,14 +522,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_sdw_rt712_vb_l3_rt1320_l3, + .drv_name = "sof_sdw", +- .machine_check = snd_soc_acpi_intel_sdca_is_device_rt712_vb, ++ .machine_check = snd_soc_acpi_intel_rt712_vb_no_function_topology, + .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,14 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) ++{ ++ return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && ++ snd_soc_acpi_intel_no_function_topology(arg); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_rt712_vb_no_function_topology, ++ "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + MODULE_DESCRIPTION("ASoC ACPI Intel SDCA quirks"); + MODULE_LICENSE("GPL"); + MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,5 +11,6 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c +--- a/sound/soc/intel/keembay/kmb_platform.c ++++ b/sound/soc/intel/keembay/kmb_platform.c +@@ -873,6 +873,8 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) + + if (kmb_i2s->use_pio) { + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, + pdev->name, kmb_i2s); +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -2,24 +2,131 @@ + // + // Loongson ASoC Audio Machine driver + // +-// Copyright (C) 2023 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // Author: Yingkun Meng ++// Binbin Zhou + // + ++#include ++#include + #include ++#include ++#include ++#include + #include + #include +-#include +-#include +-#include + + static char codec_name[SND_ACPI_I2C_ID_LEN]; + + struct loongson_card_data { + struct snd_soc_card snd_card; + unsigned int mclk_fs; ++ struct gpio_desc *gpiod_hp_det; ++ struct gpio_desc *gpiod_hp_ctl; ++ struct gpio_desc *gpiod_spkr_en; ++ const struct loongson_card_config *cfg; + }; + ++struct loongson_card_config { ++ unsigned int fmt; ++ bool add_hp_jack; ++ bool add_dapm_widgets; ++ bool add_dapm_routes; ++}; ++ ++static const struct loongson_card_config ls2k1000_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_forever_pi_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = true, ++ .add_dapm_widgets = true, ++ .add_dapm_routes = true, ++}; ++ ++static int tegra_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); ++ struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Speaker")) ++ gpiod_set_value_cansleep(priv->gpiod_spkr_en, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Headphone")) ++ gpiod_set_value_cansleep(priv->gpiod_hp_ctl, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", tegra_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++}; ++ ++/* Headphones Jack */ ++ ++static struct snd_soc_jack loongson_asoc_hp_jack; ++ ++static struct snd_soc_jack_pin loongson_asoc_hp_jack_pins[] = { ++ { ++ .pin = "Headphone", ++ .mask = SND_JACK_HEADPHONE ++ }, ++ { ++ .pin = "Speaker", ++ .mask = SND_JACK_HEADPHONE, ++ .invert = 1 ++ }, ++}; ++ ++static struct snd_soc_jack_gpio loongson_asoc_hp_jack_gpio = { ++ .name = "Headphones detection", ++ .report = SND_JACK_HEADPHONE, ++ .debounce_time = 150, ++}; ++ ++static int loongson_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_card *card = rtd->card; ++ struct loongson_card_data *ls_priv = snd_soc_card_get_drvdata(card); ++ int ret = 0; ++ ++ if (!ls_priv->cfg->add_hp_jack || !ls_priv->gpiod_hp_det) ++ return 0; ++ ++ ret = snd_soc_card_jack_new_pins(card, "Headphones Jack", ++ SND_JACK_HEADPHONE, ++ &loongson_asoc_hp_jack, ++ loongson_asoc_hp_jack_pins, ++ ARRAY_SIZE(loongson_asoc_hp_jack_pins)); ++ if (ret) { ++ dev_err(rtd->dev, "Headphones Jack creation failed: %d\n", ret); ++ return ret; ++ } ++ ++ loongson_asoc_hp_jack_gpio.desc = ls_priv->gpiod_hp_det; ++ ++ ret = snd_soc_jack_add_gpios(&loongson_asoc_hp_jack, 1, &loongson_asoc_hp_jack_gpio); ++ if (ret) ++ dev_err(rtd->dev, "Headphone GPIO not added: %d\n", ret); ++ ++ return ret; ++} ++ + static int loongson_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +@@ -45,7 +152,7 @@ static int loongson_card_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, ls_card->cfg->fmt); + } + + static const struct snd_soc_ops loongson_ops = { +@@ -61,8 +168,7 @@ static struct snd_soc_dai_link loongson_dai_links[] = { + { + .name = "Loongson Audio Port", + .stream_name = "Loongson Audio", +- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF +- | SND_SOC_DAIFMT_CBC_CFC, ++ .init = loongson_asoc_machine_init, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, +@@ -123,16 +229,35 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) + + static int loongson_card_parse_of(struct loongson_card_data *data) + { +- struct device_node *cpu, *codec; + struct snd_soc_card *card = &data->snd_card; ++ struct device_node *cpu, *codec; + struct device *dev = card->dev; + int ret, i; + ++ data->gpiod_hp_det = devm_gpiod_get_optional(dev, "hp-det", GPIOD_IN); ++ if (IS_ERR(data->gpiod_hp_det)) ++ return PTR_ERR(data->gpiod_hp_det); ++ ++ data->gpiod_hp_ctl = devm_gpiod_get_optional(dev, "hp-ctl", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_hp_ctl)) ++ return PTR_ERR(data->gpiod_hp_ctl); ++ ++ data->gpiod_spkr_en = devm_gpiod_get_optional(dev, "spkr-en", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_spkr_en)) ++ return PTR_ERR(data->gpiod_spkr_en); ++ ++ if (data->cfg->add_dapm_routes) { ++ ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); ++ if (ret) ++ return ret; ++ } ++ + cpu = of_get_child_by_name(dev->of_node, "cpu"); + if (!cpu) { + dev_err(dev, "platform property missing or invalid\n"); + return -EINVAL; + } ++ + codec = of_get_child_by_name(dev->of_node, "codec"); + if (!codec) { + dev_err(dev, "audio-codec property missing or invalid\n"); +@@ -177,12 +302,22 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + if (!ls_priv) + return -ENOMEM; + ++ ls_priv->cfg = (const struct loongson_card_config *)device_get_match_data(dev); ++ if (!ls_priv->cfg) ++ return -EINVAL; ++ + card = &ls_priv->snd_card; + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = loongson_dai_links; + card->num_links = ARRAY_SIZE(loongson_dai_links); ++ ++ if (ls_priv->cfg->add_dapm_widgets) { ++ card->dapm_widgets = loongson_aosc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ } ++ + snd_soc_card_set_drvdata(card, ls_priv); + + ret = device_property_read_string(dev, "model", &card->name); +@@ -202,7 +337,19 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_asoc_dt_ids[] = { +- { .compatible = "loongson,ls-audio-card" }, ++ /* Loongson-2K1000/Loongson-2K2000/LS7A */ ++ { ++ .compatible = "loongson,ls-audio-card", ++ .data = &ls2k1000_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-forever-pi-audio-card", ++ .data = &ls2k0300_forever_pi_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-dl2k0300b-audio-card", ++ .data = &ls2k0300_dl2k0300b_card_config ++ }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids); +diff --git a/sound/soc/loongson/loongson_i2s_plat.c b/sound/soc/loongson/loongson_i2s_plat.c +--- a/sound/soc/loongson/loongson_i2s_plat.c ++++ b/sound/soc/loongson/loongson_i2s_plat.c +@@ -2,7 +2,7 @@ + // + // Loongson I2S controller master mode dirver(platform device) + // +-// Copyright (C) 2023-2024 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // + // Author: Yingkun Meng + // Binbin Zhou +@@ -21,6 +21,7 @@ + #include "loongson_i2s.h" + #include "loongson_dma.h" + ++/* Loongson-2K1000 APBDMA routing */ + #define LOONGSON_I2S_RX_DMA_OFFSET 21 + #define LOONGSON_I2S_TX_DMA_OFFSET 18 + +@@ -30,6 +31,11 @@ + #define LOONGSON_DMA3_CONF 0x3 + #define LOONGSON_DMA4_CONF 0x4 + ++struct loongson_i2s_plat_config { ++ int rev_id; ++ int (*i2s_dma_config)(struct platform_device *pdev); ++}; ++ + static int loongson_i2s_apbdma_config(struct platform_device *pdev) + { + int val; +@@ -47,8 +53,18 @@ static int loongson_i2s_apbdma_config(struct platform_device *pdev) + return 0; + } + ++static const struct loongson_i2s_plat_config ls2k0300_i2s_plat_config = { ++ .rev_id = 1, ++}; ++ ++static const struct loongson_i2s_plat_config ls2k1000_i2s_plat_config = { ++ .rev_id = 0, ++ .i2s_dma_config = loongson_i2s_apbdma_config, ++}; ++ + static int loongson_i2s_plat_probe(struct platform_device *pdev) + { ++ const struct loongson_i2s_plat_config *plat_config; + struct device *dev = &pdev->dev; + struct loongson_i2s *i2s; + struct resource *res; +@@ -59,12 +75,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (!i2s) + return -ENOMEM; + +- ret = loongson_i2s_apbdma_config(pdev); +- if (ret) +- return ret; ++ plat_config = device_get_match_data(dev); ++ if (!plat_config) ++ return -EINVAL; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- i2s->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (plat_config->i2s_dma_config) { ++ ret = plat_config->i2s_dma_config(pdev); ++ if (ret) ++ return ret; ++ } ++ ++ i2s->reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->reg_base)) + return dev_err_probe(dev, PTR_ERR(i2s->reg_base), + "devm_ioremap_resource failed\n"); +@@ -87,11 +108,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (IS_ERR(i2s_clk)) + return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n"); + i2s->clk_rate = clk_get_rate(i2s_clk); ++ i2s->rev_id = plat_config->rev_id; + + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + dev_set_name(dev, LS_I2S_DRVNAME); + dev_set_drvdata(dev, i2s); + ++ if (i2s->rev_id == 1) { ++ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET, I2S_CTRL_RESET); ++ fsleep(200); ++ } ++ + ret = devm_snd_soc_register_component(dev, &loongson_i2s_edma_component, + &loongson_i2s_dai, 1); + if (ret) +@@ -102,7 +129,8 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_i2s_ids[] = { +- { .compatible = "loongson,ls2k1000-i2s" }, ++ { .compatible = "loongson,ls2k0300-i2s", .data = &ls2k0300_i2s_plat_config }, ++ { .compatible = "loongson,ls2k1000-i2s", .data = &ls2k1000_i2s_plat_config }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_i2s_ids); +diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig +--- a/sound/soc/mediatek/Kconfig ++++ b/sound/soc/mediatek/Kconfig +@@ -7,7 +7,7 @@ config SND_SOC_MEDIATEK + + config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" +- depends on ARCH_MEDIATEK ++ depends on ARCH_MEDIATEK || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards +diff --git a/sound/soc/mediatek/common/mtk-dsp-sof-common.c b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +--- a/sound/soc/mediatek/common/mtk-dsp-sof-common.c ++++ b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +@@ -232,6 +232,7 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + const char *propname) + { + struct device_node *np = dev->of_node; ++ struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link *parsed_dai_link; + const char *dai_name = NULL; + int i, j, ret, num_links, parsed_num_links = 0; +@@ -254,9 +255,9 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + return ret; + } + dev_dbg(dev, "ASoC: Property get dai_name:%s\n", dai_name); +- for (j = 0; j < card->num_links; j++) { +- if (!strcmp(dai_name, card->dai_link[j].name)) { +- memcpy(&parsed_dai_link[parsed_num_links++], &card->dai_link[j], ++ for_each_card_prelinks(card, j, dai_link) { ++ if (!strcmp(dai_name, dai_link->name)) { ++ memcpy(&parsed_dai_link[parsed_num_links++], dai_link, + sizeof(struct snd_soc_dai_link)); + break; + } +diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +--- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c ++++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +@@ -315,7 +315,7 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, +- "%s snd_soc_register_card fail %d\n", ++ "%s device_property_read_u32() fail %d\n", + __func__, ret); + } + } +diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +--- a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c ++++ b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +@@ -385,8 +385,6 @@ static int mtk_adda_pad_top_event(struct snd_soc_dapm_widget *w, + if (event == SND_SOC_DAPM_PRE_PMU) { + if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2_CLK_P2) + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB8); +- else if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2) +- regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + else + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + } +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -1019,7 +1019,7 @@ static struct snd_soc_card mt8192_mt6359_rt1015p_rt5682x_card = { + .num_dapm_routes = ARRAY_SIZE(mt8192_mt6359_rt1015p_rt5682x_routes), + }; + +-static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, ++static int mt8192_mt6359_card_set_be_link(struct device *dev, + struct snd_soc_dai_link *link, + struct device_node *node, + char *link_name) +@@ -1027,9 +1027,9 @@ static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, + int ret; + + if (node && strcmp(link->name, link_name) == 0) { +- ret = snd_soc_of_get_dai_link_codecs(card->dev, node, link); ++ ret = snd_soc_of_get_dai_link_codecs(dev, node, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, "get dai link codecs fail\n"); ++ dev_err_probe(dev, ret, "get dai link codecs fail\n"); + return ret; + } + } +@@ -1065,21 +1065,21 @@ static int mt8192_mt6359_legacy_probe(struct mtk_soc_card_data *soc_card_data) + } + + for_each_card_prelinks(card, i, dai_link) { +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, speaker_codec, "I2S3"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, speaker_codec, "I2S3"); + if (ret) { + dev_err_probe(dev, ret, "%s set speaker_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S8"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S8"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S9"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S9"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -104,8 +104,6 @@ static const struct snd_kcontrol_new mt8196_nau8825_controls[] = { + + #define EXT_SPK_AMP_W_NAME "Ext_Speaker_Amp" + +-static struct snd_soc_card mt8196_nau8825_soc_card; +- + static const struct snd_soc_dapm_widget mt8196_nau8825_card_widgets[] = { + /* SOF Uplink */ + SND_SOC_DAPM_MIXER("SOF_DMA_UL0", SND_SOC_NOPM, 0, 0, NULL, 0), +diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +--- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c ++++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +@@ -2120,19 +2120,15 @@ static int mt8365_afe_pcm_dev_probe(struct platform_device *pdev) + spin_lock_init(&afe_priv->afe_ctrl_lock); + mutex_init(&afe_priv->afe_clk_mutex); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- afe->base_addr = devm_ioremap_resource(&pdev->dev, res); ++ afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 1); +- if (res) { +- afe_priv->afe_sram_vir_addr = +- devm_ioremap_resource(&pdev->dev, res); +- if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { +- afe_priv->afe_sram_phy_addr = res->start; +- afe_priv->afe_sram_size = resource_size(res); +- } ++ afe_priv->afe_sram_vir_addr = ++ devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { ++ afe_priv->afe_sram_phy_addr = res->start; ++ afe_priv->afe_sram_size = resource_size(res); + } + + /* initial audio related clock */ +diff --git a/sound/soc/mediatek/mt8365/mt8365-mt6357.c b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +--- a/sound/soc/mediatek/mt8365/mt8365-mt6357.c ++++ b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +@@ -70,7 +70,8 @@ static const struct snd_soc_dapm_route mt8365_mt6357_routes[] = { + static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -101,7 +102,8 @@ static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + static void mt8365_mt6357_int_adda_shutdown(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -245,7 +247,8 @@ static struct snd_soc_dai_link mt8365_mt6357_dais[] = { + + static int mt8365_mt6357_gpio_probe(struct snd_soc_card *card) + { +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + struct device *dev = card->dev; + int ret, i; + +@@ -294,7 +297,6 @@ static int mt8365_mt6357_dev_probe(struct mtk_soc_card_data *soc_card_data, bool + struct mt8365_mt6357_priv *mach_priv; + int ret; + +- card->dev = dev; + ret = parse_dai_link_info(card); + if (ret) + goto err; +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -4,6 +4,8 @@ snd-soc-meson-aiu-y := aiu.o + snd-soc-meson-aiu-y += aiu-acodec-ctrl.o + snd-soc-meson-aiu-y += aiu-codec-ctrl.o + snd-soc-meson-aiu-y += aiu-encoder-i2s.o ++snd-soc-meson-aiu-y += gx-formatter.o ++snd-soc-meson-aiu-y += aiu-formatter-i2s.o + snd-soc-meson-aiu-y += aiu-encoder-spdif.o + snd-soc-meson-aiu-y += aiu-fifo.o + snd-soc-meson-aiu-y += aiu-fifo-i2s.o +diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c +--- a/sound/soc/meson/aiu-encoder-i2s.c ++++ b/sound/soc/meson/aiu-encoder-i2s.c +@@ -10,14 +10,11 @@ + #include + + #include "aiu.h" ++#include "gx-formatter.h" ++#include "gx-interface.h" + +-#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) +-#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) +-#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) + #define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) +-#define AIU_RST_SOFT_I2S_FAST BIT(0) + +-#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) + #define AIU_CLK_CTRL_I2S_DIV_EN BIT(0) + #define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2) + #define AIU_CLK_CTRL_AOCLK_INVERT BIT(6) +@@ -35,49 +32,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component, + enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0); + } + +-static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) +-{ +- /* Always operate in split (classic interleaved) mode */ +- unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT; +- +- /* Reset required to update the pipeline */ +- snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST); +- snd_soc_component_read(component, AIU_I2S_SYNC); +- +- switch (params_physical_width(params)) { +- case 16: /* Nothing to do */ +- break; +- +- case 32: +- desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT); +- break; +- +- default: +- return -EINVAL; +- } +- +- switch (params_channels(params)) { +- case 2: /* Nothing to do */ +- break; +- case 8: +- desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; +- break; +- default: +- return -EINVAL; +- } +- +- snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC, +- AIU_I2S_SOURCE_DESC_MODE_8CH | +- AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT | +- AIU_I2S_SOURCE_DESC_MODE_SPLIT, +- desc); +- +- return 0; +-} +- + static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -108,6 +62,32 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + return 0; + } + ++/* ++ * Return true if the given combination of channels and sample width requires ++ * the bs quirk. Return false otherwise. ++ */ ++static bool aiu_encoder_is_bs_quirk(unsigned int channels, int width) ++{ ++ return (channels == 8) && (width == 16); ++} ++ ++static int aiu_encoder_check_bs_quirk(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); ++ ++ /* Nothing to do if the other stream doesn't exist or it's not configured yet. */ ++ if (!other_stream || !other_stream->channels) ++ return 0; ++ ++ if (aiu_encoder_is_bs_quirk(other_stream->channels, other_stream->width) != ++ aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -119,7 +99,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + * increased by 50% to get the correct output rate. + * No idea why ! + */ +- if (params_width(params) == 16 && params_channels(params) == 8) { ++ if (aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) { + if (bs % 2) { + dev_err(component->dev, + "Cannot increase i2s divider by 50%%\n"); +@@ -141,25 +121,23 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + return 0; + } + +-static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) ++static int aiu_encoder_i2s_set_clocks(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { ++ struct snd_soc_component *component = dai->component; + struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int srate = params_rate(params); + unsigned int fs, bs; + int ret; + + /* Get the oversampling factor */ +- fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate); ++ fs = DIV_ROUND_CLOSEST(iface->mclk_rate, srate); + +- if (fs % 64) ++ if ((fs % 64) || (fs == 0)) + return -EINVAL; + +- /* Send data MSB first */ +- snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG, +- AIU_I2S_DAC_CFG_MSB_FIRST, +- AIU_I2S_DAC_CFG_MSB_FIRST); +- + /* Set bclk to lrlck ratio */ + snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL, + AIU_CODEC_DAC_LRCLK_CTRL_DIV, +@@ -168,10 +146,22 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, + + bs = fs / 64; + +- if (aiu->platform->has_clk_ctrl_more_i2s_div) ++ if (aiu->platform->has_clk_ctrl_more_i2s_div) { ++ /* ++ * The hw rules added in startup() make this unreachable in the ++ * sequential case, but both streams may be refined concurrently ++ * before either commits its config, since only ops->hw_params ++ * runs under the card's pcm_mutex. Re-check against the committed ++ * state of the other stream, which is stable under that mutex. ++ */ ++ if (aiu_encoder_check_bs_quirk(substream, params, dai)) { ++ dev_err(dai->dev, "bclk requirements incompatible with other stream\n"); ++ return -EINVAL; ++ } + ret = aiu_encoder_i2s_set_more_div(component, params, bs); +- else ++ } else { + ret = aiu_encoder_i2s_set_legacy_div(component, params, bs); ++ } + + if (ret) + return ret; +@@ -188,23 +178,37 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ ret = aiu_encoder_i2s_set_clocks(substream, params, dai); ++ if (ret) { ++ dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret); ++ return ret; ++ } ++ ++ ts->physical_width = params_physical_width(params); ++ ts->width = params_width(params); ++ ts->channels = params_channels(params); ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + int ret; + +- /* Disable the clock while changing the settings */ +- aiu_encoder_i2s_divider_enable(component, false); ++ if (ts->clk_enabled) ++ return 0; + +- ret = aiu_encoder_i2s_setup_desc(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s desc failed\n"); ++ ret = clk_prepare_enable(ts->iface->mclk); ++ if (ret) + return ret; +- } + +- ret = aiu_encoder_i2s_set_clocks(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s clocks failed\n"); +- return ret; +- } ++ ts->clk_enabled = true; + + aiu_encoder_i2s_divider_enable(component, true); + +@@ -214,9 +218,24 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + +- aiu_encoder_i2s_divider_enable(component, false); ++ /* ++ * If this is the last substream being closed then disable the i2s ++ * clock divider. ++ */ ++ if (snd_soc_dai_active(dai) <= 1) ++ aiu_encoder_i2s_divider_enable(component, 0); ++ ++ if (ts->clk_enabled) { ++ clk_disable_unprepare(ts->iface->mclk); ++ ts->clk_enabled = false; ++ } ++ ++ ts->channels = 0; ++ ts->width = 0; ++ ts->physical_width = 0; + + return 0; + } +@@ -224,6 +243,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct snd_soc_component *component = dai->component; ++ struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int val = 0; + unsigned int skew; +@@ -255,9 +276,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + skew = 0; + break; + default: ++ dev_err(dai->dev, "unsupported dai format\n"); + return -EINVAL; + } + ++ iface->fmt = fmt; ++ + val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew); + snd_soc_component_update_bits(component, AIU_CLK_CTRL, + AIU_CLK_CTRL_LRCLK_INVERT | +@@ -272,6 +296,7 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_iface *iface = &aiu->i2s.iface; + int ret; + + if (WARN_ON(clk_id != 0)) +@@ -280,11 +305,15 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + if (dir == SND_SOC_CLOCK_IN) + return 0; + +- ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq); +- if (ret) +- dev_err(dai->dev, "Failed to set sysclk to %uHz", freq); ++ ret = clk_set_rate(iface->mclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret); ++ return ret; ++ } + +- return ret; ++ iface->mclk_rate = freq; ++ ++ return 0; + } + + static const unsigned int hw_channels[] = {2, 8}; +@@ -294,10 +323,45 @@ static const struct snd_pcm_hw_constraint_list hw_channel_constraints = { + .mask = 0, + }; + ++static int aiu_encoder_i2s_pcm_hw_rule(struct snd_pcm_hw_params *params, ++ struct snd_pcm_hw_rule *rule) ++{ ++ struct gx_stream *other = rule->private; ++ struct snd_interval *ch = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); ++ /* ++ * The quirk is technically based on the significant bits whereas here ++ * we're using the physical width for simplicity. This works because ++ * S16_LE is the only format supported by this encoder that has: ++ * significant bits = physical width = 16-bits ++ */ ++ struct snd_interval *phys_width = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); ++ struct snd_interval new_i; ++ ++ if (other->channels == 0) ++ return 0; ++ ++ snd_interval_any(&new_i); ++ ++ if (rule->var == SNDRV_PCM_HW_PARAM_CHANNELS) { ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 8; ++ else if (snd_interval_single(phys_width) && phys_width->min == 16) ++ new_i.max = 2; /* Force 2ch */ ++ } else { /* SNDRV_PCM_HW_PARAM_SAMPLE_BITS */ ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 16; ++ else if (snd_interval_single(ch) && ch->min == 8) ++ new_i.min = 17; /* Request physical width > 16 bits */ ++ } ++ ++ return snd_interval_refine(hw_param_interval(params, rule->var), &new_i); ++} ++ + static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); + int ret; + + /* Make sure the encoder gets either 2 or 8 channels */ +@@ -305,15 +369,79 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_channel_constraints); + if (ret) { +- dev_err(dai->dev, "adding channels constraints failed\n"); ++ dev_err(dai->dev, "adding channels constraints failed: %d\n", ret); + return ret; + } + +- ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks); +- if (ret) +- dev_err(dai->dev, "failed to enable i2s clocks\n"); ++ /* ++ * If DAI supports both playback and capture streams ensure the bs-quirk is ++ * handled correctly. ++ * This is only valid for GX platforms (has_clk_ctrl_more_i2s_div=true). ++ */ ++ if (aiu->platform->has_clk_ctrl_more_i2s_div && other_stream) { ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; + +- return ret; ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Enable only clocks which are required for the interface internal ++ * logic. MCLK is enabled/disabled from the formatter and the I2S ++ * divider is enabled/disabled in "hw_params"/"hw_free", respectively. ++ */ ++ ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable PCLK: %d\n", ret); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable MIXER: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ++ /* ++ * We're always operating in split mode for the playback stream. ++ * ++ * This setting arguably belong to the 'aiu-formatter', but it's kept ++ * here for backward compatibility reason. At reset the I2S encoder ++ * operates in normal mode which would only support 8ch, but by default ++ * only 2ch are enabled. If a playback stream is started without ++ * changing to split mode, then the I2S encoder doesn't consume audio ++ * samples and the playback fails. ++ * Moving this to 'aiu-formatter' would cause the split mode to be set ++ * only when the formatter is enabled, which doesn't happen at boot as ++ * the default value for "HDMI CTRL SRC" is "DISABLED". ++ */ ++ ret = snd_soc_component_update_bits(dai->component, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT); ++ if (ret < 0) ++ dev_err(dai->dev, "failed to update AIU_I2S_SOURCE_DESC: %d", ret); ++ ++ return 0; + } + + static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, +@@ -321,14 +449,89 @@ static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); + +- clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks); ++ clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++} ++ ++static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, ++ int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ ret = gx_stream_start(ts); ++ break; ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ case SNDRV_PCM_TRIGGER_STOP: ++ gx_stream_stop(ts); ++ ret = 0; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai) ++{ ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ ts = snd_soc_dai_dma_data_get(dai, stream); ++ if (ts) ++ gx_stream_free(ts); ++ ++ snd_soc_dai_dma_data_set(dai, stream, NULL); ++ } ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai) ++{ ++ struct aiu *aiu = snd_soc_dai_get_drvdata(dai); ++ struct gx_iface *iface = &aiu->i2s.iface; ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ if (!snd_soc_dai_get_widget(dai, stream)) ++ continue; ++ ++ ts = gx_stream_alloc(iface); ++ if (!ts) { ++ aiu_encoder_i2s_remove_dai(dai); ++ return -ENOMEM; ++ } ++ snd_soc_dai_dma_data_set(dai, stream, ts); ++ } ++ ++ iface->mclk = aiu->i2s.clks[MCLK].clk; ++ iface->mclk_rate = clk_get_rate(iface->mclk); ++ ++ return 0; + } + + const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = { ++ .probe = aiu_encoder_i2s_probe_dai, ++ .remove = aiu_encoder_i2s_remove_dai, + .hw_params = aiu_encoder_i2s_hw_params, ++ .prepare = aiu_encoder_i2s_prepare, + .hw_free = aiu_encoder_i2s_hw_free, + .set_fmt = aiu_encoder_i2s_set_fmt, + .set_sysclk = aiu_encoder_i2s_set_sysclk, + .startup = aiu_encoder_i2s_startup, + .shutdown = aiu_encoder_i2s_shutdown, ++ .trigger = aiu_encoder_i2s_trigger, + }; +diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/aiu-formatter-i2s.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++ ++#include "aiu.h" ++#include "gx-formatter.h" ++ ++#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) ++#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) ++#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) ++ ++#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) ++ ++static struct snd_soc_dai * ++aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dapm_path *p; ++ struct snd_soc_dai *be; ++ ++ snd_soc_dapm_widget_for_each_sink_path(w, p) { ++ if (!p->connect) ++ continue; ++ ++ if (p->sink->id == snd_soc_dapm_dai_in) ++ return (struct snd_soc_dai *)p->sink->priv; ++ ++ be = aiu_formatter_i2s_get_be(p->sink); ++ if (be) ++ return be; ++ } ++ ++ return NULL; ++} ++ ++static struct gx_stream * ++aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w); ++ ++ if (!be) ++ return NULL; ++ ++ return snd_soc_dai_dma_data_get_playback(be); ++} ++ ++static int aiu_formatter_i2s_prepare(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts) ++{ ++ /* Always operate in split (classic interleaved) mode */ ++ unsigned int desc = 0; ++ ++ /* ++ * Pipeline reset is already implemented in aiu_fifo_i2s_trigger() at ++ * trigger time. ++ */ ++ ++ switch (ts->physical_width) { ++ case 16: /* Nothing to do */ ++ break; ++ ++ case 32: ++ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ switch (ts->channels) { ++ case 2: /* Nothing to do */ ++ break; ++ case 8: ++ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(map, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_8CH | ++ AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT, ++ desc); ++ ++ /* Send data MSB first */ ++ regmap_update_bits(map, AIU_I2S_DAC_CFG, ++ AIU_I2S_DAC_CFG_MSB_FIRST, ++ AIU_I2S_DAC_CFG_MSB_FIRST); ++ ++ return 0; ++} ++ ++const struct gx_formatter_ops aiu_formatter_i2s_ops = { ++ .get_stream = aiu_formatter_i2s_get_stream, ++ .prepare = aiu_formatter_i2s_prepare, ++}; +diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c +--- a/sound/soc/meson/aiu.c ++++ b/sound/soc/meson/aiu.c +@@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, + static const struct snd_kcontrol_new aiu_spdif_encode_mux = + SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); + +-static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { +- SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, +- &aiu_spdif_encode_mux), ++#define AIU_WIDGET_SPDIF_SRC_SEL 0 ++#define AIU_WIDGET_I2S_FORMATTER 1 ++ ++static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { ++ [AIU_WIDGET_SPDIF_SRC_SEL] = ++ SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, ++ &aiu_spdif_encode_mux), ++ [AIU_WIDGET_I2S_FORMATTER] = ++ SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0, ++ gx_formatter_event, ++ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), + }; + + static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = { +- { "I2S Encoder Playback", NULL, "I2S FIFO Playback" }, ++ { "I2S Formatter", NULL, "I2S FIFO Playback" }, ++ { "I2S Encoder Playback", NULL, "I2S Formatter" }, + { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" }, + { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" }, + { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" }, +@@ -145,6 +154,7 @@ static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = { + .formats = AIU_FORMATS, + }, + .ops = &aiu_encoder_i2s_dai_ops, ++ .symmetric_rate = 1, + }, + [CPU_SPDIF_ENCODER] = { + .name = "SPDIF Encoder", +@@ -172,6 +182,11 @@ static const struct regmap_config aiu_regmap_cfg = { + .max_register = 0x2ac, + }; + ++static const struct gx_formatter_driver aiu_formatter_i2s_drv = { ++ .regmap_cfg = &aiu_regmap_cfg, ++ .ops = &aiu_formatter_i2s_ops, ++}; ++ + static int aiu_clk_bulk_get(struct device *dev, + const char * const *ids, + unsigned int num, +@@ -282,6 +297,14 @@ static int aiu_probe(struct platform_device *pdev) + if (ret) + return ret; + ++ /* Allocate the aiu-formatter into its widget */ ++ ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER], ++ &aiu_formatter_i2s_drv, map); ++ if (ret) { ++ dev_err(dev, "Failed to allocate aiu formatter\n"); ++ goto err; ++ } ++ + /* Register the cpu component of the aiu */ + ret = snd_soc_register_component(dev, &aiu_cpu_component, + aiu_cpu_dai_drv, +@@ -310,12 +333,14 @@ static int aiu_probe(struct platform_device *pdev) + + return 0; + err: ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(dev); + return ret; + } + + static void aiu_remove(struct platform_device *pdev) + { ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(&pdev->dev); + } + +diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h +--- a/sound/soc/meson/aiu.h ++++ b/sound/soc/meson/aiu.h +@@ -7,6 +7,8 @@ + #ifndef _MESON_AIU_H + #define _MESON_AIU_H + ++#include "gx-formatter.h" ++ + struct clk; + struct clk_bulk_data; + struct device; +@@ -25,6 +27,7 @@ struct aiu_interface { + struct clk_bulk_data *clks; + unsigned int clk_num; + int irq; ++ struct gx_iface iface; + }; + + struct aiu_platform_data { +@@ -58,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops; ++extern const struct gx_formatter_ops aiu_formatter_i2s_ops; + + #define AIU_IEC958_BPF 0x000 + #define AIU_958_MISC 0x010 +diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c +--- a/sound/soc/meson/axg-card.c ++++ b/sound/soc/meson/axg-card.c +@@ -107,6 +107,7 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + struct snd_soc_dai_link *pad; + struct snd_soc_dai_link *lb; + struct snd_soc_dai_link_component *dlc; ++ struct device *dev = card->dev; + int ret; + + /* extend links */ +@@ -117,11 +118,11 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + pad = &card->dai_link[*index]; + lb = &card->dai_link[*index + 1]; + +- lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name); ++ lb->name = devm_kasprintf(dev, GFP_KERNEL, "%s-lb", pad->name); + if (!lb->name) + return -ENOMEM; + +- dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL); ++ dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + +@@ -158,13 +159,14 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + struct device_node *node, + struct axg_dai_link_tdm_data *be) + { ++ struct device *dev = card->dev; + char propname[32]; + u32 tx, rx; + int i; + +- be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->tx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->tx_mask), GFP_KERNEL); +- be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->rx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->rx_mask), GFP_KERNEL); + if (!be->tx_mask || !be->rx_mask) + return -ENOMEM; +@@ -191,7 +193,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + + /* ... but the interface should at least have one direction */ + if (!tx && !rx) { +- dev_err(card->dev, "tdm link has no cpu slots\n"); ++ dev_err(dev, "tdm link has no cpu slots\n"); + return -EINVAL; + } + +@@ -207,7 +209,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + * Error if the slots can't accommodate the largest mask or + * if it is just too big + */ +- dev_err(card->dev, "bad slot number\n"); ++ dev_err(dev, "bad slot number\n"); + return -EINVAL; + } + +@@ -222,8 +224,9 @@ static int axg_card_parse_codecs_masks(struct snd_soc_card *card, + struct axg_dai_link_tdm_data *be) + { + struct axg_dai_link_tdm_mask *codec_mask; ++ struct device *dev = card->dev; + +- codec_mask = devm_kcalloc(card->dev, link->num_codecs, ++ codec_mask = devm_kcalloc(dev, link->num_codecs, + sizeof(*codec_mask), GFP_KERNEL); + if (!codec_mask) + return -ENOMEM; +@@ -249,10 +252,11 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct axg_dai_link_tdm_data *be; ++ struct device *dev = card->dev; + int ret; + + /* Allocate tdm link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -266,7 +270,7 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + + ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); + if (ret) { +- dev_err(card->dev, "error parsing tdm link slots\n"); ++ dev_err(dev, "error parsing tdm link slots\n"); + return ret; + } + +@@ -310,9 +314,10 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/g12a-toacodec.c b/sound/soc/meson/g12a-toacodec.c +--- a/sound/soc/meson/g12a-toacodec.c ++++ b/sound/soc/meson/g12a-toacodec.c +@@ -312,7 +312,7 @@ static int g12a_toacodec_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/g12a-tohdmitx.c b/sound/soc/meson/g12a-tohdmitx.c +--- a/sound/soc/meson/g12a-tohdmitx.c ++++ b/sound/soc/meson/g12a-tohdmitx.c +@@ -251,7 +251,7 @@ static int g12a_tohdmitx_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/gx-card.c b/sound/soc/meson/gx-card.c +--- a/sound/soc/meson/gx-card.c ++++ b/sound/soc/meson/gx-card.c +@@ -48,9 +48,10 @@ static int gx_card_parse_i2s(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct gx_dai_link_i2s_data *be; ++ struct device *dev = card->dev; + + /* Allocate i2s link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -81,9 +82,10 @@ static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.c +@@ -0,0 +1,282 @@ ++// SPDX-License-Identifier: (GPL-2.0 OR MIT) ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++#include ++ ++#include "gx-formatter.h" ++ ++struct gx_formatter { ++ struct list_head list; ++ struct gx_stream *stream; ++ const struct gx_formatter_driver *drv; ++ bool enabled; ++ struct regmap *map; ++}; ++ ++static int gx_formatter_enable(struct gx_formatter *formatter) ++{ ++ int ret; ++ ++ /* Do nothing if the formatter is already enabled */ ++ if (formatter->enabled) ++ return 0; ++ ++ /* Setup the stream parameter in the formatter */ ++ if (formatter->drv->ops->prepare) { ++ ret = formatter->drv->ops->prepare(formatter->map, ++ formatter->drv->quirks, ++ formatter->stream); ++ if (ret) ++ return ret; ++ } ++ ++ /* Finally, actually enable the formatter */ ++ if (formatter->drv->ops->enable) ++ formatter->drv->ops->enable(formatter->map); ++ ++ formatter->enabled = true; ++ ++ return 0; ++} ++ ++static void gx_formatter_disable(struct gx_formatter *formatter) ++{ ++ /* Do nothing if the formatter is already disabled */ ++ if (!formatter->enabled) ++ return; ++ ++ if (formatter->drv->ops->disable) ++ formatter->drv->ops->disable(formatter->map); ++ ++ formatter->enabled = false; ++} ++ ++static int gx_formatter_attach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Catch up if the stream is already running when we attach */ ++ if (ts->ready) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ list_add_tail(&formatter->list, &ts->formatter_list); ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static void gx_formatter_detach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ ++ if (!ts) ++ return; ++ ++ mutex_lock(&ts->lock); ++ list_del(&formatter->list); ++ mutex_unlock(&ts->lock); ++ ++ gx_formatter_disable(formatter); ++} ++ ++static int gx_formatter_power_up(struct gx_formatter *formatter, ++ struct snd_soc_dapm_widget *w) ++{ ++ struct gx_stream *ts = formatter->drv->ops->get_stream(w); ++ int ret; ++ ++ /* ++ * If we don't get a stream at this stage, it would mean that the ++ * widget is powering up but is not attached to any backend DAI. ++ * It should not happen, ever ! ++ */ ++ if (WARN_ON(!ts)) ++ return -ENODEV; ++ ++ formatter->stream = ts; ++ INIT_LIST_HEAD(&formatter->list); ++ ret = gx_formatter_attach(formatter); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void gx_formatter_power_down(struct gx_formatter *formatter) ++{ ++ gx_formatter_detach(formatter); ++ formatter->stream = NULL; ++} ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event) ++{ ++ struct snd_soc_component *c; ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ c = snd_soc_dapm_to_component(w->dapm); ++ ++ if (w->priv) ++ formatter = w->priv; ++ else ++ formatter = snd_soc_component_get_drvdata(c); ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ ret = gx_formatter_power_up(formatter, w); ++ break; ++ ++ case SND_SOC_DAPM_PRE_PMD: ++ gx_formatter_power_down(formatter); ++ break; ++ ++ default: ++ dev_err(c->dev, "Unexpected event %d\n", event); ++ return -EINVAL; ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_event); ++ ++int gx_formatter_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct gx_formatter_driver *drv; ++ struct gx_formatter *formatter; ++ void __iomem *regs; ++ ++ drv = of_device_get_match_data(dev); ++ if (!drv) { ++ dev_err(dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, formatter); ++ formatter->drv = drv; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); ++ if (IS_ERR(formatter->map)) { ++ dev_err(dev, "failed to init regmap: %ld\n", ++ PTR_ERR(formatter->map)); ++ return PTR_ERR(formatter->map); ++ } ++ ++ return devm_snd_soc_register_component(dev, drv->component_drv, ++ NULL, 0); ++} ++EXPORT_SYMBOL_GPL(gx_formatter_probe); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap) ++{ ++ struct gx_formatter *formatter; ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ ++ formatter->drv = drv; ++ formatter->map = regmap; ++ ++ w->priv = formatter; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_create); ++ ++int gx_stream_start(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Start all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ ts->ready = true; ++ ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_stream_start); ++ ++void gx_stream_stop(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ ++ mutex_lock(&ts->lock); ++ ts->ready = false; ++ ++ /* Stop all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ gx_formatter_disable(formatter); ++ } ++ ++ mutex_unlock(&ts->lock); ++} ++EXPORT_SYMBOL_GPL(gx_stream_stop); ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface) ++{ ++ struct gx_stream *ts; ++ ++ ts = kzalloc(sizeof(*ts), GFP_KERNEL); ++ if (ts) { ++ INIT_LIST_HEAD(&ts->formatter_list); ++ mutex_init(&ts->lock); ++ ts->iface = iface; ++ } ++ ++ return ts; ++} ++EXPORT_SYMBOL_GPL(gx_stream_alloc); ++ ++void gx_stream_free(struct gx_stream *ts) ++{ ++ /* ++ * If the list is not empty, it would mean that one of the formatter ++ * widget is still powered and attached to the interface while we ++ * are removing the TDM DAI. It should not be possible ++ */ ++ WARN_ON(!list_empty(&ts->formatter_list)); ++ mutex_destroy(&ts->lock); ++ kfree(ts); ++} ++EXPORT_SYMBOL_GPL(gx_stream_free); ++ ++MODULE_DESCRIPTION("Amlogic GX formatter driver"); ++MODULE_AUTHOR("Valerio Setti "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.h +@@ -0,0 +1,56 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_FORMATTER_H ++#define _MESON_GX_FORMATTER_H ++ ++#include "gx-interface.h" ++ ++struct platform_device; ++struct regmap; ++struct snd_soc_dapm_widget; ++struct snd_kcontrol; ++ ++struct gx_formatter_hw { ++ unsigned int skew_offset; ++}; ++ ++struct gx_formatter_ops { ++ struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w); ++ void (*enable)(struct regmap *map); ++ void (*disable)(struct regmap *map); ++ int (*prepare)(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts); ++}; ++ ++struct gx_formatter_driver { ++ const struct snd_soc_component_driver *component_drv; ++ const struct regmap_config *regmap_cfg; ++ const struct gx_formatter_ops *ops; ++ const struct gx_formatter_hw *quirks; ++}; ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event); ++int gx_formatter_probe(struct platform_device *pdev); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap); ++ ++/* ++ * Formatter data is already freed when the associated device is removed, ++ * so we just need to remove the pointer from the widget. ++ */ ++static inline void gx_formatter_free(struct snd_soc_dapm_widget *w) ++{ ++ w->priv = NULL; ++} ++ ++#endif /* _MESON_GX_FORMATTER_H */ +diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-interface.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_INTERFACE_H ++#define _MESON_GX_INTERFACE_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct gx_iface { ++ struct clk *mclk; ++ unsigned long mclk_rate; ++ ++ /* format is common to all the DAIs of the iface */ ++ unsigned int fmt; ++}; ++ ++struct gx_stream { ++ struct gx_iface *iface; ++ struct list_head formatter_list; ++ struct mutex lock; ++ unsigned int channels; ++ unsigned int width; ++ unsigned int physical_width; ++ bool ready; ++ ++ /* For continuous clock tracking */ ++ bool clk_enabled; ++}; ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface); ++void gx_stream_free(struct gx_stream *ts); ++int gx_stream_start(struct gx_stream *ts); ++void gx_stream_stop(struct gx_stream *ts); ++ ++#endif /* _MESON_GX_INTERFACE_H */ +diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c +--- a/sound/soc/meson/meson-card-utils.c ++++ b/sound/soc/meson/meson-card-utils.c +@@ -71,6 +71,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + struct device_node *node, + struct snd_soc_dai_link_component *dlc) + { ++ struct device *dev = card->dev; + int ret; + + if (!dlc || !node) +@@ -78,7 +79,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + + ret = snd_soc_of_get_dlc(node, NULL, dlc, 0); + if (ret) +- return dev_err_probe(card->dev, ret, "can't parse dai\n"); ++ return dev_err_probe(dev, ret, "can't parse dai\n"); + + return ret; + } +@@ -89,7 +90,8 @@ static int meson_card_set_link_name(struct snd_soc_card *card, + struct device_node *node, + const char *prefix) + { +- char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", ++ struct device *dev = card->dev; ++ char *name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", + prefix, node->full_name); + if (!name) + return -ENOMEM; +@@ -132,16 +134,17 @@ int meson_card_set_be_link(struct snd_soc_card *card, + struct device_node *node) + { + struct snd_soc_dai_link_component *codec; ++ struct device *dev = card->dev; + int ret, num_codecs; + + num_codecs = of_get_child_count(node); + if (!num_codecs) { +- dev_err(card->dev, "be link %s has no codec\n", ++ dev_err(dev, "be link %s has no codec\n", + node->full_name); + return -EINVAL; + } + +- codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); ++ codec = devm_kcalloc(dev, num_codecs, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + +@@ -158,7 +161,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, + + ret = meson_card_set_link_name(card, link, node, "be"); + if (ret) +- dev_err(card->dev, "error setting %pOFn link name\n", node); ++ dev_err(dev, "error setting %pOFn link name\n", node); + + return ret; + } +@@ -189,12 +192,13 @@ EXPORT_SYMBOL_GPL(meson_card_set_fe_link); + static int meson_card_add_links(struct snd_soc_card *card) + { + struct meson_card *priv = snd_soc_card_get_drvdata(card); +- struct device_node *node = card->dev->of_node; ++ struct device *dev = card->dev; ++ struct device_node *node = dev->of_node; + int num, i, ret; + + num = of_get_child_count(node); + if (!num) { +- dev_err(card->dev, "card has no links\n"); ++ dev_err(dev, "card has no links\n"); + return -EINVAL; + } + +@@ -219,8 +223,10 @@ static int meson_card_parse_of_optional(struct snd_soc_card *card, + int (*func)(struct snd_soc_card *c, + const char *p)) + { ++ struct device *dev = card->dev; ++ + /* If property is not provided, don't fail ... */ +- if (!of_property_present(card->dev->of_node, propname)) ++ if (!of_property_present(dev->of_node, propname)) + return 0; + + /* ... but do fail if it is provided and the parsing fails */ +diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c +--- a/sound/soc/meson/t9015.c ++++ b/sound/soc/meson/t9015.c +@@ -78,8 +78,14 @@ static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static const u64 t9015_dai_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops t9015_dai_ops = { + .set_fmt = t9015_dai_set_fmt, ++ .auto_selectable_formats = &t9015_dai_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver t9015_dai = { +@@ -265,10 +271,8 @@ static int t9015_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(priv->avdd), "failed to AVDD\n"); + + ret = device_reset(dev); +- if (ret) { +- dev_err(dev, "reset failed\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { +diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c +--- a/sound/soc/mxs/mxs-saif.c ++++ b/sound/soc/mxs/mxs-saif.c +@@ -826,12 +826,9 @@ static int mxs_saif_probe(struct platform_device *pdev) + mxs_saif[saif->id] = saif; + + saif->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(saif->clk)) { +- ret = PTR_ERR(saif->clk); +- dev_err(&pdev->dev, "Cannot get the clock: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(saif->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(saif->clk), ++ "Cannot get the clock\n"); + + saif->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(saif->base)) +@@ -844,10 +841,8 @@ static int mxs_saif_probe(struct platform_device *pdev) + saif->dev = &pdev->dev; + ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, + dev_name(&pdev->dev), saif); +- if (ret) { +- dev_err(&pdev->dev, "failed to request irq\n"); ++ if (ret) + return ret; +- } + + platform_set_drvdata(pdev, saif); + +@@ -860,16 +855,12 @@ static int mxs_saif_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &mxs_saif_component, + &mxs_saif_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "register DAI failed\n"); ++ if (ret) + return ret; +- } + + ret = mxs_pcm_platform_register(&pdev->dev); +- if (ret) { +- dev_err(&pdev->dev, "register PCM failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c +--- a/sound/soc/mxs/mxs-sgtl5000.c ++++ b/sound/soc/mxs/mxs-sgtl5000.c +@@ -155,8 +155,6 @@ static int mxs_sgtl5000_probe(struct platform_device *pdev) + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", +- ret); + mxs_saif_put_mclk(0); + return ret; + } +diff --git a/sound/soc/pxa/pxa2xx-ac97-lib.c b/sound/soc/pxa/pxa2xx-ac97-lib.c +--- a/sound/soc/pxa/pxa2xx-ac97-lib.c ++++ b/sound/soc/pxa/pxa2xx-ac97-lib.c +@@ -316,10 +316,8 @@ int pxa2xx_ac97_hw_probe(struct platform_device *dev) + int irq; + + ac97_reg_base = devm_platform_ioremap_resource(dev, 0); +- if (IS_ERR(ac97_reg_base)) { +- dev_err(&dev->dev, "Missing MMIO resource\n"); ++ if (IS_ERR(ac97_reg_base)) + return PTR_ERR(ac97_reg_base); +- } + + if (cpu_is_pxa27x()) { + /* Assert reset using GPIOD_OUT_HIGH, because reset is GPIO_ACTIVE_LOW */ +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -23,6 +23,161 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { + SND_SOC_DAPM_SPK("DP7 Jack", NULL), + }; + ++static struct device_node *qcom_snd_get_link_node(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_card *card = rtd->card; ++ struct of_phandle_args args; ++ int ret; ++ ++ if (!card->dev || !card->dev->of_node) ++ return NULL; ++ ++ for_each_available_child_of_node_scoped(card->dev->of_node, np) { ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(np, "cpu"); ++ ++ if (!cpu_np) ++ continue; ++ ++ ret = of_parse_phandle_with_args(cpu_np, "sound-dai", "#sound-dai-cells", 0, ++ &args); ++ if (ret) ++ continue; ++ ++ if (args.np == rtd->dai_link->cpus[0].of_node && ++ args.args_count == 1 && args.args[0] == cpu_dai->id) { ++ of_node_put(args.np); ++ return of_node_get(np); ++ } ++ ++ of_node_put(args.np); ++ } ++ ++ return NULL; ++} ++ ++static int qcom_snd_parse_tdm_slot(struct device_node *np, ++ struct qcom_snd_tdm_slot_cfg *cfg) ++{ ++ memset(cfg, 0, sizeof(*cfg)); ++ ++ return snd_soc_of_parse_tdm_slot(np, &cfg->tx_mask, &cfg->rx_mask, ++ &cfg->slots, &cfg->slot_width); ++} ++ ++static int qcom_snd_normalize_tdm_slots(struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ unsigned int slots; ++ unsigned int slot_width; ++ ++ if (cpu_cfg->slots && codec_cfg->slots && cpu_cfg->slots != codec_cfg->slots) ++ return -EINVAL; ++ ++ if (cpu_cfg->slot_width && codec_cfg->slot_width && ++ cpu_cfg->slot_width != codec_cfg->slot_width) ++ return -EINVAL; ++ ++ slots = cpu_cfg->slots ?: codec_cfg->slots; ++ if (!slots) ++ return 0; ++ ++ slot_width = cpu_cfg->slot_width ?: codec_cfg->slot_width; ++ if (!slot_width) ++ return -EINVAL; ++ ++ cpu_cfg->slots = slots; ++ codec_cfg->slots = slots; ++ cpu_cfg->slot_width = slot_width; ++ codec_cfg->slot_width = slot_width; ++ ++ return 0; ++} ++ ++static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct device_node *link_np __free(device_node) = qcom_snd_get_link_node(rtd); ++ int ret; ++ ++ if (!link_np) ++ return -EINVAL; ++ ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(link_np, "cpu"); ++ struct device_node *codec_np __free(device_node) = ++ of_get_child_by_name(link_np, "codec"); ++ if (!cpu_np || !codec_np) ++ return -EINVAL; ++ ++ ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_parse_tdm_slot(codec_np, codec_cfg); ++} ++ ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ int ret; ++ ++ ret = qcom_snd_parse_dai_tdm_slots(rtd, cpu_cfg, codec_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_normalize_tdm_slots(cpu_cfg, codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_get_dai_tdm_slots); ++ ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ int i; ++ int ret; ++ ++ if (!cpu_cfg->slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_tdm_slot(cpu_dai, cpu_cfg->tx_mask, cpu_cfg->rx_mask, ++ cpu_cfg->slots, cpu_cfg->slot_width); ++ if (ret) ++ return ret; ++ ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_tdm_slot(codec_dai, ++ codec_cfg->tx_mask, ++ codec_cfg->rx_mask, ++ codec_cfg->slots, ++ codec_cfg->slot_width); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots_cfg); ++ ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ int ret; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots); ++ + int qcom_snd_parse_of(struct snd_soc_card *card) + { + struct device *dev = card->dev; +@@ -91,7 +246,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { +- dev_err(card->dev, "error getting codec dai_link name\n"); ++ dev_err(dev, "error getting codec dai_link name\n"); + return ret; + } + +@@ -109,7 +264,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); + if (ret) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: error getting cpu dai name\n", link->name); + return ret; + } +@@ -126,7 +281,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + "sound-dai", + 0); + if (!link->platforms->of_node) { +- dev_err(card->dev, "%s: platform dai not found\n", link->name); ++ dev_err(dev, "%s: platform dai not found\n", link->name); + return -EINVAL; + } + } else { +@@ -136,7 +291,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: codec dai not found\n", link->name); + return ret; + } +@@ -215,6 +370,7 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + } + + switch (cpu_dai->id) { ++ case LPI_MI2S_RX_0: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: +diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h +--- a/sound/soc/qcom/common.h ++++ b/sound/soc/qcom/common.h +@@ -7,9 +7,23 @@ + #include + #include + +-#define LPASS_MAX_PORT (SENARY_MI2S_TX + 1) ++#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) ++ ++struct qcom_snd_tdm_slot_cfg { ++ unsigned int tx_mask; ++ unsigned int rx_mask; ++ unsigned int slots; ++ unsigned int slot_width; ++}; + + int qcom_snd_parse_of(struct snd_soc_card *card); ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); + int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); + int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, +diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c +--- a/sound/soc/qcom/qdsp6/audioreach.c ++++ b/sound/soc/qcom/qdsp6/audioreach.c +@@ -152,6 +152,13 @@ struct apm_i2s_module_intf_cfg { + + #define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8) + ++struct apm_audio_if_module_intf_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_audio_if_intf_cfg cfg; ++} __packed; ++ ++#define APM_AUDIO_IF_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_audio_if_module_intf_cfg), 8) ++ + struct apm_module_hw_ep_mf_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_mf mf; +@@ -168,6 +175,13 @@ struct apm_module_frame_size_factor_cfg { + + #define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8) + ++struct apm_module_hw_ep_frame_duration_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_hw_ep_frame_duration frame_duration; ++} __packed; ++ ++#define APM_HW_EP_FRAME_DURATION_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_frame_duration_cfg), 8) ++ + struct apm_module_hw_ep_power_mode_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_power_mode_cfg power_mode; +@@ -703,6 +717,7 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + int pm_sz = APM_HW_EP_PMODE_CFG_PSIZE; + int size = ic_sz + ep_sz + fs_sz + pm_sz; + void *p; ++ int i; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) +@@ -741,7 +756,12 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + + intf_cfg->cfg.lpaif_type = module->hw_interface_type; + intf_cfg->cfg.intf_index = module->hw_interface_idx; +- intf_cfg->cfg.active_channels_mask = (1 << cfg->num_channels) - 1; ++ intf_cfg->cfg.active_channels_mask = 0; ++ /* Convert the physical channel mapping into a bit field */ ++ for (i = 0; i < AR_PCM_MAX_NUM_CHANNEL; i++) ++ if (cfg->channel_map[i]) ++ intf_cfg->cfg.active_channels_mask |= BIT(i); ++ + p += ic_sz; + + pm_cfg = p; +@@ -840,7 +860,7 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + uint32_t num_channels = cfg->num_channels; + int payload_size = APM_MFC_CFG_PSIZE(media_format, num_channels) + + APM_MODULE_PARAM_DATA_SIZE; +- int i; ++ int i, j; + void *p; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); +@@ -860,8 +880,12 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + media_format->sample_rate = cfg->sample_rate; + media_format->bit_width = cfg->bit_width; + media_format->num_channels = cfg->num_channels; +- for (i = 0; i < num_channels; i++) +- media_format->channel_mapping[i] = cfg->channel_map[i]; ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!cfg->channel_map[i]) ++ continue; ++ media_format->channel_mapping[j++] = cfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1042,6 +1066,81 @@ static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } + ++static int audioreach_audio_if_set_media_format(struct q6apm_graph *graph, ++ const struct audioreach_module *module, ++ const struct audioreach_module_config *cfg) ++{ ++ struct apm_module_hw_ep_frame_duration_cfg *fd_cfg; ++ struct apm_module_param_data *param_data; ++ struct apm_audio_if_module_intf_cfg *intf_cfg; ++ struct apm_module_hw_ep_mf_cfg *hw_cfg; ++ int ic_sz = APM_AUDIO_IF_INTF_CFG_PSIZE; ++ int ep_sz = APM_HW_EP_CFG_PSIZE; ++ int fd_sz = APM_HW_EP_FRAME_DURATION_PSIZE; ++ int size = ic_sz + ep_sz + fd_sz; ++ u32 slot_mask = cfg->slot_mask ? cfg->slot_mask : module->slot_mask; ++ u16 nslots_per_frame = cfg->nslots_per_frame ? ++ (u16)cfg->nslots_per_frame : module->nslots_per_frame; ++ u16 slot_width = cfg->slot_width ? (u16)cfg->slot_width : module->slot_width; ++ void *p; ++ ++ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); ++ if (IS_ERR(pkt)) ++ return PTR_ERR(pkt); ++ ++ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; ++ intf_cfg = p; ++ ++ param_data = &intf_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_AUDIO_IF_INTF_CFG; ++ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; ++ intf_cfg->cfg.qaif_type = module->qaif_type; ++ intf_cfg->cfg.intf_idx = (u16)module->hw_interface_idx; ++ intf_cfg->cfg.intf_mode = module->intf_mode; ++ intf_cfg->cfg.ctrl_data_out_enable = module->ctrl_data_out_enable; ++ intf_cfg->cfg.active_slot_mask = slot_mask; ++ intf_cfg->cfg.nslots_per_frame = nslots_per_frame; ++ intf_cfg->cfg.slot_width = slot_width; ++ intf_cfg->cfg.active_lane_mask = module->active_lane_mask; ++ intf_cfg->cfg.frame_sync_rate = module->frame_sync_rate; ++ intf_cfg->cfg.frame_sync_src = module->sync_src; ++ intf_cfg->cfg.frame_sync_mode = module->sync_mode; ++ intf_cfg->cfg.invert_frame_sync_pulse = module->ctrl_invert_sync_pulse; ++ intf_cfg->cfg.frame_sync_data_delay = module->ctrl_sync_data_delay; ++ intf_cfg->cfg.bit_clk_type = module->bit_clk_type; ++ intf_cfg->cfg.inv_int_bit_clk = module->inv_int_bit_clk; ++ intf_cfg->cfg.inv_ext_bit_clk = module->inv_ext_bit_clk; ++ ++ p += ic_sz; ++ hw_cfg = p; ++ param_data = &hw_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_MF_CFG; ++ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; ++ ++ hw_cfg->mf.sample_rate = cfg->sample_rate; ++ hw_cfg->mf.bit_width = cfg->bit_width; ++ hw_cfg->mf.num_channels = cfg->num_channels; ++ hw_cfg->mf.data_format = module->data_format; ++ ++ p += ep_sz; ++ fd_cfg = p; ++ param_data = &fd_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_FRAME_DURATION; ++ param_data->param_size = fd_sz - APM_MODULE_PARAM_DATA_SIZE; ++ fd_cfg->frame_duration.frame_duration_in_us = AUDIO_IF_FRAME_DURATION_US; ++ fd_cfg->frame_duration.allow_frame_duration_normalization = AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE; ++ fd_cfg->frame_duration.min_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MIN_US; ++ fd_cfg->frame_duration.max_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MAX_US; ++ ++ return q6apm_send_cmd_sync(graph->apm, pkt, 0); ++} ++ + static int audioreach_logging_set_media_format(struct q6apm_graph *graph, + const struct audioreach_module *module) + { +@@ -1080,6 +1179,7 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + struct apm_pcm_module_media_fmt_cmd *cfg; + struct apm_module_param_data *param_data; + int payload_size; ++ int i, j; + + if (num_channels > 4) { + dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels); +@@ -1113,7 +1213,12 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + media_cfg->num_channels = mcfg->num_channels; + media_cfg->q_factor = mcfg->bit_width - 1; + media_cfg->bits_per_sample = mcfg->bit_width; +- memcpy(media_cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < mcfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ media_cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1163,6 +1268,7 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + struct payload_media_fmt_pcm *cfg; + struct media_format *header; + int rc, payload_size; ++ int i, j; + void *p; + + if (num_channels > 4) { +@@ -1202,7 +1308,12 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; +- memcpy(cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + } else { + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) +@@ -1279,7 +1390,7 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + struct apm_module_sp_vi_ex_mode_cfg *ex_cfg; + int op_sz, cm_sz, ex_sz; + struct apm_module_param_data *param_data; +- int rc, i, payload_size; ++ int rc, i, payload_size, j; + struct gpr_pkt *pkt; + void *p; + +@@ -1320,14 +1431,19 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + param_data->param_size = cm_sz - APM_MODULE_PARAM_DATA_SIZE; + + cm_cfg->cfg.num_channels = num_channels * 2; +- for (i = 0; i < num_channels; i++) { ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; + /* + * Map speakers into Vsense and then Isense of each channel. + * E.g. for PCM_CHANNEL_FL and PCM_CHANNEL_FR to: + * [1, 2, 3, 4] + */ +- cm_cfg->cfg.channel_mapping[2 * i] = (mcfg->channel_map[i] - 1) * 2 + 1; +- cm_cfg->cfg.channel_mapping[2 * i + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ cm_cfg->cfg.channel_mapping[2 * j] = (mcfg->channel_map[i] - 1) * 2 + 1; ++ cm_cfg->cfg.channel_mapping[2 * j + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ ++ ++j; + } + + p += cm_sz; +@@ -1411,6 +1527,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, + if (!rc) + rc = audioreach_module_enable(graph, module, true); + break; ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ case MODULE_ID_AUDIO_IF_SINK: ++ rc = audioreach_audio_if_set_media_format(graph, module, cfg); ++ break; + + default: + rc = 0; +diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h +--- a/sound/soc/qcom/qdsp6/audioreach.h ++++ b/sound/soc/qcom/qdsp6/audioreach.h +@@ -36,6 +36,8 @@ struct q6apm_graph; + #define MODULE_ID_SPEAKER_PROTECTION 0x070010E2 + #define MODULE_ID_SPEAKER_PROTECTION_VI 0x070010E3 + #define MODULE_ID_OPUS_DEC 0x07001174 ++#define MODULE_ID_AUDIO_IF_SINK 0x0700117C ++#define MODULE_ID_AUDIO_IF_SOURCE 0x0700117D + + #define APM_CMD_GET_SPF_STATE 0x01001021 + #define APM_CMD_RSP_GET_SPF_STATE 0x02001007 +@@ -544,6 +546,74 @@ struct param_id_i2s_intf_cfg { + #define PORT_ID_I2S_OUPUT 1 + #define I2S_STACK_SIZE 2048 + ++#define PARAM_ID_AUDIO_IF_INTF_CFG 0x08001B11 ++ ++/* ++ * struct param_id_audio_if_intf_cfg - Audio interface configuration ++ * @qaif_type: Audio interface type (e.g. QAIF, QAIF_VA) ++ * @intf_idx: Interface instance index ++ * @intf_mode: Interface operating mode (TDM/PCM/I2S) ++ * @ctrl_data_out_enable: Enable sharing of data-out signal with other masters ++ * @active_slot_mask: Bitmask indicating active slots ++ * @nslots_per_frame: Number of slots per audio frame ++ * @slot_width: Width of each slot in bits ++ * @active_lane_mask: Bitmask of active data lanes ++ * @frame_sync_rate: Frame sync rate in Hz ++ * @frame_sync_src: Frame sync source selection ++ * @frame_sync_mode: Frame sync mode configuration ++ * @invert_frame_sync_pulse: Invert frame sync polarity when set ++ * @frame_sync_data_delay: Data delay from frame sync in bit clocks ++ * @bit_clk_type: Bit clock type (internal / external) ++ * @inv_int_bit_clk: Invert internal bit clock when set ++ * @inv_ext_bit_clk: Invert external bit clock when set ++ * ++ * This structure defines configuration parameters for the Qualcomm ++ * Audio Interface (QAIF) block. It is used to program interface ++ * characteristics such as slot configuration, clocking and frame ++ * synchronization behaviour. ++ */ ++struct param_id_audio_if_intf_cfg { ++ uint16_t qaif_type; ++ uint16_t intf_idx; ++ uint16_t intf_mode; ++ uint16_t ctrl_data_out_enable; ++ uint32_t active_slot_mask; ++ uint16_t nslots_per_frame; ++ uint16_t slot_width; ++ uint32_t active_lane_mask; ++ uint32_t frame_sync_rate; ++ uint16_t frame_sync_src; ++ uint16_t frame_sync_mode; ++ uint16_t invert_frame_sync_pulse; ++ uint16_t frame_sync_data_delay; ++ uint16_t bit_clk_type; ++ uint8_t inv_int_bit_clk; ++ uint8_t inv_ext_bit_clk; ++} __packed; ++ ++#define PARAM_ID_HW_EP_FRAME_DURATION 0x08001B2F ++#define AUDIO_IF_FRAME_DURATION_US 1000 ++#define AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE 1 ++#define AUDIO_IF_FRAME_DURATION_MIN_US 1 ++#define AUDIO_IF_FRAME_DURATION_MAX_US 100000 ++ ++/** ++ * struct param_id_hw_ep_frame_duration - Hardware endpoint frame duration ++ * @frame_duration_in_us: Frame duration in microseconds. ++ * @allow_frame_duration_normalization: Permit SPF to normalize frame duration. ++ * @min_normalized_frame_dur_us: Minimum normalized frame duration in microseconds. ++ * @max_normalized_frame_dur_us: Maximum normalized frame duration in microseconds. ++ * ++ * This structure configures the frame duration for the Audio IF hardware ++ * endpoint and, when enabled, the allowed normalization range. ++ */ ++struct param_id_hw_ep_frame_duration { ++ uint32_t frame_duration_in_us; ++ uint32_t allow_frame_duration_normalization; ++ uint32_t min_normalized_frame_dur_us; ++ uint32_t max_normalized_frame_dur_us; ++} __packed; ++ + #define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154 + + struct param_id_display_port_intf_cfg { +@@ -877,6 +947,23 @@ struct audioreach_module { + uint32_t data_format; + uint32_t hw_interface_type; + ++ /* Audio IF module (TDM/PCM/I2S) */ ++ u32 slot_mask; ++ u32 active_lane_mask; ++ u32 frame_sync_rate; ++ u16 qaif_type; ++ u16 sync_src; ++ u16 ctrl_data_out_enable; ++ u16 nslots_per_frame; ++ u16 slot_width; ++ u16 intf_mode; ++ u16 sync_mode; ++ u16 ctrl_invert_sync_pulse; ++ u16 ctrl_sync_data_delay; ++ u16 bit_clk_type; ++ u8 inv_int_bit_clk; ++ u8 inv_ext_bit_clk; ++ + /* PCM module specific */ + uint32_t interleave_type; + +@@ -907,6 +994,9 @@ struct audioreach_module_config { + u32 channel_allocation; + u32 sd_line_mask; + int fmt; ++ u32 slot_mask; ++ u16 nslots_per_frame; ++ u16 slot_width; + struct snd_codec codec; + u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; + }; +diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c +--- a/sound/soc/qcom/qdsp6/q6afe-dai.c ++++ b/sound/soc/qcom/qdsp6/q6afe-dai.c +@@ -412,6 +412,8 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + if (rc < 0) { +@@ -665,6 +667,21 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + + /* USB playback AFE port receives data for playback, hence use the RX port */ + {"USB Playback", NULL, "USB_RX"}, ++ ++ {"LPI RX0 MI2S Playback", NULL, "LPI_MI2S_RX_0"}, ++ {"LPI_MI2S_TX_0", NULL, "LPI TX0 MI2S Capture"}, ++ {"LPI RX1 MI2S Playback", NULL, "LPI_MI2S_RX_1"}, ++ {"LPI_MI2S_TX_1", NULL, "LPI TX1 MI2S Capture"}, ++ {"LPI RX2 MI2S Playback", NULL, "LPI_MI2S_RX_2"}, ++ {"LPI_MI2S_TX_2", NULL, "LPI TX2 MI2S Capture"}, ++ {"LPI RX3 MI2S Playback", NULL, "LPI_MI2S_RX_3"}, ++ {"LPI_MI2S_TX_3", NULL, "LPI TX3 MI2S Capture"}, ++ {"LPI RX4 MI2S Playback", NULL, "LPI_MI2S_RX_4"}, ++ {"LPI_MI2S_TX_4", NULL, "LPI TX4 MI2S Capture"}, ++ {"LPI RX5 MI2S Playback", NULL, "LPI_MI2S_RX_5"}, ++ {"LPI_MI2S_TX_5", NULL, "LPI TX5 MI2S Capture"}, ++ {"LPI RX6 MI2S Playback", NULL, "LPI_MI2S_RX_6"}, ++ {"LPI_MI2S_TX_6", NULL, "LPI TX6 MI2S Capture"}, + }; + + static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +@@ -1011,6 +1028,35 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0), ++ ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), + }; + + static const struct snd_soc_component_driver q6afe_dai_component = { +@@ -1045,6 +1091,8 @@ static void of_q6afe_parse_dai_data(struct device *dev, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + priv = &data->priv[id]; + ret = of_property_read_variable_u32_array(node, + "qcom,sd-lines", +diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c +--- a/sound/soc/qcom/qdsp6/q6afe.c ++++ b/sound/soc/qcom/qdsp6/q6afe.c +@@ -132,6 +132,20 @@ + #define AFE_PORT_ID_QUINARY_MI2S_TX 0x1017 + #define AFE_PORT_ID_SENARY_MI2S_RX 0x1018 + #define AFE_PORT_ID_SENARY_MI2S_TX 0x1019 ++#define AFE_PORT_ID_INT0_MI2S_RX 0x102e ++#define AFE_PORT_ID_INT0_MI2S_TX 0x102f ++#define AFE_PORT_ID_INT1_MI2S_RX 0x1030 ++#define AFE_PORT_ID_INT1_MI2S_TX 0x1031 ++#define AFE_PORT_ID_INT2_MI2S_RX 0x1032 ++#define AFE_PORT_ID_INT2_MI2S_TX 0x1033 ++#define AFE_PORT_ID_INT3_MI2S_RX 0x1034 ++#define AFE_PORT_ID_INT3_MI2S_TX 0x1035 ++#define AFE_PORT_ID_INT4_MI2S_RX 0x1036 ++#define AFE_PORT_ID_INT4_MI2S_TX 0x1037 ++#define AFE_PORT_ID_INT5_MI2S_RX 0x1038 ++#define AFE_PORT_ID_INT5_MI2S_TX 0x1039 ++#define AFE_PORT_ID_INT6_MI2S_RX 0x103a ++#define AFE_PORT_ID_INT6_MI2S_TX 0x103b + + /* Start of the range of port IDs for TDM devices. */ + #define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 +@@ -931,6 +945,34 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, + RX_CODEC_DMA_RX_7, 1, 1}, + [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1}, ++ [LPI_MI2S_RX_0] = { AFE_PORT_ID_INT0_MI2S_RX, ++ LPI_MI2S_RX_0, 1, 1}, ++ [LPI_MI2S_TX_0] = { AFE_PORT_ID_INT0_MI2S_TX, ++ LPI_MI2S_TX_0, 0, 1}, ++ [LPI_MI2S_RX_1] = { AFE_PORT_ID_INT1_MI2S_RX, ++ LPI_MI2S_RX_1, 1, 1}, ++ [LPI_MI2S_TX_1] = { AFE_PORT_ID_INT1_MI2S_TX, ++ LPI_MI2S_TX_1, 0, 1}, ++ [LPI_MI2S_RX_2] = { AFE_PORT_ID_INT2_MI2S_RX, ++ LPI_MI2S_RX_2, 1, 1}, ++ [LPI_MI2S_TX_2] = { AFE_PORT_ID_INT2_MI2S_TX, ++ LPI_MI2S_TX_2, 0, 1}, ++ [LPI_MI2S_RX_3] = { AFE_PORT_ID_INT3_MI2S_RX, ++ LPI_MI2S_RX_3, 1, 1}, ++ [LPI_MI2S_TX_3] = { AFE_PORT_ID_INT3_MI2S_TX, ++ LPI_MI2S_TX_3, 0, 1}, ++ [LPI_MI2S_RX_4] = { AFE_PORT_ID_INT4_MI2S_RX, ++ LPI_MI2S_RX_4, 1, 1}, ++ [LPI_MI2S_TX_4] = { AFE_PORT_ID_INT4_MI2S_TX, ++ LPI_MI2S_TX_4, 0, 1}, ++ [LPI_MI2S_RX_5] = { AFE_PORT_ID_INT5_MI2S_RX, ++ LPI_MI2S_RX_5, 1, 1}, ++ [LPI_MI2S_TX_5] = { AFE_PORT_ID_INT5_MI2S_TX, ++ LPI_MI2S_TX_5, 0, 1}, ++ [LPI_MI2S_RX_6] = { AFE_PORT_ID_INT6_MI2S_RX, ++ LPI_MI2S_RX_6, 1, 1}, ++ [LPI_MI2S_TX_6] = { AFE_PORT_ID_INT6_MI2S_TX, ++ LPI_MI2S_TX_6, 0, 1}, + }; + + static void q6afe_port_free(struct kref *ref) +@@ -1785,6 +1827,20 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) + case AFE_PORT_ID_QUINARY_MI2S_TX: + case AFE_PORT_ID_SENARY_MI2S_RX: + case AFE_PORT_ID_SENARY_MI2S_TX: ++ case AFE_PORT_ID_INT0_MI2S_RX: ++ case AFE_PORT_ID_INT0_MI2S_TX: ++ case AFE_PORT_ID_INT1_MI2S_RX: ++ case AFE_PORT_ID_INT1_MI2S_TX: ++ case AFE_PORT_ID_INT2_MI2S_RX: ++ case AFE_PORT_ID_INT2_MI2S_TX: ++ case AFE_PORT_ID_INT3_MI2S_RX: ++ case AFE_PORT_ID_INT3_MI2S_TX: ++ case AFE_PORT_ID_INT4_MI2S_RX: ++ case AFE_PORT_ID_INT4_MI2S_TX: ++ case AFE_PORT_ID_INT5_MI2S_RX: ++ case AFE_PORT_ID_INT5_MI2S_TX: ++ case AFE_PORT_ID_INT6_MI2S_RX: ++ case AFE_PORT_ID_INT6_MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +--- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c ++++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +@@ -2,10 +2,12 @@ + // Copyright (c) 2021, Linaro Limited + + #include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -15,15 +17,54 @@ + #include "q6dsp-common.h" + #include "audioreach.h" + #include "q6apm.h" ++#include "q6prm.h" + + #define AUDIOREACH_BE_PCM_BASE 16 + ++struct q6apm_dai_priv_data { ++ struct clk *mclk; ++ struct clk *bclk; ++ bool mclk_enabled, bclk_enabled; ++}; ++ + struct q6apm_lpass_dai_data { + struct q6apm_graph *graph[APM_PORT_MAX]; + bool is_port_started[APM_PORT_MAX]; + struct audioreach_module_config module_config[APM_PORT_MAX]; ++ struct q6apm_dai_priv_data priv[APM_PORT_MAX]; + }; + ++static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id) ++{ ++ if (dai_data->priv[id].mclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].mclk); ++ dai_data->priv[id].mclk_enabled = false; ++ } ++ ++ if (dai_data->priv[id].bclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].bclk); ++ dai_data->priv[id].bclk_enabled = false; ++ } ++} ++ ++static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data) ++{ ++ int i; ++ ++ for (i = 0; i < APM_PORT_MAX; i++) { ++ q6apm_lpass_dai_disable_clocks(dai_data, i); ++ ++ if (dai_data->priv[i].mclk) { ++ clk_put(dai_data->priv[i].mclk); ++ dai_data->priv[i].mclk = NULL; ++ } ++ if (dai_data->priv[i].bclk) { ++ clk_put(dai_data->priv[i].bclk); ++ dai_data->priv[i].bclk = NULL; ++ } ++ } ++} ++ + static int q6dma_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, + const unsigned int *tx_ch_mask, +@@ -251,6 +292,62 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s + return 0; + } + ++static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ return q6apm_lpass_dai_startup(substream, dai); ++} ++ ++static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ ++ q6apm_lpass_dai_shutdown(substream, dai); ++ q6apm_lpass_dai_disable_clocks(dai_data, dai->id); ++} ++ ++static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct clk *sysclk = NULL; ++ bool *enabled = NULL; ++ int ret = 0; ++ ++ switch (clk_id) { ++ case LPAIF_MI2S_MCLK: ++ sysclk = dai_data->priv[dai->id].mclk; ++ enabled = &dai_data->priv[dai->id].mclk_enabled; ++ break; ++ case LPAIF_MI2S_BCLK: ++ sysclk = dai_data->priv[dai->id].bclk; ++ enabled = &dai_data->priv[dai->id].bclk_enabled; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (sysclk) { ++ ret = clk_set_rate(sysclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n", ++ freq, clk_id); ++ return ret; ++ } ++ ++ if (*enabled) ++ return 0; ++ ++ ret = clk_prepare_enable(sysclk); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); ++ return ret; ++ } ++ ++ *enabled = true; ++ } ++ ++ return ret; ++} ++ + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); +@@ -261,6 +358,53 @@ static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_mask, ++ unsigned int rx_mask, ++ int slots, int slot_width) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; ++ unsigned int cap_mask, slot_mask; ++ ++ if (slot_width != 16 && slot_width != 32) { ++ dev_err(dai->dev, "%s: invalid slot_width %d\n", __func__, slot_width); ++ return -EINVAL; ++ } ++ ++ switch (slots) { ++ case 2: ++ case 4: ++ case 8: ++ case 16: ++ cap_mask = GENMASK(slots - 1, 0); ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid slots %d\n", __func__, slots); ++ return -EINVAL; ++ } ++ ++ switch (dai->id) { ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; ++ if (slot_mask & ~cap_mask) { ++ dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", ++ __func__, slot_mask, slots); ++ return -EINVAL; ++ } ++ ++ cfg->nslots_per_frame = slots; ++ cfg->slot_width = slot_width; ++ cfg->slot_mask = slot_mask; ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid dai id 0x%x\n", __func__, dai->id); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + static const struct snd_soc_dai_ops q6dma_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, +@@ -272,11 +416,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { + + static const struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6apm_lpass_dai_prepare, +- .startup = q6apm_lpass_dai_startup, +- .shutdown = q6apm_lpass_dai_shutdown, ++ .startup = q6i2s_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, + .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, + .trigger = q6apm_lpass_dai_trigger, + }; + +@@ -289,6 +434,17 @@ static const struct snd_soc_dai_ops q6hdmi_ops = { + .trigger = q6apm_lpass_dai_trigger, + }; + ++static const struct snd_soc_dai_ops q6tdm_ops = { ++ .prepare = q6apm_lpass_dai_prepare, ++ .startup = q6apm_lpass_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, ++ .set_tdm_slot = q6tdm_set_tdm_slot, ++ .hw_params = q6dma_hw_params, ++ .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, ++ .trigger = q6apm_lpass_dai_trigger, ++}; ++ + static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .name = "q6apm-be-dai-component", + .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, +@@ -297,6 +453,65 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .remove_order = SND_SOC_COMP_ORDER_FIRST, + }; + ++static int of_q6apm_parse_dai_data(struct device *dev, ++ struct q6apm_lpass_dai_data *data) ++{ ++ int ret; ++ ++ for_each_child_of_node_scoped(dev->of_node, node) { ++ struct q6apm_dai_priv_data *priv; ++ int id; ++ ++ ret = of_property_read_u32(node, "reg", &id); ++ if (ret || id < 0 || id >= APM_PORT_MAX) { ++ dev_err(dev, "valid dai id not found:%d\n", ret); ++ continue; ++ } ++ ++ switch (id) { ++ /* MI2S specific properties */ ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ priv = &data->priv[id]; ++ priv->mclk = of_clk_get_by_name(node, "mclk"); ++ if (IS_ERR(priv->mclk)) { ++ int err = PTR_ERR(priv->mclk); ++ ++ priv->mclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s mclk\n"); ++ } ++ } ++ ++ priv->bclk = of_clk_get_by_name(node, "bclk"); ++ if (IS_ERR(priv->bclk)) { ++ int err = PTR_ERR(priv->bclk); ++ ++ priv->bclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s bclk\n"); ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return 0; ++} ++ ++static void q6apm_lpass_dai_clocks_action(void *data) ++{ ++ q6apm_lpass_dai_put_clocks(data); ++} ++ + static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + { + struct q6dsp_audio_port_dai_driver_config cfg; +@@ -304,17 +519,26 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + struct snd_soc_dai_driver *dais; + struct device *dev = &pdev->dev; + int num_dais; ++ int ret; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); ++ ret = of_q6apm_parse_dai_data(dev, dai_data); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data); ++ if (ret) ++ return ret; + + memset(&cfg, 0, sizeof(cfg)); + cfg.q6i2s_ops = &q6i2s_ops; + cfg.q6dma_ops = &q6dma_ops; + cfg.q6hdmi_ops = &q6hdmi_ops; ++ cfg.q6tdm_ops = &q6tdm_ops; + dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + + return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +@@ -12,7 +12,7 @@ + #include + #include "q6dsp-lpass-clocks.h" + +-#define Q6DSP_MAX_CLK_ID 104 ++#define Q6DSP_MAX_CLK_ID 105 + #define Q6DSP_LPASS_CLK_ROOT_DEFAULT 0 + + +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +@@ -553,11 +553,15 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("LPI RX3", LPI_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("LPI RX4", LPI_MI2S_RX_4), ++ Q6AFE_MI2S_RX_DAI("LPI RX5", LPI_MI2S_RX_5), ++ Q6AFE_MI2S_RX_DAI("LPI RX6", LPI_MI2S_RX_6), + Q6AFE_MI2S_TX_DAI("LPI TX0", LPI_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("LPI TX1", LPI_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("LPI TX2", LPI_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("LPI TX3", LPI_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), ++ Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), ++ Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), +@@ -712,6 +716,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -42,6 +42,11 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_1), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_2), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_3), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_4), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_5), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK), +@@ -59,10 +64,40 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, + "LPASS_HW_DCODEC"), ++ Q6DSP_VOTE_CLK(LPASS_HW_LPR_VOTE, Q6PRM_HW_LPR_VOTE, ++ "LPASS_HW_LPR_VOTE"), + }; + + static const struct q6dsp_clk_desc q6dsp_clk_q6prm __maybe_unused = { +diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c +--- a/sound/soc/qcom/qdsp6/q6prm.c ++++ b/sound/soc/qcom/qdsp6/q6prm.c +@@ -31,10 +31,16 @@ struct q6prm { + #define PARAM_ID_RSC_HW_CORE 0x08001032 + #define PARAM_ID_RSC_LPASS_CORE 0x0800102B + #define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C ++#define PARAM_ID_RSC_CPU_LPR 0x08001A6E ++ ++#define LPR_CPU_SS_SLEEP_DISABLE 0x1 + + struct prm_cmd_request_hw_core { + struct apm_module_param_data param_data; +- uint32_t hw_clk_id; ++ union { ++ u32 hw_clk_id; ++ u32 lpr_state; ++ }; + } __packed; + + struct prm_cmd_request_rsc { +@@ -62,6 +68,7 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + struct prm_cmd_request_hw_core *req; + gpr_device_t *gdev = prm->gdev; + uint32_t opcode, rsp_opcode; ++ bool lpr_req = (hw_block_id == Q6PRM_HW_LPR_VOTE); + + if (enable) { + opcode = PRM_CMD_REQUEST_HW_RSC; +@@ -82,10 +89,13 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + + param_data->module_instance_id = GPR_PRM_MODULE_IID; + param_data->error_code = 0; +- param_data->param_id = PARAM_ID_RSC_HW_CORE; ++ param_data->param_id = lpr_req ? PARAM_ID_RSC_CPU_LPR : PARAM_ID_RSC_HW_CORE; + param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; + +- req->hw_clk_id = hw_block_id; ++ if (lpr_req) ++ req->lpr_state = LPR_CPU_SS_SLEEP_DISABLE; ++ else ++ req->hw_clk_id = hw_block_id; + + return q6prm_send_cmd_sync(prm, pkt, rsp_opcode); + } +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -3,6 +3,9 @@ + #ifndef __Q6PRM_H__ + #define __Q6PRM_H__ + ++#define LPAIF_MI2S_MCLK 1 ++#define LPAIF_MI2S_BCLK 2 ++ + /* Clock ID for Primary I2S IBIT */ + #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 + /* Clock ID for Primary I2S EBIT */ +@@ -52,6 +55,17 @@ + /* Clock ID for QUINARY MI2S OSR CLK */ + #define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + ++/* Clock ID for MCLK1 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_1 0x300 ++/* Clock ID for MCLK2 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_2 0x301 ++/* Clock ID for MCLK3 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_3 0x302 ++/* Clock ID for MCLK4 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_4 0x303 ++/* Clock ID for MCLK5 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_5 0x304 ++ + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305 + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306 + +@@ -83,10 +97,40 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++ + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 + #define Q6PRM_HW_CORE_ID_LPASS 1 + #define Q6PRM_HW_CORE_ID_DCODEC 2 ++#define Q6PRM_HW_LPR_VOTE 3 + + int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, + int clk_root, unsigned int freq); +diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c +--- a/sound/soc/qcom/qdsp6/q6routing.c ++++ b/sound/soc/qcom/qdsp6/q6routing.c +@@ -127,7 +127,14 @@ + { mix_name, "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2"}, \ + { mix_name, "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3"}, \ + { mix_name, "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4"}, \ +- { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"} ++ { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"}, \ ++ { mix_name, "LPI_MI2S_TX_0", "LPI_MI2S_TX_0" }, \ ++ { mix_name, "LPI_MI2S_TX_1", "LPI_MI2S_TX_1" }, \ ++ { mix_name, "LPI_MI2S_TX_2", "LPI_MI2S_TX_2" }, \ ++ { mix_name, "LPI_MI2S_TX_3", "LPI_MI2S_TX_3" }, \ ++ { mix_name, "LPI_MI2S_TX_4", "LPI_MI2S_TX_4" }, \ ++ { mix_name, "LPI_MI2S_TX_5", "LPI_MI2S_TX_5" }, \ ++ { mix_name, "LPI_MI2S_TX_6", "LPI_MI2S_TX_6" } + + #define Q6ROUTING_TX_MIXERS(id) \ + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \ +@@ -320,6 +327,27 @@ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_5", TX_CODEC_DMA_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_0", LPI_MI2S_TX_0, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_1", LPI_MI2S_TX_1, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_2", LPI_MI2S_TX_2, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_3", LPI_MI2S_TX_3, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_4", LPI_MI2S_TX_4, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_5", LPI_MI2S_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_6", LPI_MI2S_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), + +@@ -709,6 +737,26 @@ static const struct snd_kcontrol_new rxcodec_dma_rx_6_mixer_controls[] = { + static const struct snd_kcontrol_new rx_codec_dma_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_7) }; + ++static const struct snd_kcontrol_new lpi_mi2s_rx_0_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_0) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_1_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_1) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_2_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_2) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_3_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_3) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_4_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_4) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_5_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_5) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_6_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_6) }; + + static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) }; +@@ -938,6 +986,27 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + usb_rx_mixer_controls, + ARRAY_SIZE(usb_rx_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_0_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_0_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_1_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_1_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_2_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_2_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_3_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_3_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_4_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_4_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_5_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_5_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_6_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, +@@ -1031,6 +1100,13 @@ static const struct snd_soc_dapm_route intercon[] = { + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_0 Audio Mixer", "LPI_MI2S_RX_0"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_1 Audio Mixer", "LPI_MI2S_RX_1"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_2 Audio Mixer", "LPI_MI2S_RX_2"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_3 Audio Mixer", "LPI_MI2S_RX_3"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_4 Audio Mixer", "LPI_MI2S_RX_4"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_5 Audio Mixer", "LPI_MI2S_RX_5"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_6 Audio Mixer", "LPI_MI2S_RX_6"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), +diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c +--- a/sound/soc/qcom/qdsp6/topology.c ++++ b/sound/soc/qcom/qdsp6/topology.c +@@ -753,6 +753,103 @@ static int audioreach_widget_i2s_module_load(struct audioreach_module *mod, + return 0; + } + ++static int audioreach_widget_audio_if_module_load(struct audioreach_module *mod, ++ const struct snd_soc_tplg_vendor_array *mod_array) ++{ ++ const struct snd_soc_tplg_vendor_value_elem *mod_elem; ++ int tkn_count = 0; ++ u32 val; ++ ++ mod_elem = mod_array->value; ++ ++ while (tkn_count < le32_to_cpu(mod_array->num_elems)) { ++ val = le32_to_cpu(mod_elem->value); ++ switch (le32_to_cpu(mod_elem->token)) { ++ case AR_TKN_U32_MODULE_HW_IF_IDX: ++ mod->hw_interface_idx = val; ++ break; ++ case AR_TKN_U32_MODULE_FMT_DATA: ++ mod->data_format = val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_SRC: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_src = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_data_out_enable = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_SLOT_MASK: ++ mod->slot_mask = val; ++ break; ++ case AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->nslots_per_frame = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SLOT_WIDTH: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->slot_width = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_INTF_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->intf_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_invert_sync_pulse = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_sync_data_delay = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_QAIF_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->qaif_type = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: ++ mod->active_lane_mask = val; ++ break; ++ case AR_TKN_U32_MODULE_FRAME_SYNC_RATE: ++ mod->frame_sync_rate = val; ++ break; ++ case AR_TKN_U16_MODULE_BIT_CLK_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->bit_clk_type = (u16)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_INT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_int_bit_clk = (u8)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_ext_bit_clk = (u8)val; ++ break; ++ default: ++ break; ++ } ++ tkn_count++; ++ mod_elem++; ++ } ++ ++ return 0; ++} ++ + static int audioreach_widget_dp_module_load(struct audioreach_module *mod, + const struct snd_soc_tplg_vendor_array *mod_array) + { +@@ -806,6 +903,12 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, + case MODULE_ID_I2S_SOURCE: + audioreach_widget_i2s_module_load(mod, mod_array); + break; ++ case MODULE_ID_AUDIO_IF_SINK: ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ ret = audioreach_widget_audio_if_module_load(mod, mod_array); ++ if (ret) ++ return ret; ++ break; + case MODULE_ID_DISPLAY_PORT_SINK: + audioreach_widget_dp_module_load(mod, mod_array); + break; +@@ -1313,7 +1416,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + { + struct snd_soc_card *card = component->card; + struct device *dev = component->dev; +- const struct firmware *fw; + int ret; + + /* Inline with Qualcomm UCM configs and linux-firmware path */ +@@ -1323,6 +1425,7 @@ int audioreach_tplg_init(struct snd_soc_component *component) + if (!tplg_fw_name) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_fw_name, dev); + if (ret < 0) { + dev_err(dev, "tplg firmware loading %s failed %d\n", tplg_fw_name, ret); +@@ -1335,8 +1438,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + dev_err(dev, "tplg component load failed: %d\n", ret); + } + +- release_firmware(fw); +- + return ret; + } + EXPORT_SYMBOL_GPL(audioreach_tplg_init); +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -12,17 +12,157 @@ + #include + #include + #include "qdsp6/q6afe.h" ++#include "qdsp6/q6apm.h" ++#include "qdsp6/q6prm.h" ++#include "qdsp6/q6dsp-common.h" + #include "common.h" + #include "sdw.h" + ++#define I2S_MCLKFS 256 ++ ++#define I2S_MCLK_RATE(rate) \ ++ ((rate) * (I2S_MCLKFS)) ++#define I2S_BIT_RATE(rate, channels, format) \ ++ ((rate) * (channels) * (format)) ++ ++static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_SPK("DP0 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP1 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP2 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP3 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP4 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP5 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP6 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP7 Jack", NULL), ++}; ++ ++static const struct snd_kcontrol_new max98090_controls[] = { ++ SOC_DAPM_PIN_SWITCH("Headset Mic12"), ++ SOC_DAPM_PIN_SWITCH("Headphone"), ++ SOC_DAPM_PIN_SWITCH("Headset Mic56"), ++ SOC_DAPM_PIN_SWITCH("Speaker"), ++ SOC_DAPM_PIN_SWITCH("Receiver"), ++ SOC_DAPM_PIN_SWITCH("Int Mic"), ++}; ++ ++static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_HP("Headphone", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic12", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic56", NULL), ++ SND_SOC_DAPM_MIC("Int Mic", NULL), ++ SND_SOC_DAPM_SPK("Receiver", NULL), ++ SND_SOC_DAPM_SPK("Speaker", NULL), ++}; ++ ++struct qcom_snd_soc_common { ++ const char *driver_name; ++ const struct snd_soc_dapm_widget *dapm_widgets; ++ int num_dapm_widgets; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ const struct snd_kcontrol_new *controls; ++ int num_controls; ++ unsigned int codec_dai_fmt; ++ bool codec_sysclk_set; ++ bool mi2s_mclk_enable; ++ bool mi2s_bclk_enable; ++ bool wcd_jack; ++ int (*snd_prepare)(struct snd_pcm_substream *substream); ++}; ++ + struct sc8280xp_snd_data { + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + struct snd_soc_jack jack; + struct snd_soc_jack dp_jack[8]; ++ const struct qcom_snd_soc_common *priv; + bool jack_setup; + }; + ++static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) ++{ ++ int rate = params_rate(params); ++ ++ switch (rate) { ++ case 11025: ++ case 44100: ++ case 88200: ++ return I2S_MCLK_RATE(44100); ++ default: ++ break; ++ } ++ ++ return I2S_MCLK_RATE(rate); ++} ++ ++static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params) ++{ ++ return I2S_BIT_RATE(params_rate(params), ++ params_channels(params), ++ snd_pcm_format_width(params_format(params))); ++} ++ ++static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ unsigned int bclk_freq; ++ int ret; ++ int i; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ if (!cpu_cfg.slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret) ++ return ret; ++ ++ ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret; ++ ++ bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); ++ if (!bclk_freq) ++ return -EINVAL; ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", ++ __func__, ret); ++ return ret; ++ } ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", ++ __func__, codec_dai->name, ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ + static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + { + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); +@@ -32,10 +172,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + int dp_pcm_id = 0; + + switch (cpu_dai->id) { +- case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX: +- case QUINARY_MI2S_RX...QUINARY_MI2S_TX: +- snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- break; + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + /* +@@ -64,7 +200,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + if (dp_jack) + return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); + +- return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ if (data->priv->wcd_jack) ++ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ ++ return 0; + } + + static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, +@@ -96,12 +235,118 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + return 0; + } + ++static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ int mclk_freq = sc8280xp_get_mclk_freq(params); ++ int bclk_freq = sc8280xp_get_bclk_freq(params); ++ int ret; ++ ++ switch (cpu_dai->id) { ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ ++ if (data->priv->codec_dai_fmt) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_mclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_MCLK, mclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, ++ 0, mclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ break; ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ return sc8280xp_tdm_hw_params(substream, params); ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * WSA and WSA2 are handled as a single interface with the ++ * following channels mask: ++ * __________________________________________________ ++ * | Bits | 3 | 2 | 1 | 0 | ++ * --------------------------------------------------- ++ * | Line | WSA2 Ch2 | WSA2 Ch1 | WSA Ch2 | WSA Ch1 | ++ * --------------------------------------------------- ++ * ++ * The Ayaneo Pocket S2 speakers are connected only to ++ * the WSA2 interface and the WSA interface is not enabled. ++ * ++ * Set the channel mapping on the WSA2 channels only. ++ */ ++static const unsigned int ayaneo_ps2_channels_mapping[] = { ++ 0, /* WSA Ch1 */ ++ 0, /* WSA Ch2 */ ++ PCM_CHANNEL_FL, /* WSA2 Ch1 */ ++ PCM_CHANNEL_FR /* WSA2 Ch2 */ ++}; ++ ++static int ayaneo_ps2_snd_prepare(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ unsigned int channels = substream->runtime->channels; ++ ++ if (cpu_dai->id != WSA_CODEC_DMA_RX_0) ++ return 0; ++ ++ if (channels != 2) ++ return -EINVAL; ++ ++ return snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, ++ ARRAY_SIZE(ayaneo_ps2_channels_mapping), ++ ayaneo_ps2_channels_mapping); ++} ++ + static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + ++ if (data->priv->snd_prepare) { ++ int ret; ++ ++ ret = data->priv->snd_prepare(substream); ++ if (ret) ++ return ret; ++ } ++ + return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]); + } + +@@ -117,6 +362,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) + static const struct snd_soc_ops sc8280xp_be_ops = { + .startup = qcom_snd_sdw_startup, + .shutdown = qcom_snd_sdw_shutdown, ++ .hw_params = sc8280xp_snd_hw_params, + .hw_free = sc8280xp_snd_hw_free, + .prepare = sc8280xp_snd_prepare, + }; +@@ -145,38 +391,182 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; +- card->owner = THIS_MODULE; ++ + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + ++ data->priv = of_device_get_match_data(dev); ++ if (!data->priv) ++ return -ENODEV; ++ ++ card->owner = THIS_MODULE; + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); ++ card->dapm_widgets = data->priv->dapm_widgets; ++ card->num_dapm_widgets = data->priv->num_dapm_widgets; ++ card->dapm_routes = data->priv->dapm_routes; ++ card->num_dapm_routes = data->priv->num_dapm_routes; ++ card->controls = data->priv->controls; ++ card->num_controls = data->priv->num_controls; ++ + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + +- card->driver_name = of_device_get_match_data(dev); ++ card->driver_name = data->priv->driver_name; + sc8280xp_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); + } + ++static struct qcom_snd_soc_common ayaneo_ps2_priv_data = { ++ .driver_name = "ayaneo-ps2", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .snd_prepare = ayaneo_ps2_snd_prepare, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common eliza_priv_data = { ++ .driver_name = "eliza", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common hawi_priv_data = { ++ .driver_name = "hawi", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++ .mi2s_bclk_enable = true, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common kaanapali_priv_data = { ++ .driver_name = "kaanapali", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs9100_priv_data = { ++ .driver_name = "sa8775p", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++}; ++ ++static const struct qcom_snd_soc_common qcs615_priv_data = { ++ .driver_name = "qcs615", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++}; ++ ++static const struct qcom_snd_soc_common qcm6490_priv_data = { ++ .driver_name = "qcm6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs6490_priv_data = { ++ .driver_name = "qcs6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs8275_priv_data = { ++ .driver_name = "qcs8300", ++ .dapm_widgets = max98090_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(max98090_dapm_widgets), ++ .controls = max98090_controls, ++ .num_controls = ARRAY_SIZE(max98090_controls), ++ .codec_sysclk_set = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_BC_FC, ++}; ++ ++static const struct qcom_snd_soc_common sc8280xp_priv_data = { ++ .driver_name = "sc8280xp", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8450_priv_data = { ++ .driver_name = "sm8450", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8475_priv_data = { ++ .driver_name = "sm8475", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8550_priv_data = { ++ .driver_name = "sm8550", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8650_priv_data = { ++ .driver_name = "sm8650", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8750_priv_data = { ++ .driver_name = "sm8750", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ + static const struct of_device_id snd_sc8280xp_dt_match[] = { +- {.compatible = "qcom,eliza-sndcard", "eliza"}, +- {.compatible = "qcom,kaanapali-sndcard", "kaanapali"}, +- {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, +- {.compatible = "qcom,qcs615-sndcard", "qcs615"}, +- {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, +- {.compatible = "qcom,qcs8275-sndcard", "qcs8300"}, +- {.compatible = "qcom,qcs9075-sndcard", "sa8775p"}, +- {.compatible = "qcom,qcs9100-sndcard", "sa8775p"}, +- {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"}, +- {.compatible = "qcom,sm8450-sndcard", "sm8450"}, +- {.compatible = "qcom,sm8550-sndcard", "sm8550"}, +- {.compatible = "qcom,sm8650-sndcard", "sm8650"}, +- {.compatible = "qcom,sm8750-sndcard", "sm8750"}, ++ { .compatible = "ayaneo,pocket-s2-sndcard", .data = &ayaneo_ps2_priv_data }, ++ { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, ++ { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, ++ { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, ++ { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, ++ { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, ++ { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, ++ { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data }, ++ { .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data }, ++ { .compatible = "qcom,sm8475-sndcard", .data = &sm8475_priv_data }, ++ { .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data }, ++ { .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data }, ++ { .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data }, + {} + }; + +diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c +--- a/sound/soc/qcom/sdm845.c ++++ b/sound/soc/qcom/sdm845.c +@@ -36,7 +36,6 @@ struct sdm845_snd_data { + bool jack_setup; + bool slim_port_setup; + bool stream_prepared[AFE_PORT_MAX]; +- struct snd_soc_card *card; + uint32_t pri_mi2s_clk_count; + uint32_t sec_mi2s_clk_count; + uint32_t quat_tdm_clk_count; +@@ -564,7 +563,6 @@ static int sdm845_snd_platform_probe(struct platform_device *pdev) + if (ret) + return ret; + +- data->card = card; + snd_soc_card_set_drvdata(card, data); + + sdm845_add_ops(card); +diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c +--- a/sound/soc/qcom/sm8250.c ++++ b/sound/soc/qcom/sm8250.c +@@ -112,6 +112,22 @@ static int sm8250_snd_startup(struct snd_pcm_substream *substream) + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; ++ case LPI_MI2S_RX_0: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; ++ case LPI_MI2S_TX_3: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; + default: + break; + } +@@ -194,6 +210,7 @@ static const struct of_device_id snd_sm8250_dt_match[] = { + { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" }, + { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" }, + { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" }, ++ { .compatible = "qcom,sdm660-sndcard", .data = "sdm660" }, + { .compatible = "qcom,sm8250-sndcard", .data = "sm8250" }, + {} + }; +diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c +--- a/sound/soc/qcom/storm.c ++++ b/sound/soc/qcom/storm.c +@@ -64,21 +64,21 @@ static struct snd_soc_dai_link storm_dai_link = { + SND_SOC_DAILINK_REG(hifi), + }; + +-static int storm_parse_of(struct snd_soc_card *card) ++static int storm_parse_of(struct device *dev) + { +- struct snd_soc_dai_link *dai_link = card->dai_link; +- struct device_node *np = card->dev->of_node; ++ struct snd_soc_dai_link *dai_link = &storm_dai_link; ++ struct device_node *np = dev->of_node; + + dai_link->cpus->of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpus->of_node) { +- dev_err(card->dev, "error getting cpu phandle\n"); ++ dev_err(dev, "error getting cpu phandle\n"); + return -EINVAL; + } + dai_link->platforms->of_node = dai_link->cpus->of_node; + + dai_link->codecs->of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codecs->of_node) { +- dev_err(card->dev, "error getting codec phandle\n"); ++ dev_err(dev, "error getting codec phandle\n"); + return -EINVAL; + } + +@@ -106,7 +106,7 @@ static int storm_platform_probe(struct platform_device *pdev) + card->dai_link = &storm_dai_link; + card->num_links = 1; + +- ret = storm_parse_of(card); ++ ret = storm_parse_of(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "error resolving dai links: %d\n", ret); + return ret; +diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c +--- a/sound/soc/renesas/fsi.c ++++ b/sound/soc/renesas/fsi.c +@@ -1979,8 +1979,8 @@ static const struct of_device_id fsi_of_match[] = { + MODULE_DEVICE_TABLE(of, fsi_of_match); + + static const struct platform_device_id fsi_id_table[] = { +- { "sh_fsi", (kernel_ulong_t)&fsi1_core }, +- {}, ++ /* an array with no valid entry prevents matching on driver name */ ++ { } + }; + MODULE_DEVICE_TABLE(platform, fsi_id_table); + +@@ -1992,7 +1992,7 @@ static int fsi_probe(struct platform_device *pdev) + const struct fsi_core *core; + struct fsi_priv *fsi; + struct resource *res; +- unsigned int irq; ++ int irq; + int ret; + + memset(&info, 0, sizeof(info)); +@@ -2007,12 +2007,15 @@ static int fsi_probe(struct platform_device *pdev) + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- irq = platform_get_irq(pdev, 0); +- if (!res || (int)irq <= 0) { ++ if (!res) { + dev_err(&pdev->dev, "Not enough FSI platform resources.\n"); + return -ENODEV; + } + ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; +diff --git a/sound/soc/renesas/rcar/adg.c b/sound/soc/renesas/rcar/adg.c +--- a/sound/soc/renesas/rcar/adg.c ++++ b/sound/soc/renesas/rcar/adg.c +@@ -565,7 +565,7 @@ static struct clk *rsnd_adg_create_null_clk(struct rsnd_priv *priv, + struct clk *clk; + + clk = clk_register_fixed_rate(dev, name, parent, 0, 0); +- if (IS_ERR_OR_NULL(clk)) { ++ if (IS_ERR(clk)) { + dev_err(dev, "create null clk error\n"); + return ERR_CAST(clk); + } +@@ -618,7 +618,7 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + * No "adg" is not error + */ + clk = devm_clk_get(dev, "adg"); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); + adg->adg = clk; + +@@ -626,9 +626,9 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + for (i = 0; i < clkin_size; i++) { + clk = devm_clk_get(dev, clkin_name[i]); + +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + goto err; + + adg->clkin[i] = clk; +diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c +--- a/sound/soc/renesas/siu_dai.c ++++ b/sound/soc/renesas/siu_dai.c +@@ -715,7 +715,6 @@ static struct snd_soc_dai_driver siu_i2s_dai = { + + static int siu_probe(struct platform_device *pdev) + { +- const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; +@@ -726,6 +725,7 @@ static int siu_probe(struct platform_device *pdev) + siu_i2s_data = info; + info->dev = &pdev->dev; + ++ const struct firmware *fw_entry __free(firmware) = NULL; + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + return ret; +@@ -736,8 +736,6 @@ static int siu_probe(struct platform_device *pdev) + */ + memcpy(&info->fw, fw_entry->data, fw_entry->size); + +- release_firmware(fw_entry); +- + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; +diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c +--- a/sound/soc/rockchip/rk3288_hdmi_analog.c ++++ b/sound/soc/rockchip/rk3288_hdmi_analog.c +@@ -185,10 +185,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en"); + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(card->dev, "SoC parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + rk_dailink.codecs[0].of_node = of_parse_phandle(np, + "rockchip,audio-codec", +@@ -207,10 +205,9 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + } + + ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); +- if (ret) { +- dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Unable to get codec_dai_name\n"); + + rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", + 0); +@@ -223,11 +220,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); +- if (ret) { +- dev_err(&pdev->dev, +- "Unable to parse 'rockchip,routing' property\n"); ++ if (ret) + return ret; +- } + + snd_soc_card_set_drvdata(card, machine); + +diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c +--- a/sound/soc/rockchip/rockchip_i2s.c ++++ b/sound/soc/rockchip/rockchip_i2s.c +@@ -767,16 +767,14 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + /* try to prepare related clocks */ + i2s->hclk = devm_clk_get_enabled(&pdev->dev, "i2s_hclk"); +- if (IS_ERR(i2s->hclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); +- return PTR_ERR(i2s->hclk); +- } ++ if (IS_ERR(i2s->hclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->hclk), ++ "Can't retrieve i2s bus clock\n"); + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); +- if (IS_ERR(i2s->mclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); +- return PTR_ERR(i2s->mclk); +- } ++ if (IS_ERR(i2s->mclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mclk), ++ "Can't retrieve i2s master clock\n"); + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) +@@ -792,7 +790,11 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + i2s->bclk_ratio = 64; + i2s->pinctrl = devm_pinctrl_get(&pdev->dev); +- if (!IS_ERR(i2s->pinctrl)) { ++ if (IS_ERR(i2s->pinctrl)) { ++ if (PTR_ERR(i2s->pinctrl) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); ++ } else { + i2s->bclk_on = pinctrl_lookup_state(i2s->pinctrl, "bclk_on"); + if (!IS_ERR_OR_NULL(i2s->bclk_on)) { + i2s->bclk_off = pinctrl_lookup_state(i2s->pinctrl, "bclk_off"); +@@ -801,8 +803,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + } +- } else { +- dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); + } + + i2s_pinctrl_select_bclk_off(i2s); +@@ -831,16 +831,12 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + &rockchip_i2s_component, + dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c +--- a/sound/soc/rockchip/rockchip_i2s_tdm.c ++++ b/sound/soc/rockchip/rockchip_i2s_tdm.c +@@ -1263,16 +1263,14 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "tx-m"); + if (IS_ERR(i2s_tdm->tx_reset)) { +- ret = PTR_ERR(i2s_tdm->tx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->tx_reset), + "Error in tx-m reset control\n"); + } + + i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "rx-m"); + if (IS_ERR(i2s_tdm->rx_reset)) { +- ret = PTR_ERR(i2s_tdm->rx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->rx_reset), + "Error in rx-m reset control\n"); + } + +@@ -1363,17 +1361,12 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_tdm_component, + i2s_tdm->dai, 1); +- +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + goto err_suspend; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c +--- a/sound/soc/rockchip/rockchip_max98090.c ++++ b/sound/soc/rockchip/rockchip_max98090.c +@@ -428,11 +428,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + + /* Parse card name. */ + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + /* register the soc card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); +diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c +--- a/sound/soc/rockchip/rockchip_pdm.c ++++ b/sound/soc/rockchip/rockchip_pdm.c +@@ -630,10 +630,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + &rockchip_pdm_component, + &rockchip_pdm_dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "could not register dai: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + rockchip_pdm_rxctrl(pdm, 0); + +@@ -642,10 +640,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register pcm: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_rt5645.c b/sound/soc/rockchip/rockchip_rt5645.c +--- a/sound/soc/rockchip/rockchip_rt5645.c ++++ b/sound/soc/rockchip/rockchip_rt5645.c +@@ -191,11 +191,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + goto put_cpu_of_node; +- } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c +--- a/sound/soc/rockchip/rockchip_sai.c ++++ b/sound/soc/rockchip/rockchip_sai.c +@@ -1428,12 +1428,13 @@ static int rockchip_sai_probe(struct platform_device *pdev) + "Failed to initialize regmap\n"); + + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, rockchip_sai_isr, + IRQF_SHARED, node->name, sai); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "Failed to request irq %d\n", irq); ++ return ret; + } else { + dev_dbg(&pdev->dev, "Asked for an IRQ but got %d\n", irq); + } +@@ -1456,7 +1457,7 @@ static int rockchip_sai_probe(struct platform_device *pdev) + + ret = rockchip_sai_parse_paths(sai, node); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Failed to parse paths\n"); ++ return ret; + + /* + * From here on, all register accesses need to be wrapped in +@@ -1471,18 +1472,14 @@ static int rockchip_sai_probe(struct platform_device *pdev) + return dev_err_probe(&pdev->dev, ret, "Failed to resume device\n"); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register PCM: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_sai_component, + dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register component: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_put(&pdev->dev); +diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c +--- a/sound/soc/rockchip/rockchip_spdif.c ++++ b/sound/soc/rockchip/rockchip_spdif.c +@@ -396,13 +396,13 @@ static int rk_spdif_probe(struct platform_device *pdev) + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register PCM\n"); ++ return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &rk_spdif_component, + &rk_spdif_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register DAI\n"); ++ return ret; + + return 0; + } +diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c +--- a/sound/soc/samsung/i2s.c ++++ b/sound/soc/samsung/i2s.c +@@ -1116,15 +1116,31 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { + + static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { + { "Playback Mixer", NULL, "Primary Playback" }, +- { "Playback Mixer", NULL, "Secondary Playback" }, +- + { "Mixer DAI TX", NULL, "Playback Mixer" }, + { "Primary Capture", NULL, "Mixer DAI RX" }, + }; + ++static const struct snd_soc_dapm_route samsung_i2s_dapm_routes_sec_play[] = { ++ { "Playback Mixer", NULL, "Secondary Playback" }, ++}; ++ ++static int samsung_i2s_component_probe(struct snd_soc_component *component) ++{ ++ struct samsung_i2s_priv *priv = snd_soc_component_get_drvdata(component); ++ ++ if (priv->quirks & QUIRK_SEC_DAI) ++ snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component), ++ samsung_i2s_dapm_routes_sec_play, ++ ARRAY_SIZE(samsung_i2s_dapm_routes_sec_play)); ++ ++ return 0; ++} ++ + static const struct snd_soc_component_driver samsung_i2s_component = { + .name = "samsung-i2s", + ++ .probe = samsung_i2s_component_probe, ++ + .dapm_widgets = samsung_i2s_widgets, + .num_dapm_widgets = ARRAY_SIZE(samsung_i2s_widgets), + +@@ -1441,10 +1457,10 @@ static int samsung_i2s_probe(struct platform_device *pdev) + regs_base = res->start; + + priv->clk = devm_clk_get(&pdev->dev, "iis"); +- if (IS_ERR(priv->clk)) { +- dev_err(&pdev->dev, "Failed to get iis clock\n"); +- return PTR_ERR(priv->clk); +- } ++ if (IS_ERR(priv->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk), ++ "Failed to get iis clock\n"); ++ + + ret = clk_prepare_enable(priv->clk); + if (ret != 0) { +@@ -1650,8 +1666,7 @@ static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = { + }; + + static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = { +- .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | +- QUIRK_SUPPORTS_TDM, ++ .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, + .pcm_rates = SNDRV_PCM_RATE_8000_192000, + .i2s_variant_regs = &i2sv7_regs, + }; +@@ -1675,7 +1690,7 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { + .name = "samsung-i2s", + .driver_data = (kernel_ulong_t)&i2sv3_dai_type, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids); + +diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c +--- a/sound/soc/samsung/smdk_spdif.c ++++ b/sound/soc/samsung/smdk_spdif.c +@@ -130,12 +130,8 @@ static int smdk_hw_params(struct snd_pcm_substream *substream, + return ret; + + /* Set S/PDIF uses internal source clock */ +- ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, +- rclk_rate, SND_SOC_CLOCK_IN); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, ++ rclk_rate, SND_SOC_CLOCK_IN); + } + + static const struct snd_soc_ops smdk_spdif_ops = { +diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c +--- a/sound/soc/samsung/spdif.c ++++ b/sound/soc/samsung/spdif.c +@@ -380,8 +380,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->pclk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif->pclk)) { +- dev_err(&pdev->dev, "failed to get peri-clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->pclk), ++ "failed to get peri-clock\n"); + goto err0; + } + ret = clk_prepare_enable(spdif->pclk); +@@ -390,8 +390,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif"); + if (IS_ERR(spdif->sclk)) { +- dev_err(&pdev->dev, "failed to get internal source clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->sclk), ++ "failed to get internal source clock\n"); + goto err1; + } + ret = clk_prepare_enable(spdif->sclk); +diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c +--- a/sound/soc/sdca/sdca_asoc.c ++++ b/sound/soc/sdca/sdca_asoc.c +@@ -364,7 +364,6 @@ static int entity_parse_ot(struct device *dev, + + /** + * sdca_asoc_pde_poll_actual_ps - Verify PDE power state reached target state +- * @dev: Pointer to the device for error logging. + * @regmap: Register map for reading ACTUAL_PS register. + * @function_id: SDCA function identifier. + * @entity_id: SDCA entity identifier for the power domain. +@@ -389,11 +388,11 @@ static int entity_parse_ot(struct device *dev, + * polling times out before reaching the target state, or a negative error code if + * a register read fails. + */ +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays) ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays) + { + static const int polls = 100; + static const int default_poll_us = 1000; +@@ -451,14 +450,14 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget, + return 0; + } + +- ret = sdca_asoc_pde_poll_actual_ps(component->dev, component->regmap, ++ ret = sdca_asoc_pde_poll_actual_ps(component->regmap, + SDW_SDCA_CTL_FUNC(widget->reg), + SDW_SDCA_CTL_ENT(widget->reg), + from, to, + entity->pde.max_delay, + entity->pde.num_max_delay); + if (ret) +- dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ dev_err(component->dev, "%s: pde transition %x -> %x failed: %d\n", + entity->label, from, to, ret); + + return ret; +@@ -1231,7 +1230,7 @@ static u64 width_find_mask(unsigned int bits) + } + } + +-static int populate_rate_format(struct device *dev, ++int sdca_asoc_populate_rate_format(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_pcm_stream *stream) +@@ -1292,6 +1291,7 @@ static int populate_rate_format(struct device *dev, + + return 0; + } ++EXPORT_SYMBOL_NS(sdca_asoc_populate_rate_format, "SND_SOC_SDCA"); + + /** + * sdca_asoc_populate_dais - fill in an array of DAI drivers for a Function +@@ -1344,7 +1344,7 @@ int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *funct + stream->channels_min = 1; + stream->channels_max = SDCA_MAX_CHANNEL_COUNT; + +- ret = populate_rate_format(dev, function, entity, stream); ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, stream); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -329,7 +329,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, + drv->core = core; + drv->function = &sdev->function; + +- ret = sdca_parse_function(dev, core->sdw, drv->function); ++ ret = sdca_parse_function(dev, drv->function); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c +--- a/sound/soc/sdca/sdca_fdl.c ++++ b/sound/soc/sdca/sdca_fdl.c +@@ -195,7 +195,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + { + struct device *dev = interrupt->dev; + struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; +- const struct firmware *firmware = NULL; + struct acpi_sw_file *swf = NULL, *tmp; + struct sdca_fdl_file *fdl_file; + char *disk_filename; +@@ -230,6 +229,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + ++ const struct firmware *firmware __free(firmware) = NULL; + ret = firmware_request_nowarn(&firmware, disk_filename, dev); + kfree(disk_filename); + if (ret) { +@@ -258,8 +258,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + if (!swf) { + dev_err(dev, "failed to locate SWF\n"); +- ret = -ENOENT; +- goto error; ++ return -ENOENT; + } + + dev_info(dev, "loading SWF: %x-%x-%x\n", +@@ -271,9 +270,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, + SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, + swf->file_length - offsetof(struct acpi_sw_file, data)); +- +-error: +- release_firmware(firmware); + return ret; + } + +diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c +--- a/sound/soc/sdca/sdca_function_device.c ++++ b/sound/soc/sdca/sdca_function_device.c +@@ -32,7 +32,8 @@ static void sdca_dev_release(struct device *dev) + + /* alloc, init and add link devices */ + static struct sdca_dev *sdca_dev_register(struct device *parent, +- struct sdca_function_desc *function_desc) ++ struct sdca_function_desc *function_desc, ++ struct acpi_table_swft *swft) + { + struct sdca_dev *sdev; + struct auxiliary_device *auxdev; +@@ -50,6 +51,7 @@ static struct sdca_dev *sdca_dev_register(struct device *parent, + auxdev->dev.release = sdca_dev_release; + + sdev->function.desc = function_desc; ++ sdev->function.fdl_data.swft = swft; + + rc = ida_alloc(&sdca_function_ida, GFP_KERNEL); + if (rc < 0) { +@@ -99,7 +101,8 @@ int sdca_dev_register_functions(struct sdw_slave *slave) + struct sdca_dev *func_dev; + + func_dev = sdca_dev_register(&slave->dev, +- &sdca_data->function[i]); ++ &sdca_data->function[i], ++ sdca_data->swft); + if (IS_ERR(func_dev)) { + ret = PTR_ERR(func_dev); + /* +diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c +--- a/sound/soc/sdca/sdca_functions.c ++++ b/sound/soc/sdca/sdca_functions.c +@@ -7,6 +7,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -18,7 +19,6 @@ + #include + #include + #include +-#include + + /* + * Should be long enough to encompass all the MIPI DisCo properties. +@@ -1365,90 +1365,76 @@ static int find_sdca_entity_ge(struct device *dev, + return -EINVAL; + } + +-static int +-find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, +- struct fwnode_handle *entity_node, struct sdca_entity *entity) ++static int find_sdca_entity_hide(struct device *dev, ++ struct fwnode_handle *entity_node, ++ struct sdca_entity *entity) + { + struct sdca_entity_hide *hide = &entity->hide; +- unsigned int delay, *af_list = hide->af_number_list; +- int nval, ret; +- unsigned char *report_desc = NULL; ++ int num_reports, ret; ++ unsigned int delay; + + ret = fwnode_property_read_u32(entity_node, +- "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay); ++ "mipi-sdca-RxUMP-ownership-transition-max-delay", ++ &delay); + if (!ret) + hide->max_delay = delay; + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDTx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidtx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid tx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidtx_ids = num_reports; + hide->hidtx_ids = devm_kcalloc(dev, hide->num_hidtx_ids, + sizeof(*hide->hidtx_ids), GFP_KERNEL); + if (!hide->hidtx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDTx-supported-report-ids", +- hide->hidtx_ids, +- hide->num_hidtx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids", ++ hide->hidtx_ids, hide->num_hidtx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDRx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidrx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid rx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidrx_ids = num_reports; + hide->hidrx_ids = devm_kcalloc(dev, hide->num_hidrx_ids, + sizeof(*hide->hidrx_ids), GFP_KERNEL); + if (!hide->hidrx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDRx-supported-report-ids", +- hide->hidrx_ids, +- hide->num_hidrx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids", ++ hide->hidrx_ids, hide->num_hidrx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-hide-related-audio-function-list"); +- if (nval <= 0) { ++ /* ++ * FIXME: This should probably link to the actual sdca_function_data pointer, ++ * but updating to do so should probably wait until we have a user. ++ */ ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-hide-related-audio-function-list"); ++ if (num_reports <= 0) { + dev_err(dev, "%pfwP: audio function numbers list missing: %d\n", +- entity_node, nval); ++ entity_node, num_reports); + return -EINVAL; +- } else if (nval > SDCA_MAX_FUNCTION_COUNT) { +- dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", entity_node); ++ } else if (num_reports > ARRAY_SIZE(hide->af_number_list)) { ++ dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", ++ entity_node); + return -EINVAL; + } + +- hide->hide_reside_function_num = nval; ++ hide->hide_reside_function_num = num_reports; + fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-hide-related-audio-function-list", af_list, nval); +- +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); +- if (nval) +- fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", +- (u8 *)&hide->hid_desc, nval); +- +- if (hide->hid_desc.bNumDescriptors) { +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); +- if (nval) { +- report_desc = devm_kzalloc(dev, nval, GFP_KERNEL); +- if (!report_desc) +- return -ENOMEM; +- hide->hid_report_desc = report_desc; +- fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", +- report_desc, nval); +- +- /* add HID device */ +- ret = sdca_add_hid_device(dev, sdw, entity); +- if (ret) { +- dev_err(dev, "%pfwP: failed to add HID device: %d\n", entity_node, ret); +- return ret; +- } +- } +- } ++ "mipi-sdca-hide-related-audio-function-list", ++ hide->af_number_list, num_reports); + + return 0; + } +@@ -1475,8 +1461,7 @@ static int find_sdca_entity_xu(struct device *dev, + return 0; + } + +-static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function, ++static int find_sdca_entity(struct device *dev, struct sdca_function_data *function, + struct fwnode_handle *function_node, + struct fwnode_handle *entity_node, + struct sdca_entity *entity) +@@ -1528,8 +1513,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + ret = find_sdca_entity_ge(dev, entity_node, entity); + break; + case SDCA_ENTITY_TYPE_HIDE: +- ret = find_sdca_entity_hide(dev, sdw, function_node, +- entity_node, entity); ++ ret = find_sdca_entity_hide(dev, entity_node, entity); + break; + default: + break; +@@ -1544,8 +1528,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_entities(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + struct sdca_entity *entities; +@@ -1596,7 +1579,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return -EINVAL; + } + +- ret = find_sdca_entity(dev, sdw, function, function_node, ++ ret = find_sdca_entity(dev, function, function_node, + entity_node, &entities[i]); + fwnode_handle_put(entity_node); + if (ret) +@@ -1619,7 +1602,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data *function, ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, + const char *entity_label) + { + struct sdca_entity *entity = NULL; +@@ -1648,6 +1631,7 @@ static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data * + + return NULL; + } ++EXPORT_SYMBOL_NS(sdca_find_entity_by_label, "SND_SOC_SDCA"); + + static struct sdca_entity *find_sdca_entity_by_id(struct sdca_function_data *function, + const int id) +@@ -1688,7 +1672,7 @@ static int find_sdca_entity_connection_iot(struct device *dev, + return ret; + } + +- clock_entity = find_sdca_entity_by_label(function, clock_label); ++ clock_entity = sdca_find_entity_by_label(function, clock_label); + if (!clock_entity) { + dev_err(dev, "%s: failed to find clock with label %s\n", + entity->label, clock_label); +@@ -1873,7 +1857,7 @@ static int find_sdca_entity_connection(struct device *dev, + return ret; + } + +- connected_entity = find_sdca_entity_by_label(function, connected_label); ++ connected_entity = sdca_find_entity_by_label(function, connected_label); + if (!connected_entity) { + dev_err(dev, "%s: failed to find entity with label %s\n", + entity->label, connected_label); +@@ -2088,8 +2072,7 @@ static int find_sdca_clusters(struct device *dev, + return 0; + } + +-static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_filesets(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + static const int mult_fileset = 3; +@@ -2171,23 +2154,65 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, + set->files = files; + } + +- function->fdl_data.swft = sdw->sdca_data.swft; + function->fdl_data.num_sets = num_sets; + function->fdl_data.sets = sets; + + return 0; + } + ++static int find_sdca_hid(struct device *dev, struct fwnode_handle *function_node, ++ struct sdca_function_data *function) ++{ ++ int num_desc; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); ++ if (!num_desc) { ++ return 0; ++ } else if (num_desc < 0) { ++ dev_err(dev, "%pfwP: failed to read hid descriptor: %d\n", ++ function_node, num_desc); ++ return num_desc; ++ } else if (num_desc > sizeof(function->hid.desc)) { ++ dev_err(dev, "%pfwP: hid descriptor too large: %d\n", ++ function_node, num_desc); ++ return -EINVAL; ++ } ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", ++ (u8 *)&function->hid.desc, num_desc); ++ ++ if (!function->hid.desc.bNumDescriptors) ++ return 0; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); ++ if (num_desc <= 0) { ++ dev_err(dev, "%pfwP: failed to read report descriptor: %d\n", ++ function_node, num_desc); ++ ++ if (!num_desc) ++ return -EINVAL; ++ ++ return num_desc; ++ } ++ ++ function->hid.report_desc = devm_kzalloc(dev, num_desc, GFP_KERNEL); ++ if (!function->hid.report_desc) ++ return -ENOMEM; ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", ++ function->hid.report_desc, num_desc); ++ ++ return 0; ++} ++ + /** + * sdca_parse_function - parse ACPI DisCo for a Function + * @dev: Pointer to device against which function data will be allocated. +- * @sdw: SoundWire slave device to be processed. + * @function: Pointer to the Function information, to be populated. + * + * Return: Returns 0 for success. + */ +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function) ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function) + { + struct fwnode_handle *node = function->desc->node; + u32 tmp; +@@ -2213,7 +2238,7 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret) + return ret; + +- ret = find_sdca_entities(dev, sdw, node, function); ++ ret = find_sdca_entities(dev, node, function); + if (ret) + return ret; + +@@ -2225,10 +2250,20 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret < 0) + return ret; + +- ret = find_sdca_filesets(dev, sdw, node, function); ++ ret = find_sdca_filesets(dev, node, function); + if (ret) + return ret; + ++ switch (function->desc->type) { ++ case SDCA_FUNCTION_TYPE_HID: ++ ret = find_sdca_hid(dev, node, function); ++ if (ret) ++ return ret; ++ break; ++ default: ++ break; ++ } ++ + return 0; + } + EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA"); +diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c +--- a/sound/soc/sdca/sdca_hid.c ++++ b/sound/soc/sdca/sdca_hid.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -23,18 +24,18 @@ + + static int sdwhid_parse(struct hid_device *hid) + { +- struct sdca_entity *entity = hid->driver_data; ++ struct sdca_function_data *function = hid->driver_data; + unsigned int rsize; + int ret; + +- rsize = le16_to_cpu(entity->hide.hid_desc.rpt_desc.wDescriptorLength); ++ rsize = le16_to_cpu(function->hid.desc.rpt_desc.wDescriptorLength); + + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dev_err(&hid->dev, "invalid size of report descriptor (%u)\n", rsize); + return -EINVAL; + } + +- ret = hid_parse_report(hid, entity->hide.hid_report_desc, rsize); ++ ret = hid_parse_report(hid, function->hid.report_desc, rsize); + + if (!ret) + return 0; +@@ -85,10 +86,16 @@ static const struct hid_ll_driver sdw_hid_driver = { + .raw_request = sdwhid_raw_request, + }; + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++/** ++ * sdca_add_hid_device - create a new SDCA HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ * ++ * Return: Zero on success, and a negative error code on failure. ++ */ ++int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { +- struct sdw_bus *bus = sdw->bus; ++ struct device *dev = interrupt->dev; ++ struct sdca_function_data *function = interrupt->function; + struct hid_device *hid; + int ret; + +@@ -100,16 +107,13 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + + hid->dev.parent = dev; + hid->bus = BUS_SDW; +- hid->version = le16_to_cpu(entity->hide.hid_desc.bcdHID); ++ hid->version = le16_to_cpu(function->hid.desc.bcdHID); + +- snprintf(hid->name, sizeof(hid->name), +- "HID sdw:%01x:%01x:%04x:%04x:%02x", +- bus->controller_id, bus->link_id, sdw->id.mfg_id, +- sdw->id.part_id, sdw->id.class_id); ++ strscpy(hid->phys, dev_name(dev)); ++ snprintf(hid->name, sizeof(hid->name), "SDCA %s:%02x", ++ function->desc->name, function->desc->adr); + +- snprintf(hid->phys, sizeof(hid->phys), "%s", dev->bus->name); +- +- hid->driver_data = entity; ++ hid->driver_data = function; + + ret = hid_add_device(hid); + if (ret && ret != -ENODEV) { +@@ -118,12 +122,24 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + return ret; + } + +- entity->hide.hid = hid; ++ interrupt->priv = hid; + + return 0; + } + EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + ++/** ++ * sdca_destroy_hid_device - destroy the HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ */ ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct hid_device *hid = interrupt->priv; ++ ++ hid_destroy_device(hid); ++} ++EXPORT_SYMBOL_NS(sdca_destroy_hid_device, "SND_SOC_SDCA"); ++ + /** + * sdca_hid_process_report - read a HID event from the device and report + * @interrupt: Pointer to the SDCA interrupt information structure. +@@ -133,7 +149,7 @@ EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + struct device *dev = interrupt->dev; +- struct hid_device *hid = interrupt->entity->hide.hid; ++ struct hid_device *hid = interrupt->priv; + void *val __free(kfree) = NULL; + int len, ret; + +diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c +--- a/sound/soc/sdca/sdca_interrupts.c ++++ b/sound/soc/sdca/sdca_interrupts.c +@@ -487,6 +487,12 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, + } + break; + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ interrupt->free_priv = sdca_destroy_hid_device; ++ ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ + interrupt->handler = hid_handler; + break; + default: +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -834,6 +834,35 @@ struct asoc_sdw_codec_info codec_info_list[] = { + }, + .dai_num = 2, + }, ++ { ++ .vendor_id = 0x01fa, ++ .part_id = 0x3562, ++ .name_prefix = "AMP", ++ .is_amp = true, ++ .dais = { ++ { ++ .direction = {true, false}, ++ .dai_name = "cs35l56-sdw1", ++ .component_name = "cs35l56", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, ++ .init = asoc_sdw_cs_amp_init, ++ .rtd_init = asoc_sdw_cs_spk_rtd_init, ++ .controls = generic_spk_controls, ++ .num_controls = ARRAY_SIZE(generic_spk_controls), ++ .widgets = generic_spk_widgets, ++ .num_widgets = ARRAY_SIZE(generic_spk_widgets), ++ }, ++ { ++ .direction = {false, true}, ++ .dai_name = "cs35l56-sdw1c", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, ++ .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, ++ }, ++ }, ++ .dai_num = 2, ++ }, + { + .vendor_id = 0x01fa, + .part_id = 0x3563, +@@ -1789,9 +1818,7 @@ static int is_sdca_aux_dev_present(struct device *dev, + int adr_index) + { + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret = 0; + int i; + + if (!aux_codec_name) +@@ -1801,7 +1828,8 @@ static int is_sdca_aux_dev_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1811,24 +1839,19 @@ static int is_sdca_aux_dev_present(struct device *dev, + + if (!slave->sdca_data.interface_revision) { + dev_warn(dev, "No SDCA properties, assuming aux '%s' present\n", aux_codec_name); +- ret = 1; +- goto put_dev; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { + const char *fname = slave->sdca_data.function[i].name; + +- if (fname && strstr(aux_codec_name, fname)) { +- ret = 1; +- goto put_dev; +- } ++ if (fname && strstr(aux_codec_name, fname)) ++ return 1; + } + + dev_dbg(dev, "SDCA function for aux '%s' NOT FOUND on slave, skipping\n", aux_codec_name); + +-put_dev: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, +@@ -1928,9 +1951,8 @@ static int is_sdca_endpoint_present(struct device *dev, + const struct snd_soc_acpi_endpoint *adr_end; + const struct asoc_sdw_dai_info *dai_info; + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret, i; ++ int i; + + adr_end = &adr_dev->endpoints[end_index]; + dai_info = &codec_info->dais[adr_end->num]; +@@ -1939,7 +1961,8 @@ static int is_sdca_endpoint_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1950,8 +1973,7 @@ static int is_sdca_endpoint_present(struct device *dev, + /* Make sure BIOS provides SDCA properties */ + if (!slave->sdca_data.interface_revision) { + dev_warn(&slave->dev, "SDCA properties not found in the BIOS\n"); +- ret = 1; +- goto put_device; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { +@@ -1960,8 +1982,7 @@ static int is_sdca_endpoint_present(struct device *dev, + if (dai_type == dai_info->dai_type) { + dev_dbg(&slave->dev, "DAI type %d sdca function %s found\n", + dai_type, slave->sdca_data.function[i].name); +- ret = 1; +- goto put_device; ++ return 1; + } + } + +@@ -1969,11 +1990,7 @@ static int is_sdca_endpoint_present(struct device *dev, + "SDCA device function for DAI type %d not supported, skip endpoint\n", + dai_info->dai_type); + +- ret = 0; +- +-put_device: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_parse_sdw_endpoints(struct device *dev, +diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c +--- a/sound/soc/soc-component.c ++++ b/sound/soc/soc-component.c +@@ -29,17 +29,42 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, + func, component->name, reg); + } + +-static inline int soc_component_field_shift(struct snd_soc_component *component, +- unsigned int mask) ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev) + { +- if (!mask) { +- dev_err(component->dev, "ASoC: error field mask is zero for %s\n", +- component->name); +- return 0; +- } ++ struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + +- return (ffs(mask) - 1); ++ if (!component) ++ return NULL; ++ ++ component->dev = dev; ++ ++ return component; + } ++EXPORT_SYMBOL_GPL(snd_soc_component_alloc); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) ++{ ++ component->name = name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_name); ++ ++const char *snd_soc_component_name(struct snd_soc_component *component) ++{ ++ return component->name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_name); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv) ++{ ++ component->priv = priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_priv); ++ ++void *snd_soc_component_to_priv(struct snd_soc_component *component) ++{ ++ return component->priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_to_priv); + + /* + * We might want to check substream by using list. +@@ -820,6 +845,18 @@ int snd_soc_component_update_bits_async(struct snd_soc_component *component, + } + EXPORT_SYMBOL_GPL(snd_soc_component_update_bits_async); + ++static inline int soc_component_field_shift(struct snd_soc_component *component, ++ unsigned int mask) ++{ ++ if (!mask) { ++ dev_err(component->dev, "ASoC: error field mask is zero for %s\n", ++ component->name); ++ return 0; ++ } ++ ++ return (ffs(mask) - 1); ++} ++ + /** + * snd_soc_component_read_field() - Read register field value + * @component: Component to read from +diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c +--- a/sound/soc/soc-core.c ++++ b/sound/soc/soc-core.c +@@ -2707,10 +2707,11 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) + list_del(&component->list); + } + +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev) ++static int soc_component_initialize(struct snd_soc_component *component, ++ const struct snd_soc_component_driver *driver) + { ++ struct device *dev = component->dev; ++ + component->dapm = snd_soc_dapm_alloc(dev); + if (!component->dapm) + return -ENOMEM; +@@ -2730,16 +2731,14 @@ int snd_soc_component_initialize(struct snd_soc_component *component, + } + } + +- component->dev = dev; + component->driver = driver; + + return 0; + } +-EXPORT_SYMBOL_GPL(snd_soc_component_initialize); + +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai) ++static int soc_component_add(struct snd_soc_component *component, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) + { + struct snd_soc_card *card, *c; + int ret; +@@ -2778,27 +2777,36 @@ int snd_soc_add_component(struct snd_soc_component *component, + + return ret; + } +-EXPORT_SYMBOL_GPL(snd_soc_add_component); + +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) + { +- struct snd_soc_component *component; + int ret; + +- component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); +- if (!component) +- return -ENOMEM; +- +- ret = snd_soc_component_initialize(component, component_driver, dev); ++ ret = soc_component_initialize(component, component_driver); + if (ret < 0) + return ret; + +- return snd_soc_add_component(component, dai_drv, num_dai); ++ return soc_component_add(component, dai_drv, num_dai); + } +-EXPORT_SYMBOL_GPL(snd_soc_register_component); ++EXPORT_SYMBOL_GPL(snd_soc_register_component_c); ++ ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) ++{ ++ struct snd_soc_component *component; ++ ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ ++ return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); ++} ++EXPORT_SYMBOL_GPL(snd_soc_register_component_d); + + /** + * snd_soc_unregister_component_by_driver - Unregister component using a given driver +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -1203,6 +1203,8 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + wname_in_long_name = true; + kcname_in_long_name = true; + break; +@@ -2785,6 +2787,8 @@ static ssize_t dapm_widget_show_component(struct snd_soc_component *component, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: +@@ -3367,6 +3371,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + dapm_new_pga(w); + break; + case snd_soc_dapm_dai_link: +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -3,6 +3,7 @@ + // Copyright (C) 2013, Analog Devices Inc. + // Author: Lars-Peter Clausen + ++#include + #include + #include + #include +@@ -77,7 +78,7 @@ static int dmaengine_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; +@@ -99,7 +100,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct device *dma_dev = dmaengine_dma_dev(pcm, substream); + struct dma_chan *chan = pcm->chan[substream->stream]; + struct snd_dmaengine_dai_dma_data *dma_data; +@@ -148,7 +149,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + static int dmaengine_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = pcm->chan[substream->stream]; + int ret; + +@@ -176,7 +177,7 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct snd_dmaengine_dai_dma_data *dma_data; + + if (rtd->dai_link->num_cpus > 1) { +@@ -219,7 +220,7 @@ static bool dmaengine_pcm_can_report_residue(struct device *dev, + static int dmaengine_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + const struct snd_dmaengine_pcm_config *config = pcm->config; + struct device *dev = component->dev; + size_t prealloc_buffer_size; +@@ -279,7 +280,7 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( + struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + return snd_dmaengine_pcm_pointer_no_residue(substream); +@@ -293,7 +294,7 @@ static int dmaengine_copy(struct snd_soc_component *component, + struct iov_iter *iter, unsigned long bytes) + { + struct snd_pcm_runtime *runtime = substream->runtime; +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + unsigned long bytes) = pcm->config->process; +@@ -395,6 +396,27 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; ++ ++ bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); ++ bool name_exists_in_fw = false; ++ ++ if (has_fw_node) ++ name_exists_in_fw = device_property_match_string(dev, ++ "dma-names", ++ name) >= 0; ++ ++ if (has_fw_node && name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (has_fw_node && !name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (!has_fw_node) ++ dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ + pcm->chan[i] = NULL; + } else { + pcm->chan[i] = chan; +@@ -406,6 +428,12 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + pcm->chan[1] = pcm->chan[0]; + ++ if (!pcm->chan[0] && ++ !pcm->chan[1]) { ++ dev_err(dev, "no DMA channel found for either playback or capture\n"); ++ return -ENODEV; ++ } ++ + return 0; + } + +@@ -435,10 +463,15 @@ static const struct snd_dmaengine_pcm_config snd_dmaengine_pcm_default_config = + int snd_dmaengine_pcm_register(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags) + { ++ struct snd_soc_component *component; + const struct snd_soc_component_driver *driver; + struct dmaengine_pcm *pcm; + int ret; + ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ + pcm = kzalloc_obj(*pcm); + if (!pcm) + return -ENOMEM; +@@ -449,7 +482,8 @@ int snd_dmaengine_pcm_register(struct device *dev, + pcm->flags = flags; + + if (config->name) +- pcm->component.name = config->name; ++ snd_soc_component_set_name(component, config->name); ++ snd_soc_component_set_priv(component, pcm); + + ret = dmaengine_pcm_request_chan_of(pcm, dev, config); + if (ret) +@@ -460,11 +494,7 @@ int snd_dmaengine_pcm_register(struct device *dev, + else + driver = &dmaengine_pcm_component; + +- ret = snd_soc_component_initialize(&pcm->component, driver, dev); +- if (ret) +- goto err_free_dma; +- +- ret = snd_soc_add_component(&pcm->component, NULL, 0); ++ ret = snd_soc_register_component(component, driver, NULL, 0); + if (ret) + goto err_free_dma; + +@@ -493,7 +523,7 @@ void snd_dmaengine_pcm_unregister(struct device *dev) + if (!component) + return; + +- pcm = soc_component_to_pcm(component); ++ pcm = snd_soc_component_to_priv(component); + + snd_soc_unregister_component_by_driver(dev, component->driver); + dmaengine_pcm_release_chan(pcm); +diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c +--- a/sound/soc/soc-topology-test.c ++++ b/sound/soc/soc-topology-test.c +@@ -45,15 +45,13 @@ static void snd_soc_tplg_test_exit(struct kunit *test) + struct kunit_soc_component { + struct kunit *kunit; + int expect; /* what result we expect when loading topology */ +- struct snd_soc_component comp; + struct snd_soc_card card; + struct firmware fw; + }; + + static int d_probe(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_load(component, NULL, &kunit_comp->fw); +@@ -65,8 +63,7 @@ static int d_probe(struct snd_soc_component *component) + + static void d_remove(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_remove(component); +@@ -214,8 +211,7 @@ static struct tplg_tmpl_002 tplg_tmpl_with_pcm = { + */ + static int d_probe_null_comp(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing component pointer as first argument, pass NULL here */ +@@ -234,6 +230,7 @@ static const struct snd_soc_component_driver test_component_null_comp = { + static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -249,15 +246,17 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_comp, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -276,6 +275,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -291,15 +291,17 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -318,8 +320,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + */ + static int d_probe_null_fw(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing fw pointer as third argument, pass NULL here */ +@@ -338,6 +339,7 @@ static const struct snd_soc_component_driver test_component_null_fw = { + static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -353,15 +355,17 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_fw, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -375,6 +379,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -401,15 +406,17 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -425,6 +432,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -456,15 +464,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -480,6 +490,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -511,15 +522,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -535,6 +548,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -566,15 +580,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -590,6 +606,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -622,15 +639,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -644,6 +663,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -670,15 +690,17 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -693,6 +715,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -720,16 +743,19 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + + for (i = 0; i < 100; i++) { +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -745,6 +771,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -772,11 +799,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + +- /* run test */ +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ snd_soc_component_set_priv(component, kunit_comp); ++ ++ /* run test */ ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + for (i = 0; i < 100; i++) { +diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig +--- a/sound/soc/sof/amd/Kconfig ++++ b/sound/soc/sof/amd/Kconfig +@@ -104,4 +104,15 @@ config SND_SOC_SOF_AMD_ACP70 + AMD ACP7.0/ACP7.1 version based platforms. + Say Y if you want to enable SOF on ACP7.0/ACP7.1 based platforms. + ++config SND_SOC_SOF_AMD_ACP7X ++ tristate "SOF support for ACP7.B/7.F platforms" ++ depends on SND_SOC_SOF_PCI ++ depends on AMD_NODE ++ select SND_SOC_SOF_AMD_COMMON ++ help ++ Select this option for SOF support on AMD ACP7.B and ACP7.F PCI ++ revision based platforms. ++ Say Y if you want to enable SOF on ACP7.B/7.F based platforms. ++ If unsure select "N". ++ + endif +diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile +--- a/sound/soc/sof/amd/Makefile ++++ b/sound/soc/sof/amd/Makefile +@@ -11,6 +11,7 @@ snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o + snd-sof-amd-vangogh-y := pci-vangogh.o vangogh.o + snd-sof-amd-acp63-y := pci-acp63.o acp63.o + snd-sof-amd-acp70-y := pci-acp70.o acp70.o ++snd-sof-amd-acp7x-y := pci-acp7x.o acp7x.o + + obj-$(CONFIG_SND_SOC_SOF_AMD_COMMON) += snd-sof-amd-acp.o + obj-$(CONFIG_SND_SOC_SOF_AMD_RENOIR) += snd-sof-amd-renoir.o +@@ -18,3 +19,4 @@ obj-$(CONFIG_SND_SOC_SOF_AMD_REMBRANDT) += snd-sof-amd-rembrandt.o + obj-$(CONFIG_SND_SOC_SOF_AMD_VANGOGH) += snd-sof-amd-vangogh.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP63) += snd-sof-amd-acp63.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP70) += snd-sof-amd-acp70.o ++obj-$(CONFIG_SND_SOC_SOF_AMD_ACP7X) += snd-sof-amd-acp7x.o +diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h +--- a/sound/soc/sof/amd/acp-dsp-offset.h ++++ b/sound/soc/sof/amd/acp-dsp-offset.h +@@ -68,12 +68,14 @@ + #define ACP5X_PGFSM_BASE 0x1424 + #define ACP6X_PGFSM_BASE 0x1024 + #define ACP70_PGFSM_BASE ACP6X_PGFSM_BASE ++#define ACP7X_PGFSM_BASE ACP6X_PGFSM_BASE + #define PGFSM_CONTROL_OFFSET 0x0 + #define PGFSM_STATUS_OFFSET 0x4 + #define ACP3X_CLKMUX_SEL 0x1424 + #define ACP5X_CLKMUX_SEL 0x142C + #define ACP6X_CLKMUX_SEL 0x102C + #define ACP70_CLKMUX_SEL ACP6X_CLKMUX_SEL ++#define ACP7X_CLKMUX_SEL ACP6X_CLKMUX_SEL + + /* Registers from ACP_INTR block */ + #define ACP3X_EXT_INTR_STAT 0x1808 +@@ -86,21 +88,31 @@ + #define ACP70_EXTERNAL_INTR_CNTL ACP6X_EXTERNAL_INTR_CNTL + #define ACP70_EXT_INTR_STAT ACP6X_EXT_INTR_STAT + #define ACP70_EXT_INTR_STAT1 ACP6X_EXT_INTR_STAT1 ++#define ACP7X_EXTERNAL_INTR_ENB ACP6X_EXTERNAL_INTR_ENB ++#define ACP7X_EXTERNAL_INTR_CNTL 0x1A04 ++#define ACP7X_EXT_INTR_STAT 0x1A1C ++#define ACP7X_EXTERNAL_INTR_CNTL1 0x1A08 ++#define ACP7X_EXT_INTR_STAT1 0x1A20 + + #define ACP3X_DSP_SW_INTR_BASE 0x1814 + #define ACP5X_DSP_SW_INTR_BASE 0x1814 + #define ACP6X_DSP_SW_INTR_BASE 0x1808 + #define ACP70_DSP_SW_INTR_BASE ACP6X_DSP_SW_INTR_BASE ++#define ACP7X_DSP_SW_INTR_BASE 0x1860 + #define DSP_SW_INTR_CNTL_OFFSET 0x0 + #define DSP_SW_INTR_STAT_OFFSET 0x4 ++#define ACP7X_DSP_SW_INTR_STAT (ACP7X_DSP_SW_INTR_BASE + DSP_SW_INTR_STAT_OFFSET) + #define DSP_SW_INTR_TRIG_OFFSET 0x8 ++#define ACP7X_DSP_SW_INTR_TRIG_OFFSET 0x30 + #define ACP3X_ERROR_STATUS 0x18C4 + #define ACP6X_ERROR_STATUS 0x1A4C + #define ACP70_ERROR_STATUS ACP6X_ERROR_STATUS ++#define ACP7X_ERROR_STATUS 0x1A88 + #define ACP3X_AXI2DAGB_SEM_0 0x1880 + #define ACP5X_AXI2DAGB_SEM_0 0x1884 + #define ACP6X_AXI2DAGB_SEM_0 0x1874 + #define ACP70_AXI2DAGB_SEM_0 ACP6X_AXI2DAGB_SEM_0 ++#define ACP7X_AXI2DAGB_SEM_0 0x18F4 + + /* ACP common registers to report errors related to I2S & SoundWire interfaces */ + #define ACP3X_SW_I2S_ERROR_REASON 0x18C8 +@@ -123,6 +135,7 @@ + #define ACP_SCRATCH_REG_0 0x10000 + #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 + #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL ++#define ACP7X_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL + + /* Cache window registers */ + #define ACP_DSP0_CACHE_OFFSET0 0x0420 +@@ -139,5 +152,9 @@ + #define ACP70_SDW1_HOST_WAKE_STAT BIT(25) + #define ACP70_SDW0_PME_STAT BIT(26) + #define ACP70_SDW1_PME_STAT BIT(27) ++#define ACP7X_DSP0_IDMA_ERROR_MASK 0x4B0 ++#define ACP7X_IDMA_ERROR_MASK 0x1FF9FF ++#define ACP7X_ZSC_DSP_CTRL 0x001014 ++#define ACP7X_PME_EN ACP70_PME_EN + + #endif +diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c +--- a/sound/soc/sof/amd/acp-ipc.c ++++ b/sound/soc/sof/amd/acp-ipc.c +@@ -32,11 +32,20 @@ static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata) + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 swintr_trigger; ++ unsigned int swintr_trigger_reg_offset; + ++ switch (adata->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ swintr_trigger_reg_offset = ACP7X_DSP_SW_INTR_TRIG_OFFSET; ++ break; ++ default: ++ swintr_trigger_reg_offset = DSP_SW_INTR_TRIG_OFFSET; ++ } + swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->dsp_intr_base + +- DSP_SW_INTR_TRIG_OFFSET); ++ swintr_trigger_reg_offset); + swintr_trigger |= 0x01; +- snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_TRIG_OFFSET, ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + swintr_trigger_reg_offset, + swintr_trigger); + } + +diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c +--- a/sound/soc/sof/amd/acp-loader.c ++++ b/sound/soc/sof/amd/acp-loader.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "../ops.h" + #include "acp-dsp-offset.h" +@@ -173,10 +174,31 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) + + adata = sdev->pdata->hw_pdata; + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if (adata->pci_rev >= ACP7B_PCI_ID) { ++ if (adata->acp_sof_signed_firmware_image) { ++ if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, "Invalid signed firmware size %u\n", ++ adata->fw_bin_size); ++ return -EINVAL; ++ } ++ size_fw = get_unaligned_le32(adata->bin_buf + ++ ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); ++ if (!size_fw || ++ size_fw > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, ++ "Invalid signed firmware payload size %u (max %u)\n", ++ size_fw, adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE); ++ return -EINVAL; ++ } ++ size_fw += ACP_IMAGE_HEADER_SIZE; ++ } else { ++ size_fw = adata->fw_bin_size; ++ } ++ } else if (adata->quirks && adata->quirks->signed_fw_image) { + size_fw = adata->fw_bin_size - ACP_FIRMWARE_SIGNATURE; +- else ++ } else { + size_fw = adata->fw_bin_size; ++ } + + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; +@@ -312,9 +334,14 @@ int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev) + } + kfree(fw_filename); + +- ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, +- (void *)adata->fw_dbin->data, +- adata->fw_dbin->size); ++ if (adata->pci_rev >= ACP7B_PCI_ID) ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); ++ else ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); + return ret; + } + EXPORT_SYMBOL_NS(acp_sof_load_signed_firmware, "SND_SOC_SOF_AMD_COMMON"); +diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c +--- a/sound/soc/sof/amd/acp.c ++++ b/sound/soc/sof/amd/acp.c +@@ -12,6 +12,7 @@ + * Hardware interface for generic AMD ACP processor + */ + ++#include + #include + #include + #include +@@ -60,6 +61,8 @@ static void init_dma_descriptor(struct acp_dev_data *adata) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_desc_base_addr = ACP70_DMA_DESC_BASE_ADDR; + acp_dma_desc_max_num_dscr = ACP70_DMA_DESC_MAX_NUM_DSCR; + break; +@@ -101,6 +104,8 @@ static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch, + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_cntl_0 = ACP70_DMA_CNTL_0; + acp_dma_ch_rst_sts = ACP70_DMA_CH_RST_STS; + acp_dma_dscr_err_sts_0 = ACP70_DMA_ERR_STS_0; +@@ -283,7 +288,8 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, + } + } + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if ((adata->quirks && adata->quirks->signed_fw_image) || ++ adata->acp_sof_signed_firmware_image) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); +@@ -342,6 +348,8 @@ int acp_dma_status(struct acp_dev_data *adata, unsigned char ch) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_ch_sts = ACP70_DMA_CH_STS; + break; + default: +@@ -534,8 +542,17 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_sw0_i2s_err_reason, 0); + /* ACP_SW1_I2S_ERROR_REASON is newly added register from rmb platform onwards */ +- if (adata->pci_rev >= ACP_RMB_PCI_ID) ++ switch (adata->pci_rev) { ++ case ACP_RMB_PCI_ID: ++ case ACP63_PCI_ID: ++ case ACP70_PCI_ID: ++ case ACP71_PCI_ID: ++ case ACP72_PCI_ID: + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SW1_I2S_ERROR_REASON, 0); ++ break; ++ default: ++ break; ++ } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + irq_flag = 1; + } +@@ -564,6 +581,35 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + return IRQ_NONE; + } + ++static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) ++{ ++ struct snd_sof_dev *sdev = dev_id; ++ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); ++ unsigned int base = desc->dsp_intr_base; ++ unsigned int val; ++ unsigned int ext_intr_stat; ++ int irq_flag = 0; ++ ++ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); ++ if (val & ACP_DSP_TO_HOST_IRQ) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ++ ACP_DSP_TO_HOST_IRQ); ++ return IRQ_WAKE_THREAD; ++ } ++ ++ ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); ++ if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); ++ irq_flag = 1; ++ } ++ ++ if (irq_flag) ++ return IRQ_HANDLED; ++ ++ return IRQ_NONE; ++} ++ + static int acp_power_on(struct snd_sof_dev *sdev) + { + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); +@@ -571,6 +617,7 @@ static int acp_power_on(struct snd_sof_dev *sdev) + unsigned int base = desc->pgfsm_base; + unsigned int val; + unsigned int acp_pgfsm_status_mask, acp_pgfsm_cntl_mask; ++ bool use_masked_status = false; + int ret; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET); +@@ -595,6 +642,12 @@ static int acp_power_on(struct snd_sof_dev *sdev) + acp_pgfsm_status_mask = ACP70_PGFSM_STATUS_MASK; + acp_pgfsm_cntl_mask = ACP70_PGFSM_CNTL_POWER_ON_MASK; + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ acp_pgfsm_status_mask = ACP7X_PGFSM_STATUS_MASK; ++ acp_pgfsm_cntl_mask = ACP7X_PGFSM_CNTL_POWER_ON_MASK; ++ use_masked_status = true; ++ break; + default: + return -EINVAL; + } +@@ -603,8 +656,17 @@ static int acp_power_on(struct snd_sof_dev *sdev) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + PGFSM_CONTROL_OFFSET, + acp_pgfsm_cntl_mask); + +- ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET, val, +- !val, ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); ++ if (use_masked_status) ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !(val & acp_pgfsm_status_mask), ++ ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); ++ else ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !val, ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n"); + +@@ -703,6 +765,13 @@ static int acp_init(struct snd_sof_dev *sdev) + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP70_PME_EN, 1); + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ++ ACP7X_IDMA_ERROR_MASK); ++ break; + } + return 0; + } +@@ -749,6 +818,8 @@ int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state) + case ACP72_PCI_ID: + enable = true; + break; ++ default: ++ break; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + +@@ -990,6 +1061,194 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) + } + EXPORT_SYMBOL_NS(amd_sof_acp_remove, "SND_SOC_SOF_AMD_COMMON"); + ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) ++{ ++ struct pci_dev *pci = to_pci_dev(sdev->dev); ++ struct acp_dev_data *adata; ++ const struct sof_amd_acp_desc *chip; ++ const union acpi_object *obj; ++ struct acpi_device *adev; ++ unsigned int addr; ++ unsigned int irqflags; ++ int ret; ++ ++ chip = get_chip_info(sdev->pdata); ++ if (!chip) { ++ dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); ++ return -EIO; ++ } ++ adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data), GFP_KERNEL); ++ if (!adata) ++ return -ENOMEM; ++ ++ adata->dev = sdev; ++ adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", ++ PLATFORM_DEVID_NONE, NULL, 0); ++ if (IS_ERR(adata->dmic_dev)) { ++ dev_err(sdev->dev, "failed to register platform for dmic codec\n"); ++ return PTR_ERR(adata->dmic_dev); ++ } ++ ++ addr = pci_resource_start(pci, ACP_DSP_BAR); ++ sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR)); ++ if (!sdev->bar[ACP_DSP_BAR]) { ++ dev_err(sdev->dev, "ioremap error\n"); ++ ret = -ENXIO; ++ goto unregister_dev; ++ } ++ ++ pci_set_master(pci); ++ adata->addr = addr; ++ adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; ++ adata->pci_rev = pci->revision; ++ mutex_init(&adata->acp_lock); ++ sdev->pdata->hw_pdata = adata; ++ ++ ret = acp_init(sdev); ++ if (ret < 0) ++ goto unregister_dev; ++ ++ adev = ACPI_COMPANION(&pci->dev); ++ ++ sdev->ipc_irq = pci->irq; ++ irqflags = IRQF_SHARED; ++ ++ ret = request_threaded_irq(pci->irq, acp7x_irq_handler, acp_irq_thread, ++ irqflags, "AudioDSP", sdev); ++ if (ret < 0) { ++ dev_err(sdev->dev, "failed to register IRQ %d\n", sdev->ipc_irq); ++ goto unregister_dev; ++ } ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ adata->acp_sof_signed_firmware_image = obj->integer.value; ++ } ++ ++ sdev->dsp_box.offset = 0; ++ sdev->dsp_box.size = BOX_SIZE_512; ++ ++ sdev->host_box.offset = sdev->dsp_box.offset + sdev->dsp_box.size; ++ sdev->host_box.size = BOX_SIZE_512; ++ ++ sdev->debug_box.offset = sdev->host_box.offset + sdev->host_box.size; ++ sdev->debug_box.size = BOX_SIZE_1024; ++ ++ if (adata->acp_sof_signed_firmware_image) { ++ adata->fw_code_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-code.bin", chip->name); ++ if (!adata->fw_code_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ adata->fw_data_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-data.bin", chip->name); ++ if (!adata->fw_data_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ } ++ ++ adata->enable_fw_debug = enable_fw_debug; ++ acp_memory_init(sdev); ++ acp_dsp_stream_init(sdev); ++ ++ return 0; ++ ++free_ipc_irq: ++ free_irq(sdev->ipc_irq, sdev); ++unregister_dev: ++ platform_device_unregister(adata->dmic_dev); ++ return ret; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_probe, "SND_SOC_SOF_AMD_COMMON"); ++ ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *adata = sdev->pdata->hw_pdata; ++ ++ if (sdev->ipc_irq) ++ free_irq(sdev->ipc_irq, sdev); ++ ++ if (adata->dmic_dev) ++ platform_device_unregister(adata->dmic_dev); ++ ++ acp_reset(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ bool enable = false; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_reset(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Reset failed\n"); ++ return ret; ++ } ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ enable = true; ++ break; ++ default: ++ break; ++ } ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Init failed\n"); ++ return ret; ++ } ++ ret = acp_memory_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Memory init failed\n"); ++ return ret; ++ } ++ ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_suspend(sdev, 0); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_resume(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("AMD ACP sof driver"); + MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT"); +diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h +--- a/sound/soc/sof/amd/acp.h ++++ b/sound/soc/sof/amd/acp.h +@@ -76,6 +76,12 @@ + #define ACP70_PCI_ID 0x70 + #define ACP71_PCI_ID 0x71 + #define ACP72_PCI_ID 0x72 ++#define ACP7B_PCI_ID 0x7B ++#define ACP7F_PCI_ID 0x7F ++ ++#define ACP7X_PGFSM_CNTL_POWER_ON_MASK 0x7F ++#define ACP7X_PGFSM_STATUS_MASK 0xFFF ++#define ACP7X_SRAM_PTE_OFFSET ACP6X_SRAM_PTE_OFFSET + + #define HOST_BRIDGE_CZN 0x1630 + #define HOST_BRIDGE_VGH 0x1645 +@@ -107,6 +113,9 @@ + #define PROBE_STATUS_BIT BIT(31) + + #define ACP_FIRMWARE_SIGNATURE 0x100 ++#define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE ++#define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 ++ + #define ACP_ERROR_IRQ_MASK BIT(29) + #define ACP_SDW0_IRQ_MASK BIT(21) + #define ACP_SDW1_IRQ_MASK BIT(2) +@@ -271,6 +280,7 @@ struct acp_dev_data { + /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ + bool acp70_sdw1_wake_event; + unsigned int pci_rev; ++ int acp_sof_signed_firmware_image; + }; + + void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); +@@ -344,6 +354,16 @@ int sof_acp63_ops_init(struct snd_sof_dev *sdev); + extern struct snd_sof_dsp_ops sof_acp70_ops; + int sof_acp70_ops_init(struct snd_sof_dev *sdev); + ++extern struct snd_sof_dsp_ops sof_acp7x_ops; ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev); ++ ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev); ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state); ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev); ++ + struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev); + /* Machine configuration */ + int snd_amd_acp_find_config(struct pci_dev *pci); +diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/acp7x.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * Hardware interface for Audio DSP on ACP7.B/7.F platforms ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-audio.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define I2S_TDM0_INSTANCE 0 ++#define I2S_TDM1_INSTANCE 1 ++#define I2S_TDM2_INSTANCE 2 ++#define PDM0_DMIC_INSTANCE 3 ++#define PDM1_DMIC_INSTANCE 4 ++ ++static struct snd_soc_dai_driver acp7x_sof_dai[] = { ++ [I2S_TDM0_INSTANCE] = { ++ .id = I2S_TDM0_INSTANCE, ++ .name = "acp-sof-i2s0", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S HS controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM1_INSTANCE] = { ++ .id = I2S_TDM1_INSTANCE, ++ .name = "acp-sof-i2s1", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S BT controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM2_INSTANCE] = { ++ .id = I2S_TDM2_INSTANCE, ++ .name = "acp-sof-i2s2", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S SP controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM0_DMIC_INSTANCE] = { ++ .id = PDM0_DMIC_INSTANCE, ++ .name = "acp-sof-dmic0", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM1_DMIC_INSTANCE] = { ++ .id = PDM1_DMIC_INSTANCE, ++ .name = "acp-sof-dmic1", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ }, ++}; ++ ++static int sof_acp7x_post_fw_run_delay(struct snd_sof_dev *sdev) ++{ ++ /* ++ * Resuming from suspend in some cases may cause the DSP firmware ++ * to enter an unrecoverable faulty state. Delaying a bit any host ++ * to DSP transmission right after firmware boot completion seems ++ * to resolve the issue. ++ */ ++ if (!sdev->first_boot) ++ usleep_range(100, 150); ++ ++ return 0; ++} ++ ++struct snd_sof_dsp_ops sof_acp7x_ops; ++EXPORT_SYMBOL_NS(sof_acp7x_ops, "SND_SOC_SOF_AMD_COMMON"); ++ ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&to_pci_dev(sdev->dev)->dev); ++ const union acpi_object *obj; ++ int acp_sof_signed_firmware_image = 0; ++ int acp_sof_post_fw_run_delay = 0; ++ ++ /* common defaults */ ++ memcpy(&sof_acp7x_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops)); ++ ++ sof_acp7x_ops.drv = acp7x_sof_dai; ++ sof_acp7x_ops.num_drv = ARRAY_SIZE(acp7x_sof_dai); ++ sof_acp7x_ops.probe = amd_sof_acp7x_probe; ++ sof_acp7x_ops.remove = amd_sof_acp7x_remove; ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_signed_firmware_image = obj->integer.value; ++ ++ if (!acpi_dev_get_property(adev, "acp-sof-post_fw_run_delay", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_post_fw_run_delay = obj->integer.value; ++ } ++ ++ if (acp_sof_signed_firmware_image) ++ sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; ++ ++ if (acp_sof_post_fw_run_delay) ++ sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; ++ ++ sof_acp7x_ops.suspend = amd_sof_acp7x_suspend; ++ sof_acp7x_ops.resume = amd_sof_acp7x_resume; ++ sof_acp7x_ops.runtime_suspend = amd_sof_acp7x_suspend_runtime; ++ sof_acp7x_ops.runtime_resume = amd_sof_acp7x_resume_runtime; ++ ++ return 0; ++} +diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/pci-acp7x.c +@@ -0,0 +1,116 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. All rights reserved. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * PCI interface for ACP7.B/7.F devices ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-pci-dev.h" ++#include "../../amd/mach-config.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define ACP7X_FUTURE_REG_ACLK_0 0x18e0 ++#define ACP7X_REG_START 0x1240000 ++#define ACP7X_REG_END 0x125C000 ++ ++static const struct sof_amd_acp_desc acp7x_chip_info = { ++ .name = "acp7x", ++ .pgfsm_base = ACP7X_PGFSM_BASE, ++ .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, ++ .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, ++ .ext_intr_stat = ACP7X_EXT_INTR_STAT, ++ .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, ++ .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, ++ .acp_error_stat = ACP7X_ERROR_STATUS, ++ .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, ++ .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, ++ .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, ++ .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, ++ .reg_start_addr = ACP7X_REG_START, ++ .reg_end_addr = ACP7X_REG_END, ++}; ++ ++static const struct sof_dev_desc acp7x_desc = { ++ .machines = snd_soc_acpi_amd_acp7x_sof_machines, ++ .resindex_lpe_base = 0, ++ .resindex_pcicfg_base = -1, ++ .resindex_imr_base = -1, ++ .irqindex_host_ipc = -1, ++ .chip_info = &acp7x_chip_info, ++ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), ++ .ipc_default = SOF_IPC_TYPE_3, ++ .default_fw_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof", ++ }, ++ .default_tplg_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof-tplg", ++ }, ++ .default_fw_filename = { ++ [SOF_IPC_TYPE_3] = "sof-acp7x.ri", ++ }, ++ .nocodec_tplg_filename = "sof-acp.tplg", ++ .ops = &sof_acp7x_ops, ++ .ops_init = sof_acp7x_ops_init, ++}; ++ ++static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) ++{ ++ unsigned int flag; ++ ++ switch (pci->revision) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ flag = snd_amd_acp_find_config(pci); ++ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) ++ return -ENODEV; ++ ++ return sof_pci_probe(pci, pci_id); ++} ++ ++static void acp7x_pci_remove(struct pci_dev *pci) ++{ ++ sof_pci_remove(pci); ++} ++ ++/* PCI IDs */ ++static const struct pci_device_id acp7x_pci_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), ++ .driver_data = (unsigned long)&acp7x_desc}, ++ { 0, } ++}; ++MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); ++ ++/* pci_driver definition */ ++static struct pci_driver snd_sof_pci_amd_acp7x_driver = { ++ .name = KBUILD_MODNAME, ++ .id_table = acp7x_pci_ids, ++ .probe = acp7x_pci_probe, ++ .remove = acp7x_pci_remove, ++ .driver = { ++ .pm = pm_ptr(&sof_pci_pm), ++ }, ++}; ++module_pci_driver(snd_sof_pci_amd_acp7x_driver); ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DESCRIPTION("ACP7X SOF Driver"); ++MODULE_IMPORT_NS("SND_SOC_SOF_AMD_COMMON"); ++MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV"); +diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c +--- a/sound/soc/sof/fw-file-profile.c ++++ b/sound/soc/sof/fw-file-profile.c +@@ -16,20 +16,19 @@ static int sof_test_firmware_file(struct device *dev, + enum sof_ipc_type *ipc_type_to_adjust) + { + enum sof_ipc_type fw_ipc_type; +- const struct firmware *fw; +- const char *fw_filename; + const u32 *magic; + int ret; + +- fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, +- profile->fw_name); ++ const char *fw_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, ++ profile->fw_name); + if (!fw_filename) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = firmware_request_nowarn(&fw, fw_filename, dev); + if (ret < 0) { + dev_dbg(dev, "Failed to open firmware file: %s\n", fw_filename); +- kfree(fw_filename); + return ret; + } + +@@ -44,8 +43,7 @@ static int sof_test_firmware_file(struct device *dev, + break; + default: + dev_err(dev, "Invalid firmware magic: %#x\n", *magic); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + if (ipc_type_to_adjust) { +@@ -54,13 +52,10 @@ static int sof_test_firmware_file(struct device *dev, + dev_err(dev, + "ipc type mismatch between %s and expected: %d vs %d\n", + fw_filename, fw_ipc_type, profile->ipc_type); +- ret = -EINVAL; ++ return -EINVAL; + } +-out: +- release_firmware(fw); +- kfree(fw_filename); + +- return ret; ++ return 0; + } + + static int sof_test_topology_file(struct device *dev, +diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c +--- a/sound/soc/sof/intel/hda-ctrl.c ++++ b/sound/soc/sof/intel/hda-ctrl.c +@@ -186,6 +186,7 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON" + int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + { + struct hdac_bus *bus = sof_to_bus(sdev); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct hdac_stream *stream; + int sd_offset, ret = 0; + u32 gctl; +@@ -193,6 +194,16 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + if (bus->chip_init) + return 0; + ++ /* ++ * The controller reset clears the ACE2+ link DMA stream allocation ++ * constraints; reset the masks to reflect this. ++ */ ++ memset(sof_hda->link_dma_active_sdw_mask, 0, ++ sizeof(sof_hda->link_dma_active_sdw_mask)); ++ memset(sof_hda->link_dma_active_multi_mask, 0, ++ sizeof(sof_hda->link_dma_active_multi_mask)); ++ sof_hda->link_dma_out_hda_used_mask = 0; ++ + hda_codec_set_codec_wakeup(sdev, true); + + hda_dsp_ctrl_misc_clock_gating(sdev, false); +@@ -223,6 +234,14 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + /* Accept unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); + ++ /* Perform a one-time enumeration of the Multi-Link capability */ ++ ret = hda_bus_ml_init(bus); ++ if (ret < 0) { ++ dev_err(sdev->dev, "%s: failed to enumerate multi-links\n", ++ __func__); ++ goto err; ++ } ++ + if (detect_codec) + hda_codec_detect_mask(sdev); + +diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c +--- a/sound/soc/sof/intel/hda-dai-ops.c ++++ b/sound/soc/sof/intel/hda-dai-ops.c +@@ -20,7 +20,7 @@ + /* These ops are only applicable for the HDA DAI's in their current form */ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) + /* +- * This function checks if the host dma channel corresponding ++ * This function checks if the host DMA stream corresponding + * to the link DMA stream_tag argument is assigned to one + * of the FEs connected to the BE DAI. + */ +@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, + } + + static struct hdac_ext_stream * +-hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream) ++hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream, ++ enum hda_bus_ml_link_type link_type) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct sof_intel_hda_stream *hda_stream; + const struct sof_intel_dsp_desc *chip; + struct snd_sof_dev *sdev; + struct hdac_ext_stream *res = NULL; + struct hdac_stream *hstream = NULL; +- + int stream_dir = substream->stream; ++ bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL; ++ bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK; ++ bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW; ++ bool is_hda = link_type == HDA_BUS_ML_LINK_HDA; ++ u32 concur_block_mask = 0; ++ u32 seq_block_mask = 0; ++ unsigned int stream_idx; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + ++ /* ++ * On ACE2+ the link DMA stream allocator must avoid two HW errata, ++ * see the comment on struct sof_intel_hda_dev. ++ * ++ * - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp ++ * and UAOL on the same physical stream index; SSP and DMIC are safe. ++ * - Sequential playback: a stream index previously used by an HDA/iDisp ++ * link cannot drive any non-HDA/iDisp link in the same direction ++ * until the next controller reset. ++ * ++ * The masks are protected by bus->reg_lock; sample them inside the ++ * lock together with the stream walk to keep the decision atomic ++ * with concurrent allocations and releases. ++ */ + guard(spinlock_irq)(&bus->reg_lock); ++ ++ if (is_sdw) ++ concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir]; ++ else if (is_multi) ++ concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir]; ++ if (is_play && !is_hda) ++ seq_block_mask = sof_hda->link_dma_out_hda_used_mask; ++ + list_for_each_entry(hstream, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = + stream_to_hdac_ext_stream(hstream); +@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + sdev = hda_stream->sdev; + chip = get_chip_info(sdev->pdata); + ++ stream_idx = hstream->stream_tag - 1; ++ ++ /* skip streams blocked by the ACE2+ allocator constraints */ ++ if ((concur_block_mask | seq_block_mask) & BIT(stream_idx)) ++ continue; ++ + /* check if link is available */ + if (!hext_stream->link_locked) { + /* +@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + /* + * This must be a hostless stream. +- * So reserve the host DMA channel. ++ * So reserve the host DMA stream. + */ + hda_stream->host_reserved = 1; + break; +@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + res->link_locked = 1; + res->link_substream = substream; ++ ++ stream_idx = res->hstream.stream_tag - 1; ++ if (is_sdw) ++ sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx); ++ else if (is_multi) ++ sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx); ++ ++ /* persistent OUT HDA/iDisp shadow, cleared only on CRST# */ ++ if (is_hda && is_play) ++ sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx); + } + + return res; +@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev + + static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream) ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink) + { + struct hdac_ext_stream *hext_stream; ++ enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink); + +- hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream); ++ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type); + if (!hext_stream) + return NULL; + +@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai + struct snd_pcm_substream *substream) + { + struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream); ++ struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata; ++ struct hdac_bus *bus = sof_to_bus(sdev); ++ int dir = substream->stream; ++ unsigned int stream_idx = hext_stream->hstream.stream_tag - 1; ++ ++ /* ++ * Drop the stream index from the per-direction active concurrency masks. ++ * The two masks are mutually exclusive for a given stream/direction ++ * (and a stream of the SSP/DMIC kind appears in neither), so a blind ++ * clear of both is safe and lets us avoid having to remember the ++ * link type at allocation time. ++ */ ++ scoped_guard(spinlock_irq, &bus->reg_lock) { ++ sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx); ++ sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx); ++ } + + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); +diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c +--- a/sound/soc/sof/intel/hda-dai.c ++++ b/sound/soc/sof/intel/hda-dai.c +@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, + + if (!hext_stream) { + if (ops->assign_hext_stream) +- hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream); ++ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink); + } + + if (!hext_stream) +diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c +--- a/sound/soc/sof/intel/hda-dsp.c ++++ b/sound/soc/sof/intel/hda-dsp.c +@@ -1114,11 +1114,7 @@ static int hda_dsp_s5_quirk(struct snd_sof_dev *sdev) + usleep_range(500, 1000); + + /* Restore state for shutdown, back to reset */ +- ret = hda_dsp_ctrl_link_reset(sdev, true); +- if (ret < 0) +- return ret; +- +- return ret; ++ return hda_dsp_ctrl_link_reset(sdev, true); + } + + int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev) +diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c +--- a/sound/soc/sof/intel/hda-loader.c ++++ b/sound/soc/sof/intel/hda-loader.c +@@ -216,9 +216,10 @@ int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int + EXPORT_SYMBOL_NS(hda_cl_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax) + { +- return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, false); ++ return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, ++ is_iccmax, false); + } + EXPORT_SYMBOL_NS(hda_cl_cleanup, "SND_SOC_SOF_INTEL_HDA_COMMON"); + +@@ -302,7 +303,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->iccmax_dmab, +- persistent_cl_buffer, iccmax_stream); ++ persistent_cl_buffer, iccmax_stream, true); + if (ret1 < 0) { + dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); + +@@ -458,7 +459,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, +- persistent_cl_buffer, hext_stream); ++ persistent_cl_buffer, hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); + +@@ -587,7 +588,7 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, + cleanup: + /* clean up even in case of error and return the first error */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, persistent_cl_buffer, +- hext_stream); ++ hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "%s: Code loader DSP cleanup failed\n", __func__); + +diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c +--- a/sound/soc/sof/intel/hda-mlink.c ++++ b/sound/soc/sof/intel/hda-mlink.c +@@ -432,6 +432,10 @@ int hda_bus_ml_init(struct hdac_bus *bus) + if (!bus->mlcap) + return 0; + ++ /* Enumeration is a one time operation, skip if already done */ ++ if (!list_empty(&bus->hlink_list)) ++ return 0; ++ + link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count); +@@ -880,19 +884,6 @@ int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK"); + +-void hda_bus_ml_put_all(struct hdac_bus *bus) +-{ +- struct hdac_ext_link *hlink; +- +- list_for_each_entry(hlink, &bus->hlink_list, list) { +- struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); +- +- if (!h2link->alt) +- snd_hdac_ext_bus_link_put(bus, hlink); +- } +-} +-EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK"); +- + void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +@@ -903,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + } + EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK"); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) ++{ ++ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); ++ ++ if (!h2link->alt) ++ return HDA_BUS_ML_LINK_HDA; ++ ++ switch (h2link->elid) { ++ case AZX_REG_ML_LEPTR_ID_SDW: ++ return HDA_BUS_ML_LINK_SDW; ++ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL: ++ return HDA_BUS_ML_LINK_UAOL; ++ default: ++ return HDA_BUS_ML_LINK_OTHER; ++ } ++} ++EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK"); ++ + int hda_bus_ml_resume(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c +--- a/sound/soc/sof/intel/hda-sdw-bpt.c ++++ b/sound/soc/sof/intel/hda-sdw-bpt.c +@@ -163,7 +163,7 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream + u32 mask; + int ret; + +- ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, true); ++ ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, false, true); + if (ret < 0) { + dev_err(sdev->dev, "%s: SDW BPT DMA cleanup failed\n", + __func__); +diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c +--- a/sound/soc/sof/intel/hda-stream.c ++++ b/sound/soc/sof/intel/hda-stream.c +@@ -198,13 +198,18 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + + mask = (1 << hstream->index); + ++ /* Reset the spib_addr before disabling SPIB */ ++ if (!enable) ++ sof_io_write(sdev, hstream->spib_addr, 0); ++ + /* enable/disable SPIB for the stream */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR, + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask, + enable << hstream->index); + + /* set the SPIB value */ +- sof_io_write(sdev, hstream->spib_addr, size); ++ if (enable) ++ sof_io_write(sdev, hstream->spib_addr, size); + + return 0; + } +@@ -608,6 +613,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + return ret; + } + ++ /* Host DMA is not running */ ++ hstream->running = false; ++ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, +@@ -1323,16 +1331,18 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + EXPORT_SYMBOL_NS(hda_data_stream_prepare, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair) + { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct hdac_stream *hstream = hdac_stream(hext_stream); + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret = 0; + +- if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) ++ if (!is_iccmax) + ret = hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); +- else ++ ++ if (hstream->direction == SNDRV_PCM_STREAM_CAPTURE) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_SD_CTL_DMA_START, 0); + +diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c +--- a/sound/soc/sof/intel/hda.c ++++ b/sound/soc/sof/intel/hda.c +@@ -421,6 +421,20 @@ static inline void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev) { } + /* pre fw run operations */ + int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { ++ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; ++ const struct sof_intel_dsp_desc *chip = hda->desc; ++ int ret; ++ ++ /* Power down DSP if left enabled to ensure a clean boot state. */ ++ if (hda_dsp_core_is_enabled(sdev, chip->host_managed_cores_mask)) { ++ dev_dbg(sdev->dev, "DSP core enabled, power down DSP first\n"); ++ ++ ret = chip->power_down_dsp(sdev); ++ if (ret < 0) ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ } ++ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); + } +@@ -625,8 +639,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + return ret; + } + +- hda_bus_ml_init(bus); +- + /* Skip SoundWire if it is not supported */ + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + goto skip_soundwire; +@@ -670,8 +682,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + if (!HDA_IDISP_CODEC(bus->codec_mask)) + hda_codec_i915_display_power(sdev, false); + +- hda_bus_ml_put_all(bus); +- + return 0; + } + +diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h +--- a/sound/soc/sof/intel/hda.h ++++ b/sound/soc/sof/intel/hda.h +@@ -523,6 +523,29 @@ struct sof_intel_hda_dev { + /* the maximum number of streams (playback + capture) supported */ + u32 stream_max; + ++ /* ++ * ACE2+ link DMA stream allocation constraints (stream index = ++ * stream_tag - 1, shared between input and output directions). All ++ * masks are cleared by hda_dsp_ctrl_init_chip() on controller reset ++ * (CRST#). ++ * ++ * - Concurrent (cross-direction) constraint: a SoundWire stream and ++ * a HDA/iDisp/UAOL stream cannot share a physical stream index ++ * across directions, the resulting LLP/timestamp values are wrong. ++ * link_dma_active_sdw_mask and link_dma_active_multi_mask ++ * (indexed by SNDRV_PCM_STREAM_*) track currently allocated ++ * streams per direction in each of the conflicting groups; SSP ++ * and DMIC do not participate. Bits are cleared on stream release. ++ * ++ * - Sequential (playback only) constraint: once a HDA/iDisp link ++ * has used a playback stream index, that index cannot drive a ++ * non-HDA/iDisp link in the same direction until the next CRST#. ++ * link_dma_out_hda_used_mask records this. ++ */ ++ u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_out_hda_used_mask; ++ + /* PM related */ + bool l1_disabled;/* is DMI link L1 disabled? */ + +@@ -739,7 +762,7 @@ struct hdac_ext_stream *hda_cl_prepare(struct device *dev, unsigned int format, + int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int cmd); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax); + int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); + #define HDA_CL_STREAM_FORMAT 0x40 + +@@ -911,7 +934,8 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + bool is_iccmax, bool pair); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair); + + /* common dai driver */ + extern struct snd_soc_dai_driver skl_dai[]; +@@ -1030,7 +1054,8 @@ struct hda_dai_widget_dma_ops { + struct snd_pcm_substream *substream); + struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream); ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink); + void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, +diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c +--- a/sound/soc/sof/intel/icl.c ++++ b/sound/soc/sof/intel/icl.c +@@ -9,10 +9,14 @@ + * Hardware interface for audio DSP on IceLake. + */ + +-#include +-#include +-#include ++#include + #include ++#include ++#include ++#include ++#include ++#include ++ + #include "../ipc4-priv.h" + #include "../ops.h" + #include "hda.h" +diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c +--- a/sound/soc/sof/intel/mtl.c ++++ b/sound/soc/sof/intel/mtl.c +@@ -236,6 +236,17 @@ int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) + } + EXPORT_SYMBOL_NS(mtl_enable_interrupts, "SND_SOC_SOF_INTEL_MTL"); + ++static bool mtl_dsp_is_enabled(struct snd_sof_dev *sdev) ++{ ++ int val; ++ ++ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS); ++ if (val & MTL_HFDSSCS_CPA_MASK) ++ return true; ++ ++ return false; ++} ++ + /* pre fw run operations */ + static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { +@@ -249,6 +260,18 @@ static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + u32 dsppwrsts; + const struct sof_intel_dsp_desc *chip; + ++ /* Power down the DSP if it is left enabled to ensure clean boot state */ ++ if (mtl_dsp_is_enabled(sdev)) { ++ dev_dbg(sdev->dev, "powering down DSP first\n"); ++ ++ ret = mtl_power_down_dsp(sdev); ++ if (ret < 0) { ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ /* Continue anyway to attempt recovery */ ++ } ++ } ++ + chip = get_chip_info(sdev->pdata); + if (chip->hw_ip_version > SOF_INTEL_ACE_2_0) { + dsppwrctl = PTL_HFPWRCTL2; +diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c +--- a/sound/soc/sof/ipc3-pcm.c ++++ b/sound/soc/sof/ipc3-pcm.c +@@ -421,6 +421,12 @@ static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + dev_dbg(component->dev, "AMD_SDW channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; ++ case SOF_DAI_AMD_I2S: ++ rate->min = private->dai_config->acp_i2s.fsync_rate; ++ rate->max = private->dai_config->acp_i2s.fsync_rate; ++ channels->min = private->dai_config->acp_i2s.tdm_slots; ++ channels->max = private->dai_config->acp_i2s.tdm_slots; ++ break; + default: + dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type); + break; +diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c +--- a/sound/soc/sof/ipc3-topology.c ++++ b/sound/soc/sof/ipc3-topology.c +@@ -281,7 +281,10 @@ static const struct sof_topology_token acpdmic_tokens[] = { + offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)}, + }; + +-/* ACPI2S */ ++/* ++ * ACPI2S tokens fill struct sof_ipc_dai_acp_params; SOF_DAI_AMD_I2S (ACPTDM ++ * on ACP7.B/7.F) reuses this tuple group rather than defining a parallel set. ++ */ + static const struct sof_topology_token acpi2s_tokens[] = { + {SOF_TKN_AMD_ACPI2S_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, fsync_rate)}, +@@ -289,6 +292,8 @@ static const struct sof_topology_token acpi2s_tokens[] = { + offsetof(struct sof_ipc_dai_acp_params, tdm_slots)}, + {SOF_TKN_AMD_ACPI2S_TDM_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, tdm_mode)}, ++ {SOF_TKN_AMD_ACPI2S_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, ++ offsetof(struct sof_ipc_dai_acp_params, format)}, + }; + + /* MICFIL PDM */ +@@ -1363,6 +1368,35 @@ static int sof_link_acp_sdw_load(struct snd_soc_component *scomp, struct snd_sof + return 0; + } + ++static int sof_link_acp_i2s_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, ++ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) ++{ ++ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; ++ struct sof_dai_private_data *private = dai->private; ++ u32 size = sizeof(*config); ++ int ret; ++ ++ /* handle master/slave and inverted clocks */ ++ sof_dai_set_format(hw_config, config); ++ ++ /* init IPC */ ++ memset(&config->acp_i2s, 0, sizeof(config->acp_i2s)); ++ config->hdr.size = size; ++ ++ ret = sof_update_ipc_object(scomp, &config->acp_i2s, SOF_ACPI2S_TOKENS, slink->tuples, ++ slink->num_tuples, size, slink->num_hw_configs); ++ if (ret < 0) ++ return ret; ++ ++ dai->number_configs = 1; ++ dai->current_config = 0; ++ private->dai_config = kmemdup(config, size, GFP_KERNEL); ++ if (!private->dai_config) ++ return -ENOMEM; ++ ++ return 0; ++} ++ + static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) + { +@@ -1692,6 +1726,9 @@ static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget) + case SOF_DAI_AMD_SDW: + ret = sof_link_acp_sdw_load(scomp, slink, config, dai); + break; ++ case SOF_DAI_AMD_I2S: ++ ret = sof_link_acp_i2s_load(scomp, slink, config, dai); ++ break; + default: + break; + } +diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c +--- a/sound/soc/sof/ipc4-topology.c ++++ b/sound/soc/sof/ipc4-topology.c +@@ -163,11 +163,11 @@ static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_SCHED_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_domain, + offsetof(struct snd_sof_widget, comp_domain)}, + {SOF_TKN_COMP_DOMAIN_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_domain_id)}, ++ offsetof(struct snd_sof_widget, domain_id)}, + {SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_heap_bytes)}, ++ offsetof(struct snd_sof_widget, heap_bytes)}, + {SOF_TKN_COMP_STACK_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_stack_bytes)}, ++ offsetof(struct snd_sof_widget, stack_bytes)}, + }; + + static const struct sof_topology_token gain_tokens[] = { +@@ -406,6 +406,39 @@ sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) + return NULL; + } + ++static void ++sof_ipc4_evaluate_params_change(struct sof_ipc4_available_audio_format *available_fmt) ++{ ++ struct sof_ipc4_audio_format *fmt; ++ u32 in_rate, in_channels, in_valid_bits; ++ u32 out_rate, out_channels, out_valid_bits; ++ u32 changed_params = 0; ++ int i, j; ++ ++ for (i = 0; i < available_fmt->num_input_formats; i++) { ++ fmt = &available_fmt->input_pin_fmts[i].audio_fmt; ++ in_rate = fmt->sampling_frequency; ++ in_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ in_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ for (j = 0; j < available_fmt->num_output_formats; j++) { ++ fmt = &available_fmt->output_pin_fmts[j].audio_fmt; ++ out_rate = fmt->sampling_frequency; ++ out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ if (in_rate != out_rate) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_RATE); ++ if (in_channels != out_channels) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_CHANNELS); ++ if (in_valid_bits != out_valid_bits) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ } ++ } ++ ++ available_fmt->changed_params = changed_params; ++} ++ + /** + * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples + * @scomp: pointer to pointer to SOC component +@@ -497,6 +530,8 @@ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, + available_fmt->num_output_formats); + } + ++ sof_ipc4_evaluate_params_change(available_fmt); ++ + return 0; + + err_out: +@@ -661,6 +696,9 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + /* + * This callback is used by host copier and module-to-module copier, + * and only host copier needs to set gtw_cfg. +@@ -789,6 +827,9 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + ret = sof_update_ipc_object(scomp, &node_type, + SOF_COPIER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(node_type), 1); +@@ -1371,6 +1412,22 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * + pipeline = pipe_widget->private; + pipeline->mem_usage += total; + ++ /* ++ * If this is not a Data Processing module instance, add the ++ * required heap sizes to the sum of all module instances belonging ++ * to the same pipeline, and find the maximum stack requirement ++ * among all module instances belonging to the same pipeline. ++ */ ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { ++ pipe_widget->heap_bytes += swidget->heap_bytes; ++ if (pipe_widget->stack_bytes < swidget->stack_bytes) ++ pipe_widget->stack_bytes = swidget->stack_bytes; ++ ++ dev_dbg(sdev->dev, "%s mem reqs to %s heap %u stack %u", ++ swidget->widget->name, pipe_widget->widget->name, ++ pipe_widget->heap_bytes, pipe_widget->stack_bytes); ++ } ++ + /* Update base_config->cpc from the module manifest */ + sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config); + +@@ -1688,6 +1745,8 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + pipeline->mem_usage = 0; ++ pipe_widget->heap_bytes = 0; ++ pipe_widget->stack_bytes = 0; + + if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) { + if (pipeline->use_chain_dma) { +@@ -2065,10 +2124,57 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget + } + + static int +-sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, +- struct snd_pcm_hw_params *fe_params, +- struct snd_sof_platform_stream_params *platform_params, +- struct snd_pcm_hw_params *pipeline_params, int dir) ++sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *pipeline_params) ++{ ++ struct snd_soc_component *scomp = swidget->scomp; ++ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); ++ struct sof_ipc4_copier_data *copier_data; ++ struct sof_ipc4_copier *ipc4_copier; ++ ++ switch (swidget->id) { ++ case snd_soc_dapm_aif_in: ++ case snd_soc_dapm_aif_out: ++ case snd_soc_dapm_buffer: ++ ipc4_copier = swidget->private; ++ copier_data = &ipc4_copier->data; ++ break; ++ case snd_soc_dapm_dai_in: ++ case snd_soc_dapm_dai_out: ++ { ++ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; ++ struct sof_ipc4_pipeline *pipeline = pipe_widget->private; ++ struct snd_sof_dai *dai; ++ ++ if (pipeline->use_chain_dma) ++ return 0; ++ ++ dai = swidget->private; ++ ++ ipc4_copier = (struct sof_ipc4_copier *)dai->private; ++ copier_data = &ipc4_copier->data; ++ ++ break; ++ } ++ default: ++ dev_err(sdev->dev, "unsupported type %d for copier %s", ++ swidget->id, swidget->widget->name); ++ return -EINVAL; ++ } ++ ++ /* modify the input params for the next widget */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &copier_data->out_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++} ++ ++static int ++_sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) + { + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; +@@ -2540,6 +2646,21 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, + return 0; + } + ++static int ++sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) ++{ ++ if (swidget->prepared) ++ return sof_ipc4_copier_module_update_params(swidget, ++ pipeline_params); ++ ++ return _sof_ipc4_prepare_copier_module(swidget, fe_params, ++ platform_params, pipeline_params, ++ dir); ++} ++ + static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, +@@ -2553,6 +2674,10 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &gain->data.base_config, + pipeline_params, +@@ -2598,6 +2723,10 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* Already prepared, nothing to do */ ++ if (swidget->prepared) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &mixer->base_config, + pipeline_params, +@@ -2644,6 +2773,10 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int output_fmt_index, input_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &src->data.base_config, + pipeline_params, +@@ -2810,6 +2943,18 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + int ret; + + if (available_fmt->num_input_formats) { ++ if (swidget->prepared) { ++ if (!available_fmt->num_output_formats) ++ return 0; ++ ++ /* modify the pipeline params with the output format */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &process->output_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ } ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &process->base_config, + pipeline_params, +@@ -2822,39 +2967,69 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + if (available_fmt->num_output_formats) { + struct sof_ipc4_audio_format *in_fmt; + struct sof_ipc4_pin_format *pin_fmt; +- u32 out_ref_rate, out_ref_channels; +- int out_ref_valid_bits, out_ref_type; ++ u32 ref_rate, ref_channels; ++ int ref_valid_bits, ref_type; + + if (available_fmt->num_input_formats) { ++ /* ++ * The process module can change parameters and their operation ++ * depends on the direction: ++ * Playback: typically they have single output format. This is ++ * to 'force' the conversion from input to output. ++ * Use the input format as reference since the single ++ * format is going to be picked. ++ * Capture: typically they have multiple output formats to ++ * convert from dai (input) to FE (output) parameters. ++ * Use the input format as base and replace the param ++ * which is changed by the module with the FE parameter ++ * Reason: we can have module which changes the ++ * parameters in path, we cannot use the full ++ * FE param set for the module output lookup. ++ */ + in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; + +- out_ref_rate = in_fmt->sampling_frequency; +- out_ref_channels = ++ ref_rate = in_fmt->sampling_frequency; ++ ref_channels = + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); +- out_ref_valid_bits = ++ ref_valid_bits = + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); +- out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); ++ ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ +- out_ref_rate = params_rate(fe_params); +- out_ref_channels = params_channels(fe_params); ++ ref_rate = params_rate(fe_params); ++ ref_channels = params_channels(fe_params); + ret = sof_ipc4_get_sample_type(sdev, fe_params); + if (ret < 0) + return ret; +- out_ref_type = (u32)ret; ++ ref_type = (u32)ret; + +- out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); +- if (out_ref_valid_bits < 0) +- return out_ref_valid_bits; ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; + } + ++ if (dir == SNDRV_PCM_STREAM_CAPTURE) { ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) ++ ref_rate = params_rate(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) ++ ref_channels = params_channels(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; ++ ++ ref_type = sof_ipc4_get_sample_type(sdev, fe_params); ++ if (ref_type < 0) ++ return ref_type; ++ } ++ } + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, + &process->base_config, + available_fmt, +- out_ref_rate, +- out_ref_channels, +- out_ref_valid_bits, +- out_ref_type); ++ ref_rate, ++ ref_channels, ++ ref_valid_bits, ++ ref_type); + if (output_fmt_index < 0) + return output_fmt_index; + +@@ -2868,9 +3043,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + /* modify the pipeline params with the output format */ + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, + &process->output_format, +- BIT(SNDRV_PCM_HW_PARAM_FORMAT) | +- BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | +- BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ available_fmt->changed_params); + if (ret) + return ret; + } +@@ -3085,27 +3258,47 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr + return 0; + } + +-static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, +- struct snd_sof_widget *swidget, +- struct sof_ipc4_msg *msg, +- void *ipc_data, u32 ipc_size, +- void **new_data) ++static void sof_ipc4_add_init_ext_dp_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_module_init_ext_object **hdr) ++{ ++ /* Add memory_data if comp_domain indicates DP */ ++ if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { ++ struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; ++ ++ *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | ++ SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; ++ dp_mem_data->domain_id = swidget->domain_id; ++ dp_mem_data->stack_bytes = swidget->stack_bytes; ++ dp_mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ } ++} ++ ++static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void *ipc_data, u32 ipc_size, ++ void **new_data) + { +- struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; + struct sof_ipc4_module_init_ext_init *ext_init; +- struct sof_ipc4_module_init_ext_object *hdr; ++ struct sof_ipc4_module_init_ext_object *hdr = NULL; + int new_size; + u32 *payload; + u32 ext_pos; + +- /* For the moment the only reason for adding init_ext_init payload is DP +- * memory data. If both stack and heap size are 0 (= use default), then +- * there is no need for init_ext_init payload. ++ /* ++ * Only DP widgets currently add init-ext objects here. Avoid allocating ++ * a max-sized payload buffer for widgets that will immediately return 0. + */ +- if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { +- msg->extension &= ~SOF_IPC4_MOD_EXT_EXTENDED_INIT_MASK; ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) + return 0; +- } + + payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); + if (!payload) +@@ -3113,27 +3306,26 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + + /* Add ext_init first and set objects array flag to 1 */ + ext_init = (struct sof_ipc4_module_init_ext_init *)payload; +- ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; + ext_pos = DIV_ROUND_UP(sizeof(*ext_init), sizeof(u32)); + + /* Add object array objects after ext_init */ + +- /* Add dp_memory_data if comp_domain indicates DP */ +- if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { +- hdr = (struct sof_ipc4_module_init_ext_object *)&payload[ext_pos]; +- hdr->header = SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK | +- SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | +- SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), +- sizeof(u32))); +- ext_pos += DIV_ROUND_UP(sizeof(*hdr), sizeof(u32)); +- dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[ext_pos]; +- dp_mem_data->domain_id = swidget->dp_domain_id; +- dp_mem_data->stack_bytes = swidget->dp_stack_bytes; +- dp_mem_data->heap_bytes = swidget->dp_heap_bytes; +- ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ ++ /* Add following object array items here */ ++ ++ if (!hdr) { ++ /* ++ * NOTE: Remove this early bail out, when struct ++ * sof_ipc4_module_init_ext_init alone has some ++ * function. ++ */ ++ kfree(payload); ++ return 0; + } + +- /* If another array object is added, remember clear previous OBJ_LAST bit */ ++ ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; ++ hdr->header |= SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK; + + /* Calculate final size and check that it fits to max payload size */ + new_size = ext_pos * sizeof(u32) + ipc_size; +@@ -3156,6 +3348,69 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + return new_size; + } + ++static void sof_ipc4_widget_pipe_ext_obj_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_glb_pipe_ext_object **hdr) ++{ ++ struct sof_ipc4_glb_pipe_ext_obj_memory_data *mem_data; ++ ++ *hdr = (struct sof_ipc4_glb_pipe_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA) | ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ mem_data = (struct sof_ipc4_glb_pipe_ext_obj_memory_data *)&payload[*ext_pos]; ++ mem_data->domain_id = swidget->domain_id; ++ mem_data->stack_bytes = swidget->stack_bytes; ++ mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*mem_data), sizeof(u32)); ++ ++ dev_dbg(sdev->dev, ++ "%s; domain_id %u stack %u heap %u bytes", ++ swidget->widget->name, mem_data->domain_id, mem_data->stack_bytes, ++ mem_data->heap_bytes); ++} ++ ++static int sof_ipc4_widget_pipe_create_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void **new_data) ++{ ++ struct sof_ipc4_glb_pipe_payload *payload_hdr; ++ struct sof_ipc4_glb_pipe_ext_object *hdr = NULL; ++ u32 *payload; ++ u32 ext_pos; ++ ++ payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); ++ if (!payload) ++ return -ENOMEM; ++ ++ /* Add sof_ipc4_glb_pipe_payload and set array bit to 1 */ ++ payload_hdr = (struct sof_ipc4_glb_pipe_payload *)payload; ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK; ++ ext_pos = DIV_ROUND_UP(sizeof(*payload_hdr), sizeof(u32)); ++ ++ sof_ipc4_widget_pipe_ext_obj_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ /* Add following array objects here */ ++ ++ /* Mark end of object array */ ++ hdr->header |= SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK; ++ ++ /* Put total payload size in words to the payload header */ ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(ext_pos); ++ *new_data = payload; ++ ++ /* Update msg extension bits according to the payload changes */ ++ msg->extension |= SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ++ dev_dbg(sdev->dev, "%s: payload word0 %#x", swidget->widget->name, ++ payload_hdr->word0); ++ ++ return ext_pos * sizeof(int32_t); ++} ++ + static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) + { + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; +@@ -3342,8 +3597,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name, swidget->pipeline_id, module_id, + swidget->instance_id, swidget->core); + +- ret = sof_ipc4_widget_setup_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, +- &ext_data); ++ ret = sof_ipc4_widget_mod_init_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, ++ &ext_data); + if (ret < 0) + goto fail; + +@@ -3355,6 +3610,17 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + dev_dbg(sdev->dev, "Create pipeline %s (pipe %d) - instance %d, core %d\n", + swidget->widget->name, swidget->pipeline_id, + swidget->instance_id, swidget->core); ++ ++ msg->extension &= ~SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ret = sof_ipc4_widget_pipe_create_msg_payload(sdev, swidget, msg, ++ &ext_data); ++ if (ret < 0) ++ goto fail; ++ ++ if (ret > 0) { ++ ipc_size = ret; ++ ipc_data = ext_data; ++ } + } + + msg->data_size = ipc_size; +@@ -3412,6 +3678,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name); + + pipeline->mem_usage = 0; ++ swidget->heap_bytes = 0; ++ swidget->stack_bytes = 0; + pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + ida_free(&pipeline_ida, swidget->instance_id); + swidget->instance_id = -EINVAL; +diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h +--- a/sound/soc/sof/ipc4-topology.h ++++ b/sound/soc/sof/ipc4-topology.h +@@ -167,7 +167,7 @@ struct sof_ipc4_pipeline { + }; + + /** +- * struct sof_ipc4_multi_pipeline_data - multi pipeline trigger IPC data ++ * struct ipc4_pipeline_set_state_data - multi pipeline trigger IPC data + * @count: Number of pipelines to be triggered + * @pipeline_instance_ids: Flexible array of IDs of the pipelines to be triggered + */ +@@ -197,12 +197,15 @@ struct sof_ipc4_pin_format { + * @input_pin_fmts: Available input pin formats + * @num_input_formats: Number of input pin formats + * @num_output_formats: Number of output pin formats ++ * @changed_params: Mask of changed params by the module instance between it's ++ * input and output formts (rate, channels, depth) + */ + struct sof_ipc4_available_audio_format { + struct sof_ipc4_pin_format *output_pin_fmts; + struct sof_ipc4_pin_format *input_pin_fmts; + u32 num_input_formats; + u32 num_output_formats; ++ u32 changed_params; + }; + + /** +@@ -210,7 +213,7 @@ struct sof_ipc4_available_audio_format { + * @node_id: ID of Gateway Node + * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes) + * @config_length: Length of gateway node configuration blob specified in #config_data +- * config_data: Gateway node configuration blob ++ * @config_data: Gateway node configuration blob + */ + struct sof_copier_gateway_cfg { + uint32_t node_id; +@@ -285,7 +288,9 @@ struct sof_ipc4_dma_stream_ch_map { + struct sof_ipc4_dma_config { + uint8_t dma_method; + uint8_t pre_allocated_by_host; ++ /* private: */ + uint16_t rsvd; ++ /* public: */ + uint32_t dma_channel_id; + uint32_t stream_id; + struct sof_ipc4_dma_stream_ch_map dma_stream_channel_map; +@@ -296,7 +301,7 @@ struct sof_ipc4_dma_config { + #define SOF_IPC4_GTW_DMA_CONFIG_ID 0x1000 + + /** +- * struct sof_ipc4_dma_config: DMA configuration ++ * struct sof_ipc4_dma_config_tlv - DMA configuration + * @type: set to SOF_IPC4_GTW_DMA_CONFIG_ID + * @length: sizeof(struct sof_ipc4_dma_config) + dma_config.dma_priv_config_size + * @dma_config: actual DMA configuration +@@ -321,6 +326,7 @@ struct sof_ipc4_alh_configuration_blob { + * @data: IPC copier data + * @copier_config: Copier + blob + * @ipc_config_size: Size of copier_config ++ * @ipc_config_data: Copier module config data + * @available_fmt: Available audio format + * @frame_fmt: frame format + * @msg: message structure for copier +diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c +--- a/sound/soc/sof/ipc4.c ++++ b/sound/soc/sof/ipc4.c +@@ -289,6 +289,89 @@ static void sof_ipc4_dump_payload(struct snd_sof_dev *sdev, + 16, 4, ipc_data, size, false); + } + ++static const char *sof_ipc4_resource_type_str(u32 type) ++{ ++ switch (type) { ++ case SOF_IPC4_MODULE_INSTANCE: ++ return "resource: MODULE_INSTANCE"; ++ case SOF_IPC4_PIPELINE: ++ return "resource: PIPELINE"; ++ case SOF_IPC4_GATEWAY: ++ return "resource: GATEWAY"; ++ case SOF_IPC4_EDF_TASK: ++ return "resource: EDF_TASK"; ++ case SOF_IPC4_INVALID_RESOURCE_TYPE: ++ return "Resource is invalid"; ++ default: ++ return "Unknown resource type"; ++ } ++} ++ ++static const char *sof_ipc4_resource_event_type_str(u32 event_type) ++{ ++ switch (event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ return "event: MIXER_UNDERRUN_DETECTED"; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ return "event: PROCESS_DATA_ERROR"; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ return "event: GATEWAY_UNDERRUN_DETECTED"; ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ return "event: GATEWAY_OVERRUN_DETECTED"; ++ default: ++ return "Unknown event type"; ++ } ++} ++ ++static void sof_ipc4_resource_event_handler(struct snd_sof_dev *sdev, ++ struct sof_ipc4_msg *ipc4_msg) ++{ ++ struct sof_ipc4_notify_resource_data *data = ipc4_msg->data_ptr; ++ ++ /* Print event details */ ++ switch (data->event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u): eos %u, mixed %u, expected %u\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.mixer_underrun.eos_flag, ++ data->data.mixer_underrun.data_mixed, ++ data->data.mixer_underrun.expected_data_mixed); ++ break; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ dev_dbg(sdev->dev, "%s (%u): error_code %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.process_data_error.error_code); ++ break; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u)\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type); ++ break; ++ default: ++ dev_dbg(sdev->dev, "%s (%u): raw dws %#x %#x %#x %#x %#x %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, ++ data->data.dws[0], data->data.dws[1], data->data.dws[2], ++ data->data.dws[3], data->data.dws[4], data->data.dws[5]); ++ break; ++ } ++ ++ /* Print resource details */ ++ if (data->resource_type == SOF_IPC4_MODULE_INSTANCE) { ++ u32 module_id = SOF_IPC4_MOD_ID_GET(data->resource_id); ++ u32 instance_id = SOF_IPC4_MOD_INSTANCE_GET(data->resource_id); ++ ++ dev_dbg(sdev->dev, "%s (%u), module_id %u, instance_id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, module_id, instance_id); ++ } else if (data->resource_type != SOF_IPC4_INVALID_RESOURCE_TYPE) { ++ dev_dbg(sdev->dev, "%s (%u), id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, data->resource_id); ++ } ++} ++ + static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) + { + struct snd_sof_ipc_msg *msg = sdev->msg; +@@ -734,6 +817,7 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev) + break; + case SOF_IPC4_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct sof_ipc4_notify_resource_data); ++ handler_func = sof_ipc4_resource_event_handler; + break; + case SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS: + sof_ipc4_mtrace_update_pos(sdev, SOF_IPC4_LOG_CORE_GET(ipc4_msg->primary)); +diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c +--- a/sound/soc/sof/nocodec.c ++++ b/sound/soc/sof/nocodec.c +@@ -21,12 +21,13 @@ static struct snd_soc_card sof_nocodec_card = { + static int sof_nocodec_bes_setup(struct device *dev, + struct snd_soc_dai_driver *drv, + struct snd_soc_dai_link *links, +- int link_num, struct snd_soc_card *card) ++ int link_num) + { ++ struct snd_soc_card *card = &sof_nocodec_card; + struct snd_soc_dai_link_component *dlc; + int i; + +- if (!drv || !links || !card) ++ if (!drv || !links) + return -EINVAL; + + /* set up BE dai_links */ +@@ -67,10 +68,10 @@ static int sof_nocodec_bes_setup(struct device *dev, + return 0; + } + +-static int sof_nocodec_setup(struct device *dev, +- u32 num_dai_drivers, +- struct snd_soc_dai_driver *dai_drivers) ++static int sof_nocodec_setup(struct device *dev, struct snd_soc_acpi_mach *mach) + { ++ u32 num_dai_drivers = mach->mach_params.num_dai_drivers; ++ struct snd_soc_dai_driver *dai_drivers = mach->mach_params.dai_drivers; + struct snd_soc_dai_link *links; + + /* create dummy BE dai_links */ +@@ -78,7 +79,7 @@ static int sof_nocodec_setup(struct device *dev, + if (!links) + return -ENOMEM; + +- return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers, &sof_nocodec_card); ++ return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers); + } + + static int sof_nocodec_probe(struct platform_device *pdev) +@@ -92,8 +93,7 @@ static int sof_nocodec_probe(struct platform_device *pdev) + + snd_soc_card_set_topology_name(card, "sof"); + +- ret = sof_nocodec_setup(card->dev, mach->mach_params.num_dai_drivers, +- mach->mach_params.dai_drivers); ++ ret = sof_nocodec_setup(card->dev, mach); + if (ret < 0) + return ret; + +diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c +--- a/sound/soc/sof/pcm.c ++++ b/sound/soc/sof/pcm.c +@@ -62,7 +62,7 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) + * To avoid sending IPC before the previous IPC is handled, we + * schedule delayed work here to call the snd_pcm_period_elapsed(). + */ +- schedule_work(&spcm->stream[substream->stream].period_elapsed_work); ++ queue_work(system_highpri_wq, &spcm->stream[substream->stream].period_elapsed_work); + } + EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); + +diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c +--- a/sound/soc/sof/sof-audio.c ++++ b/sound/soc/sof/sof-audio.c +@@ -501,9 +501,8 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget + !sof_widget_in_same_direction(swidget, dir)) + return 0; + +- /* skip widgets already prepared or aggregated DAI widgets*/ +- if (!widget_ops[widget->id].ipc_prepare || swidget->prepared || +- is_aggregated_dai(swidget)) ++ /* skip widgets aggregated DAI widgets */ ++ if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget)) + goto sink_prepare; + + /* prepare the source widget */ +diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h +--- a/sound/soc/sof/sof-audio.h ++++ b/sound/soc/sof/sof-audio.h +@@ -459,10 +459,10 @@ struct snd_sof_widget { + /* Scheduling domain (enum sof_comp_domain), unset, Low Latency, or Data Processing */ + u32 comp_domain; + +- /* The values below are added to mod_init pay load if comp_domain indicates DP component */ +- u32 dp_domain_id; /* DP process userspace domain ID */ +- u32 dp_stack_bytes; /* DP process stack size requirement in bytes */ +- u32 dp_heap_bytes; /* DP process heap size requirement in bytes */ ++ /* Module instance's memory configuration. */ ++ u32 domain_id; /* Module instance's userspace domain ID */ ++ u32 stack_bytes; /* Module instance's stack size requirement */ ++ u32 heap_bytes; /* Module instance's heap size requirement */ + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ +diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c +--- a/sound/soc/sof/topology.c ++++ b/sound/soc/sof/topology.c +@@ -309,7 +309,7 @@ static const struct sof_dai_types sof_dais[] = { + {"ACPHS_VIRTUAL", SOF_DAI_AMD_HS_VIRTUAL}, + {"MICFIL", SOF_DAI_IMX_MICFIL}, + {"ACP_SDW", SOF_DAI_AMD_SDW}, +- ++ {"ACPTDM", SOF_DAI_AMD_I2S}, + }; + + static enum sof_ipc_dai_type find_dai(const char *name) +@@ -1108,7 +1108,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, + + full = NULL; + partial = NULL; +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (rtd->dai_link->stream_name) { + if (!strcmp(rtd->dai_link->stream_name, w->sname)) { +@@ -1173,7 +1173,7 @@ static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, + else + return; + +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (!rtd->dai_link->stream_name || + !strstr(rtd->dai_link->stream_name, sname)) +@@ -1949,8 +1949,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + private->array, le32_to_cpu(private->size)); + if (ret < 0) { + dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); +- kfree(slink); +- return ret; ++ goto free_slink; + } + + token_list = tplg_ops ? tplg_ops->token_list : NULL; +@@ -2001,6 +2000,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_HS_VIRTUAL: ++ case SOF_DAI_AMD_I2S: + token_id = SOF_ACPI2S_TOKENS; + num_tuples += token_list[SOF_ACPI2S_TOKENS].count; + break; +@@ -2019,8 +2019,8 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + /* allocate memory for tuples array */ + slink->tuples = kzalloc_objs(*slink->tuples, num_tuples); + if (!slink->tuples) { +- kfree(slink); +- return -ENOMEM; ++ ret = -ENOMEM; ++ goto free_slink; + } + + if (token_list[SOF_DAI_LINK_TOKENS].tokens) { +@@ -2076,6 +2076,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + + err: + kfree(slink->tuples); ++free_slink: + kfree(slink); + + return ret; +@@ -2511,13 +2512,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const char *tplg_filename_prefix = sof_pdata->tplg_filename_prefix; +- const struct firmware *fw; +- const char **tplg_files; + int tplg_cnt = 0; + int ret; + int i; + +- tplg_files = kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); ++ const char **tplg_files __free(kfree) = ++ kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + if (!tplg_files) + return -ENOMEM; + +@@ -2543,10 +2543,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + tplg_filename_prefix, + &tplg_files, + no_fallback); +- if (tplg_cnt < 0) { +- kfree(tplg_files); ++ if (tplg_cnt < 0) + return tplg_cnt; +- } + } + + /* +@@ -2557,8 +2555,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (strstr(file, "dummy")) { + dev_err(scomp->dev, + "Function topology is required, please upgrade sof-firmware\n"); +- +- kfree(tplg_files); + return -EINVAL; + } + tplg_files[0] = file; +@@ -2573,6 +2569,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (tplg_files[0] != file) + dev_info(scomp->dev, "loading topology %d: %s\n", i, tplg_files[i]); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_files[i], scomp->dev); + if (ret < 0) { + /* +@@ -2591,8 +2588,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "tplg %s component load failed %d\n", + tplg_files[i], ret); +@@ -2611,6 +2606,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + goto out; + } + dev_info(scomp->dev, "loading feature topology %d: %s\n", i, feature_topology); ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, feature_topology, scomp->dev); + if (ret < 0) { + /* +@@ -2635,8 +2632,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "feature tplg %s component load failed %d\n", + feature_topologies[i], ret); +@@ -2655,8 +2650,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (ret >= 0 && sdev->led_present) + ret = snd_ctl_led_request(); + +- kfree(tplg_files); +- + return ret; + } + EXPORT_SYMBOL(snd_sof_load_topology); +diff --git a/sound/soc/sophgo/cv1800b-tdm.c b/sound/soc/sophgo/cv1800b-tdm.c +--- a/sound/soc/sophgo/cv1800b-tdm.c ++++ b/sound/soc/sophgo/cv1800b-tdm.c +@@ -558,7 +558,7 @@ static const struct snd_soc_dai_ops cv1800b_i2s_dai_ops = { + .set_sysclk = cv1800b_i2s_dai_set_sysclk, + }; + +-static const struct snd_soc_dai_driver cv1800b_i2s_dai_template = { ++static struct snd_soc_dai_driver cv1800b_i2s_dai_template = { + .name = "cv1800b-i2s", + .playback = { + .stream_name = "Playback", +@@ -636,7 +636,6 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + struct cv1800b_i2s *i2s; + struct resource *res; + void __iomem *regs; +- struct snd_soc_dai_driver *dai; + int ret; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); +@@ -666,21 +665,14 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, i2s); + cv1800b_i2s_setup_tdm(i2s); + +- dai = devm_kmemdup(dev, &cv1800b_i2s_dai_template, sizeof(*dai), +- GFP_KERNEL); +- if (!dai) +- return -ENOMEM; +- +- ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, dai, +- 1); ++ ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, ++ &cv1800b_i2s_dai_template, 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(dev, &cv1800b_i2s_pcm_config, 0); +- if (ret) { +- dev_err(dev, "dmaengine_pcm_register failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c +--- a/sound/soc/spacemit/k1_i2s.c ++++ b/sound/soc/spacemit/k1_i2s.c +@@ -335,6 +335,11 @@ static int spacemit_i2s_dai_remove(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 spacemit_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .probe = spacemit_i2s_dai_probe, + .remove = spacemit_i2s_dai_remove, +@@ -343,6 +348,8 @@ static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .set_sysclk = spacemit_i2s_set_sysclk, + .set_fmt = spacemit_i2s_set_fmt, + .trigger = spacemit_i2s_trigger, ++ .auto_selectable_formats = &spacemit_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver spacemit_i2s_dai = { +@@ -459,7 +466,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + + i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->base)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->base), "failed to map registers\n"); ++ return PTR_ERR(i2s->base); + + i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s->reset)) +@@ -476,7 +483,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + &spacemit_i2s_component, + dai, 1); + if (ret) +- return dev_err_probe(i2s->dev, ret, "failed to register component"); ++ return ret; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, &spacemit_dmaengine_pcm_config, 0); + } +diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c +--- a/sound/soc/spear/spdif_in.c ++++ b/sound/soc/spear/spdif_in.c +@@ -55,7 +55,7 @@ static int spdif_in_dai_probe(struct snd_soc_dai *dai) + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + host->dma_params_rx.filter_data = &host->dma_params; +- dai->capture_dma_data = &host->dma_params_rx; ++ snd_soc_dai_dma_data_set_capture(dai, &host->dma_params_rx); + + return 0; + } +@@ -150,12 +150,12 @@ static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + + static const struct snd_soc_dai_ops spdif_in_dai_ops = { + .shutdown = spdif_in_shutdown, ++ .probe = spdif_in_dai_probe, + .trigger = spdif_in_trigger, + .hw_params = spdif_in_hw_params, + }; + + static struct snd_soc_dai_driver spdif_in_dai = { +- .probe = spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, +@@ -218,10 +218,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + host->io_base = io_base; + host->irq = platform_get_irq(pdev, 0); +- if (host->irq < 0) { +- dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq); ++ if (host->irq < 0) + return host->irq; +- } + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) +@@ -243,10 +241,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, + "spdif-in", host); +- if (ret) { +- dev_warn(&pdev->dev, "request_irq failed\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component, + &spdif_in_dai, 1); +diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c +--- a/sound/soc/spear/spdif_out.c ++++ b/sound/soc/spear/spdif_out.c +@@ -249,6 +249,7 @@ static int spdif_soc_dai_probe(struct snd_soc_dai *dai) + } + + static const struct snd_soc_dai_ops spdif_out_dai_ops = { ++ .probe = spdif_soc_dai_probe, + .mute_stream = spdif_mute, + .startup = spdif_out_startup, + .shutdown = spdif_out_shutdown, +@@ -266,7 +267,6 @@ static struct snd_soc_dai_driver spdif_out_dai = { + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +- .probe = spdif_soc_dai_probe, + .ops = &spdif_out_dai_ops, + }; + +diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c +--- a/sound/soc/sprd/sprd-mcdt.c ++++ b/sound/soc/sprd/sprd-mcdt.c +@@ -931,10 +931,8 @@ static int sprd_mcdt_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, + 0, "sprd-mcdt", mcdt); +- if (ret) { +- dev_err(&pdev->dev, "Failed to request MCDT IRQ\n"); ++ if (ret) + return ret; +- } + + sprd_mcdt_init_chans(mcdt, res); + +diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c +--- a/sound/soc/sprd/sprd-pcm-dma.c ++++ b/sound/soc/sprd/sprd-pcm-dma.c +@@ -469,8 +469,6 @@ static int sprd_soc_platform_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &sprd_soc_component, + NULL, 0); +- if (ret) +- dev_err(&pdev->dev, "could not register platform:%d\n", ret); + + return ret; + } +diff --git a/sound/soc/starfive/jh7110_pwmdac.c b/sound/soc/starfive/jh7110_pwmdac.c +--- a/sound/soc/starfive/jh7110_pwmdac.c ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -477,25 +477,22 @@ static int jh7110_pwmdac_probe(struct platform_device *pdev) + &jh7110_pwmdac_component, + &jh7110_pwmdac_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); ++ return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); ++ return ret; + + pm_runtime_enable(dev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_pwmdac_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_pwmdac_remove(struct platform_device *pdev) +diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c +--- a/sound/soc/starfive/jh7110_tdm.c ++++ b/sound/soc/starfive/jh7110_tdm.c +@@ -559,10 +559,8 @@ static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, + tdm->clks[5].id = "tdm"; + + ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(tdm->clks), tdm->clks); +- if (ret) { +- dev_err(&pdev->dev, "Failed to get tdm clocks\n"); ++ if (ret) + return ret; +- } + + tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev); + if (IS_ERR(tdm->resets)) { +@@ -589,42 +587,33 @@ static int jh7110_tdm_probe(struct platform_device *pdev) + tdm->dev = &pdev->dev; + + ret = jh7110_tdm_clk_reset_get(pdev, tdm); +- if (ret) { +- dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n"); ++ if (ret) + return ret; +- } + + jh7110_tdm_init_params(tdm); + + dev_set_drvdata(&pdev->dev, tdm); + ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component, + &jh7110_tdm_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register dai\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &jh7110_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +- if (ret) { +- dev_err(&pdev->dev, "Could not register pcm: %d\n", ret); ++ if (ret) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_tdm_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_tdm_dev_remove(struct platform_device *pdev) +diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c +--- a/sound/soc/stm/stm32_adfsdm.c ++++ b/sound/soc/stm/stm32_adfsdm.c +@@ -360,11 +360,8 @@ static int stm32_adfsdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_soc_platform, + NULL, 0); +- if (ret < 0) { +- dev_err(&pdev->dev, "%s: Failed to register PCM platform\n", +- __func__); ++ if (ret < 0) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + +diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c +--- a/sound/soc/stm/stm32_i2s.c ++++ b/sound/soc/stm/stm32_i2s.c +@@ -1238,10 +1238,8 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); +- if (ret) { +- dev_err(&pdev->dev, "irq request returned %d\n", ret); ++ if (ret) + return ret; +- } + + /* Reset */ + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); +@@ -1295,7 +1293,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) + + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, &stm32_i2s_component, + i2s->dai_drv, 1); +diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c +--- a/sound/soc/stm/stm32_sai_sub.c ++++ b/sound/soc/stm/stm32_sai_sub.c +@@ -1696,19 +1696,15 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + goto err_unprepare_pclk; +- } + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = snd_dmaengine_pcm_register(&pdev->dev, conf, 0); +- if (ret) { +- ret = dev_err_probe(&pdev->dev, ret, "Could not register pcm dma\n"); ++ if (ret) + goto err_unprepare_pclk; +- } + + ret = snd_soc_register_component(&pdev->dev, &stm32_component, + &sai->cpu_dai_drv, 1); +diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c +--- a/sound/soc/stm/stm32_spdifrx.c ++++ b/sound/soc/stm/stm32_spdifrx.c +@@ -970,10 +970,8 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, spdifrx->irq, stm32_spdifrx_isr, 0, + dev_name(&pdev->dev), spdifrx); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + return ret; +- } + + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(rst)) +@@ -987,7 +985,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + pcm_config = &stm32_spdifrx_pcm_config; + ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, + &stm32_spdifrx_component, +diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c +--- a/sound/soc/sunxi/sun4i-codec.c ++++ b/sound/soc/sunxi/sun4i-codec.c +@@ -22,13 +22,13 @@ + #include + + #include ++#include ++#include + #include + #include + #include + #include + #include +-#include +-#include + + /* Codec DAC digital controls and FIFO registers */ + #define SUN4I_CODEC_DAC_DPC (0x00) +@@ -2317,67 +2317,54 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + quirks->regmap_config); +- if (IS_ERR(scodec->regmap)) { +- dev_err(&pdev->dev, "Failed to create our regmap\n"); +- return PTR_ERR(scodec->regmap); +- } ++ if (IS_ERR(scodec->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->regmap), ++ "Failed to create our regmap\n"); + + /* Get the clocks from the DT */ + scodec->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb"); +- if (IS_ERR(scodec->clk_apb)) { +- dev_err(&pdev->dev, "Failed to get the APB clock\n"); +- return PTR_ERR(scodec->clk_apb); +- } ++ if (IS_ERR(scodec->clk_apb)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_apb), ++ "Failed to get the APB clock\n"); + + scodec->clk_module = devm_clk_get(&pdev->dev, "codec"); +- if (IS_ERR(scodec->clk_module)) { +- dev_err(&pdev->dev, "Failed to get the module clock\n"); +- return PTR_ERR(scodec->clk_module); +- } ++ if (IS_ERR(scodec->clk_module)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_module), ++ "Failed to get the module clock\n"); + + if (quirks->has_reset) { + scodec->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); +- if (IS_ERR(scodec->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(scodec->rst); +- } ++ if (IS_ERR(scodec->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->rst), ++ "Failed to get reset control\n"); + } + + scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", + GPIOD_OUT_LOW); +- if (IS_ERR(scodec->gpio_pa)) { +- ret = PTR_ERR(scodec->gpio_pa); +- dev_err_probe(&pdev->dev, ret, "Failed to get pa gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_pa)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_pa), ++ "Failed to get pa gpio\n"); ++ + + scodec->gpio_hp = devm_gpiod_get_optional(&pdev->dev, "hp-det", GPIOD_IN); +- if (IS_ERR(scodec->gpio_hp)) { +- ret = PTR_ERR(scodec->gpio_hp); +- dev_err_probe(&pdev->dev, ret, "Failed to get hp-det gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_hp)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_hp), ++ "Failed to get hp-det gpio\n"); + + /* reg_field setup */ + scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_adc_fifoc); +- if (IS_ERR(scodec->reg_adc_fifoc)) { +- ret = PTR_ERR(scodec->reg_adc_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_adc_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_adc_fifoc), ++ "Failed to create regmap fields\n"); + + scodec->reg_dac_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_dac_fifoc); +- if (IS_ERR(scodec->reg_dac_fifoc)) { +- ret = PTR_ERR(scodec->reg_dac_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_dac_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_dac_fifoc), ++ "Failed to create regmap fields\n"); + + /* DMA configuration for TX FIFO */ + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; +@@ -2394,39 +2381,29 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, + &sun4i_codec_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our codec\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_codec_component, + &dummy_cpu_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); ++ if (ret) + return ret; +- } + + card = quirks->create_card(&pdev->dev); +- if (IS_ERR(card)) { +- ret = PTR_ERR(card); +- dev_err(&pdev->dev, "Failed to create our card\n"); +- return ret; +- } ++ if (IS_ERR(card)) ++ return PTR_ERR(card); + + snd_soc_card_set_drvdata(card, scodec); + + ret = snd_soc_register_card(card); +- if (ret) { +- dev_err_probe(&pdev->dev, ret, "Failed to register our card\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Failed to register our card\n"); + + return 0; + } +diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c +--- a/sound/soc/sunxi/sun4i-i2s.c ++++ b/sound/soc/sunxi/sun4i-i2s.c +@@ -1550,30 +1550,26 @@ static int sun4i_i2s_probe(struct platform_device *pdev) + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(i2s->bus_clk)) { +- dev_err(&pdev->dev, "Can't get our bus clock\n"); +- return PTR_ERR(i2s->bus_clk); +- } ++ if (IS_ERR(i2s->bus_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->bus_clk), ++ "Can't get our bus clock\n"); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + i2s->variant->sun4i_i2s_regmap); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "Regmap initialisation failed\n"); +- return PTR_ERR(i2s->regmap); +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), ++ "Regmap initialisation failed\n"); + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); +- if (IS_ERR(i2s->mod_clk)) { +- dev_err(&pdev->dev, "Can't get our mod clock\n"); +- return PTR_ERR(i2s->mod_clk); +- } ++ if (IS_ERR(i2s->mod_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mod_clk), ++ "Can't get our mod clock\n"); + + if (i2s->variant->has_reset) { + i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); +- if (IS_ERR(i2s->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(i2s->rst); +- } ++ if (IS_ERR(i2s->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->rst), ++ "Failed to get reset control\n"); + } + + if (!IS_ERR(i2s->rst)) { +diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c +--- a/sound/soc/sunxi/sun4i-spdif.c ++++ b/sound/soc/sunxi/sun4i-spdif.c +@@ -684,26 +684,23 @@ static int sun4i_spdif_probe(struct platform_device *pdev) + + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_spdif_regmap_config); +- if (IS_ERR(host->regmap)) { +- dev_err(&pdev->dev, "failed to initialise regmap.\n"); +- return PTR_ERR(host->regmap); +- } ++ if (IS_ERR(host->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->regmap), ++ "failed to initialise regmap.\n"); + + /* Clocks */ + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(host->apb_clk)) { +- dev_err(&pdev->dev, "failed to get a apb clock.\n"); +- return PTR_ERR(host->apb_clk); +- } ++ if (IS_ERR(host->apb_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->apb_clk), ++ "failed to get a apb clock.\n"); + + if (quirks->tx_clk_name) + tx_clk_name = quirks->tx_clk_name; + host->spdif_clk = devm_clk_get(&pdev->dev, tx_clk_name); +- if (IS_ERR(host->spdif_clk)) { +- dev_err(&pdev->dev, "failed to get the \"%s\" clock.\n", +- tx_clk_name); +- return PTR_ERR(host->spdif_clk); +- } ++ if (IS_ERR(host->spdif_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->spdif_clk), ++ "failed to get the \"%s\" clock.\n", ++ tx_clk_name); + + host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; + host->dma_params_tx.maxburst = 8; +diff --git a/sound/soc/sunxi/sun50i-codec-analog.c b/sound/soc/sunxi/sun50i-codec-analog.c +--- a/sound/soc/sunxi/sun50i-codec-analog.c ++++ b/sound/soc/sunxi/sun50i-codec-analog.c +@@ -551,16 +551,13 @@ static int sun50i_codec_analog_probe(struct platform_device *pdev) + bool enable; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + enable = device_property_read_bool(&pdev->dev, + "allwinner,internal-bias-resistor"); +diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c +--- a/sound/soc/sunxi/sun8i-codec-analog.c ++++ b/sound/soc/sunxi/sun8i-codec-analog.c +@@ -822,16 +822,13 @@ static int sun8i_codec_analog_probe(struct platform_device *pdev) + void __iomem *base; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + return devm_snd_soc_register_component(&pdev->dev, + &sun8i_codec_analog_cmpnt_drv, +diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c +--- a/sound/soc/tegra/tegra20_das.c ++++ b/sound/soc/tegra/tegra20_das.c +@@ -167,10 +167,9 @@ static int tegra20_das_probe(struct platform_device *pdev) + + das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_das_regmap_config); +- if (IS_ERR(das->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- return PTR_ERR(das->regmap); +- } ++ if (IS_ERR(das->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(das->regmap), ++ "regmap init failed\n"); + + tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); +diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c +--- a/sound/soc/tegra/tegra210_adx.c ++++ b/sound/soc/tegra/tegra210_adx.c +@@ -496,16 +496,12 @@ static struct snd_kcontrol_new tegra264_adx_controls[] = { + static int tegra210_adx_component_probe(struct snd_soc_component *component) + { + struct tegra210_adx *adx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (adx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, adx->soc_data->controls, +- adx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add ADX controls, err: %d\n", err); +- } ++ if (adx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, adx->soc_data->controls, ++ adx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_adx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c +--- a/sound/soc/tegra/tegra210_amx.c ++++ b/sound/soc/tegra/tegra210_amx.c +@@ -504,16 +504,12 @@ static struct snd_kcontrol_new tegra264_amx_controls[] = { + static int tegra210_amx_component_probe(struct snd_soc_component *component) + { + struct tegra210_amx *amx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (amx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, amx->soc_data->controls, +- amx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add AMX controls, err: %d\n", err); +- } ++ if (amx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, amx->soc_data->controls, ++ amx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_amx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c +--- a/sound/soc/tegra/tegra210_i2s.c ++++ b/sound/soc/tegra/tegra210_i2s.c +@@ -1090,13 +1090,13 @@ static int tegra210_i2s_probe(struct platform_device *pdev) + "can't retrieve I2S bit clock\n"); + + /* +- * Not an error, as this clock is needed only when some other I/O +- * requires input clock from current I2S instance, which is +- * configurable from DT. ++ * This clock is optional and is only needed when another I/O uses ++ * the current I2S instance as its input clock, as configured in DT. + */ +- i2s->clk_sync_input = devm_clk_get(dev, "sync_input"); ++ i2s->clk_sync_input = devm_clk_get_optional(dev, "sync_input"); + if (IS_ERR(i2s->clk_sync_input)) +- dev_dbg(dev, "can't retrieve I2S sync input clock\n"); ++ return dev_err_probe(dev, PTR_ERR(i2s->clk_sync_input), ++ "can't retrieve I2S sync input clock\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/ti/ams-delta.c b/sound/soc/ti/ams-delta.c +--- a/sound/soc/ti/ams-delta.c ++++ b/sound/soc/ti/ams-delta.c +@@ -562,21 +562,21 @@ static int ams_delta_probe(struct platform_device *pdev) + + card->dev = &pdev->dev; + +- handset_mute = devm_gpiod_get(card->dev, "handset_mute", ++ handset_mute = devm_gpiod_get(&pdev->dev, "handset_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handset_mute)) + return PTR_ERR(handset_mute); + +- handsfree_mute = devm_gpiod_get(card->dev, "handsfree_mute", ++ handsfree_mute = devm_gpiod_get(&pdev->dev, "handsfree_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handsfree_mute)) + return PTR_ERR(handsfree_mute); + + ret = snd_soc_register_card(card); + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + card->dev = NULL; +- return ret; ++ return dev_err_probe(&pdev->dev, ret, ++ "snd_soc_register_card() failed\n"); + } + return 0; + } +diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c +--- a/sound/soc/ti/davinci-evm.c ++++ b/sound/soc/ti/davinci-evm.c +@@ -245,9 +245,8 @@ static int davinci_evm_probe(struct platform_device *pdev) + + snd_soc_card_set_drvdata(&evm_soc_card, drvdata); + ret = devm_snd_soc_register_card(&pdev->dev, &evm_soc_card); +- + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); + goto err_put; + } + +diff --git a/sound/soc/ti/j721e-evm.c b/sound/soc/ti/j721e-evm.c +--- a/sound/soc/ti/j721e-evm.c ++++ b/sound/soc/ti/j721e-evm.c +@@ -870,10 +870,9 @@ static int j721e_soc_probe(struct platform_device *pdev) + card->num_dapm_routes = ARRAY_SIZE(j721e_cpb_dapm_routes); + card->fully_routed = 1; + +- if (snd_soc_of_parse_card_name(card, "model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "model"); ++ if (ret) ++ return ret; + + link_cnt = 0; + conf_cnt = 0; +diff --git a/sound/soc/ti/omap-abe-twl6040.c b/sound/soc/ti/omap-abe-twl6040.c +--- a/sound/soc/ti/omap-abe-twl6040.c ++++ b/sound/soc/ti/omap-abe-twl6040.c +@@ -234,16 +234,13 @@ static int omap_abe_probe(struct platform_device *pdev) + card->dapm_routes = audio_map; + card->num_dapm_routes = ARRAY_SIZE(audio_map); + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); +- if (ret) { +- dev_err(&pdev->dev, "Error while parsing DAPM routing\n"); ++ if (ret) + return ret; +- } + + dai_node = of_parse_phandle(node, "ti,mcpdm", 0); + if (!dai_node) { +@@ -299,8 +296,8 @@ static int omap_abe_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); ++ dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return ret; + } +diff --git a/sound/soc/ti/omap-dmic.c b/sound/soc/ti/omap-dmic.c +--- a/sound/soc/ti/omap-dmic.c ++++ b/sound/soc/ti/omap-dmic.c +@@ -465,10 +465,9 @@ static int asoc_dmic_probe(struct platform_device *pdev) + mutex_init(&dmic->mutex); + + dmic->fclk = devm_clk_get(dmic->dev, "fck"); +- if (IS_ERR(dmic->fclk)) { +- dev_err(dmic->dev, "can't get fck\n"); +- return -ENODEV; +- } ++ if (IS_ERR(dmic->fclk)) ++ return dev_err_probe(dmic->dev, PTR_ERR(dmic->fclk), ++ "can't get fck\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) { +diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c +--- a/sound/soc/ti/omap-hdmi.c ++++ b/sound/soc/ti/omap-hdmi.c +@@ -24,8 +24,6 @@ + #define DRV_NAME "omap-hdmi-audio" + + struct hdmi_audio_data { +- struct snd_soc_card *card; +- + const struct omap_hdmi_audio_ops *ops; + struct device *dssdev; + struct snd_dmaengine_dai_dma_data dma_data; +@@ -375,12 +373,9 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) + card->dev = dev; + + ret = devm_snd_soc_register_card(dev, card); +- if (ret) { +- dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "snd_soc_register_card() failed\n"); + +- ad->card = card; + snd_soc_card_set_drvdata(card, ad); + + dev_set_drvdata(dev, ad); +diff --git a/sound/soc/ti/omap-twl4030.c b/sound/soc/ti/omap-twl4030.c +--- a/sound/soc/ti/omap-twl4030.c ++++ b/sound/soc/ti/omap-twl4030.c +@@ -17,7 +17,6 @@ + */ + + #include +-#include + #include + #include + +@@ -29,6 +28,7 @@ + #include "omap-mcbsp.h" + + struct omap_twl4030 { ++ struct snd_soc_jack_gpio hs_jack_gpio; + struct snd_soc_jack hs_jack; + }; + +@@ -124,38 +124,18 @@ static struct snd_soc_jack_pin hs_jack_pins[] = { + }, + }; + +-/* Headset jack detection gpios */ +-static struct snd_soc_jack_gpio hs_jack_gpios[] = { +- { +- .name = "ti,jack-det", +- .report = SND_JACK_HEADSET, +- .debounce_time = 200, +- }, +-}; +- +-static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, +- int connected, char *pin) +-{ +- if (!connected) +- snd_soc_dapm_disable_pin(dapm, pin); +-} +- + static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + { + struct snd_soc_card *card = rtd->card; +- struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); +- struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); ++ + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); +- int ret = 0; ++ int ret; + + /* + * This is a bit of a hack, but the GPIO is optional so we + * only want to add the jack detection if the GPIO is there. + */ + if (of_property_present(card->dev->of_node, "ti,jack-det-gpio")) { +- hs_jack_gpios[0].gpiod_dev = card->dev; +- hs_jack_gpios[0].idx = 0; +- + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET, + &priv->hs_jack, hs_jack_pins, +@@ -163,36 +143,19 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + if (ret) + return ret; + +- ret = snd_soc_jack_add_gpios(&priv->hs_jack, +- ARRAY_SIZE(hs_jack_gpios), +- hs_jack_gpios); ++ priv->hs_jack_gpio.name = "ti,jack-det"; ++ priv->hs_jack_gpio.report = SND_JACK_HEADSET; ++ priv->hs_jack_gpio.debounce_time = 200; ++ priv->hs_jack_gpio.gpiod_dev = card->dev; ++ priv->hs_jack_gpio.idx = 0; ++ ++ ret = snd_soc_jack_add_gpios(&priv->hs_jack, 1, ++ &priv->hs_jack_gpio); + if (ret) + return ret; + } + +- /* +- * NULL pdata means we booted with DT. In this case the routing is +- * provided and the card is fully routed, no need to mark pins. +- */ +- if (!pdata || !pdata->custom_routing) +- return ret; +- +- /* Disable not connected paths if not used */ +- twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); +- twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); +- +- twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); +- +- return ret; ++ return 0; + } + + /* Digital audio interface glue - connects codec <--> CPU */ +@@ -237,11 +200,15 @@ static struct snd_soc_card omap_twl4030_card = { + + static int omap_twl4030_probe(struct platform_device *pdev) + { +- struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); +- struct device_node *node = pdev->dev.of_node; + struct snd_soc_card *card = &omap_twl4030_card; ++ struct device_node *node, *dai_node; + struct omap_twl4030 *priv; +- int ret = 0; ++ struct property *prop; ++ int ret; ++ ++ node = pdev->dev.of_node; ++ if (!node) ++ return -ENODEV; + + card->dev = &pdev->dev; + +@@ -249,69 +216,52 @@ static int omap_twl4030_probe(struct platform_device *pdev) + if (priv == NULL) + return -ENOMEM; + +- if (node) { +- struct device_node *dai_node; +- struct property *prop; ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- dai_node = of_parse_phandle(node, "ti,mcbsp", 0); +- if (!dai_node) { +- dev_err(&pdev->dev, "McBSP node is not provided\n"); +- return -EINVAL; +- } +- omap_twl4030_dai_links[0].cpus->dai_name = NULL; +- omap_twl4030_dai_links[0].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[0].platforms->name = NULL; +- omap_twl4030_dai_links[0].platforms->of_node = dai_node; +- +- dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); +- if (!dai_node) { +- card->num_links = 1; +- } else { +- omap_twl4030_dai_links[1].cpus->dai_name = NULL; +- omap_twl4030_dai_links[1].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[1].platforms->name = NULL; +- omap_twl4030_dai_links[1].platforms->of_node = dai_node; +- } +- +- /* Optional: audio routing can be provided */ +- prop = of_find_property(node, "ti,audio-routing", NULL); +- if (prop) { +- ret = snd_soc_of_parse_audio_routing(card, +- "ti,audio-routing"); +- if (ret) +- return ret; +- +- card->fully_routed = 1; +- } +- } else if (pdata) { +- if (pdata->card_name) { +- card->name = pdata->card_name; +- } else { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- if (!pdata->voice_connected) +- card->num_links = 1; +- } else { +- dev_err(&pdev->dev, "Missing pdata\n"); ++ if (!card->name) { ++ dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + ++ dai_node = of_parse_phandle(node, "ti,mcbsp", 0); ++ if (!dai_node) { ++ dev_err(&pdev->dev, "McBSP node is not provided\n"); ++ return -EINVAL; ++ } ++ omap_twl4030_dai_links[0].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[0].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[0].platforms->name = NULL; ++ omap_twl4030_dai_links[0].platforms->of_node = dai_node; ++ ++ dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); ++ if (!dai_node) { ++ card->num_links = 1; ++ } else { ++ omap_twl4030_dai_links[1].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[1].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[1].platforms->name = NULL; ++ omap_twl4030_dai_links[1].platforms->of_node = dai_node; ++ } ++ ++ /* Optional: audio routing can be provided */ ++ prop = of_find_property(node, "ti,audio-routing", NULL); ++ if (prop) { ++ ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); ++ if (ret) ++ return ret; ++ ++ card->fully_routed = 1; ++ } ++ + snd_soc_card_set_drvdata(card, priv); + ret = devm_snd_soc_register_card(&pdev->dev, card); +- if (ret) { +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/ti/rx51.c b/sound/soc/ti/rx51.c +--- a/sound/soc/ti/rx51.c ++++ b/sound/soc/ti/rx51.c +@@ -358,21 +358,22 @@ static struct snd_soc_card rx51_sound_card = { + static int rx51_soc_probe(struct platform_device *pdev) + { + struct rx51_audio_pdata *pdata; +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct snd_soc_card *card = &rx51_sound_card; + int err; + + if (!of_machine_is_compatible("nokia,omap3-n900")) + return -ENODEV; + +- card->dev = &pdev->dev; ++ card->dev = dev; + + if (np) { + struct device_node *dai_node; + + dai_node = of_parse_phandle(np, "nokia,cpu-dai", 0); + if (!dai_node) { +- dev_err(card->dev, "McBSP node is not provided\n"); ++ dev_err(dev, "McBSP node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].cpus->dai_name = NULL; +@@ -382,7 +383,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 0); + if (!dai_node) { +- dev_err(card->dev, "Codec node is not provided\n"); ++ dev_err(dev, "Codec node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].codecs->name = NULL; +@@ -390,7 +391,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 1); + if (!dai_node) { +- dev_err(card->dev, "Auxiliary Codec node is not provided\n"); ++ dev_err(dev, "Auxiliary Codec node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[0].dlc.name = NULL; +@@ -400,7 +401,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0); + if (!dai_node) { +- dev_err(card->dev, "Headphone amplifier node is not provided\n"); ++ dev_err(dev, "Headphone amplifier node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[1].dlc.name = NULL; +@@ -409,40 +410,36 @@ static int rx51_soc_probe(struct platform_device *pdev) + rx51_codec_conf[1].dlc.of_node = dai_node; + } + +- pdata = devm_kzalloc(card->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, pdata); + +- pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, ++ pdata->tvout_selection_gpio = devm_gpiod_get(dev, + "tvout-selection", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->tvout_selection_gpio)) { +- dev_err(card->dev, "could not get tvout selection gpio\n"); +- return PTR_ERR(pdata->tvout_selection_gpio); +- } ++ if (IS_ERR(pdata->tvout_selection_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->tvout_selection_gpio), ++ "could not get tvout selection gpio\n"); + +- pdata->eci_sw_gpio = devm_gpiod_get(card->dev, "eci-switch", ++ pdata->eci_sw_gpio = devm_gpiod_get(dev, "eci-switch", + GPIOD_OUT_HIGH); +- if (IS_ERR(pdata->eci_sw_gpio)) { +- dev_err(card->dev, "could not get eci switch gpio\n"); +- return PTR_ERR(pdata->eci_sw_gpio); +- } ++ if (IS_ERR(pdata->eci_sw_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->eci_sw_gpio), ++ "could not get eci switch gpio\n"); + +- pdata->speaker_amp_gpio = devm_gpiod_get(card->dev, ++ pdata->speaker_amp_gpio = devm_gpiod_get(dev, + "speaker-amplifier", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->speaker_amp_gpio)) { +- dev_err(card->dev, "could not get speaker enable gpio\n"); +- return PTR_ERR(pdata->speaker_amp_gpio); +- } ++ if (IS_ERR(pdata->speaker_amp_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->speaker_amp_gpio), ++ "could not get speaker enable gpio\n"); + +- err = devm_snd_soc_register_card(card->dev, card); +- if (err) { +- dev_err(card->dev, "snd_soc_register_card failed (%d)\n", err); +- return err; +- } ++ err = devm_snd_soc_register_card(dev, card); ++ if (err) ++ return dev_err_probe(dev, err, ++ "snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c +--- a/sound/soc/uniphier/aio-cpu.c ++++ b/sound/soc/uniphier/aio-cpu.c +@@ -795,16 +795,12 @@ int uniphier_aio_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(dev, &uniphier_aio_component, + chip->chip_spec->dais, + chip->chip_spec->num_dais); +- if (ret) { +- dev_err(dev, "Register component failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + ret = uniphier_aiodma_soc_register_platform(pdev); +- if (ret) { +- dev_err(dev, "Register platform failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + return 0; + +diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h +--- a/sound/soc/uniphier/aio.h ++++ b/sound/soc/uniphier/aio.h +@@ -156,7 +156,7 @@ struct uniphier_aio_selector { + int hw; + }; + +-/** ++/* + * 'SoftWare MAPping' setting of UniPhier AIO registers. + * + * We have to setup 'virtual' register maps to access 'real' registers of AIO. +diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c +--- a/sound/soc/ux500/mop500.c ++++ b/sound/soc/ux500/mop500.c +@@ -68,10 +68,11 @@ static void mop500_of_node_put(void) + of_node_put(mop500_dai_links[0].codecs->of_node); + } + +-static int mop500_of_probe(struct platform_device *pdev, +- struct device_node *np) ++static int mop500_of_probe(struct snd_soc_card *card) + { ++ struct device *dev = card->dev; + struct device_node *codec_np, *msp_np[2]; ++ struct device_node *np = dev->of_node; + int i; + + msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); +@@ -79,7 +80,7 @@ static int mop500_of_probe(struct platform_device *pdev, + codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); + + if (!(msp_np[0] && msp_np[1] && codec_np)) { +- dev_err(&pdev->dev, "Phandle missing or invalid\n"); ++ dev_err(dev, "Phandle missing or invalid\n"); + for (i = 0; i < 2; i++) + of_node_put(msp_np[i]); + of_node_put(codec_np); +@@ -95,21 +96,20 @@ static int mop500_of_probe(struct platform_device *pdev, + mop500_dai_links[i].codecs->name = NULL; + } + +- snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name"); ++ snd_soc_of_parse_card_name(card, "stericsson,card-name"); + + return 0; + } + + static int mop500_probe(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; + int ret; + + dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); + + mop500_card.dev = &pdev->dev; + +- ret = mop500_of_probe(pdev, np); ++ ret = mop500_of_probe(&mop500_card); + if (ret) + return ret; + +diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c +--- a/sound/soc/ux500/mop500_ab8500.c ++++ b/sound/soc/ux500/mop500_ab8500.c +@@ -433,5 +433,5 @@ void mop500_ab8500_remove(struct snd_soc_card *card) + clk_put(drvdata->clk_ptr_ulpclk); + clk_put(drvdata->clk_ptr_intclk); + +- snd_soc_card_set_drvdata(card, drvdata); ++ snd_soc_card_set_drvdata(card, NULL); + } +diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c +--- a/sound/soc/xilinx/xlnx_formatter_pcm.c ++++ b/sound/soc/xilinx/xlnx_formatter_pcm.c +@@ -9,8 +9,6 @@ + #include + #include + #include +-#include +-#include + #include + + #include +diff --git a/sound/soc/xilinx/xlnx_i2s.c b/sound/soc/xilinx/xlnx_i2s.c +--- a/sound/soc/xilinx/xlnx_i2s.c ++++ b/sound/soc/xilinx/xlnx_i2s.c +@@ -9,9 +9,8 @@ + + #include + #include +-#include +-#include + #include ++#include + #include + #include + +@@ -174,7 +173,6 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + int ret; + u32 format; + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) +@@ -184,18 +182,16 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + if (IS_ERR(drv_data->base)) + return PTR_ERR(drv_data->base); + +- ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels); +- if (ret < 0) { +- dev_err(dev, "cannot get supported channels\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,num-channels", &drv_data->channels); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get supported channels\n"); ++ + drv_data->channels *= 2; + +- ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width); +- if (ret < 0) { +- dev_err(dev, "cannot get data width\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,dwidth", &drv_data->data_width); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get data width\n"); ++ + switch (drv_data->data_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; +@@ -207,7 +203,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + +- if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) { ++ if (device_is_compatible(dev, "xlnx,i2s-transmitter-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_playback"; + drv_data->dai_drv.playback.stream_name = "Playback"; + drv_data->dai_drv.playback.formats = format; +@@ -215,7 +211,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + drv_data->dai_drv.playback.channels_max = drv_data->channels; + drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000; + drv_data->dai_drv.ops = &xlnx_i2s_dai_ops; +- } else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) { ++ } else if (device_is_compatible(dev, "xlnx,i2s-receiver-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_capture"; + drv_data->dai_drv.capture.stream_name = "Capture"; + drv_data->dai_drv.capture.formats = format; +@@ -233,10 +229,8 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component, + &drv_data->dai_drv, 1); +- if (ret) { +- dev_err(&pdev->dev, "i2s component registration failed\n"); ++ if (ret) + return ret; +- } + + dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name); + +diff --git a/sound/soc/xilinx/xlnx_spdif.c b/sound/soc/xilinx/xlnx_spdif.c +--- a/sound/soc/xilinx/xlnx_spdif.c ++++ b/sound/soc/xilinx/xlnx_spdif.c +@@ -10,8 +10,7 @@ + #include + #include + #include +-#include +-#include ++#include + #include + #include + #include +@@ -242,28 +241,24 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + struct spdif_dev_data *ctx; + + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->axi_clk = devm_clk_get_enabled(dev, "s_axi_aclk"); +- if (IS_ERR(ctx->axi_clk)) { +- ret = PTR_ERR(ctx->axi_clk); +- dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret); +- return ret; +- } ++ if (IS_ERR(ctx->axi_clk)) ++ return dev_err_probe(dev, PTR_ERR(ctx->axi_clk), ++ "failed to get s_axi_aclk\n"); + + ctx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctx->base)) + return PTR_ERR(ctx->base); + +- ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode); +- if (ret < 0) { +- dev_err(dev, "cannot get SPDIF mode\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,spdif-mode", &ctx->mode); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get SPDIF mode\n"); ++ + if (ctx->mode) { + dai_drv = &xlnx_spdif_tx_dai; + } else { +@@ -274,29 +269,23 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + ret = devm_request_irq(dev, ret, + xlnx_spdifrx_irq_handler, + 0, "XLNX_SPDIF_RX", ctx); +- if (ret) { +- dev_err(dev, "spdif rx irq request failed\n"); +- return -ENODEV; +- } ++ if (ret) ++ return ret; + + init_waitqueue_head(&ctx->chsts_q); + dai_drv = &xlnx_spdif_rx_dai; + } + +- ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk); +- if (ret < 0) { +- dev_err(dev, "cannot get aud_clk_i value\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,aud_clk_i", &ctx->aclk); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get aud_clk_i value\n"); + + dev_set_drvdata(dev, ctx); + + ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component, + dai_drv, 1); +- if (ret) { +- dev_err(dev, "SPDIF component registration failed\n"); ++ if (ret) + return ret; +- } + + writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG); + dev_info(dev, "%s DAI registered\n", dai_drv->name); +diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c +--- a/sound/soc/xtensa/xtfpga-i2s.c ++++ b/sound/soc/xtensa/xtfpga-i2s.c +@@ -533,34 +533,25 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + int err, irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); +- if (!i2s) { +- err = -ENOMEM; +- goto err; +- } ++ if (!i2s) ++ return -ENOMEM; ++ + platform_set_drvdata(pdev, i2s); + i2s->dev = &pdev->dev; + dev_dbg(&pdev->dev, "dev: %p, i2s: %p\n", &pdev->dev, i2s); + + i2s->regs = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(i2s->regs)) { +- err = PTR_ERR(i2s->regs); +- goto err; +- } ++ if (IS_ERR(i2s->regs)) ++ return PTR_ERR(i2s->regs); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->regs, + &xtfpga_i2s_regmap_config); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- err = PTR_ERR(i2s->regmap); +- goto err; +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "regmap init failed\n"); + + i2s->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(i2s->clk)) { +- dev_err(&pdev->dev, "couldn't get clock\n"); +- err = PTR_ERR(i2s->clk); +- goto err; +- } ++ if (IS_ERR(i2s->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->clk), "couldn't get clock\n"); + + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, + (0x1 << XTFPGA_I2S_CONFIG_CHANNEL_BASE)); +@@ -568,41 +559,34 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, XTFPGA_I2S_INT_UNDERRUN); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- err = irq; +- goto err; +- } ++ if (irq < 0) ++ return irq; ++ + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xtfpga_i2s_threaded_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + pdev->name, i2s); +- if (err < 0) { +- dev_err(&pdev->dev, "request_irq failed\n"); +- goto err; +- } ++ if (err < 0) ++ return err; + + err = devm_snd_soc_register_component(&pdev->dev, + &xtfpga_i2s_component, + xtfpga_i2s_dai, + ARRAY_SIZE(xtfpga_i2s_dai)); +- if (err < 0) { +- dev_err(&pdev->dev, "couldn't register component\n"); +- goto err; +- } ++ if (err < 0) ++ return err; ++ + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = xtfpga_i2s_runtime_resume(&pdev->dev); +- if (err) +- goto err_pm_disable; ++ if (err) { ++ pm_runtime_disable(&pdev->dev); ++ return err; ++ } + } +- return 0; + +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +-err: +- dev_err(&pdev->dev, "%s: err = %d\n", __func__, err); +- return err; ++ return 0; + } + + static void xtfpga_i2s_remove(struct platform_device *pdev) diff --git a/pkgbuilds/linux-omarchy-eevdf/0511-asoc-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0511-asoc-updates.patch.sig new file mode 100644 index 0000000..943e088 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0511-asoc-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0512-sound-fixes-1.patch b/pkgbuilds/linux-omarchy-eevdf/0512-sound-fixes-1.patch new file mode 100644 index 0000000..7de337b --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0512-sound-fixes-1.patch @@ -0,0 +1,661 @@ +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info + int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, + int mode, struct snd_rawmidi_file *rfile, + int depth); +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); + int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, +@@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, + return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); + } + ++static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_release_nested(rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c +--- a/sound/aoa/soundbus/i2sbus/core.c ++++ b/sound/aoa/soundbus/i2sbus/core.c +@@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, + } + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + int irq = irq_of_parse_and_map(np, i); ++ if (!irq) ++ goto err; + if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = irq; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; +- guard(mutex)(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; +@@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + } + + /* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth) + { + struct snd_rawmidi *rmidi; + +@@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) + return -ENXIO; + + rmidi = rfile->rmidi; ++ mutex_lock_nested(&rmidi->open_mutex, depth); + rawmidi_release_priv(rfile); ++ mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + return 0; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_release); ++EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); + + static int snd_rawmidi_release(struct inode *inode, struct file *file) + { +@@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) + + rfile = file->private_data; + rmidi = rfile->rmidi; +- rawmidi_release_priv(rfile); ++ scoped_guard(mutex, &rmidi->open_mutex) ++ rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) + ump->legacy_substreams[dir][group] = NULL; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) +- snd_rawmidi_kernel_release(&ump->legacy_out_rfile); ++ snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + } + return 0; + } +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) + snd_hda_gen_remove(codec); + } + +-static void cx_process_headset_plugin(struct hda_codec *codec) ++static void cx_process_headset_detect_plug_type(struct hda_codec *codec) + { + unsigned int val; + unsigned int count = 0; +@@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) + count++; + } while (count < 3); + val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); +- if (val & 0x800) { +- codec_dbg(codec, "headset plugin, type is CTIA\n"); +- snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); +- } else if (val & 0x400) { +- codec_dbg(codec, "headset plugin, type is OMTP\n"); ++ if (val & 0xc00) { ++ codec_dbg(codec, "headset plugin, type is %s\n", ++ val & 0x800 ? "CTIA" : "OMTP"); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } else { + codec_dbg(codec, "headphone plugin\n"); +@@ -243,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + * Check hp&mic tag to process headset plugin & plugout. + */ + mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); +- if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ ++ if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); +- else +- cx_process_headset_plugin(codec); ++ } else { ++ cx_process_headset_detect_plug_type(codec); ++ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); ++ } + } + + static int cx_suspend(struct hda_codec *codec) +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -1370,6 +1370,61 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); + } + ++/* ++ * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix ++ * ++ * The speaker mute LED is controlled via VREF100 on NID 0x1a. ++ * This pin must remain powered (D0) even during suspend, otherwise ++ * the LED state is lost and the pin defaults to Hi-Z on resume, ++ * causing the LED to stop responding to mute toggles. ++ * Windows keeps this pin powered unconditionally, so matching that ++ * behavior ensures consistent LED operation across suspend/resume. ++ * The mic-mute LED is controlled via GPIO 0 with active-low polarity. ++ */ ++static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, ++ hda_nid_t nid, ++ unsigned int power_state) ++{ ++ if (nid == 0x1a) ++ return AC_PWRST_D0; ++ return snd_hda_gen_path_power_filter(codec, nid, power_state); ++} ++ ++static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); ++ unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; ++ ++ snd_hda_set_pin_ctl_cache(codec, 0x1a, val); ++ return 0; ++} ++ ++static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->micmute_led_polarity = 1; ++ alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); ++ ++ spec->mute_led_polarity = 0; ++ snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); ++ ++ codec->power_filter = hp_8bb6_power_filter; ++ spec->no_shutup_pins = 1; ++ break; ++ ++ case HDA_FIXUP_ACT_INIT: ++ if (spec->gen.vmaster_mute.hook) ++ snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); ++ break; ++ } ++} ++ + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -4089,6 +4144,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, + ALC225_FIXUP_S3_POP_NOISE, + ALC700_FIXUP_INTEL_REFERENCE, ++ ALC700_FIXUP_INTEL_HADES_CANYON, + ALC274_FIXUP_DELL_BIND_DACS, + ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + ALC298_FIXUP_TPT470_DOCK_FIX, +@@ -4152,6 +4208,7 @@ enum { + ALC236_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ++ ALC236_FIXUP_HP_15_FD0XXX, + ALC236_FIXUP_LENOVO_INV_DMIC, + ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, +@@ -4297,6 +4354,7 @@ enum { + ALC287_FIXUP_LENOVO_LEGION_AW88399, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, ++ ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4333,6 +4391,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + __snd_hda_apply_fixup(codec, id, action, 0); + } + ++static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ /* ++ * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken ++ * analog playback after rebooting into Linux. Restore the bit ++ * during codec initialization. ++ */ ++ if (action == HDA_FIXUP_ACT_INIT) ++ alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); ++} ++ + static const struct hda_fixup alc269_fixups[] = { + [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { + .type = HDA_FIXUP_PINS, +@@ -5411,6 +5482,10 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC700_FIXUP_INTEL_HADES_CANYON] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc700_fixup_intel_hades_canyon, ++ }, + [ALC274_FIXUP_DELL_BIND_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_bind_dacs, +@@ -5913,6 +5988,10 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_gpio, + }, ++ [ALC236_FIXUP_HP_15_FD0XXX] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc236_fixup_hp_15_fd0xxx, ++ }, + [ALC236_FIXUP_LENOVO_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, +@@ -7010,6 +7089,12 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_lenovo_dac_rename, + }, ++ [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_TXNW2781_I2C, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7448,6 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), +@@ -8031,6 +8117,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), ++ SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8044,14 +8131,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), ++ SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), +- HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ ++ HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8231,6 +8319,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ++ ALC700_FIXUP_INTEL_HADES_CANYON), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), + SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), +diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c +--- a/sound/usb/mixer.c ++++ b/sound/usb/mixer.c +@@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) + } + + /* +- * Additional checks for sticky mixers ++ * Additional checks for sticky GET_CUR + * +- * Some devices' volume control mixers are sticky, which accept SET_CUR but +- * do absolutely nothing. ++ * Some devices' volume control mixers have sticky GET_CUR, which implies either ++ * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts ++ * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only ++ * way to control the volume. For the latter case, the mixer has effective ++ * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we ++ * always provide mixer value from the ceche. + * + * Check the return values of GET_CUR with different SET_CUR values. Consider +- * the mixer as sticky if GET_CUR always returns a constant value. ++ * GET_CUR as sticky if GET_CUR always returns a constant value. + * +- * Some devices have effective SET_CUR despite GET_CUR being constant. Do not +- * consider the mixer as sticky if a quirk flag indicates that. ++ * Unfortunately, we can't distinguish between stubbed SET_CUR and broken ++ * GET_CUR with simple read-back tests. Disabling the mixer regardless and ++ * forcing userspace to use soft mixer instead can lead to audible distortion at ++ * low volume on some wireless headphones, probably due to their poorly- ++ * performed lossy codec. + * +- * Gate the registration of sticky mixers to prevent confusing userspace, so +- * that they won't cause ineffective volume control. However, for mixers with +- * effective SET_CUR but broken GET_CUR, the registration can continue normally +- * but further GET_CUR requests will be gated. ++ * Instead, mark GET_CUR as broken regardless and only provide mixer value from ++ * the cache. Users may opt into soft mixer in userspace audio stack if they ++ * need it. + */ + static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +@@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + return 0; + } + +- if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { +- usb_audio_info(cval->head.mixer->chip, +- "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); +- +- cval->get_cur_broken = 1; +- return -ENXIO; +- } +- +- usb_audio_err(cval->head.mixer->chip, +- "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); + usb_audio_info(cval->head.mixer->chip, +- "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); ++ "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", ++ cval->head.id, mixer_ctrl_intf(cval->head.mixer), ++ cval->min, cval->max, cval->res, saved); + +- return -ENODEV; ++ cval->get_cur_broken = 1; ++ return -ENXIO; + } + + /* +@@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + goto no_checks; + + ret = check_sticky_volume_control(cval, minchn, saved); +- if (ret == -ENODEV) +- goto sticky; + if (ret) + goto no_checks; + +@@ -1454,34 +1447,15 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + } + } + +- return 0; +- +-sticky: + /* +- * It makes no sense to restore the saved value for a sticky mixer, +- * since setting any value is a no-op. +- * +- * However, in some rare cases, SET_CUR is effective despite GET_CUR +- * always returns a constant value. These mixers are not sticky, but +- * there's no way to distinguish them. Without any additional +- * information, the best thing we can do is to set the mixer value to +- * the maximum before bailing out, so that a soft mixer can still reach +- * the maximum hardware volume if the mixer turns out to be non-sticky. +- * Meanwhile, all channels must be synchronized to prevent imbalance +- * volume. ++ * When GET_CUR is sticky, the saved value is bogus, so mixer values set ++ * by the sanity checks must be discarded through init_cur_mix_raw(). ++ * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ +- if (!cval->cmask) { +- snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); +- } else { +- idx = 0; +- for (i = 0; i < MAX_CHANNELS; i++) { +- if (cval->cmask & BIT(i)) { +- snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); +- idx++; +- } +- } +- } +- return ret; ++ if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) ++ cval->get_cur_broken = 0; ++ ++ return 0; + } + + #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + +-/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels +- * and sits on the monitor mixer branch, but it is traced through to the +- * Speaker output terminal and gets named "Speaker Playback Volume". Userspace +- * then adopts it as the stream's hardware volume, and any setting below 0 dB +- * attenuates some channels but not others (20 dB imbalance at 80%). Give it a +- * non-standard name so that it is no longer taken for the stream's master +- * volume, while remaining reachable for anyone who wants the monitor gain. ++/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is ++ * traced through to the Speaker output terminal, so it is named "Speaker ++ * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI ++ * testing found asymmetric attenuation within the main stereo pair. Userspace ++ * adopts this control as the stream's hardware volume, causing imbalance below ++ * 0 dB. Give it a non-standard name so that userspace no longer treats it as ++ * the stream master, while keeping the monitor gain reachable. + */ + static const struct usbmix_name_map audient_id14_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ +@@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .map = maya44_map, + }, + { +- /* Audient iD14 */ ++ /* Audient iD14 MkI */ ++ .id = USB_ID(0x2708, 0x0002), ++ .map = audient_id14_map, ++ }, ++ { ++ /* Audient iD14 MkII */ + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * M-Audio Venom ++ * ++ * The AudioControl interface times out on every GET_CUR request, ++ * which adds around 47 seconds to the card registration and ++ * freezes the device, blocking streaming. ++ * Using an explicit composite quirk to skip the mixer entirely. ++ */ ++ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), ++ QUIRK_DRIVER_INFO { ++ .vendor_name = "M-Audio", ++ .product_name = "Venom", ++ QUIRK_DATA_COMPOSITE { ++ { QUIRK_DATA_IGNORE(0) }, ++ { QUIRK_DATA_STANDARD_AUDIO(1) }, ++ { QUIRK_DATA_STANDARD_AUDIO(2) }, ++ { QUIRK_DATA_STANDARD_MIDI(3) }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* Casio devices */ + { +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2215,10 +2215,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ +@@ -2256,8 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), ++ DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ ++ QUIRK_FLAG_MIXER_GET_CUR_OK | ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ +@@ -2329,9 +2330,11 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ ++ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ +@@ -2370,8 +2373,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ +@@ -2379,8 +2380,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ +@@ -2393,6 +2392,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ ++ QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | ++ QUIRK_FLAG_SKIP_IFACE_SETUP), + DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ + 0), /* Doesn't have the vendor quirk which would otherwise apply */ + DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ +@@ -2505,8 +2507,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ + QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ +@@ -2647,7 +2647,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), +- QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), + QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), + QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -243,24 +243,18 @@ extern bool snd_usb_skip_validation; + * from snd_usb_handle_sync_urb. Instead fall through and enqueue a + * packet_info containing only size-0 packets, so the OUT ring keeps + * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). +- * QUIRK_FLAG_MIXER_GET_CUR_BROKEN +- * Some mixers are sticky, which means that setting their current volume is a +- * no-op, and reading the current volume returns a constant value. The sticky +- * check disables these mixers to prevent confusing userspace. However, some +- * devices do have a tunable volume despite the reported current volume being +- * constant. As the sticky check can't distinguish between the two categories, +- * setting this flag tells that the device should fall into the second +- * category when GET_CUR returns a constant value, resulting in the sticky +- * check being non-fatal and only disabling GET_CUR instead of the whole mixer. +- * The current volume will then be provided by the internal cache that stores +- * the last set volume ++ * QUIRK_FLAG_MIXER_GET_CUR_OK ++ * On some devices, whether their GET_CUR being sticky depends on whether ++ * hotpluggable components are present. When the hotpluggable components are ++ * missing on probe, their GET_CUR behavior is classified as broken. Set the ++ * flag to prevent the heuristics from gating GET_CUR. + * QUIRK_FLAG_PLAYBACK_URB_FIXUP + * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. + * This is needed for devices that exhibit boot-time audio stuttering due + * to insufficient buffer depth combined with xHCI scheduling variability. + * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling + * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. +- * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * QUIRK_FLAG_ALWAYS_SET_RATE + * Issue SET_CUR for the sample rate even when the clock already reports the + * requested rate. A device advertising a single rate is otherwise never sent + * the request at all, and some require it before streaming will start. +@@ -297,10 +291,10 @@ enum { + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, +- QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_MIXER_GET_CUR_OK = 30, + QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, + QUIRK_TYPE_ALWAYS_SET_RATE = 32, +-/* Please also edit snd_usb_audio_quirk_flag_names */ ++/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ + }; + + #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) +@@ -335,7 +329,7 @@ enum { + #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) +-#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) + #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) + #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + diff --git a/pkgbuilds/linux-omarchy-eevdf/0512-sound-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0512-sound-fixes-1.patch.sig new file mode 100644 index 0000000..cdb2260 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0512-sound-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0513-asoc-fixes-1.patch similarity index 51% rename from pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch rename to pkgbuilds/linux-omarchy-eevdf/0513-asoc-fixes-1.patch index c32b5c3..49c1eb3 100644 --- a/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0513-asoc-fixes-1.patch @@ -64,400 +64,9 @@ diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bin #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"), }, @@ -816,51 +425,6 @@ diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c 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 @@ -1275,18 +839,6 @@ diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_u 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 @@ -1317,339 +869,3 @@ diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaen 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-eevdf/0513-asoc-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0513-asoc-fixes-1.patch.sig new file mode 100644 index 0000000..c7079ef Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0513-asoc-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0514-sound-fixes-2.patch similarity index 74% rename from pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch rename to pkgbuilds/linux-omarchy-eevdf/0514-sound-fixes-2.patch index 9566376..043c34e 100644 --- a/pkgbuilds/linux-omarchy-bore/0512-sound-fixes.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0514-sound-fixes-2.patch @@ -1,92 +1,3 @@ -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 @@ -233,62 +144,6 @@ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269 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 diff --git a/pkgbuilds/linux-omarchy-eevdf/0514-sound-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0514-sound-fixes-2.patch.sig new file mode 100644 index 0000000..cd8ab3f Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0514-sound-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy-eevdf/0515-xps13-sof-quirk.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch rename to pkgbuilds/linux-omarchy-eevdf/0515-xps13-sof-quirk.patch diff --git a/pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0515-xps13-sof-quirk.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0513-xps13-sof-quirk.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0515-xps13-sof-quirk.patch.sig diff --git a/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch b/pkgbuilds/linux-omarchy-eevdf/0516-rt766-stream-config-type.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch rename to pkgbuilds/linux-omarchy-eevdf/0516-rt766-stream-config-type.patch diff --git a/pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0516-rt766-stream-config-type.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0514-rt766-stream-config-type.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0516-rt766-stream-config-type.patch.sig diff --git a/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy-eevdf/0517-hda-realtek-rog-strix-g733zw-speakers.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch rename to pkgbuilds/linux-omarchy-eevdf/0517-hda-realtek-rog-strix-g733zw-speakers.patch diff --git a/pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0516-hda-realtek-rog-strix-g733zw-speakers.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig diff --git a/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy-eevdf/0518-asoc-amd-yc-acer-aspire-a314-23p.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch rename to pkgbuilds/linux-omarchy-eevdf/0518-asoc-amd-yc-acer-aspire-a314-23p.patch diff --git a/pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0517-asoc-amd-yc-acer-aspire-a314-23p.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0522-sound-fixes-3.patch b/pkgbuilds/linux-omarchy-eevdf/0522-sound-fixes-3.patch new file mode 100644 index 0000000..48c9872 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0522-sound-fixes-3.patch @@ -0,0 +1,432 @@ +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 +@@ -7465,6 +7465,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8890, "HP Elite Dragonfly Max G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8896, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8898, "HP EliteBook 845 G8 Notebook PC", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), +@@ -7580,6 +7581,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), ++ SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 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), +diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c +--- a/sound/pci/rme9652/hdspm.c ++++ b/sound/pci/rme9652/hdspm.c +@@ -1071,14 +1071,8 @@ struct hdspm { + + + static const struct pci_device_id snd_hdspm_ids[] = { +- { +- .vendor = PCI_VENDOR_ID_XILINX, +- .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, +- .subvendor = PCI_ANY_ID, +- .subdevice = PCI_ANY_ID, +- .class = 0, +- .class_mask = 0, +- .driver_data = 0}, ++ { PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI) }, ++ { PCI_DEVICE(0x1d18, 0x3fc6) }, /* RME HDSPe AIO PCI express audio */ + {0,} + }; + +diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c +--- a/sound/usb/hiface/pcm.c ++++ b/sound/usb/hiface/pcm.c +@@ -24,7 +24,7 @@ + struct pcm_urb { + struct hiface_chip *chip; + +- struct urb instance; ++ struct urb *instance; + struct usb_anchor submitted; + u8 *buffer; + }; +@@ -193,7 +193,7 @@ static void hiface_pcm_stream_stop(struct pcm_runtime *rt) + if (!time) + usb_kill_anchored_urbs( + &rt->out_urbs[i].submitted); +- usb_kill_urb(&rt->out_urbs[i].instance); ++ usb_kill_urb(rt->out_urbs[i].instance); + } + + rt->stream_state = STREAM_DISABLED; +@@ -215,9 +215,9 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) + rt->stream_state = STREAM_STARTING; + for (i = 0; i < PCM_N_URBS; i++) { + memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE); +- usb_anchor_urb(&rt->out_urbs[i].instance, ++ usb_anchor_urb(rt->out_urbs[i].instance, + &rt->out_urbs[i].submitted); +- ret = usb_submit_urb(&rt->out_urbs[i].instance, ++ ret = usb_submit_urb(rt->out_urbs[i].instance, + GFP_ATOMIC); + if (ret) { + hiface_pcm_stream_stop(rt); +@@ -334,7 +334,7 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) + if (do_period_elapsed) + snd_pcm_period_elapsed(sub->instance); + +- ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC); ++ ret = usb_submit_urb(out_urb->instance, GFP_ATOMIC); + if (ret < 0) + goto out_fail; + +@@ -492,16 +492,18 @@ static int hiface_pcm_init_urb(struct pcm_urb *urb, + void (*handler)(struct urb *)) + { + urb->chip = chip; +- usb_init_urb(&urb->instance); ++ urb->instance = usb_alloc_urb(0, GFP_KERNEL); ++ if (!urb->instance) ++ return -ENOMEM; + + urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL); + if (!urb->buffer) + return -ENOMEM; + +- usb_fill_bulk_urb(&urb->instance, chip->dev, ++ usb_fill_bulk_urb(urb->instance, chip->dev, + usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer, + PCM_PACKET_SIZE, handler, urb); +- if (usb_urb_ep_type_check(&urb->instance)) ++ if (usb_urb_ep_type_check(urb->instance)) + return -EINVAL; + init_usb_anchor(&urb->submitted); + +@@ -520,24 +522,26 @@ void hiface_pcm_abort(struct hiface_chip *chip) + } + } + +-static void hiface_pcm_destroy(struct hiface_chip *chip) ++static void hiface_pcm_destroy(struct pcm_runtime *rt) + { +- struct pcm_runtime *rt = chip->pcm; + int i; + +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); ++ if (!rt) ++ return; + +- kfree(chip->pcm); +- chip->pcm = NULL; ++ if (rt->chip) ++ rt->chip->pcm = NULL; ++ ++ for (i = 0; i < PCM_N_URBS; i++) { ++ usb_free_urb(rt->out_urbs[i].instance); ++ kfree(rt->out_urbs[i].buffer); ++ } ++ kfree(rt); + } + + static void hiface_pcm_free(struct snd_pcm *pcm) + { +- struct pcm_runtime *rt = pcm->private_data; +- +- if (rt) +- hiface_pcm_destroy(rt->chip); ++ hiface_pcm_destroy(pcm->private_data); + } + + int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) +@@ -587,8 +591,6 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) + return 0; + + error: +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); +- kfree(rt); ++ hiface_pcm_destroy(rt); + return ret; + } +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -109,10 +109,10 @@ struct ua101 { + unsigned int buffer_pos; + unsigned int queue_length; + struct ua101_urb { +- struct urb urb; +- struct usb_iso_packet_descriptor iso_frame_desc[1]; ++ struct urb *urb; + struct list_head ready_list; +- } *urbs[MAX_QUEUE_LENGTH]; ++ struct ua101 *ua; ++ } urbs[MAX_QUEUE_LENGTH]; + struct { + unsigned int size; + void *addr; +@@ -167,15 +167,15 @@ static void abort_usb_playback(struct ua101 *ua) + wake_up(&ua->alsa_playback_wait); + } + +-static void playback_urb_complete(struct urb *usb_urb) ++static void playback_urb_complete(struct urb *urb) + { +- struct ua101_urb *urb = (struct ua101_urb *)usb_urb; +- struct ua101 *ua = urb->urb.context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + +- if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ +- urb->urb.status == -ENODEV || /* device removed */ +- urb->urb.status == -ECONNRESET || /* unlinked */ +- urb->urb.status == -ESHUTDOWN)) { /* device disabled */ ++ if (unlikely(urb->status == -ENOENT || /* unlinked */ ++ urb->status == -ENODEV || /* device removed */ ++ urb->status == -ECONNRESET || /* unlinked */ ++ urb->status == -ESHUTDOWN)) { /* device disabled */ + abort_usb_playback(ua); + abort_alsa_playback(ua); + return; +@@ -184,18 +184,19 @@ static void playback_urb_complete(struct urb *usb_urb) + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { + /* append URB to FIFO */ + guard(spinlock_irqsave)(&ua->lock); +- list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); ++ list_add_tail(&ua_urb->ready_list, &ua->ready_playback_urbs); + if (ua->rate_feedback_count > 0) + queue_work(system_highpri_wq, &ua->playback_work); + ua->playback.substream->runtime->delay -= +- urb->urb.iso_frame_desc[0].length / ++ urb->iso_frame_desc[0].length / + ua->playback.frame_bytes; + } + } + + static void first_playback_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = playback_urb_complete; + playback_urb_complete(urb); +@@ -248,7 +249,8 @@ static void playback_work(struct work_struct *work) + { + struct ua101 *ua = container_of(work, struct ua101, playback_work); + unsigned int frames; +- struct ua101_urb *urb; ++ struct ua101_urb *ua_urb; ++ struct urb *urb; + bool do_period_elapsed = false; + int err; + +@@ -275,23 +277,24 @@ static void playback_work(struct work_struct *work) + ua->rate_feedback_count--; + + /* take URB out of FIFO */ +- urb = list_first_entry(&ua->ready_playback_urbs, +- struct ua101_urb, ready_list); +- list_del(&urb->ready_list); ++ ua_urb = list_first_entry(&ua->ready_playback_urbs, ++ struct ua101_urb, ready_list); ++ list_del(&ua_urb->ready_list); ++ urb = ua_urb->urb; + + /* fill packet with data or silence */ +- urb->urb.iso_frame_desc[0].length = ++ urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, +- &urb->urb, ++ urb, + frames); + else +- memset(urb->urb.transfer_buffer, 0, +- urb->urb.iso_frame_desc[0].length); ++ memset(urb->transfer_buffer, 0, ++ urb->iso_frame_desc[0].length); + + /* and off you go ... */ +- err = usb_submit_urb(&urb->urb, GFP_ATOMIC); ++ err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + abort_usb_playback(ua); + abort_alsa_playback(ua); +@@ -342,7 +345,8 @@ static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, + + static void capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + struct ua101_stream *stream = &ua->capture; + unsigned int frames, write_ptr; + bool do_period_elapsed; +@@ -413,7 +417,8 @@ static void capture_urb_complete(struct urb *urb) + + static void first_capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = capture_urb_complete; + capture_urb_complete(urb); +@@ -427,7 +432,7 @@ static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); ++ int err = usb_submit_urb(stream->urbs[i].urb, GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); +@@ -442,8 +447,8 @@ static void kill_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) +- if (stream->urbs[i]) +- usb_kill_urb(&stream->urbs[i]->urb); ++ if (stream->urbs[i].urb) ++ usb_kill_urb(stream->urbs[i].urb); + } + + static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) +@@ -508,7 +513,7 @@ static int start_usb_capture(struct ua101 *ua) + return err; + + clear_bit(CAPTURE_URB_COMPLETED, &ua->states); +- ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; ++ ua->capture.urbs[0].urb->complete = first_capture_urb_complete; + ua->rate_feedback_start = 0; + ua->rate_feedback_count = 0; + +@@ -550,7 +555,7 @@ static int start_usb_playback(struct ua101 *ua) + return err; + + clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); +- ua->playback.urbs[0]->urb.complete = ++ ua->playback.urbs[0].urb->complete = + first_playback_urb_complete; + scoped_guard(spinlock_irq, &ua->lock) { + INIT_LIST_HEAD(&ua->ready_playback_urbs); +@@ -580,7 +585,7 @@ static int start_usb_playback(struct ua101 *ua) + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + } +- urb = &ua->playback.urbs[i]->urb; ++ urb = ua->playback.urbs[i].urb; + urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + memset(urb->transfer_buffer, 0, +@@ -1059,7 +1064,7 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + void (*urb_complete)(struct urb *)) + { + unsigned max_packet_size = stream->max_packet_bytes; +- struct ua101_urb *urb; ++ struct urb *urb; + unsigned int b, u = 0; + + for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { +@@ -1070,23 +1075,24 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + while (size >= max_packet_size) { + if (u >= stream->queue_length) + goto bufsize_error; +- urb = kmalloc_obj(*urb); ++ urb = usb_alloc_urb(1, GFP_KERNEL); + if (!urb) + return -ENOMEM; +- usb_init_urb(&urb->urb); +- urb->urb.dev = ua->dev; +- urb->urb.pipe = stream->usb_pipe; +- urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP; +- urb->urb.transfer_buffer = addr; +- urb->urb.transfer_dma = dma; +- urb->urb.transfer_buffer_length = max_packet_size; +- urb->urb.number_of_packets = 1; +- urb->urb.interval = 1; +- urb->urb.context = ua; +- urb->urb.complete = urb_complete; +- urb->urb.iso_frame_desc[0].offset = 0; +- urb->urb.iso_frame_desc[0].length = max_packet_size; +- stream->urbs[u++] = urb; ++ urb->dev = ua->dev; ++ urb->pipe = stream->usb_pipe; ++ urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ urb->transfer_buffer = addr; ++ urb->transfer_dma = dma; ++ urb->transfer_buffer_length = max_packet_size; ++ urb->number_of_packets = 1; ++ urb->interval = 1; ++ urb->context = &stream->urbs[u]; ++ urb->complete = urb_complete; ++ urb->iso_frame_desc[0].offset = 0; ++ urb->iso_frame_desc[0].length = max_packet_size; ++ stream->urbs[u].ua = ua; ++ stream->urbs[u].urb = urb; ++ u++; + size -= max_packet_size; + addr += max_packet_size; + dma += max_packet_size; +@@ -1104,8 +1110,8 @@ static void free_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- kfree(stream->urbs[i]); +- stream->urbs[i] = NULL; ++ usb_free_urb(stream->urbs[i].urb); ++ stream->urbs[i].urb = NULL; + } + } + +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -849,6 +849,27 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) + return 0; + } + ++/* ++ * A post configuration device descriptor read is needed to make the CM1A ++ * operational after reenumeration. ++ */ ++static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) ++{ ++ struct usb_device_descriptor *desc __free(kfree) = kmalloc_obj(*desc); ++ int err; ++ ++ if (!desc) ++ return -ENOMEM; ++ ++ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); ++ if (err < 0) { ++ dev_err(&dev->dev, "failed to read device descriptor: %d\n", err); ++ return err; ++ } ++ ++ return 0; ++} ++ + /* + * Some sound cards from Native Instruments are in fact compliant to the USB + * audio standard of version 2 and other approved USB standards, even though +@@ -1681,6 +1702,8 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); ++ case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ ++ return snd_usb_cm1a_boot_quirk(dev); + } + + return 0; +@@ -2390,6 +2413,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x050c, /* Behringer Flow 8 */ + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), ++ DEVICE_FLG(0x1397, 0x0510, /* Behringer UV1 */ ++ QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0522-sound-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0522-sound-fixes-3.patch.sig new file mode 100644 index 0000000..59a91ae Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0522-sound-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0523-asoc-fixes-2.patch b/pkgbuilds/linux-omarchy-eevdf/0523-asoc-fixes-2.patch new file mode 100644 index 0000000..b65c5d4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0523-asoc-fixes-2.patch @@ -0,0 +1,1040 @@ +diff --git a/sound/hda/core/ext/controller.c b/sound/hda/core/ext/controller.c +--- a/sound/hda/core/ext/controller.c ++++ b/sound/hda/core/ext/controller.c +@@ -90,8 +90,10 @@ int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus) + + for (idx = 0; idx < link_count; idx++) { + hlink = kzalloc_obj(*hlink); +- if (!hlink) ++ if (!hlink) { ++ snd_hdac_ext_link_free_all(bus); + return -ENOMEM; ++ } + hlink->index = idx; + hlink->bus = bus; + hlink->ml_addr = bus->mlcap + AZX_ML_BASE + +diff --git a/sound/hda/core/ext/stream.c b/sound/hda/core/ext/stream.c +--- a/sound/hda/core/ext/stream.c ++++ b/sound/hda/core/ext/stream.c +@@ -102,8 +102,10 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + + for (i = 0; i < num_stream; i++) { + struct hdac_ext_stream *hext_stream = kzalloc_obj(*hext_stream); +- if (!hext_stream) ++ if (!hext_stream) { ++ snd_hdac_ext_stream_free_all(bus); + return -ENOMEM; ++ } + tag = ++stream_tag; + snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag); + idx++; +@@ -111,7 +113,6 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + } + + return 0; +- + } + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "RB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN17-41"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -899,6 +906,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "TM2423"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "HP 255R 15.6 inch G10 Notebook PC"), ++ } ++ }, + {} + }; + +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,17 +14,14 @@ + * for ST-Ericsson. + */ + +-#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include +-#include + #include + #include + #include +@@ -54,32 +51,9 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + +-/* Nr of FIR/IIR-coeff banks in ANC-block */ +-#define AB8500_NR_OF_ANC_COEFF_BANKS 2 +- +-/* Minimum duration to keep ANC IIR Init bit high or +-low before proceeding with the configuration sequence */ +-#define AB8500_ANC_SM_DELAY 2000 +- +-/* Sidetone states */ +-static const char * const enum_sid_state[] = { +- "Unconfigured", +- "Apply FIR", +- "FIR is configured", +-}; +-enum sid_state { +- SID_UNCONFIGURED = 0, +- SID_APPLY_FIR = 1, +- SID_FIR_CONFIGURED = 2, +-}; +- + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; +- struct mutex ctrl_lock; +- +- /* Sidetone */ +- enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -642,9 +616,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + +- SND_SOC_DAPM_INPUT("ANC Configure Input"), +- SND_SOC_DAPM_OUTPUT("ANC Configure Output"), +- + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -687,10 +658,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + +- /* ANC Configure */ +- {"ANC Configure Input", NULL, "Main Supply"}, +- {"ANC Configure Output", NULL, "ANC Configure Input"}, +- + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -972,75 +939,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + +-/* +- * Control-events +- */ +- +-static int sid_status_control_get(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- +- guard(mutex)(&drvdata->ctrl_lock); +- ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- +- return 0; +-} +- +-/* Write sidetone FIR-coefficients configuration sequence */ +-static int sid_status_control_put(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- unsigned int param, sidconf; +- int status = 1; +- +- dev_dbg(component->dev, "%s: Enter\n", __func__); +- +- if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { +- dev_err(component->dev, +- "%s: ERROR: This control supports '%s' only!\n", +- __func__, enum_sid_state[SID_APPLY_FIR]); +- return -EIO; +- } +- +- guard(mutex)(&drvdata->ctrl_lock); +- +- sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); +- if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +- if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { +- dev_err(component->dev, "%s: Sidetone busy while off!\n", +- __func__); +- status = -EPERM; +- } else { +- status = -EBUSY; +- } +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- return status; +- } +- +- snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +- +- for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { +- snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); +- snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); +- } +- +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), +- BIT(AB8500_SIDFIRADR_FIRSIDSET)); +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); +- +- drvdata->sid_status = SID_FIR_CONFIGURED; +- +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- +- return status; +-} +- + /* + * Controls - Non-DAPM ASoC + */ +@@ -1324,9 +1222,6 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + +-/* Sidetone */ +-static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); +- + /* ANC */ + + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1617,8 +1512,6 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ +- SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, +- sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2148,10 +2041,8 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + + static int ab8500_codec_probe(struct snd_soc_component *component) + { +- struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2184,10 +2075,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + +- snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); +- +- mutex_init(&drvdata->ctrl_lock); +- + return status; + } + +@@ -2216,7 +2103,6 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; +- drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -386,11 +386,8 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); + +- if (!cs35l56->base.init_done) +- return 0; +- +- /* runtime_resume unmasks the interrupt */ +- cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->sdw_attached) ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -18,9 +18,11 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include + #include + #include +@@ -37,6 +39,13 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + ++/* ++ * snd_soc_register_component() can call component_probe() on all instances ++ * in a card, so deferred registration must be protected across all instances. ++ */ ++static DEFINE_MUTEX(cs35l56_component_register_lock); ++static bool cs35l56_shutting_down; ++ + void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* +@@ -1957,6 +1966,11 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); + int ret; + ++ guard(mutex)(&cs35l56_component_register_lock); ++ ++ if (cs35l56_shutting_down) ++ return; ++ + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2217,6 +2231,37 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif + ++static int cs35l56_reboot_notify(struct notifier_block *nb, ++ unsigned long action, void *data) ++{ ++ guard(mutex)(&cs35l56_component_register_lock); ++ cs35l56_shutting_down = true; ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block cs35l56_reboot_notifier = { ++ .notifier_call = cs35l56_reboot_notify, ++}; ++ ++static int __init cs35l56_modinit(void) ++{ ++ /* ++ * Use reboot notifier to prevent race between shutdown and ++ * snd_soc_register_component(). Driver shutdown() callback would ++ * run too late, after device_shutdown() is already walking the ++ * device list that component registration can modify. ++ */ ++ return register_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_init(cs35l56_modinit); ++ ++static void __exit cs35l56_modexit(void) ++{ ++ unregister_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_exit(cs35l56_modexit); ++ + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -32,6 +32,7 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; ++ struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -41,6 +42,7 @@ struct cs35l56_private { + const char *fallback_fw_suffix; + bool soft_resetting; + bool sdw_attached; ++ bool component_registered; + struct completion init_completion; + + int speaker_id; +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -26,6 +26,7 @@ struct es8326_priv { + struct snd_soc_component *component; + struct delayed_work jack_detect_work; + struct delayed_work button_press_work; ++ struct delayed_work capture_pop_work; + struct snd_soc_jack *jack; + int irq; + /* The lock protects the situation that an irq is generated +@@ -628,6 +629,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, + 0x30, 0x00); + } else { ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, + 0x0F, 0x0F); + if (es8326->version > ES8326_VERSION_B) { +@@ -666,8 +668,9 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x70); + regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x00); + } +- regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, +- 0x0F, 0x00); ++ ++ queue_delayed_work(system_dfl_wq, &es8326->capture_pop_work, ++ msecs_to_jiffies(40)); + } + } + return 0; +@@ -773,6 +776,15 @@ static void es8326_disable_micbias(struct snd_soc_component *component) + snd_soc_dapm_mutex_unlock(dapm); + } + ++static void es8326_capture_pop_handler(struct work_struct *work) ++{ ++ struct es8326_priv *es8326 = ++ container_of(work, struct es8326_priv, capture_pop_work.work); ++ ++ regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, ++ 0x0F, 0x00); ++} ++ + /* + * For button detection, set the following in soundcard + * snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); +@@ -1140,6 +1152,7 @@ static int es8326_suspend(struct snd_soc_component *component) + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&es8326->jack_detect_work); ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + es8326_disable_micbias(component); + es8326->calibrated = false; + regmap_write(es8326->regmap, ES8326_CLK_MUX, 0x2d); +@@ -1291,6 +1304,8 @@ static int es8326_i2c_probe(struct i2c_client *i2c) + es8326_jack_detect_handler); + INIT_DELAYED_WORK(&es8326->button_press_work, + es8326_jack_button_handler); ++ INIT_DELAYED_WORK(&es8326->capture_pop_work, ++ es8326_capture_pop_handler); + /* ES8316 is level-based while ES8326 is edge-based */ + ret = devm_request_threaded_irq(&i2c->dev, es8326->irq, NULL, es8326_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -812,6 +812,23 @@ static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static void es8389_standby(struct snd_soc_component *component) ++{ ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); ++ regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); ++ usleep_range(70000, 72000); ++ regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); ++ regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); ++ regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); ++ regmap_write(es8389->regmap, ES8389_RESET, 0x3E); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); ++ usleep_range(8000, 8500); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); ++} ++ + static int es8389_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) + { +@@ -834,18 +851,7 @@ static int es8389_set_bias_level(struct snd_soc_component *component, + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); +- regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); +- usleep_range(70000, 72000); +- regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); +- regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); +- regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); +- regmap_write(es8389->regmap, ES8389_RESET, 0x3E); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); +- usleep_range(8000, 8500); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); +- ++ es8389_standby(component); + clk_disable_unprepare(es8389->mclk); + break; + case SND_SOC_BIAS_OFF: +@@ -1015,7 +1021,7 @@ static int es8389_suspend(struct snd_soc_component *component) + { + struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); + +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + regcache_cache_only(es8389->regmap, true); + regcache_mark_dirty(es8389->regmap); + +@@ -1084,7 +1090,7 @@ static int es8389_probe(struct snd_soc_component *component) + + es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include "rt1318-sdw.h" + +@@ -564,11 +565,10 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1318_sdw_priv *rt1318 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -582,23 +582,13 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt1318->sdw_slave, &stream_config, +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -3246,7 +3246,7 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1320_sdw_priv *rt1320 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + struct sdw_port_config dmic_port_config[2]; + struct sdw_stream_runtime *sdw_stream; +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include "rt712-sdca.h" + +@@ -1449,11 +1450,10 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); +@@ -1471,7 +1471,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT712_AIF1) + port = 1; + else if (dai->id == RT712_AIF2) +@@ -1479,7 +1478,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT712_AIF1) + port = 4; + else if (dai->id == RT712_AIF3) +@@ -1488,13 +1486,8 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt712->slave, &stream_config, +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + #include + + #include "rt721-sdca.h" +@@ -206,6 +207,7 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); ++ regmap_write(rt721->regmap, 0x2f5d, 0x1); + } + + static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) +@@ -1268,11 +1270,10 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt721_sdca_priv *rt721 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1291,7 +1292,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT721_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT721_AIF1) + port = 1; + else if (dai->id == RT721_AIF2) +@@ -1299,7 +1299,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT721_AIF1) + port = 2; + else if (dai->id == RT721_AIF3) +@@ -1307,13 +1306,9 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt721->slave, &stream_config, +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #include + + #include "rt722-sdca.h" +@@ -1442,11 +1443,10 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1465,7 +1465,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT722_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT722_AIF1) + port = 1; + else if (dai->id == RT722_AIF2) +@@ -1473,7 +1472,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT722_AIF1) + port = 2; + else if (dai->id == RT722_AIF3) +@@ -1481,13 +1479,9 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt722->slave, &stream_config, +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1001,6 +1001,31 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, + &stream_config, &port_config); ++ ++ /* ++ * The two mono amps each render one channel of the stereo stream: ++ * snd_sdw_params_to_config() hands every codec the full mask for ++ * playback, which leaves the pair in mirror mode and one channel ++ * unreproduced. Claim a single channel instead, keyed off the ++ * machine-assigned component prefix rather than the SoundWire ++ * address, which is board-specific: soc_sdw_ti_amp.c names the amps ++ * tas2783-1..4. ++ * ++ * Which side an amp then renders does not follow from the bit that ++ * is set - sdw_compute_slave_ports() advances the payload offset by ++ * the popcount of ch_mask and never looks at which bit it is - but ++ * from the amp's position in the codec order of the DAI link, which ++ * on these boards matches the prefix numbering. ++ */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && ++ params_channels(params) == 2 && component->name_prefix) { ++ const char *idx_str = strrchr(component->name_prefix, '-'); ++ unsigned long idx; ++ ++ if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) ++ port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); ++ } ++ + /* port 1 for playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port_config.num = 1; +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -15,6 +15,22 @@ + #include "../../../codecs/hda.h" + #include "../utils.h" + ++static int avs_link_startup(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ const struct snd_soc_pcm_stream *stream_info; ++ struct snd_soc_dai *codec_dai; ++ ++ codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ stream_info = snd_soc_dai_get_pcm_stream(codec_dai, substream->stream); ++ ++ return snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, stream_info->sig_bits); ++} ++ ++static const struct snd_soc_ops avs_link_ops = { ++ .startup = avs_link_startup, ++}; ++ + static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, + struct snd_soc_dai_link **links) + { +@@ -43,6 +59,7 @@ static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int + dl[i].platforms = platform; + dl[i].num_platforms = 1; + dl[i].ignore_pmdown_time = 1; ++ dl[i].ops = &avs_link_ops; + + dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -92,16 +92,28 @@ static int avs_hdac_bus_init_streams(struct hdac_bus *bus) + { + unsigned int cp_streams, pb_streams; + unsigned int gcap; ++ int ret; + + gcap = snd_hdac_chip_readw(bus, GCAP); + cp_streams = (gcap >> 8) & 0x0F; + pb_streams = (gcap >> 12) & 0x0F; + bus->num_streams = cp_streams + pb_streams; + +- snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); +- snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ ret = snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); ++ if (ret) ++ return ret; ++ ret = snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ if (ret) ++ goto err; + +- return snd_hdac_bus_alloc_stream_pages(bus); ++ ret = snd_hdac_bus_alloc_stream_pages(bus); ++ if (ret) ++ goto err; ++ ++ return 0; ++err: ++ snd_hdac_ext_stream_free_all(bus); ++ return ret; + } + + static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) +@@ -383,6 +395,18 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + struct device *dev = &pci->dev; + int ret; + ++ ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); ++ if (!ipc) ++ return -ENOMEM; ++ ++ adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); ++ if (!adev->modcfg_buf) ++ return -ENOMEM; ++ ++ ret = avs_ipc_init(ipc, dev); ++ if (ret < 0) ++ return ret; ++ + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); + if (ret < 0) + return ret; +@@ -394,17 +418,6 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + bus->mixer_assigned = -1; + mutex_init(&bus->prepare_mutex); + +- ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); +- if (!ipc) +- return -ENOMEM; +- ret = avs_ipc_init(ipc, dev); +- if (ret < 0) +- return ret; +- +- adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); +- if (!adev->modcfg_buf) +- return -ENOMEM; +- + adev->dev = dev; + adev->spec = (const struct avs_spec *)id->driver_data; + adev->ipc = ipc; +@@ -456,13 +469,14 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + + ret = pcim_request_all_regions(pci, "AVS HDAudio"); + if (ret < 0) +- return ret; ++ goto err_request_regions; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (!bus->remap_addr) { + dev_err(bus->dev, "ioremap error\n"); +- return -ENXIO; ++ ret = -ENXIO; ++ goto err_request_regions; + } + + adev->dsp_ba = pci_ioremap_bar(pci, 4); +@@ -526,6 +540,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + iounmap(adev->dsp_ba); + err_remap_bar4: + iounmap(bus->remap_addr); ++err_request_regions: ++ snd_hdac_ext_bus_exit(bus); + return ret; + } + +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,20 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) +- return -EBUSY; ++ if (kfifo_initialized(&adev->trace_fifo)) { ++ ret = -EBUSY; ++ goto err; ++ } + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- return ret; ++ goto err; + + file->private_data = adev; + return 0; ++err: ++ module_put(adev->dev->driver->owner); ++ return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -172,7 +172,7 @@ static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg + + /* Avoid deadlock as the exception may be the response to SET_D0IX. */ + if (current_work() != &ipc->d0ix_work.work) +- cancel_delayed_work_sync(&ipc->d0ix_work); ++ cancel_delayed_work(&ipc->d0ix_work); + ipc->in_d0ix = false; + /* Re-enabled on recovery completion. */ + pm_runtime_disable(adev->dev); +@@ -395,11 +395,11 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + struct avs_ipc *ipc = adev->ipc; + int ret; + ++ guard(mutex)(&ipc->msg_mutex); ++ + if (!ipc->ready) + return -EPERM; + +- guard(mutex)(&ipc->msg_mutex); +- + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); + avs_dsp_send_tx(adev, request, true); +diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig +--- a/sound/soc/intel/boards/Kconfig ++++ b/sound/soc/intel/boards/Kconfig +@@ -533,12 +533,14 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH + select SND_SOC_CS35L56_SPI + select SND_SOC_CS35L56_SDW + select SND_SOC_ES9356 ++ imply SND_SOC_TAC5XX2_SDW + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + imply SND_SOC_SDW_MOCKUP + help + Add support for Intel SoundWire-based platforms connected to +- MAX98373, RT700, RT711, RT1308 and RT715 ++ MAX98373, RT700, RT711, RT1308, RT715, TAC5XX2_SDW family (including ++ TAC5572, TAC5682). + If unsure select "N". + + endif +diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c +--- a/sound/soc/intel/boards/sof_rt5682.c ++++ b/sound/soc/intel/boards/sof_rt5682.c +@@ -908,6 +908,14 @@ static const struct platform_device_id board_ids[] = { + SOF_SSP_PORT_BT_OFFLOAD(2) | + SOF_BT_OFFLOAD_PRESENT), + }, ++ { ++ .name = "nvl_rt5682_def", ++ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | ++ SOF_SSP_PORT_CODEC(0) | ++ SOF_SSP_PORT_AMP(1) | ++ SOF_SSP_PORT_BT_OFFLOAD(2) | ++ SOF_BT_OFFLOAD_PRESENT), ++ }, + { + .name = "ptl_rt5682_c1_h02", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | +diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +@@ -51,10 +51,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { + }, + { + .comp_ids = &nvl_rt5682_rt5682s_hp, +- .drv_name = "sof_rt5682", +- .sof_tplg_filename = "sof-nvl-rt5682", /* the tplg suffix is added at run time */ +- .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | +- SND_SOC_ACPI_TPLG_INTEL_SSP_MSB, ++ .drv_name = "nvl_rt5682_def", ++ .sof_tplg_filename = "sof-nvl", /* the tplg suffix is added at run time */ ++ .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_AMP_NAME | ++ SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME, + }, + /* place amp/hdmi-in only boards in the end of table */ + { diff --git a/pkgbuilds/linux-omarchy-eevdf/0523-asoc-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0523-asoc-fixes-2.patch.sig new file mode 100644 index 0000000..1e9594c Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0523-asoc-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0524-sound-fixes-4.patch b/pkgbuilds/linux-omarchy-eevdf/0524-sound-fixes-4.patch new file mode 100644 index 0000000..39c0ebf --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0524-sound-fixes-4.patch @@ -0,0 +1,135 @@ +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -364,7 +364,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, + sound_debugfs_root); + #endif + #ifdef CONFIG_SND_CTL_DEBUG +- card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL); ++ card->value_buf = kmalloc_obj(*card->value_buf); + if (!card->value_buf) + return -ENOMEM; + #endif +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4304,6 +4304,7 @@ enum { + ALC287_FIXUP_LEGION_16ACHG6, + ALC287_FIXUP_CS35L41_I2C_2, + ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, ++ ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, + ALC287_FIXUP_CS35L41_I2C_4, + ALC245_FIXUP_CS35L41_SPI_1, + ALC245_FIXUP_CS35L41_SPI_2, +@@ -6615,6 +6616,12 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_MUTE_LED, + }, ++ [ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = cs35l41_fixup_i2c_two, ++ .chained = true, ++ .chain_id = ALC245_FIXUP_HP_X360_MUTE_LEDS, ++ }, + [ALC287_FIXUP_CS35L41_I2C_4] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, +@@ -7372,6 +7379,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), + SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), ++ SND_PCI_QUIRK(0x103c, 0x8259, "HP OMEN 15-ax202nf", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), +@@ -7582,6 +7590,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), +@@ -7674,7 +7683,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8dcd, "HP Victus 15-fa2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8d9b, "HP 17 Turbine OmniBook 7 UMA", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -449,7 +449,9 @@ static void push_back_to_ready_list(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) + { + guard(spinlock_irqsave)(&ep->lock); +- list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); ++ /* ctx may still be linked: a stale completion racing a stop/restart. */ ++ if (list_empty(&ctx->ready_list)) ++ list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + } + + /* +@@ -1037,6 +1039,7 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) + */ + static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + { ++ struct snd_urb_ctx *ctx, *n; + unsigned int i; + + if (!force && atomic_read(&ep->running)) +@@ -1046,7 +1049,9 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + return 0; + + scoped_guard(spinlock_irqsave, &ep->lock) { +- INIT_LIST_HEAD(&ep->ready_playback_urbs); ++ /* Unlink each ctx; INIT_LIST_HEAD() alone would leave them looking linked. */ ++ list_for_each_entry_safe(ctx, n, &ep->ready_playback_urbs, ready_list) ++ list_del_init(&ctx->ready_list); + ep->next_packet_head = 0; + ep->next_packet_queued = 0; + } +diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c +--- a/sound/usb/implicit.c ++++ b/sound/usb/implicit.c +@@ -76,6 +76,7 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { + + /* Implicit feedback quirk table for capture: only FIXED type */ + static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { ++ IMPLICIT_FB_FIXED_DEV(0x1397, 0x0004, 0x01, 1), /* Behringer FCA1616 */ + {} /* terminator */ + }; + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -531,6 +531,15 @@ static const struct usbmix_name_map audient_id24_map[] = { + {} + }; + ++/* ++ * The GC553Pro returns no data for GET_CUR on its advertised mute control. ++ * SET_CUR succeeds but does not mute capture, so skip the control entirely. ++ */ ++static const struct usbmix_name_map avermedia_gc553pro_map[] = { ++ { 3, NULL, UAC_FU_MUTE }, ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -577,6 +586,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0763, 0x2031), + .selector_map = c400_selectors, + }, ++ { ++ .id = USB_ID(0x07ca, 0x1553), ++ .map = avermedia_gc553pro_map, ++ }, + { + .id = USB_ID(0x08bb, 0x2702), + .map = linex_map, diff --git a/pkgbuilds/linux-omarchy-eevdf/0524-sound-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0524-sound-fixes-4.patch.sig new file mode 100644 index 0000000..ac1447b Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0524-sound-fixes-4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0525-asoc-fixes-3.patch b/pkgbuilds/linux-omarchy-eevdf/0525-asoc-fixes-3.patch new file mode 100644 index 0000000..843a983 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0525-asoc-fixes-3.patch @@ -0,0 +1,549 @@ +diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h +--- a/include/sound/soc_sdw_utils.h ++++ b/include/sound/soc_sdw_utils.h +@@ -250,8 +250,6 @@ int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, +- struct snd_soc_dai *dai); + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix); + + /* MAXIM codec support */ +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -390,7 +390,7 @@ static int mc_probe(struct platform_device *pdev) + ctx->private = amd_ctx; + card = &ctx->card; + card->dev = &pdev->dev; +- card->name = "amd-soundwire"; ++ card->name = "amd-sdw"; + card->owner = THIS_MODULE; + card->late_probe = asoc_sdw_card_late_probe; + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -524,13 +524,13 @@ config SND_SOC_ADAU1977 + tristate + + config SND_SOC_ADAU1977_SPI +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - SPI" + depends on SPI_MASTER + select SND_SOC_ADAU1977 + select REGMAP_SPI + + config SND_SOC_ADAU1977_I2C +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - I2C" + depends on I2C + select SND_SOC_ADAU1977 + select REGMAP_I2C +diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c +--- a/sound/soc/codecs/adau1977-i2c.c ++++ b/sound/soc/codecs/adau1977-i2c.c +@@ -34,9 +34,18 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { + }; + MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); + ++static const struct of_device_id adau1977_i2c_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, adau1977_i2c_of_match); ++ + static struct i2c_driver adau1977_i2c_driver = { + .driver = { + .name = "adau1977", ++ .of_match_table = adau1977_i2c_of_match, + }, + .probe = adau1977_i2c_probe, + .id_table = adau1977_i2c_ids, +diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c +--- a/sound/soc/codecs/adau1977-spi.c ++++ b/sound/soc/codecs/adau1977-spi.c +@@ -53,18 +53,18 @@ static const struct spi_device_id adau1977_spi_ids[] = { + }; + MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); + +-static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = { +- { .compatible = "adi,adau1977" }, +- { .compatible = "adi,adau1978" }, +- { .compatible = "adi,adau1979" }, +- { }, ++static const struct of_device_id adau1977_spi_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, + }; + MODULE_DEVICE_TABLE(of, adau1977_spi_of_match); + + static struct spi_driver adau1977_spi_driver = { + .driver = { + .name = "adau1977", +- .of_match_table = of_match_ptr(adau1977_spi_of_match), ++ .of_match_table = adau1977_spi_of_match, + }, + .probe = adau1977_spi_probe, + .id_table = adau1977_spi_ids, +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -18,12 +18,16 @@ + static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201a ... 0x201f: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2230 ... 0x2232: + case 0x2f01 ... 0x2f0a: + case 0x2f35 ... 0x2f36: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x2f50: + case 0x2f54: + case 0x2f58 ... 0x2f5d: +@@ -48,6 +52,7 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201b: + case 0x201c: + case 0x201d: +@@ -56,6 +61,9 @@ static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + case 0x2230: + case 0x2f01: + case 0x2f35: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x320c: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT_GE49, RT712_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUNC_NUM_HID, RT712_SDCA_ENT_HID01, RT712_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -73,14 +73,57 @@ static int rt712_sdca_index_update_bits(struct rt712_sdca_priv *rt712, + return rt712_sdca_index_write(rt712, nid, reg, tmp); + } + ++static void rt712_sdca_clk_patch(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0017); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0001); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0081); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0080); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0010); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++} ++ ++static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0x0800, ++ 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0xf000, ++ 0x0000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x0c, 0xc000, ++ 0xc000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x00, 0xc000, ++ 0xc000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++} ++ + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + { + unsigned int val, loop_rc = 0, loop_dc = 0; + struct device *dev; + struct regmap *regmap = rt712->regmap; ++ unsigned int clk_base; + int chk_cnt = 100; + int ret = 0; + ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ + mutex_lock(&rt712->calibrate_mutex); + dev = regmap_get_device(regmap); + +@@ -109,8 +152,35 @@ static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + if (ret < 0) + goto _cali_fail_; + } +- if (loop_dc == chk_cnt) +- dev_err(dev, "%s, calibration time-out!\n", __func__); ++ ++ if (loop_dc == chk_cnt) { ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_FSM_CTL, 0x4100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0x7883); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0xf893); ++ rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ for (loop_dc = 0; loop_dc < chk_cnt && ++ (val & RT712_DAC_DC_CALI_TRIGGER); loop_dc++) { ++ usleep_range(10000, 11000); ++ ret = rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ if (ret < 0) ++ goto _cali_fail_; ++ } ++ ++ if (loop_dc == chk_cnt) ++ dev_err(dev, "%s, calibration time-out!\n", __func__); ++ else ++ dev_dbg(dev, "%s, calibration success!\n", __func__); ++ } + + if (loop_dc == chk_cnt || loop_rc == chk_cnt) + ret = -ETIMEDOUT; +@@ -1759,9 +1829,13 @@ static void rt712_sdca_va_io_init(struct rt712_sdca_priv *rt712) + + static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + { +- int ret = 0; + unsigned int jack_func_status, mic_func_status, amp_func_status; + struct device *dev = &rt712->slave->dev; ++ unsigned int clk_base; ++ int ret = 0; ++ ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ dev_dbg(dev, "%s clk_base=%x", __func__, clk_base); + + regmap_read(rt712->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT0, RT712_SDCA_CTL_FUNC_STATUS, 0), &jack_func_status); +@@ -1773,6 +1847,12 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + __func__, jack_func_status, mic_func_status, amp_func_status); + + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); ++ ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -162,6 +162,16 @@ struct rt712_dmic_kctrl_priv { + #define RT712_EAPD_HIGH 0x2 + #define RT712_EAPD_LOW 0x0 + ++/* SDW clock root frequency */ ++#define RT712_SDW_ROOT_CLK 0x004d ++#define RT712_SDW_SCALE_CLK0 0x0062 ++#define RT712_SDW_SCALE_CLK1 0x0072 ++ ++/* PLL2 config */ ++#define RT712_PLL2_CONF1 0x2f3a ++#define RT712_PLL2_CONF2 0x2f3d ++#define RT712_PLL2_CONF3 0x2f41 ++ + /* RC Calibration register */ + #define RT712_RC_CAL 0x3201 + +@@ -254,6 +264,14 @@ enum rt712_sdca_version { + RT712_VB, + }; + ++enum { ++ RT712_CLK_FREQ_19_2_MHZ = 1, ++ RT712_CLK_FREQ_24MHZ = 2, ++ RT712_CLK_FREQ_24_576MHZ = 3, ++ RT712_CLK_FREQ_22_5792MHZ = 4, ++}; ++ ++ + int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave); + int rt712_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave); +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -70,6 +70,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re + case 0x0310100: + case 0x2000000 ... 0x2000003: + case 0x2000013: ++ case 0x2000026: + case 0x200002c: + case 0x200003c: + case 0x2000046: +@@ -142,6 +143,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x200000d: + case 0x2000019: + case 0x2000020: ++ case 0x2000026: + case 0x200002c: + case 0x2000030: + case 0x2000046: +@@ -155,6 +157,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x5810039: + case 0x5b10018: + case 0x5b10019: ++ case 0x6100006: + return true; + default: + return false; +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -1497,6 +1497,15 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, + &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); + } + ++static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) ++{ ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_REG, ++ RT721_VD_HIDDEN_CTRL, RT721_HIDDEN_REG_SW_RESET, ++ RT721_HIDDEN_REG_SW_RESET); ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, ++ RT721_HDA_LEGACY_RESET_CTL, 0x1, 0x1); ++} ++ + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + { + struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); +@@ -1530,9 +1539,17 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + } + + pm_runtime_get_noresume(&slave->dev); ++ ++ if (!rt721->first_hw_init) ++ rt721_sdca_reset(rt721); ++ + rt721_sdca_dmic_preset(rt721); + rt721_sdca_amp_preset(rt721); + rt721_sdca_jack_preset(rt721); ++ ++ if (rt721->hs_jack && (!rt721->first_hw_init)) ++ rt721_sdca_jack_init(rt721); ++ + if (rt721->first_hw_init) { + regcache_cache_bypass(rt721->regmap, false); + regcache_mark_dirty(rt721->regmap); +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -775,9 +775,12 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, + s++; + } + ++ adsp_dbg(dsp, "Try '%s'\n", fw->filename); + ret = wm_adsp_firmware_request(&fw->firmware, fw->filename, cs_dsp->dev); + if (ret < 0) { +- adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ if (ret != -ENOENT) ++ adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ + kfree(fw->filename); + fw->filename = NULL; + if (ret != -ENOENT) +diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c +--- a/sound/soc/intel/boards/sof_es8336.c ++++ b/sound/soc/intel/boards/sof_es8336.c +@@ -360,6 +360,15 @@ static const struct dmi_system_id sof_es8336_quirk_table[] = { + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOC_ES8336_HEADSET_MIC1) + }, ++ { ++ .callback = sof_es8336_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "NDZ-WXX9"), ++ }, ++ .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | ++ SOC_ES8336_HEADSET_MIC1) ++ }, + {} + }; + +diff --git a/sound/soc/sdw_utils/soc_sdw_cs_amp.c b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_cs_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +@@ -14,7 +14,6 @@ + #include + #include + +-#define CS_AMP_CHANNELS_PER_AMP 4 + #define CS35L56_SPK_VOLUME_0DB 400 /* 0dB Max */ + + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix) +@@ -64,51 +63,6 @@ int asoc_sdw_cs_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai + } + EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_rtd_init, "SND_SOC_SDW_UTILS"); + +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +-{ +- const struct snd_soc_dai_link *dai_link = rtd->dai_link; +- const struct snd_soc_dai_link_ch_map *ch_map; +- const struct snd_soc_dai_link_component *codec_dlc; +- struct snd_soc_dai *codec_dai; +- u8 ch_slot[8] = {}; +- unsigned int amps_per_bus, ch_per_amp, mask; +- int i, ret; +- +- WARN_ON(dai_link->num_cpus > ARRAY_SIZE(ch_slot)); +- +- /* +- * CS35L56 has 4 TX channels. When the capture is aggregated the +- * same bus slots will be allocated to all the amps on a bus. Only +- * one amp on that bus can be transmitting in each slot so divide +- * the available 4 slots between all the amps on a bus. +- */ +- amps_per_bus = dai_link->num_codecs / dai_link->num_cpus; +- if ((amps_per_bus == 0) || (amps_per_bus > CS_AMP_CHANNELS_PER_AMP)) { +- dev_err(rtd->card->dev, "Illegal num_codecs:%u / num_cpus:%u\n", +- dai_link->num_codecs, dai_link->num_cpus); +- return -EINVAL; +- } +- +- ch_per_amp = CS_AMP_CHANNELS_PER_AMP / amps_per_bus; +- +- for_each_rtd_ch_maps(rtd, i, ch_map) { +- codec_dlc = snd_soc_link_to_codec(rtd->dai_link, i); +- codec_dai = snd_soc_find_dai(codec_dlc); +- mask = GENMASK(ch_per_amp - 1, 0) << ch_slot[ch_map->cpu]; +- +- ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, mask, 4, 32); +- if (ret < 0) { +- dev_err(rtd->card->dev, "Failed to set TDM slot:%d\n", ret); +- return ret; +- } +- +- ch_slot[ch_map->cpu] += ch_per_amp; +- } +- +- return 0; +-} +-EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_feedback_rtd_init, "SND_SOC_SDW_UTILS"); +- + int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -811,7 +811,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -840,7 +839,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -869,7 +867,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -898,7 +895,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -1565,7 +1561,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link_ch_map *ch_maps; + int ch = params_channels(params); +- unsigned int ch_mask; ++ unsigned int cpu_ch_mask, codec_ch_mask; + int num_codecs; + int step; + int i; +@@ -1575,8 +1571,9 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + + /* Identical data will be sent to all codecs in playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- ch_mask = GENMASK(ch - 1, 0); ++ cpu_ch_mask = GENMASK(ch - 1, 0); + step = 0; ++ codec_ch_mask = 0; + } else { + num_codecs = rtd->dai_link->num_codecs; + +@@ -1586,17 +1583,24 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- ch_mask = GENMASK(ch / num_codecs - 1, 0); +- step = hweight_long(ch_mask); ++ cpu_ch_mask = GENMASK(ch / num_codecs - 1, 0); ++ step = hweight_long(cpu_ch_mask); ++ codec_ch_mask = cpu_ch_mask; + } + + /* + * The captured data will be combined from each cpu DAI if the dai + * link has more than one codec DAIs. Set codec channel mask and + * ASoC will set the corresponding channel numbers for each cpu dai. ++ * ++ * sdw_stream_add_slave() assigns different payload offsets to each ++ * codec in a capture stream, so that the same channels on each ++ * codec map to different channels on the CPU. + */ +- for_each_link_ch_maps(rtd->dai_link, i, ch_maps) +- ch_maps->cpu_ch_mask = ch_mask << (i * step); ++ for_each_link_ch_maps(rtd->dai_link, i, ch_maps) { ++ ch_maps->cpu_ch_mask = cpu_ch_mask << (i * step); ++ ch_maps->codec_ch_mask = codec_ch_mask; ++ } + + return 0; + } diff --git a/pkgbuilds/linux-omarchy-eevdf/0525-asoc-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0525-asoc-fixes-3.patch.sig new file mode 100644 index 0000000..0b22640 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0525-asoc-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0526-sof-amd-acp7x-suspend-unknown-revision.patch b/pkgbuilds/linux-omarchy-eevdf/0526-sof-amd-acp7x-suspend-unknown-revision.patch new file mode 100644 index 0000000..3b71148 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0526-sof-amd-acp7x-suspend-unknown-revision.patch @@ -0,0 +1,13 @@ +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 +@@ -1197,7 +1197,8 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) + enable = true; + break; + default: +- break; ++ dev_err(sdev->dev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); ++ return -EINVAL; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); diff --git a/pkgbuilds/linux-omarchy-eevdf/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig new file mode 100644 index 0000000..086972e Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0527-sound-fixes-5.patch b/pkgbuilds/linux-omarchy-eevdf/0527-sound-fixes-5.patch new file mode 100644 index 0000000..f9378d4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0527-sound-fixes-5.patch @@ -0,0 +1,513 @@ +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -340,6 +340,15 @@ static inline void snd_card_unref(struct snd_card *card) + + DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T)) + ++/* ++ * For automatic error handling at probe time, assign the card like: ++ * struct snd_card *card __free(snd_card_free) = NULL; ++ * then create and process as usual. ++ * But, don't forget to clear to NULL at successful return for avoiding ++ * the unexpected cleanup! ++ */ ++DEFINE_FREE(snd_card_free, struct snd_card *, if (_T) snd_card_free(_T)) ++ + #define snd_card_set_dev(card, devptr) ((card)->dev = (devptr)) + + /* device.c */ +diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c +--- a/sound/core/seq/seq_compat.c ++++ b/sound/core/seq/seq_compat.c +@@ -44,7 +44,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned + return -EFAULT; + data->kernel = NULL; + +- err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ scoped_guard(mutex, &client->ioctl_mutex) { ++ err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ } + if (err < 0) + return err; + +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4025,6 +4025,7 @@ enum { + ALC269_FIXUP_SONY_VAIO, + ALC275_FIXUP_SONY_VAIO_GPIO2, + ALC269_FIXUP_DELL_M101Z, ++ ALC256_FIXUP_ASUS_PM3406CHA, + ALC269_FIXUP_SKU_IGNORE, + ALC269_FIXUP_ASUS_G73JW, + ALC269_FIXUP_ASUS_N7601ZM_PINS, +@@ -4124,6 +4125,7 @@ enum { + ALC285_FIXUP_HP_SPECTRE_X360_EB1, + ALC285_FIXUP_HP_SPECTRE_X360_DF1, + ALC285_FIXUP_HP_ENVY_X360, ++ ALC285_FIXUP_HP_ENVY_13AQ, + ALC288_FIXUP_DELL_HEADSET_MODE, + ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC288_FIXUP_DELL_XPS_13, +@@ -4199,6 +4201,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE, + ALC225_FIXUP_WYSE_AUTO_MUTE, + ALC225_FIXUP_WYSE_DISABLE_MIC_VREF, ++ ALC225_FIXUP_HP_AIO_SPK, + ALC286_FIXUP_ACER_AIO_HEADSET_MIC, + ALC256_FIXUP_ASUS_HEADSET_MIC, + ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4385,6 +4388,7 @@ enum { + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, + ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ++ ALC256_FIXUP_IPASON_SMARTBOOK_S1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4482,6 +4486,14 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC256_FIXUP_ASUS_PM3406CHA] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x7f20 }, ++ {} ++ } ++ }, + [ALC269_FIXUP_SKU_IGNORE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_sku_ignore, +@@ -5720,6 +5732,14 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, ++ [ALC225_FIXUP_HP_AIO_SPK] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x0220 }, ++ { } ++ }, ++ }, + [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { +@@ -6401,6 +6421,15 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, + }, ++ [ALC285_FIXUP_HP_ENVY_13AQ] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170150 }, /* front speakers */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, ++ }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, +@@ -7088,6 +7117,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC236_FIXUP_DELL_HP_POP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_invalidate_dacs, ++ .chained = true, ++ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC274_FIXUP_HP_89E9_GPIO] = { + .type = HDA_FIXUP_FUNC, +@@ -7150,6 +7181,13 @@ static const struct hda_fixup alc269_fixups[] = { + { } + } + }, ++ [ALC256_FIXUP_IPASON_SMARTBOOK_S1] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x1b, 0x90170110 }, /* the real internal speaker */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7197,6 +7235,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), ++ SND_PCI_QUIRK(0x1025, 0x151e, "Acer Nitro AN515-45", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC287_FIXUP_ACER_MICMUTE_LED), +@@ -7210,8 +7249,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x173b, "Acer Swift SFG16-72", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), +@@ -7317,6 +7358,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), + SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x2164, "HP Pavilion 15 Notebook PC", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), +@@ -7399,9 +7441,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x854a, "HP EliteBook 830 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x854d, "HP EliteBook 840 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), ++ HDA_CODEC_QUIRK(0x103c, 0x85ba, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f1, "HP Laptop 15-dw1xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), +@@ -7409,6 +7453,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x86ad, "HP ENVY 13-aq1xxx", ALC285_FIXUP_HP_ENVY_13AQ), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), + SND_PCI_QUIRK(0x103c, 0x86c8, "HP Laptop 14s-dr1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7437,6 +7482,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x87b2, "HP Pavilion Gaming Laptop 15-ec1xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x87b7, "HP Laptop 14-fq0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87cb, "HP Pavilion 15-eg0xxx", ALC287_FIXUP_HP_GPIO_LED), +@@ -7497,6 +7543,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8975, "HP EliteBook x360 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x897d, "HP mt440 Mobile Thin Client U74", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8981, "HP Elite Dragonfly G3", ALC245_FIXUP_CS35L41_SPI_4), ++ SND_PCI_QUIRK(0x103c, 0x8987, "HP Pavilion 14-dv1001TU", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898a, "HP Pavilion 15-eg100", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898e, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x898f, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7534,7 +7581,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8a31, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a36, "HP Pavilion Plus 14-eh0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a3d, "HP Victus 15-fb0xxx (MB 8A3D)", ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT), +@@ -7595,6 +7642,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bc7, "HP Pavilion 15-eh3174nw", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bd4, "HP Victus 16-s0xxx (MB 8BD4)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), +@@ -7617,6 +7665,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c2f, "HP Victus 15-fb2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7726,6 +7775,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7829,6 +7879,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1652, "ASUS ROG Zephyrus Do 15 SE", ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), + SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), +@@ -7931,7 +7982,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x3ef0, "ASUS Strix G635LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f00, "ASUS Strix G815LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f10, "ASUS Strix G835LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x3f30, "ASUS Strix G815LR_LW", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3fd0, "ASUS B3605CVA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3ff0, "ASUS B5405CVA", ALC245_FIXUP_CS35L41_SPI_2), +@@ -7954,6 +8005,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), + SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), + SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE), ++ SND_PCI_QUIRK(0x10ec, 0x111e, "Higole F9B", ALC290_FIXUP_MONO_SPEAKERS), + SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), + SND_PCI_QUIRK(0x10ec, 0x119e, "Positivo SU C1400", ALC269_FIXUP_ASPIRE_HEADSET_MIC), + SND_PCI_QUIRK(0x10ec, 0x11bc, "VAIO VJFE-IL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), +@@ -8179,7 +8231,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), +- SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8204,6 +8255,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8254,6 +8306,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38d5, "Yoga S990-16 Pro IMH YC Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d6, "Yoga S990-16 Pro IMH VECO Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d7, "Lenovo Yoga 9 14IMH9", ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN), ++ SND_PCI_QUIRK(0x17aa, 0x38dc, "Lenovo ThinkBook G8", ALC285_FIXUP_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +@@ -8273,7 +8326,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), + SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), +@@ -8369,6 +8422,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x2782, 0x0206, "IPASON SmartBook S1", ALC256_FIXUP_IPASON_SMARTBOOK_S1), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c +--- a/sound/hda/codecs/realtek/realtek.c ++++ b/sound/hda/codecs/realtek/realtek.c +@@ -1292,6 +1292,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1407,6 +1408,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_write_coef_idx(codec, 0x45, 0xc489); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); +@@ -1562,6 +1564,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) + alc_process_coef_fw(codec, coef0225); + alc_hp_enable_unmute(codec, 75); + break; ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1663,6 +1666,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1780,6 +1784,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1881,6 +1886,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + msleep(300); +diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c +--- a/sound/hda/controllers/tegra.c ++++ b/sound/hda/controllers/tegra.c +@@ -374,7 +374,8 @@ static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) + * in powers of 2, next available ratio is 16 which can be + * used as a limiting factor here. + */ +- if (of_device_is_compatible(np, "nvidia,tegra30-hda")) ++ if (of_device_is_compatible(np, "nvidia,tegra30-hda") || ++ of_device_is_compatible(np, "nvidia,tegra194-hda")) + chip->bus.core.sdo_limit = 16; + + /* codec detection */ +diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c +--- a/sound/pci/ctxfi/cthw20k2.c ++++ b/sound/pci/ctxfi/cthw20k2.c +@@ -1895,6 +1895,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + + if (hw->model == CTSB1270) { + /* Set up the PCM4220 ADC on Titanium HD */ ++ data = hw_read_20kx(hw, GPIO_DATA); + data &= ~0x0C; + if (1 == info->msr) + data |= 0x00; /* Single Speed Mode 32-50kHz */ +@@ -1903,6 +1904,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + else + data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */ + hw_write_20kx(hw, GPIO_DATA, data); ++ usleep_range(10000, 11000); + } + + hw_adc_start(hw); +diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c +--- a/sound/pci/ice1712/ice1712.c ++++ b/sound/pci/ice1712/ice1712.c +@@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) + { + static int dev; +- struct snd_card *card __free(snd_card_unref) = NULL; ++ struct snd_card *card __free(snd_card_free) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c +--- a/sound/usb/caiaq/device.c ++++ b/sound/usb/caiaq/device.c +@@ -562,6 +562,9 @@ static int snd_probe(struct usb_interface *intf, + ret = init_card(caiaqdev(card)); + if (ret < 0) { + dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret); ++#ifdef CONFIG_SND_USB_CAIAQ_INPUT ++ snd_usb_caiaq_input_disconnect(caiaqdev(card)); ++#endif + snd_card_free(card); + return ret; + } +diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c +--- a/sound/usb/line6/playback.c ++++ b/sound/usb/line6/playback.c +@@ -182,6 +182,13 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) + + fsize *= bytes_per_frame; + ++ if (fsize > line6pcm->max_packet_size_out) { ++ dev_err(line6pcm->line6->ifcdev, ++ "playback packet too large: %d > %d\n", ++ fsize, line6pcm->max_packet_size_out); ++ return -EMSGSIZE; ++ } ++ + fout->offset = urb_size; + fout->length = fsize; + urb_size += fsize; +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -1007,6 +1007,15 @@ static int detect_usb_format(struct ua101 *ua) + } + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->playback.max_packet_bytes = usb_endpoint_maxp(epd); ++ ++ if (ua->capture.max_packet_bytes / ua->capture.frame_bytes * ++ ua->playback.frame_bytes > ua->playback.max_packet_bytes) { ++ dev_err(&ua->dev->dev, ++ "playback packet size %u too small for %u capture frames\n", ++ ua->playback.max_packet_bytes, ++ ua->capture.max_packet_bytes / ua->capture.frame_bytes); ++ return -ENXIO; ++ } + return 0; + } + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -749,6 +749,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0db0, 0xa073), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, ++ { /* MSI MAG B850M MORTAR WIFI */ ++ .id = USB_ID(0x0db0, 0xcc78), ++ .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, ++ }, + { /* MSI TRX40 */ + .id = USB_ID(0x0db0, 0x543d), + .map = trx40_mobo_map, +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -1702,10 +1702,12 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); +- case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ +- return snd_usb_cm1a_boot_quirk(dev); + } + ++ /* Behringer devices may need explicit device descriptor read at boot */ ++ if (USB_ID_VENDOR(id) == 0x1397) ++ return snd_usb_cm1a_boot_quirk(dev); ++ + return 0; + } + +@@ -2366,6 +2368,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ + QUIRK_FLAG_ALWAYS_SET_RATE), ++ DEVICE_FLG(0x0b05, 0x1826, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), ++ DEVICE_FLG(0x0b05, 0x1827, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2510,8 +2516,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x262a, 0x9302, /* ddHiFi TC44C */ + QUIRK_FLAG_DSD_RAW), +- DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ +- QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x2772, 0x0502, /* Musical Fidelity M6s DAC */ + 0), /* for avoiding QUIRK_FLAG_DSD_RAW with vendor match */ + DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ +@@ -2542,6 +2546,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x3255, 0x0000, /* Luxman D-10X */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), ++ DEVICE_FLG(0x32bb, 0x0004, /* HiBy FC4 */ ++ QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x3302, 0x17c2, /* TTGK Technology USB-C Audio */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ +@@ -2608,6 +2614,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2622, /* IAG Limited devices */ + QUIRK_FLAG_DSD_RAW), ++ VENDOR_FLG(0x2708, /* Audient devices */ ++ QUIRK_FLAG_IGNORE_CTL_ERROR), + VENDOR_FLG(0x2772, /* Musical Fidelity devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x278b, /* Rotel? */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0527-sound-fixes-5.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0527-sound-fixes-5.patch.sig new file mode 100644 index 0000000..2654792 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0527-sound-fixes-5.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0528-asoc-fixes-4.patch b/pkgbuilds/linux-omarchy-eevdf/0528-asoc-fixes-4.patch new file mode 100644 index 0000000..2290ae4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0528-asoc-fixes-4.patch @@ -0,0 +1,523 @@ +diff --git a/drivers/spi/spi-cs42l43.c b/drivers/spi/spi-cs42l43.c +--- a/drivers/spi/spi-cs42l43.c ++++ b/drivers/spi/spi-cs42l43.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -215,12 +216,108 @@ static size_t cs42l43_spi_max_length(struct spi_device *spi) + return CS42L43_SPI_MAX_LENGTH; + } + ++/* ++ * Workaround needed for two speaker ID pins in one ACPI GpioIo() but ++ * the Linux-specific _DSD property only contains one pin. ++ * Create a temporary acpi_gpio_mapping pointing at both pins. ++ */ ++static const struct acpi_gpio_params cs42l43_2bit_speaker_id_from_one_gpioio_params[] = { ++ [0] = { ++ .crs_entry_index = 0, ++ .line_index = 0, ++ }, ++ [1] = { ++ .crs_entry_index = 0, ++ .line_index = 1, ++ }, ++}; ++ ++static const struct acpi_gpio_mapping cs42l43_2bit_speaker_id_from_one_gpioio_mapping[] = { ++ { ++ .name = "spk-id-quirk-gpios", ++ .data = cs42l43_2bit_speaker_id_from_one_gpioio_params, ++ .size = ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params), ++ }, ++ { } ++}; ++ ++static int cs42l43_get_2bit_speaker_id_from_one_gpioio(struct cs42l43_spi *priv, int *result) ++{ ++ struct fwnode_reference_args args; ++ struct acpi_device *adev; ++ struct gpio_desc *desc; ++ u32 spkid = 0; ++ int i, ret; ++ ++ /* Use the _DSD property to get the node containing the GpioIo() */ ++ ret = fwnode_property_get_reference_args(dev_fwnode(priv->dev), "spk-id-gpios", ++ NULL, 3, 0, &args); ++ if (ret) ++ return ret; ++ ++ struct fwnode_handle *fwnode __free(fwnode_handle) = args.fwnode; ++ ++ /* An acpi_gpio_mapping must be added to the node that contains the GpioIo() */ ++ adev = to_acpi_device_node(fwnode); ++ if (!adev) ++ return -EINVAL; ++ ++ ret = acpi_dev_add_driver_gpios(adev, cs42l43_2bit_speaker_id_from_one_gpioio_mapping); ++ if (ret) ++ return ret; ++ ++ /* gpiod_get_array() can't read from a mapping in a child node */ ++ for (i = 0; i < ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params); i++) { ++ desc = fwnode_gpiod_get_index(fwnode, "spk-id-quirk", i, GPIOD_IN, ++ dev_name(priv->dev)); ++ if (IS_ERR(desc)) { ++ ret = PTR_ERR(desc); ++ goto out; ++ } ++ ++ ret = gpiod_get_value_cansleep(desc); ++ gpiod_put(desc); ++ if (ret < 0) ++ goto out; ++ ++ spkid |= (u32)ret << i; ++ } ++ ++ dev_dbg(priv->dev, "spk-id-gpios = %u\n", spkid); ++ *result = spkid; ++ ret = 0; ++out: ++ acpi_dev_remove_driver_gpios(adev); ++ ++ return ret; ++} ++ ++static const struct dmi_system_id cs42l43_spk_id_quirks[] = { ++ { ++ .ident = "Dell XPS 13 DX13260", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53"), ++ }, ++ .driver_data = cs42l43_get_2bit_speaker_id_from_one_gpioio, ++ }, ++ { } ++}; ++ + static int cs42l43_get_speaker_id_gpios(struct cs42l43_spi *priv, int *result) + { ++ const struct dmi_system_id *dmi_id; + struct gpio_descs *descs; + u32 spkid; + int i, ret; + ++ dmi_id = dmi_first_match(cs42l43_spk_id_quirks); ++ if (dmi_id) { ++ int (*get_speaker_id)(struct cs42l43_spi *priv, int *result) = dmi_id->driver_data; ++ ++ return get_speaker_id(priv, result); ++ } ++ + descs = gpiod_get_array_optional(priv->dev, "spk-id", GPIOD_IN); + if (!descs) + return 0; +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -51,6 +51,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83V9"), + }, + }, ++ { ++ /* Lenovo Yoga Pro 7 14ASP9 (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83HN"), ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +@@ -63,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook S14 UM5406GA"), ++ }, ++ }, + { + /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ + .matches = { +diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig +--- a/sound/soc/amd/acp/Kconfig ++++ b/sound/soc/amd/acp/Kconfig +@@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH + select SND_SOC_RT715_SDW + select SND_SOC_RT715_SDCA_SDW + select SND_SOC_RT722_SDCA_SDW ++ select SND_SOC_TAS2783_SDW + help + This option enables Legacy(No DSP) sound card support for SoundWire + enabled AMD platforms along with ACP PDM controller. +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -748,7 +748,27 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_only[] = { + {} + }; + ++static const struct snd_soc_acpi_link_adr acp70_tas2783a_l0_rt712_vb_l1[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783_2_adr), ++ .adr_d = tas2783_2_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_1_group1_adr), ++ .adr_d = rt712_vb_1_group1_adr, ++ }, ++ {} ++}; ++ + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783a_l0_rt712_vb_l1, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0), + .links = acp70_tas2783_2, +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -248,6 +248,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "21J6"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "21JN"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -381,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83UU"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -1989,9 +1989,7 @@ config SND_SOC_RT715_SDCA_SDW + config SND_SOC_RT766_SDCA_SDW + tristate "Realtek RT766 SDCA Codec - SDW" + depends on SOUNDWIRE +- depends on SND_SOC_SDCA +- select SND_SOC_SDCA_HID +- select SND_SOC_SDCA_IRQ ++ depends on SND_SOC_SDCA_CLASS + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -1841,6 +1841,7 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ unsigned int gain; + u16 gain_reg; + u16 reg; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); +@@ -1882,6 +1883,8 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + } ++ gain = snd_soc_component_read(component, gain_reg); ++ snd_soc_component_write(component, gain_reg, gain); + wsa_macro_config_ear_spkr_gain(component, wsa, + event, gain_reg); + break; +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -200,7 +200,7 @@ static int max98363_sdw_dai_hw_params(struct snd_pcm_substream *substream, + struct max98363_priv *max98363 = + snd_soc_component_get_drvdata(component); + +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_runtime *stream; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -573,11 +574,10 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt1017_sdca_priv *rt1017 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -591,28 +591,17 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + dev_dbg(dai->dev, "frame_rate %d, ch_count %d, bps %d, direction %d, ch_mask %d, port: %d\n", +- params_rate(params), num_channels, snd_pcm_format_width(params_format(params)), +- direction, ch_mask, port); ++ stream_config.frame_rate, stream_config.ch_count, stream_config.bps, ++ stream_config.direction, port_config.ch_mask, port); + + retval = sdw_stream_add_slave(rt1017->sdw_slave, &stream_config, + &port_config, 1, sdw_stream); +diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c +--- a/sound/soc/codecs/rt274.c ++++ b/sound/soc/codecs/rt274.c +@@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, + unsigned int val = 0; + int d_len_code = 0, c_len_code = 0; + ++ /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ + switch (params_rate(params)) { +- /* bit 14 0:48K 1:44.1K */ + case 44100: ++ val |= BIT(14); ++ break; + case 48000: + break; + default: +diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c +--- a/sound/soc/codecs/rt286.c ++++ b/sound/soc/codecs/rt286.c +@@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) + + if (rt286->i2c->irq) + schedule_delayed_work(&rt286->jack_detect_work, +- msecs_to_jiffies(50)); ++ msecs_to_jiffies(1250)); + return 0; + } + +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -676,7 +676,7 @@ SOC_ENUM("DRC Decay Rate", drc_decay), + SOC_ENUM("DRC FF Delay", drc_ff_delay), + SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), + SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +-SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), ++SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_1, 6, 3, 0, drc_tlv_max), + SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), + SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), + SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c +--- a/sound/soc/intel/boards/bytcr_rt5651.c ++++ b/sound/soc/intel/boards/bytcr_rt5651.c +@@ -410,6 +410,23 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, ++ { ++ /* Chuwi Hi8 (CWI513) with a BIOS using generic DMI strings */ ++ .callback = byt_rt5651_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), ++ DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), ++ /* Above matches are too generic, add bios-date match */ ++ DMI_MATCH(DMI_BIOS_DATE, "05/21/2016"), ++ }, ++ .driver_data = (void *)(BYT_RT5651_MCLK_EN | ++ BYT_RT5651_JD1_1 | ++ BYT_RT5651_OVCD_TH_2000UA | ++ BYT_RT5651_OVCD_SF_1P0 | ++ BYT_RT5651_IN2_MAP | ++ BYT_RT5651_HP_LR_SWAPPED | ++ BYT_RT5651_MONO_SPEAKER), ++ }, + { + /* Chuwi Vi8 Plus (CWI519) */ + .callback = byt_rt5651_quirk_cb, +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -745,6 +745,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_cs42l43_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0.tplg", ++ .machine_check = snd_soc_acpi_intel_force_function_topology, + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,22 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++static const struct dmi_system_id force_function_topology_quirk_table[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83R0"), ++ }, ++ }, ++ {} ++}; ++ ++bool snd_soc_acpi_intel_force_function_topology(void *arg) ++{ ++ return !dmi_check_system(force_function_topology_quirk_table); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_force_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) + { + return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,6 +11,7 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_force_function_topology(void *arg); + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c +--- a/sound/soc/qcom/lpass-cpu.c ++++ b/sound/soc/qcom/lpass-cpu.c +@@ -1234,8 +1234,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) + } + if (drvdata->mi2s_playback_sd_mode[dai_id] == + LPAIF_I2SCTL_MODE_QUAD01) { +- variant->dai_driver[dai_id].playback.channels_min = 4; +- variant->dai_driver[dai_id].playback.channels_max = 4; ++ variant->dai_driver[i].playback.channels_min = 4; ++ variant->dai_driver[i].playback.channels_max = 4; + } + } + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -388,20 +388,27 @@ static int class_function_probe(struct auxiliary_device *auxdev, + + ret = devm_pm_runtime_enable(dev); + if (ret) +- return ret; ++ goto err_pm; + + ret = class_function_boot(drv); + if (ret) +- return ret; ++ goto err_pm; + + ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais); +- if (ret) +- return dev_err_probe(dev, ret, "failed to register component\n"); ++ if (ret) { ++ dev_err_probe(dev, ret, "failed to register component\n"); ++ goto err_pm; ++ } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; ++ ++err_pm: ++ pm_runtime_put_sync(dev); ++ ++ return ret; + } + + static void class_function_remove(struct auxiliary_device *auxdev) +@@ -490,8 +497,6 @@ static int class_function_suspend(struct device *dev) + struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); + int ret; + +- drv->suspended = true; +- + /* Ensure runtime resume runs on resume */ + ret = pm_runtime_resume_and_get(dev); + if (ret) { +@@ -499,6 +504,8 @@ static int class_function_suspend(struct device *dev) + return ret; + } + ++ drv->suspended = true; ++ + sdca_irq_disable(drv->function, drv->core->irq_info); + + ret = pm_runtime_force_suspend(dev); +diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c +--- a/sound/soc/tegra/tegra186_asrc.c ++++ b/sound/soc/tegra/tegra186_asrc.c +@@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = { + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream6 Input Threshold", +- ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, ++ ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + diff --git a/pkgbuilds/linux-omarchy-eevdf/0528-asoc-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0528-asoc-fixes-4.patch.sig new file mode 100644 index 0000000..ad208a3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0528-asoc-fixes-4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch b/pkgbuilds/linux-omarchy-eevdf/0541-cvs-nova-lake-acpi-id.patch similarity index 95% rename from pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch rename to pkgbuilds/linux-omarchy-eevdf/0541-cvs-nova-lake-acpi-id.patch index 39ccf4d..2e401f7 100644 --- a/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0541-cvs-nova-lake-acpi-id.patch @@ -1,7 +1,7 @@ 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[] = { +@@ -867,6 +867,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 */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0541-cvs-nova-lake-acpi-id.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0541-cvs-nova-lake-acpi-id.patch.sig new file mode 100644 index 0000000..2e58e13 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/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-eevdf/0542-media-cvs-wake-irq-without-claiming-gpio.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch rename to pkgbuilds/linux-omarchy-eevdf/0542-media-cvs-wake-irq-without-claiming-gpio.patch diff --git a/pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0543-media-laptop-cameras.patch b/pkgbuilds/linux-omarchy-eevdf/0543-media-laptop-cameras.patch new file mode 100644 index 0000000..9ffdd20 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0543-media-laptop-cameras.patch @@ -0,0 +1,3293 @@ +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -259,6 +259,7 @@ config VIDEO_IMX335 + + config VIDEO_IMX355 + tristate "Sony IMX355 sensor support" ++ select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX355 camera. +@@ -287,6 +288,16 @@ config VIDEO_IMX415 + To compile this driver as a module, choose M here: the + module will be called imx415. + ++config VIDEO_IMX471 ++ tristate "Sony IMX471 sensor support" ++ select V4L2_CCI_I2C ++ help ++ This is a Video4Linux2 sensor driver for the Sony ++ IMX471 camera. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called imx471. ++ + config VIDEO_MAX9271_LIB + tristate + +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -61,6 +61,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o + obj-$(CONFIG_VIDEO_IMX355) += imx355.o + obj-$(CONFIG_VIDEO_IMX412) += imx412.o + obj-$(CONFIG_VIDEO_IMX415) += imx415.o ++obj-$(CONFIG_VIDEO_IMX471) += imx471.o + obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o + obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o + obj-$(CONFIG_VIDEO_KS0127) += ks0127.o +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 +@@ -31,6 +31,7 @@ + + #define PCI_DEVICE_ID_INTEL_IPU7 0x645d /* MTL / LNL */ + #define PCI_DEVICE_ID_INTEL_IPU7P5 0xb05d /* ARL / PTL */ ++#define PCI_DEVICE_ID_INTEL_IPU8 0xd719 /* NVL */ + + /* + * IPU7 PCI device IDs not covered by ipu6_pci_tbl in ipu6-pci-table.h. +@@ -39,6 +40,7 @@ + static const struct pci_device_id icvs_ipu7_tbl[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7P5) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU8) }, + { } + }; + +diff --git a/drivers/media/i2c/cvs/icvs.h b/drivers/media/i2c/cvs/icvs.h +--- a/drivers/media/i2c/cvs/icvs.h ++++ b/drivers/media/i2c/cvs/icvs.h +@@ -432,7 +432,6 @@ enum icvs_state { + * @freq_ctrl: (future) frequency control pointer + * @pads: Local media pads (sink/source) + * @nr_of_lanes: Active CSI-2 lane count +- * @link_freq: Current link frequency (Hz) + * @ipu_link: PM runtime device link (IPU consumer, CVS supplier) + * @res: Resource capability (light/full) + * @caps: Reported device protocol capabilities +@@ -458,7 +457,6 @@ struct icvs { + struct v4l2_ctrl *freq_ctrl; + struct media_pad pads[ICVS_CSI_NUM_PADS]; + u32 nr_of_lanes; +- u64 link_freq; + struct device_link *ipu_link; + enum icvs_resources res; + struct icvs_dev_capabilities caps; +diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c +--- a/drivers/media/i2c/cvs/v4l2.c ++++ b/drivers/media/i2c/cvs/v4l2.c +@@ -46,6 +46,7 @@ static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { + /** + * csi_set_link_cfg - Program default CSI-2 link parameters + * @ctx: CVS device context ++ * @link_freq: Link frequency (Hz) + * + * Populates a HOST_SET_MIPI_CONFIG command using current lane count and + * link frequency, then submits it to the device. +@@ -53,12 +54,12 @@ static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { + * + * Return: 0 on success or negative errno. + */ +-static int csi_set_link_cfg(struct icvs *ctx) ++static int csi_set_link_cfg(struct icvs *ctx, u64 link_freq) + { + struct icvs_cmd cmd = { + .cmd_id = cpu_to_be16(ICVS_HOST_SET_MIPI_CONFIG), + .param.conf.nr_of_lanes = ctx->nr_of_lanes, +- .param.conf.link_freq = ctx->link_freq, ++ .param.conf.link_freq = link_freq, + }; + size_t cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf); + +@@ -91,7 +92,7 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(ctx->remote->entity); + struct device *dev = cvs_dev(ctx); +- s64 freq; ++ s64 link_freq; + int ret; + + /* cvs_set_link_owner(ICVS_CSI_LINK_HOST) */ +@@ -99,15 +100,14 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + if (ret < 0) + return ret; + +- freq = v4l2_get_link_freq(ctx->remote, 0, 0); +- if (freq < 0) { +- ret = freq; ++ link_freq = v4l2_get_link_freq(ctx->remote, 0, 0); ++ if (link_freq < 0) { ++ ret = link_freq; + goto err_rpm_put; + } +- ctx->link_freq = freq; + + if (ctx->i2c_client) { +- ret = csi_set_link_cfg(ctx); ++ ret = csi_set_link_cfg(ctx, link_freq); + if (ret < 0) + goto err_rpm_put_sync; + } +@@ -345,7 +345,6 @@ static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + if (freq < 0) + return -EINVAL; + +- ctx->link_freq = freq; + cfg->link_freq = freq; + + return 0; +diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c +--- a/drivers/media/i2c/imx355.c ++++ b/drivers/media/i2c/imx355.c +@@ -9,74 +9,97 @@ + #include + #include + #include ++#include + #include + #include + ++#include + #include + #include + #include + #include + +-#define IMX355_REG_MODE_SELECT 0x0100 ++#define IMX355_REG_MODE_SELECT CCI_REG8(0x0100) + #define IMX355_MODE_STANDBY 0x00 + #define IMX355_MODE_STREAMING 0x01 + + /* Chip ID */ +-#define IMX355_REG_CHIP_ID 0x0016 ++#define IMX355_REG_CHIP_ID CCI_REG16(0x0016) + #define IMX355_CHIP_ID 0x0355 + ++#define IMX355_REG_LANE_SEL CCI_REG8(0x0114) ++ ++/* PLL registers that depend on the external clock frequency */ ++#define IMX355_REG_EXTCLK_FREQ CCI_REG16(0x0136) ++#define IMX355_REG_PLL_OP_PREDIV CCI_REG8(0x030d) ++#define IMX355_REG_PLL_OP_MUL CCI_REG16(0x030e) ++#define IMX355_REG_PLL_IVT_PCK_DIV CCI_REG8(0x0301) ++#define IMX355_REG_PLL_IVT_SYSCK_DIV CCI_REG8(0x0303) ++#define IMX355_PLL_OP_PREDIV 2 ++#define IMX355_PLL_IVT_PCK_DIV 5 ++ + /* V_TIMING internal */ +-#define IMX355_REG_FLL 0x0340 ++#define IMX355_REG_FLL CCI_REG16(0x0340) + #define IMX355_FLL_MAX 0xffff ++#define IMX355_VBLANK_MIN 20 ++ ++#define IMX355_REG_LLP CCI_REG16(0x0342) ++#define IMX355_LLP_MAX 0xffff ++ ++#define IMX355_REG_X_ADD_START CCI_REG16(0x0344) ++#define IMX355_REG_Y_ADD_START CCI_REG16(0x0346) ++#define IMX355_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX355_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX355_REG_X_OUT_SIZE CCI_REG16(0x034c) ++#define IMX355_REG_Y_OUT_SIZE CCI_REG16(0x034e) + + /* Exposure control */ +-#define IMX355_REG_EXPOSURE 0x0202 ++#define IMX355_REG_EXPOSURE CCI_REG16(0x0202) + #define IMX355_EXPOSURE_MIN 1 + #define IMX355_EXPOSURE_STEP 1 + #define IMX355_EXPOSURE_DEFAULT 0x0282 ++#define IMX355_EXPOSURE_OFFSET 10 + + /* Analog gain control */ +-#define IMX355_REG_ANALOG_GAIN 0x0204 ++#define IMX355_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX355_ANA_GAIN_MIN 0 + #define IMX355_ANA_GAIN_MAX 960 + #define IMX355_ANA_GAIN_STEP 1 + #define IMX355_ANA_GAIN_DEFAULT 0 + + /* Digital gain control */ +-#define IMX355_REG_DPGA_USE_GLOBAL_GAIN 0x3070 +-#define IMX355_REG_DIG_GAIN_GLOBAL 0x020e ++#define IMX355_REG_DPGA_USE_GLOBAL_GAIN CCI_REG8(0x3070) ++#define IMX355_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) + #define IMX355_DGTL_GAIN_MIN 256 + #define IMX355_DGTL_GAIN_MAX 4095 + #define IMX355_DGTL_GAIN_STEP 1 + #define IMX355_DGTL_GAIN_DEFAULT 256 + + /* Test Pattern Control */ +-#define IMX355_REG_TEST_PATTERN 0x0600 ++#define IMX355_REG_TEST_PATTERN CCI_REG16(0x0600) + #define IMX355_TEST_PATTERN_DISABLED 0 + #define IMX355_TEST_PATTERN_SOLID_COLOR 1 + #define IMX355_TEST_PATTERN_COLOR_BARS 2 + #define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3 + #define IMX355_TEST_PATTERN_PN9 4 + ++#define IMX355_REG_REQ_LINK_BIT_RATE CCI_REG16(0x0820) ++ ++#define IMX355_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX355_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX355_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ + /* Flip Control */ +-#define IMX355_REG_ORIENTATION 0x0101 ++#define IMX355_REG_ORIENTATION CCI_REG8(0x0101) + +-/* default link frequency and external clock */ +-#define IMX355_LINK_FREQ_DEFAULT 360000000LL +-#define IMX355_EXT_CLK 19200000 +-#define IMX355_LINK_FREQ_INDEX 0 +- +-/* number of data lanes */ +-#define IMX355_DATA_LANES 4 +- +-struct imx355_reg { +- u16 address; +- u8 val; +-}; ++#define IMX355_PIXEL_ARRAY_TOP 0 ++#define IMX355_PIXEL_ARRAY_LEFT 0 ++#define IMX355_PIXEL_ARRAY_WIDTH 3280 ++#define IMX355_PIXEL_ARRAY_HEIGHT 2464 + + struct imx355_reg_list { + u32 num_of_regs; +- const struct imx355_reg *regs; ++ const struct cci_reg_sequence *regs; + }; + + /* Mode : resolution and related config&values */ +@@ -85,28 +108,58 @@ struct imx355_mode { + u32 width; + /* Frame height */ + u32 height; ++ struct v4l2_rect crop; + + /* V-timing */ + u32 fll_def; +- u32 fll_min; + + /* H-timing */ + u32 llp; + +- /* index of link frequency */ +- u32 link_freq_index; +- + /* Default register values */ + struct imx355_reg_list reg_list; + }; + ++struct imx355_clk_params { ++ u32 ext_clk; ++ u16 extclk_freq; /* External clock (MHz) in 8.8 fixed point) */ ++ u16 pll_op_mpy[2]; /* OP system PLL multiplier */ ++ u8 pll_op_prediv[2]; /* OP system pre PLL d */ ++}; ++ ++/* ++ * The clock tree is in single PLL mode, so PREDIV_VT and MPY_IVT do nothing. ++ * In 4 lane mode the MIPI rate is 360Mhz (720Mbit/s) and pixel rate is ++ * 288MPix/s. ++ * In 2 lane mode the MIPI rate is 444MHz (888Mbit/s) and pixel rate ++ * 177.6MPix/s with a 24MHz clock, and 441.6MHz (883.2Mbit/s) and 176.6MPix/s ++ * with a 19.2MHz clock. ++ */ ++static const struct imx355_clk_params imx355_clk_params[] = { ++ { ++ .ext_clk = 19200000, ++ .extclk_freq = 0x1333, ++ .pll_op_mpy = { 75, 92 }, ++ .pll_op_prediv = { 2, 2 } ++ }, ++ { ++ .ext_clk = 24000000, ++ .extclk_freq = 0x1800, ++ .pll_op_mpy = { 60, 111 }, ++ .pll_op_prediv = { 2, 3 } ++ }, ++}; ++ + struct imx355_hwcfg { ++ s64 link_freq_menu; + unsigned long link_freq_bitmap; ++ unsigned int num_lanes; + }; + + struct imx355 { + struct device *dev; + struct clk *clk; ++ struct regmap *regmap; + + struct v4l2_subdev sd; + struct media_pad pad; +@@ -114,24 +167,14 @@ struct imx355 { + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; +- struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + +- /* Current mode */ +- const struct imx355_mode *cur_mode; +- + struct imx355_hwcfg *hwcfg; +- +- /* +- * Mutex for serialized access: +- * Protect sensor set pad format and start/stop streaming safely. +- * Protect access to sensor v4l2 controls. +- */ +- struct mutex mutex; ++ const struct imx355_clk_params *clk_params; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +@@ -143,750 +186,147 @@ static const struct regulator_bulk_data imx355_supplies[] = { + { .supply = "dovdd" }, + }; + +-static const struct imx355_reg imx355_global_regs[] = { +- { 0x0136, 0x13 }, +- { 0x0137, 0x33 }, +- { 0x304e, 0x03 }, +- { 0x4348, 0x16 }, +- { 0x4350, 0x19 }, +- { 0x4408, 0x0a }, +- { 0x440c, 0x0b }, +- { 0x4411, 0x5f }, +- { 0x4412, 0x2c }, +- { 0x4623, 0x00 }, +- { 0x462c, 0x0f }, +- { 0x462d, 0x00 }, +- { 0x462e, 0x00 }, +- { 0x4684, 0x54 }, +- { 0x480a, 0x07 }, +- { 0x4908, 0x07 }, +- { 0x4909, 0x07 }, +- { 0x490d, 0x0a }, +- { 0x491e, 0x0f }, +- { 0x4921, 0x06 }, +- { 0x4923, 0x28 }, +- { 0x4924, 0x28 }, +- { 0x4925, 0x29 }, +- { 0x4926, 0x29 }, +- { 0x4927, 0x1f }, +- { 0x4928, 0x20 }, +- { 0x4929, 0x20 }, +- { 0x492a, 0x20 }, +- { 0x492c, 0x05 }, +- { 0x492d, 0x06 }, +- { 0x492e, 0x06 }, +- { 0x492f, 0x06 }, +- { 0x4930, 0x03 }, +- { 0x4931, 0x04 }, +- { 0x4932, 0x04 }, +- { 0x4933, 0x05 }, +- { 0x595e, 0x01 }, +- { 0x5963, 0x01 }, +- { 0x3030, 0x01 }, +- { 0x3031, 0x01 }, +- { 0x3045, 0x01 }, +- { 0x4010, 0x00 }, +- { 0x4011, 0x00 }, +- { 0x4012, 0x00 }, +- { 0x4013, 0x01 }, +- { 0x68a8, 0xfe }, +- { 0x68a9, 0xff }, +- { 0x6888, 0x00 }, +- { 0x6889, 0x00 }, +- { 0x68b0, 0x00 }, +- { 0x3058, 0x00 }, +- { 0x305a, 0x00 }, ++static const struct cci_reg_sequence imx355_global_regs[] = { ++ { CCI_REG8(0x304e), 0x03 }, ++ { CCI_REG8(0x4348), 0x16 }, ++ { CCI_REG8(0x4350), 0x19 }, ++ { CCI_REG8(0x4408), 0x0a }, ++ { CCI_REG8(0x440c), 0x0b }, ++ { CCI_REG8(0x4411), 0x5f }, ++ { CCI_REG8(0x4412), 0x2c }, ++ { CCI_REG8(0x4623), 0x00 }, ++ { CCI_REG8(0x462c), 0x0f }, ++ { CCI_REG8(0x462d), 0x00 }, ++ { CCI_REG8(0x462e), 0x00 }, ++ { CCI_REG8(0x4684), 0x54 }, ++ { CCI_REG8(0x480a), 0x07 }, ++ { CCI_REG8(0x4908), 0x07 }, ++ { CCI_REG8(0x4909), 0x07 }, ++ { CCI_REG8(0x490d), 0x0a }, ++ { CCI_REG8(0x491e), 0x0f }, ++ { CCI_REG8(0x4921), 0x06 }, ++ { CCI_REG8(0x4923), 0x28 }, ++ { CCI_REG8(0x4924), 0x28 }, ++ { CCI_REG8(0x4925), 0x29 }, ++ { CCI_REG8(0x4926), 0x29 }, ++ { CCI_REG8(0x4927), 0x1f }, ++ { CCI_REG8(0x4928), 0x20 }, ++ { CCI_REG8(0x4929), 0x20 }, ++ { CCI_REG8(0x492a), 0x20 }, ++ { CCI_REG8(0x492c), 0x05 }, ++ { CCI_REG8(0x492d), 0x06 }, ++ { CCI_REG8(0x492e), 0x06 }, ++ { CCI_REG8(0x492f), 0x06 }, ++ { CCI_REG8(0x4930), 0x03 }, ++ { CCI_REG8(0x4931), 0x04 }, ++ { CCI_REG8(0x4932), 0x04 }, ++ { CCI_REG8(0x4933), 0x05 }, ++ { CCI_REG8(0x595e), 0x01 }, ++ { CCI_REG8(0x5963), 0x01 }, ++ { CCI_REG8(0x3030), 0x01 }, ++ { CCI_REG8(0x3031), 0x01 }, ++ { CCI_REG8(0x3045), 0x01 }, ++ { CCI_REG8(0x4010), 0x00 }, ++ { CCI_REG8(0x4011), 0x00 }, ++ { CCI_REG8(0x4012), 0x00 }, ++ { CCI_REG8(0x4013), 0x01 }, ++ { CCI_REG8(0x68a8), 0xfe }, ++ { CCI_REG8(0x68a9), 0xff }, ++ { CCI_REG8(0x6888), 0x00 }, ++ { CCI_REG8(0x6889), 0x00 }, ++ { CCI_REG8(0x68b0), 0x00 }, ++ { CCI_REG8(0x3058), 0x00 }, ++ { CCI_REG8(0x305a), 0x00 }, ++ { CCI_REG8(0x0112), 0x0a }, ++ { CCI_REG8(0x0113), 0x0a }, ++ { IMX355_REG_PLL_IVT_PCK_DIV, IMX355_PLL_IVT_PCK_DIV }, ++ { CCI_REG8(0x0303), 0x01 }, ++ { CCI_REG8(0x0305), 0x02 }, ++ { CCI_REG8(0x0306), 0x00 }, ++ { CCI_REG8(0x0307), 0x78 }, ++ { CCI_REG8(0x030b), 0x01 }, ++ { IMX355_REG_PLL_OP_PREDIV, IMX355_PLL_OP_PREDIV }, ++ { CCI_REG8(0x0310), 0x00 }, ++ { CCI_REG8(0x0220), 0x00 }, ++ { CCI_REG8(0x0222), 0x01 }, ++ { CCI_REG8(0x3088), 0x04 }, ++ { CCI_REG8(0x6813), 0x02 }, ++ { CCI_REG8(0x6835), 0x07 }, ++ { CCI_REG8(0x6836), 0x01 }, ++ { CCI_REG8(0x6837), 0x04 }, ++ { CCI_REG8(0x684d), 0x07 }, ++ { CCI_REG8(0x684e), 0x01 }, ++ { CCI_REG8(0x684f), 0x04 }, + }; + +-static const struct imx355_reg_list imx355_global_setting = { +- .num_of_regs = ARRAY_SIZE(imx355_global_regs), +- .regs = imx355_global_regs, ++static const struct cci_reg_sequence mode_3268x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3268x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcb }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc4 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3264x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3264x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xc7 }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3280x2464_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3280x2464_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xd0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0xa0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1940x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1940x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x33 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x94 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1936x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1936x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2f }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x90 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1924x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1924x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2b }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x84 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1920x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1920x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x27 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x80 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x1232_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x1232_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x922_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x922_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0x30 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x08 }, +- { 0x034b, 0x63 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x03 }, +- { 0x034f, 0x9a }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1300x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1300x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x7f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x14 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1296x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1296x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x77 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x10 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1284x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1284x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x6f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x04 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1280x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1280x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x67 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x00 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, +-}; +- +-static const struct imx355_reg mode_820x616_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x02 }, +- { 0x0341, 0x8c }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x44 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x03 }, +- { 0x034d, 0x34 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0x68 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x02 }, +- { 0x0701, 0x78 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_820x616_regs[] = { ++ { CCI_REG8(0x0700), 0x02 }, ++ { CCI_REG8(0x0701), 0x78 }, + }; + + static const char * const imx355_test_pattern_menu[] = { +@@ -897,23 +337,19 @@ static const char * const imx355_test_pattern_menu[] = { + "Pseudorandom Sequence (PN9)", + }; + +-/* +- * When adding more than the one below, make sure the disallowed ones will +- * actually be disabled in the LINK_FREQ control. +- */ +-static const s64 link_freq_menu_items[] = { +- IMX355_LINK_FREQ_DEFAULT, +-}; +- + /* Mode configs */ + static const struct imx355_mode supported_modes[] = { + { + .width = 3280, + .height = 2464, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, +@@ -922,10 +358,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3268, + .height = 2448, ++ .crop = { ++ .width = 3268, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3268x2448_regs), + .regs = mode_3268x2448_regs, +@@ -934,10 +374,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3264, + .height = 2448, ++ .crop = { ++ .width = 3264, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, +@@ -946,10 +390,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1940, + .height = 1096, ++ .crop = { ++ .width = 1940, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1940x1096_regs), + .regs = mode_1940x1096_regs, +@@ -958,10 +406,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1936, + .height = 1096, ++ .crop = { ++ .width = 1936, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs), + .regs = mode_1936x1096_regs, +@@ -970,10 +422,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1924, + .height = 1080, ++ .crop = { ++ .width = 1924, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1924x1080_regs), + .regs = mode_1924x1080_regs, +@@ -982,10 +438,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, ++ .crop = { ++ .width = 1920, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, +@@ -994,10 +454,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 1232, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs), + .regs = mode_1640x1232_regs, +@@ -1006,10 +470,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 922, ++ .crop = { ++ .width = 3280, ++ .height = 1844, ++ .left = 0, ++ .top = 304, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x922_regs), + .regs = mode_1640x922_regs, +@@ -1018,10 +486,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1300, + .height = 736, ++ .crop = { ++ .width = 2600, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1300x736_regs), + .regs = mode_1300x736_regs, +@@ -1030,10 +502,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1296, + .height = 736, ++ .crop = { ++ .width = 2592, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x736_regs), + .regs = mode_1296x736_regs, +@@ -1042,10 +518,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1284, + .height = 720, ++ .crop = { ++ .width = 2568, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1284x720_regs), + .regs = mode_1284x720_regs, +@@ -1054,10 +534,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1280, + .height = 720, ++ .crop = { ++ .width = 2560, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, +@@ -1066,10 +550,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 820, + .height = 616, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 652, +- .fll_min = 652, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_820x616_regs), + .regs = mode_820x616_regs, +@@ -1095,116 +583,28 @@ static u32 imx355_get_format_code(struct imx355 *imx355) + { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, }, + }; + +- lockdep_assert_held(&imx355->mutex); + code = codes[imx355->vflip->val][imx355->hflip->val]; + + return code; + } + +-/* Read registers up to 4 at a time */ +-static int imx355_read_reg(struct imx355 *imx355, u16 reg, u32 len, u32 *val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- struct i2c_msg msgs[2]; +- u8 addr_buf[2]; +- u8 data_buf[4] = { 0 }; +- int ret; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, addr_buf); +- /* Write register address */ +- msgs[0].addr = client->addr; +- msgs[0].flags = 0; +- msgs[0].len = ARRAY_SIZE(addr_buf); +- msgs[0].buf = addr_buf; +- +- /* Read data from register */ +- msgs[1].addr = client->addr; +- msgs[1].flags = I2C_M_RD; +- msgs[1].len = len; +- msgs[1].buf = &data_buf[4 - len]; +- +- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); +- if (ret != ARRAY_SIZE(msgs)) +- return -EIO; +- +- *val = get_unaligned_be32(data_buf); +- +- return 0; +-} +- +-/* Write registers up to 4 at a time */ +-static int imx355_write_reg(struct imx355 *imx355, u16 reg, u32 len, u32 val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- u8 buf[6]; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, buf); +- put_unaligned_be32(val << (8 * (4 - len)), buf + 2); +- if (i2c_master_send(client, buf, len + 2) != len + 2) +- return -EIO; +- +- return 0; +-} +- +-/* Write a list of registers */ +-static int imx355_write_regs(struct imx355 *imx355, +- const struct imx355_reg *regs, u32 len) +-{ +- int ret; +- u32 i; +- +- for (i = 0; i < len; i++) { +- ret = imx355_write_reg(imx355, regs[i].address, 1, regs[i].val); +- if (ret) { +- dev_err_ratelimited(imx355->dev, +- "write reg 0x%4.4x return err %d", +- regs[i].address, ret); +- +- return ret; +- } +- } +- +- return 0; +-} +- +-/* Open sub-device */ +-static int imx355_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- struct v4l2_mbus_framefmt *try_fmt = +- v4l2_subdev_state_get_format(fh->state, 0); +- +- mutex_lock(&imx355->mutex); +- +- /* Initialize try_fmt */ +- try_fmt->width = imx355->cur_mode->width; +- try_fmt->height = imx355->cur_mode->height; +- try_fmt->code = imx355_get_format_code(imx355); +- try_fmt->field = V4L2_FIELD_NONE; +- +- mutex_unlock(&imx355->mutex); +- +- return 0; +-} +- + static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + { + struct imx355 *imx355 = container_of(ctrl->handler, + struct imx355, ctrl_handler); ++ const struct v4l2_mbus_framefmt *format = NULL; ++ struct v4l2_subdev_state *state; + s64 max; + int ret; + ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ format = v4l2_subdev_state_get_format(state, 0); ++ + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ +- max = imx355->cur_mode->height + ctrl->val - 10; ++ max = format->height + ctrl->val - IMX355_EXPOSURE_OFFSET; + __v4l2_ctrl_modify_range(imx355->exposure, + imx355->exposure->minimum, + max, imx355->exposure->step, max); +@@ -1221,31 +621,31 @@ static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + /* Analog gain = 1024/(1024 - ctrl->val) times */ +- ret = imx355_write_reg(imx355, IMX355_REG_ANALOG_GAIN, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_ANALOG_GAIN, ++ ctrl->val, NULL); + break; + case V4L2_CID_DIGITAL_GAIN: +- ret = imx355_write_reg(imx355, IMX355_REG_DIG_GAIN_GLOBAL, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); + break; + case V4L2_CID_EXPOSURE: +- ret = imx355_write_reg(imx355, IMX355_REG_EXPOSURE, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_EXPOSURE, ++ ctrl->val, NULL); + break; + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ +- ret = imx355_write_reg(imx355, IMX355_REG_FLL, 2, +- imx355->cur_mode->height + ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_FLL, ++ format->height + ctrl->val, NULL); + break; + case V4L2_CID_TEST_PATTERN: +- ret = imx355_write_reg(imx355, IMX355_REG_TEST_PATTERN, +- 2, ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_TEST_PATTERN, ++ ctrl->val, NULL); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: +- ret = imx355_write_reg(imx355, IMX355_REG_ORIENTATION, 1, +- imx355->hflip->val | +- imx355->vflip->val << 1); ++ ret = cci_write(imx355->regmap, IMX355_REG_ORIENTATION, ++ imx355->hflip->val | imx355->vflip->val << 1, ++ NULL); + break; + default: + ret = -EINVAL; +@@ -1272,9 +672,7 @@ static int imx355_enum_mbus_code(struct v4l2_subdev *sd, + if (code->index > 0) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + code->code = imx355_get_format_code(imx355); +- mutex_unlock(&imx355->mutex); + + return 0; + } +@@ -1288,12 +686,9 @@ static int imx355_enum_frame_size(struct v4l2_subdev *sd, + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + if (fse->code != imx355_get_format_code(imx355)) { +- mutex_unlock(&imx355->mutex); + return -EINVAL; + } +- mutex_unlock(&imx355->mutex); + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; +@@ -1311,36 +706,10 @@ static void imx355_update_pad_format(struct imx355 *imx355, + fmt->format.height = mode->height; + fmt->format.code = imx355_get_format_code(imx355); + fmt->format.field = V4L2_FIELD_NONE; +-} +- +-static int imx355_do_get_pad_format(struct imx355 *imx355, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct v4l2_mbus_framefmt *framefmt; +- +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- fmt->format = *framefmt; +- } else { +- imx355_update_pad_format(imx355, imx355->cur_mode, fmt); +- } +- +- return 0; +-} +- +-static int imx355_get_pad_format(struct v4l2_subdev *sd, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- int ret; +- +- mutex_lock(&imx355->mutex); +- ret = imx355_do_get_pad_format(imx355, sd_state, fmt); +- mutex_unlock(&imx355->mutex); +- +- return ret; ++ fmt->format.colorspace = V4L2_COLORSPACE_RAW; ++ fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_601; ++ fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; ++ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + } + + static int +@@ -1351,13 +720,8 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + struct imx355 *imx355 = to_imx355(sd); + const struct imx355_mode *mode; + struct v4l2_mbus_framefmt *framefmt; +- s32 vblank_def; +- s32 vblank_min; ++ struct v4l2_rect *crop; + s64 h_blank; +- u64 pixel_rate; +- u32 height; +- +- mutex_lock(&imx355->mutex); + + /* + * Only one bayer order is supported. +@@ -1370,23 +734,25 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + width, height, + fmt->format.width, fmt->format.height); + imx355_update_pad_format(imx355, mode, fmt); +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- *framefmt = fmt->format; +- } else { +- imx355->cur_mode = mode; +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; +- do_div(pixel_rate, 10); +- __v4l2_ctrl_s_ctrl_int64(imx355->pixel_rate, pixel_rate); ++ framefmt = v4l2_subdev_state_get_format(sd_state, 0); ++ ++ *framefmt = fmt->format; ++ ++ crop = v4l2_subdev_state_get_crop(sd_state, 0); ++ crop->width = mode->crop.width; ++ crop->height = mode->crop.height; ++ crop->left = mode->crop.left; ++ crop->top = mode->crop.top; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Update limits and set FPS to default */ +- height = imx355->cur_mode->height; +- vblank_def = imx355->cur_mode->fll_def - height; +- vblank_min = imx355->cur_mode->fll_min - height; +- height = IMX355_FLL_MAX - height; +- __v4l2_ctrl_modify_range(imx355->vblank, vblank_min, height, 1, +- vblank_def); +- __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def); +- h_blank = mode->llp - imx355->cur_mode->width; ++ __v4l2_ctrl_modify_range(imx355->vblank, IMX355_VBLANK_MIN, ++ IMX355_FLL_MAX - mode->height, 1, ++ mode->fll_def - mode->height); ++ __v4l2_ctrl_s_ctrl(imx355->vblank, mode->fll_def - mode->height); ++ ++ h_blank = mode->llp - mode->width; ++ + /* + * Currently hblank is not changeable. + * So FPS control is done only by vblank. +@@ -1395,7 +761,42 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + h_blank, 1, h_blank); + } + +- mutex_unlock(&imx355->mutex); ++ return 0; ++} ++ ++static int imx355_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, 0); ++ return 0; ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = IMX355_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX355_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX355_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX355_PIXEL_ARRAY_HEIGHT; ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx355_entity_init_state(struct v4l2_subdev *subdev, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { }; ++ ++ fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; ++ fmt.format.code = MEDIA_BUS_FMT_SRGGB10_1X10; ++ fmt.format.width = supported_modes[0].width; ++ fmt.format.height = supported_modes[0].height; ++ ++ imx355_set_pad_format(subdev, sd_state, &fmt); + + return 0; + } +@@ -1403,52 +804,98 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + /* Start streaming */ + static int imx355_start_streaming(struct imx355 *imx355) + { +- const struct imx355_reg_list *reg_list; +- int ret; ++ const struct v4l2_mbus_framefmt *fmt; ++ struct v4l2_subdev_state *state; ++ const struct imx355_mode *mode; ++ int lane_idx = imx355->hwcfg->num_lanes == 4 ? 0 : 1; ++ struct v4l2_rect *crop; ++ u64 link_bitrate; ++ u8 binning_mode; ++ int ret = 0; + + /* Global Setting */ +- reg_list = &imx355_global_setting; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set global settings"); +- return ret; +- } ++ cci_multi_reg_write(imx355->regmap, imx355_global_regs, ++ ARRAY_SIZE(imx355_global_regs), &ret); + +- /* Apply default values of current mode */ +- reg_list = &imx355->cur_mode->reg_list; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set mode"); +- return ret; +- } ++ /* Apply values of current mode */ ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ crop = v4l2_subdev_state_get_crop(state, 0); ++ mode = v4l2_find_nearest_size(supported_modes, ++ ARRAY_SIZE(supported_modes), ++ width, height, fmt->width, fmt->height); ++ cci_multi_reg_write(imx355->regmap, mode->reg_list.regs, ++ mode->reg_list.num_of_regs, &ret); ++ ++ /* Set readout crop and size registers */ ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_START, crop->left, ++ &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_START, crop->top, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_END, ++ crop->width + crop->left - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_END, ++ crop->height + crop->top - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_OUT_SIZE, fmt->width, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_OUT_SIZE, fmt->height, &ret); ++ ++ binning_mode = ((crop->width / fmt->width) << 4) | ++ (crop->height / fmt->height); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_MODE, ++ binning_mode == 0x11 ? 0x00 : 0x01, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_TYPE, binning_mode, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_WEIGHTING, 0x00, &ret); ++ ++ /* Set PLL registers for the external clock frequency */ ++ cci_write(imx355->regmap, IMX355_REG_EXTCLK_FREQ, ++ imx355->clk_params->extclk_freq, &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_MUL, ++ imx355->clk_params->pll_op_mpy[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_PREDIV, ++ imx355->clk_params->pll_op_prediv[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_IVT_SYSCK_DIV, ++ lane_idx ? 2 : 1, &ret); ++ ++ /* Set MIPI configuration */ ++ cci_write(imx355->regmap, IMX355_REG_LANE_SEL, ++ imx355->hwcfg->num_lanes - 1, &ret); ++ ++ link_bitrate = imx355->link_freq->qmenu_int[imx355->link_freq->val] * ++ imx355->hwcfg->num_lanes * 2; ++ do_div(link_bitrate, 1000000); ++ cci_write(imx355->regmap, IMX355_REG_REQ_LINK_BIT_RATE, link_bitrate, ++ &ret); + + /* set digital gain control to all color mode */ +- ret = imx355_write_reg(imx355, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, 1); +- if (ret) +- return ret; ++ cci_write(imx355->regmap, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, &ret); ++ ++ /* set line length */ ++ cci_write(imx355->regmap, IMX355_REG_LLP, ++ imx355->hblank->val + fmt->width, &ret); + + /* Apply customized values from user */ +- ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); +- if (ret) +- return ret; ++ if (!ret) ++ ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); + +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STREAMING); ++ cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, IMX355_MODE_STREAMING, ++ &ret); ++ ++ return ret; + } + + /* Stop streaming */ + static int imx355_stop_streaming(struct imx355 *imx355) + { +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STANDBY); ++ return cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, ++ IMX355_MODE_STANDBY, NULL); + } + + static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + { + struct imx355 *imx355 = to_imx355(sd); ++ struct v4l2_subdev_state *state; + int ret = 0; + +- mutex_lock(&imx355->mutex); ++ state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) { + ret = pm_runtime_resume_and_get(imx355->dev); +@@ -1464,21 +911,21 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + goto err_rpm_put; + } else { + imx355_stop_streaming(imx355); +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + } + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx355->vflip, enable); + __v4l2_ctrl_grab(imx355->hflip, enable); + +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + + err_rpm_put: +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + err_unlock: +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + } +@@ -1487,14 +934,14 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + static int imx355_identify_module(struct imx355 *imx355) + { + int ret; +- u32 val; ++ u64 val; + +- ret = imx355_read_reg(imx355, IMX355_REG_CHIP_ID, 2, &val); ++ ret = cci_read(imx355->regmap, IMX355_REG_CHIP_ID, &val, NULL); + if (ret) + return ret; + + if (val != IMX355_CHIP_ID) { +- dev_err(imx355->dev, "chip id mismatch: %x!=%x", ++ dev_err(imx355->dev, "chip id mismatch: %x!=%llx", + IMX355_CHIP_ID, val); + return -EIO; + } +@@ -1512,9 +959,10 @@ static const struct v4l2_subdev_video_ops imx355_video_ops = { + + static const struct v4l2_subdev_pad_ops imx355_pad_ops = { + .enum_mbus_code = imx355_enum_mbus_code, +- .get_fmt = imx355_get_pad_format, ++ .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx355_set_pad_format, + .enum_frame_size = imx355_enum_frame_size, ++ .get_selection = imx355_get_selection, + }; + + static const struct v4l2_subdev_ops imx355_subdev_ops = { +@@ -1528,7 +976,7 @@ static const struct media_entity_operations imx355_subdev_entity_ops = { + }; + + static const struct v4l2_subdev_internal_ops imx355_internal_ops = { +- .open = imx355_open, ++ .init_state = imx355_entity_init_state, + }; + + static int imx355_power_off(struct device *dev) +@@ -1582,13 +1030,11 @@ static int imx355_init_controls(struct imx355 *imx355) + { + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; ++ const struct imx355_mode *mode = &supported_modes[0]; + s64 exposure_max; + s64 vblank_def; +- s64 vblank_min; + s64 hblank; + u64 pixel_rate; +- const struct imx355_mode *mode; +- u32 max; + int ret; + + ctrl_hdlr = &imx355->ctrl_handler; +@@ -1596,40 +1042,34 @@ static int imx355_init_controls(struct imx355 *imx355) + if (ret) + return ret; + +- ctrl_hdlr->lock = &imx355->mutex; +- max = ARRAY_SIZE(link_freq_menu_items) - 1; + imx355->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_LINK_FREQ, max, 0, +- link_freq_menu_items); ++ V4L2_CID_LINK_FREQ, 0, 0, ++ &imx355->hwcfg->link_freq_menu); + if (imx355->link_freq) + imx355->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; ++ pixel_rate = imx355->hwcfg->link_freq_menu * 2 * imx355->hwcfg->num_lanes; + do_div(pixel_rate, 10); +- /* By default, PIXEL_RATE is read only */ +- imx355->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_PIXEL_RATE, pixel_rate, +- pixel_rate, 1, pixel_rate); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_PIXEL_RATE, ++ pixel_rate, pixel_rate, 1, pixel_rate); + + /* Initialize vblank/hblank/exposure parameters based on current mode */ +- mode = imx355->cur_mode; + vblank_def = mode->fll_def - mode->height; +- vblank_min = mode->fll_min - mode->height; + imx355->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_VBLANK, vblank_min, ++ V4L2_CID_VBLANK, IMX355_VBLANK_MIN, + IMX355_FLL_MAX - mode->height, + 1, vblank_def); + + hblank = mode->llp - mode->width; +- imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_HBLANK, hblank, hblank, +- 1, hblank); ++ imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, ++ hblank, hblank, 1, hblank); + if (imx355->hblank) + imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* fll >= exposure time + adjust parameter (default value is 10) */ +- exposure_max = mode->fll_def - 10; ++ exposure_max = mode->fll_def - IMX355_EXPOSURE_OFFSET; + imx355->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX355_EXPOSURE_MIN, exposure_max, +@@ -1683,14 +1123,17 @@ static int imx355_init_controls(struct imx355 *imx355) + return ret; + } + +-static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) ++static struct imx355_hwcfg *imx355_get_hwcfg(struct imx355 *imx355) + { ++ struct device *dev = imx355->dev; + struct imx355_hwcfg *cfg; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; ++ const struct imx355_clk_params *clk = imx355->clk_params; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); ++ int lane_idx; + int ret; + + if (!fwnode) +@@ -1708,13 +1151,18 @@ static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) + if (!cfg) + goto out_err; + +- if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX355_DATA_LANES) ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 && ++ bus_cfg.bus.mipi_csi2.num_data_lanes != 4) + goto out_err; + ++ cfg->num_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; ++ ++ lane_idx = cfg->num_lanes == 4 ? 0 : 1; ++ cfg->link_freq_menu = (clk->ext_clk * clk->pll_op_mpy[lane_idx]) / ++ (clk->pll_op_prediv[lane_idx] * 2); + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, +- link_freq_menu_items, +- ARRAY_SIZE(link_freq_menu_items), ++ &cfg->link_freq_menu, 1, + &cfg->link_freq_bitmap); + if (ret) + goto out_err; +@@ -1741,7 +1189,10 @@ static int imx355_probe(struct i2c_client *client) + + imx355->dev = &client->dev; + +- mutex_init(&imx355->mutex); ++ imx355->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(imx355->regmap)) ++ return dev_err_probe(imx355->dev, PTR_ERR(imx355->regmap), ++ "Unable to initialize I2C\n"); + + imx355->clk = devm_v4l2_sensor_clk_get(imx355->dev, NULL); + if (IS_ERR(imx355->clk)) +@@ -1749,7 +1200,13 @@ static int imx355_probe(struct i2c_client *client) + "failed to get clock\n"); + + freq = clk_get_rate(imx355->clk); +- if (freq != IMX355_EXT_CLK) ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx355_clk_params); i++) { ++ if (freq == imx355_clk_params[i].ext_clk) { ++ imx355->clk_params = &imx355_clk_params[i]; ++ break; ++ } ++ } ++ if (!imx355->clk_params) + return dev_err_probe(imx355->dev, -EINVAL, + "external clock %lu is not supported\n", + freq); +@@ -1760,7 +1217,7 @@ static int imx355_probe(struct i2c_client *client) + &imx355->supplies); + if (ret) { + dev_err_probe(imx355->dev, ret, "could not get regulators"); +- goto error_probe; ++ return ret; + } + + imx355->reset_gpio = devm_gpiod_get_optional(imx355->dev, "reset", +@@ -1768,22 +1225,21 @@ static int imx355_probe(struct i2c_client *client) + if (IS_ERR(imx355->reset_gpio)) { + ret = dev_err_probe(imx355->dev, PTR_ERR(imx355->reset_gpio), + "failed to get gpios"); +- goto error_probe; ++ return ret; + } + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops); + +- imx355->hwcfg = imx355_get_hwcfg(imx355->dev); ++ imx355->hwcfg = imx355_get_hwcfg(imx355); + if (!imx355->hwcfg) { + dev_err(imx355->dev, "failed to get hwcfg"); +- ret = -ENODEV; +- goto error_probe; ++ return -ENODEV; + } + + ret = imx355_power_on(imx355->dev); + if (ret) +- goto error_probe; ++ return ret; + + /* Check module identity */ + ret = imx355_identify_module(imx355); +@@ -1792,9 +1248,6 @@ static int imx355_probe(struct i2c_client *client) + goto error_power_off; + } + +- /* Set default mode to max resolution */ +- imx355->cur_mode = &supported_modes[0]; +- + ret = imx355_init_controls(imx355); + if (ret) { + dev_err(imx355->dev, "failed to init controls: %d", ret); +@@ -1816,24 +1269,36 @@ static int imx355_probe(struct i2c_client *client) + goto error_handler_free; + } + ++ imx355->sd.state_lock = imx355->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&imx355->sd); ++ if (ret < 0) { ++ dev_err_probe(imx355->dev, ret, "subdev init error\n"); ++ goto error_media_entity_free; ++ } ++ + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(imx355->dev); + pm_runtime_enable(imx355->dev); ++ pm_runtime_set_autosuspend_delay(imx355->dev, 1000); ++ pm_runtime_use_autosuspend(imx355->dev); + + ret = v4l2_async_register_subdev_sensor(&imx355->sd); + if (ret < 0) +- goto error_media_entity_runtime_pm; ++ goto error_subdev_cleanup_runtime_pm; + + pm_runtime_idle(imx355->dev); + + return 0; + +-error_media_entity_runtime_pm: ++error_subdev_cleanup_runtime_pm: + pm_runtime_disable(imx355->dev); + pm_runtime_set_suspended(imx355->dev); ++ pm_runtime_dont_use_autosuspend(imx355->dev); ++ v4l2_subdev_cleanup(&imx355->sd); ++error_media_entity_free: + media_entity_cleanup(&imx355->sd.entity); + + error_handler_free: +@@ -1842,9 +1307,6 @@ static int imx355_probe(struct i2c_client *client) + error_power_off: + imx355_power_off(imx355->dev); + +-error_probe: +- mutex_destroy(&imx355->mutex); +- + return ret; + } + +@@ -1854,6 +1316,7 @@ static void imx355_remove(struct i2c_client *client) + struct imx355 *imx355 = to_imx355(sd); + + v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + +@@ -1864,7 +1327,7 @@ static void imx355_remove(struct i2c_client *client) + pm_runtime_set_suspended(imx355->dev); + } + +- mutex_destroy(&imx355->mutex); ++ pm_runtime_dont_use_autosuspend(imx355->dev); + } + + static const struct acpi_device_id imx355_acpi_ids[] __maybe_unused = { +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +new file mode 100644 +--- /dev/null ++++ b/drivers/media/i2c/imx471.c +@@ -0,0 +1,957 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * imx471.c - imx471 sensor driver ++ * ++ * Copyright (C) 2025 Intel Corporation ++ * Copyright (C) 2026 Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define IMX471_REG_MODE_SELECT CCI_REG8(0x0100) ++#define IMX471_MODE_STANDBY 0x00 ++#define IMX471_MODE_STREAMING 0x01 ++ ++/* Chip ID */ ++#define IMX471_REG_CHIP_ID CCI_REG16(0x0016) ++#define IMX471_CHIP_ID 0x0471 ++ ++/* V_TIMING internal */ ++#define IMX471_REG_FLL CCI_REG16(0x0340) ++#define IMX471_FLL_MAX 0xffff ++ ++/* Exposure control */ ++#define IMX471_REG_EXPOSURE CCI_REG16(0x0202) ++#define IMX471_EXPOSURE_MIN 1 ++#define IMX471_EXPOSURE_STEP 1 ++#define IMX471_EXPOSURE_DEFAULT 1270 ++ ++/* Default exposure margin */ ++#define IMX471_EXPOSURE_MARGIN 18 ++ ++/* Analog gain control */ ++#define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) ++#define IMX471_ANA_GAIN_MIN 0 ++#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_STEP 1 ++#define IMX471_ANA_GAIN_DEFAULT 0 ++ ++/* Digital gain control */ ++#define IMX471_REG_DPGA_USE_GLOBAL_GAIN CCI_REG16(0x3ff9) ++#define IMX471_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) ++#define IMX471_DGTL_GAIN_MIN 256 ++#define IMX471_DGTL_GAIN_MAX 4095 ++#define IMX471_DGTL_GAIN_STEP 1 ++#define IMX471_DGTL_GAIN_DEFAULT 256 ++ ++/* HFLIP and VFLIP control */ ++#define IMX471_REG_ORIENTATION CCI_REG8(0x0101) ++ ++/* Test Pattern Control */ ++#define IMX471_REG_TEST_PATTERN CCI_REG8(0x0600) ++ ++/* default link frequency and external clock */ ++#define IMX471_LINK_FREQ_DEFAULT 200000000LL ++#define IMX471_EXT_CLK 19200000 ++ ++/* PLL */ ++#define IMX471_REG_VTPXCK_DIV CCI_REG8(0x0301) ++#define IMX471_REG_VTSYCK_DIV CCI_REG8(0x0303) ++#define IMX471_REG_PREPLLCK_VT_DIV CCI_REG8(0x0305) ++#define IMX471_REG_PLL_VT_MPY CCI_REG16(0x0306) ++#define IMX471_REG_OPPXCK_DIV CCI_REG8(0x0309) ++#define IMX471_REG_OPSYCK_DIV CCI_REG8(0x030b) ++#define IMX471_REG_PLL_MULT_DRIV CCI_REG8(0x0310) ++#define IMX471_PLL_SINGLE 0 ++#define IMX471_PLL_DUAL 1 ++ ++/* IMX471 native and active pixel array size */ ++#define IMX471_NATIVE_WIDTH 4672 ++#define IMX471_NATIVE_HEIGHT 3512 ++#define IMX471_PIXEL_ARRAY_LEFT 8 ++#define IMX471_PIXEL_ARRAY_TOP 8 ++#define IMX471_PIXEL_ARRAY_WIDTH 4656 ++#define IMX471_PIXEL_ARRAY_HEIGHT 3496 ++ ++#define IMX471_REG_EXCK_FREQ CCI_REG16(0x0136) ++ ++#define IMX471_REG_CSI_DATA_FORMAT CCI_REG16(0x0112) ++#define IMX471_CSI_DATA_FORMAT_RAW10 0x0a0a ++ ++#define IMX471_REG_CSI_LANE_MODE CCI_REG8(0x0114) ++#define IMX471_CSI_2_LANE_MODE 1 ++#define IMX471_CSI_4_LANE_MODE 3 ++ ++#define IMX471_REG_X_ADD_STA CCI_REG16(0x0344) ++#define IMX471_REG_Y_ADD_STA CCI_REG16(0x0346) ++#define IMX471_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX471_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX471_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) ++#define IMX471_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) ++#define IMX471_REG_X_EVEN_INC CCI_REG8(0x0381) ++#define IMX471_REG_X_ODD_INC CCI_REG8(0x0383) ++#define IMX471_REG_Y_EVEN_INC CCI_REG8(0x0385) ++#define IMX471_REG_Y_ODD_INC CCI_REG8(0x0387) ++ ++#define IMX471_REG_DIG_CROP_X_OFFSET CCI_REG16(0x0408) ++#define IMX471_REG_DIG_CROP_Y_OFFSET CCI_REG16(0x040a) ++#define IMX471_REG_DIG_CROP_WIDTH CCI_REG16(0x040c) ++#define IMX471_REG_DIG_CROP_HEIGHT CCI_REG16(0x040e) ++ ++/* Binning mode */ ++#define IMX471_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX471_BINNING_NONE 0 ++#define IMX471_BINNING_ENABLE 1 ++#define IMX471_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX471_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ ++#define to_imx471(_sd) container_of_const(_sd, struct imx471, sd) ++ ++static const char * const imx471_supply_name[] = { ++ "vana", ++}; ++ ++struct imx471_mode { ++ u32 width; ++ u32 height; ++ ++ /* V-timing */ ++ u32 fll_def; ++ u32 fll_min; ++ ++ /* H-timing */ ++ u32 llp; ++ ++ const struct cci_reg_sequence *default_mode_regs; ++ unsigned int default_mode_regs_length; ++}; ++ ++struct imx471 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ ++ struct v4l2_ctrl_handler ctrl_handler; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *exposure; ++ ++ struct gpio_desc *reset_gpio; ++ struct regulator_bulk_data supplies[ARRAY_SIZE(imx471_supply_name)]; ++ struct clk *img_clk; ++ ++ struct device *dev; ++ struct regmap *regmap; ++}; ++ ++static const struct cci_reg_sequence imx471_global_regs[] = { ++ { IMX471_REG_EXCK_FREQ, 0x1333 }, ++ { CCI_REG8(0x3c7e), 0x08 }, ++ { CCI_REG8(0x3c7f), 0x05 }, ++ { CCI_REG8(0x3e35), 0x00 }, ++ { CCI_REG8(0x3e36), 0x00 }, ++ { CCI_REG8(0x3e37), 0x00 }, ++ { CCI_REG8(0x3f7f), 0x01 }, ++ { CCI_REG8(0x4431), 0x04 }, ++ { CCI_REG8(0x531c), 0x01 }, ++ { CCI_REG8(0x531d), 0x02 }, ++ { CCI_REG8(0x531e), 0x04 }, ++ { CCI_REG8(0x5928), 0x00 }, ++ { CCI_REG8(0x5929), 0x2f }, ++ { CCI_REG8(0x592a), 0x00 }, ++ { CCI_REG8(0x592b), 0x85 }, ++ { CCI_REG8(0x592c), 0x00 }, ++ { CCI_REG8(0x592d), 0x32 }, ++ { CCI_REG8(0x592e), 0x00 }, ++ { CCI_REG8(0x592f), 0x88 }, ++ { CCI_REG8(0x5930), 0x00 }, ++ { CCI_REG8(0x5931), 0x3d }, ++ { CCI_REG8(0x5932), 0x00 }, ++ { CCI_REG8(0x5933), 0x93 }, ++ { CCI_REG8(0x5938), 0x00 }, ++ { CCI_REG8(0x5939), 0x24 }, ++ { CCI_REG8(0x593a), 0x00 }, ++ { CCI_REG8(0x593b), 0x7a }, ++ { CCI_REG8(0x593c), 0x00 }, ++ { CCI_REG8(0x593d), 0x24 }, ++ { CCI_REG8(0x593e), 0x00 }, ++ { CCI_REG8(0x593f), 0x7a }, ++ { CCI_REG8(0x5940), 0x00 }, ++ { CCI_REG8(0x5941), 0x2f }, ++ { CCI_REG8(0x5942), 0x00 }, ++ { CCI_REG8(0x5943), 0x85 }, ++ { CCI_REG8(0x5f0e), 0x6e }, ++ { CCI_REG8(0x5f11), 0xc6 }, ++ { CCI_REG8(0x5f17), 0x5e }, ++ { CCI_REG8(0x7990), 0x01 }, ++ { CCI_REG8(0x7993), 0x5d }, ++ { CCI_REG8(0x7994), 0x5d }, ++ { CCI_REG8(0x7995), 0xa1 }, ++ { CCI_REG8(0x799a), 0x01 }, ++ { CCI_REG8(0x799d), 0x00 }, ++ { CCI_REG8(0x8169), 0x01 }, ++ { CCI_REG8(0x8359), 0x01 }, ++ { CCI_REG8(0x9302), 0x1e }, ++ { CCI_REG8(0x9306), 0x1f }, ++ { CCI_REG8(0x930a), 0x26 }, ++ { CCI_REG8(0x930e), 0x23 }, ++ { CCI_REG8(0x9312), 0x23 }, ++ { CCI_REG8(0x9316), 0x2c }, ++ { CCI_REG8(0x9317), 0x19 }, ++ { CCI_REG8(0xb046), 0x01 }, ++ { CCI_REG8(0xb048), 0x01 }, ++}; ++ ++static const struct cci_reg_sequence mode_1928x1088_regs[] = { ++ { IMX471_REG_CSI_DATA_FORMAT, IMX471_CSI_DATA_FORMAT_RAW10 }, ++ { IMX471_REG_CSI_LANE_MODE, IMX471_CSI_4_LANE_MODE }, ++ { IMX471_REG_X_ADD_STA, 8 }, ++ { IMX471_REG_Y_ADD_STA, 408 }, ++ { IMX471_REG_X_ADD_END, 4647 }, ++ { IMX471_REG_Y_ADD_END, 3051 }, ++ { IMX471_REG_X_EVEN_INC, 1 }, ++ { IMX471_REG_X_ODD_INC, 1 }, ++ { IMX471_REG_Y_EVEN_INC, 1 }, ++ { IMX471_REG_Y_ODD_INC, 1 }, ++ { IMX471_REG_BINNING_MODE, IMX471_BINNING_ENABLE }, ++ { IMX471_REG_BINNING_TYPE, 0x22 }, ++ { IMX471_REG_BINNING_WEIGHTING, 0x08 }, ++ { IMX471_REG_DIG_CROP_X_OFFSET, 208 }, ++ { IMX471_REG_DIG_CROP_Y_OFFSET, 108 }, ++ { IMX471_REG_DIG_CROP_WIDTH, 1928 }, ++ { IMX471_REG_DIG_CROP_HEIGHT, 1088 }, ++ { IMX471_REG_X_OUTPUT_SIZE, 1928 }, ++ { IMX471_REG_Y_OUTPUT_SIZE, 1088 }, ++ { IMX471_REG_VTPXCK_DIV, 0x06 }, ++ { IMX471_REG_VTSYCK_DIV, 0x02 }, ++ { IMX471_REG_PREPLLCK_VT_DIV, 0x02 }, ++ { IMX471_REG_PLL_VT_MPY, 0x0079 }, ++ { IMX471_REG_OPSYCK_DIV, 0x01 }, ++ { CCI_REG8(0x030d), 0x02 }, ++ { CCI_REG8(0x030e), 0x00 }, ++ { CCI_REG8(0x030f), 0x53 }, ++ { IMX471_REG_PLL_MULT_DRIV, IMX471_PLL_DUAL }, ++ { CCI_REG8(0x3f4c), 0x81 }, ++ { CCI_REG8(0x3f4d), 0x81 }, ++ { CCI_REG8(0x3f78), 0x01 }, ++ { CCI_REG8(0x3f79), 0x31 }, ++ { CCI_REG8(0x3ffe), 0x00 }, ++ { CCI_REG8(0x3fff), 0x8a }, ++ { CCI_REG8(0x5f0a), 0xb6 }, ++}; ++ ++static const char * const imx471_test_pattern_menu[] = { ++ "Disabled", ++ "Solid Colour", ++ "Eight Vertical Colour Bars", ++ "Colour Bars With Fade to Grey", ++ "Pseudorandom Sequence (PN9)", ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ IMX471_LINK_FREQ_DEFAULT, ++}; ++ ++/* ++ * The Bayer formats for the flipping. ++ * - no flip ++ * - h flip ++ * - v flip ++ * - h and v flips ++ */ ++static const u32 imx471_hv_flips_bayer_order[] = { ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++}; ++ ++static const struct imx471_mode imx471_modes[] = { ++ { ++ .width = 1928, ++ .height = 1088, ++ .fll_def = 1308, ++ .fll_min = 1308, ++ .llp = 2328, ++ .default_mode_regs = mode_1928x1088_regs, ++ .default_mode_regs_length = ARRAY_SIZE(mode_1928x1088_regs), ++ }, ++}; ++ ++static int imx471_get_regulators(struct device *dev, struct imx471 *sensor) ++{ ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx471_supply_name); i++) ++ sensor->supplies[i].supply = imx471_supply_name[i]; ++ ++ return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++} ++ ++static int imx471_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct imx471 *sensor = container_of_const(ctrl->handler, ++ struct imx471, ++ ctrl_handler); ++ struct v4l2_subdev_state *state = ++ v4l2_subdev_get_locked_active_state(&sensor->sd); ++ const struct v4l2_mbus_framefmt *format = ++ v4l2_subdev_state_get_format(state, 0); ++ int ret; ++ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ s64 exposure_max = format->height + ctrl->val - ++ IMX471_EXPOSURE_MARGIN; ++ ret = __v4l2_ctrl_modify_range(sensor->exposure, ++ sensor->exposure->minimum, ++ exposure_max, ++ sensor->exposure->step, ++ exposure_max); ++ if (ret) ++ return ret; ++ } ++ ++ if (!pm_runtime_get_if_in_use(sensor->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_ANALOG_GAIN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = cci_write(sensor->regmap, IMX471_REG_EXPOSURE, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_VBLANK: ++ /* Update FLL that meets expected vertical blanking */ ++ ret = cci_write(sensor->regmap, IMX471_REG_FLL, ++ format->height + ctrl->val, NULL); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = cci_write(sensor->regmap, IMX471_REG_TEST_PATTERN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_HFLIP: ++ case V4L2_CID_VFLIP: ++ ret = cci_write(sensor->regmap, IMX471_REG_ORIENTATION, ++ sensor->hflip->val | sensor->vflip->val << 1, ++ NULL); ++ break; ++ default: ++ ret = -EINVAL; ++ dev_err(sensor->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n", ++ ctrl->id, ctrl->val); ++ break; ++ } ++ ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops imx471_ctrl_ops = { ++ .s_ctrl = imx471_set_ctrl, ++}; ++ ++static u32 imx471_get_format_code(struct imx471 *sensor) ++{ ++ unsigned int i; ++ ++ i = (sensor->vflip->val ? 2 : 0) | (sensor->hflip->val ? 1 : 0); ++ ++ return imx471_hv_flips_bayer_order[i]; ++} ++ ++static int imx471_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ ++ if (code->index >= (ARRAY_SIZE(imx471_hv_flips_bayer_order) / 4)) ++ return -EINVAL; ++ ++ code->code = imx471_get_format_code(sensor); ++ ++ return 0; ++} ++ ++static int imx471_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->index >= ARRAY_SIZE(imx471_modes)) ++ return -EINVAL; ++ ++ fse->min_width = imx471_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = imx471_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static void imx471_update_pad_format(struct imx471 *sensor, ++ const struct imx471_mode *mode, ++ struct v4l2_subdev_format *fmt) ++{ ++ fmt->format.code = imx471_get_format_code(sensor); ++ fmt->format.width = mode->width; ++ fmt->format.height = mode->height; ++ fmt->format.field = V4L2_FIELD_NONE; ++} ++ ++static int imx471_set_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ int h_blank, ret; ++ ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->format.width, ++ fmt->format.height); ++ ++ imx471_update_pad_format(sensor, mode, fmt); ++ ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ return 0; ++ ++ if (media_entity_is_streaming(&sensor->sd.entity)) ++ return -EBUSY; ++ ++ ret = __v4l2_ctrl_modify_range(sensor->vblank, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ if (ret) ++ return ret; ++ ++ h_blank = mode->llp - mode->width; ++ /* ++ * Currently hblank is not changeable. ++ * So FPS control is done only by vblank. ++ */ ++ return __v4l2_ctrl_modify_range(sensor->hblank, h_blank, ++ h_blank, 1, h_blank); ++} ++ ++static int imx471_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); ++ break; ++ ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = IMX471_NATIVE_WIDTH; ++ sel->r.height = IMX471_NATIVE_HEIGHT; ++ return 0; ++ ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.top = IMX471_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX471_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX471_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX471_PIXEL_ARRAY_HEIGHT; ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx471_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_TRY, ++ .format = { ++ .code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .width = imx471_modes[0].width, ++ .height = imx471_modes[0].height, ++ }, ++ }; ++ ++ return imx471_set_pad_format(sd, sd_state, &fmt); ++} ++ ++static int imx471_identify_module(struct imx471 *sensor) ++{ ++ int ret; ++ u64 val; ++ ++ ret = cci_read(sensor->regmap, IMX471_REG_CHIP_ID, &val, NULL); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to read chip id\n"); ++ ++ if (val != IMX471_CHIP_ID) ++ return dev_err_probe(sensor->dev, -EIO, ++ "chip id mismatch: %x!=%llx\n", ++ IMX471_CHIP_ID, val); ++ ++ return 0; ++} ++ ++static int imx471_power_off(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ ++ clk_disable_unprepare(sensor->img_clk); ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ ++ return 0; ++} ++ ++static int imx471_power_on(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ if (ret < 0) { ++ dev_err(dev, "failed to enable regulators: %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(sensor->img_clk); ++ if (ret < 0) { ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ dev_err(dev, "failed to enable imaging clock: %d\n", ret); ++ return ret; ++ } ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ ++ usleep_range(10000, 15000); ++ ++ return 0; ++} ++ ++static int imx471_enable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(sensor->dev); ++ if (ret) ++ return ret; ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_multi_reg_write(sensor->regmap, imx471_global_regs, ++ ARRAY_SIZE(imx471_global_regs), NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set global settings: %d\n", ++ ret); ++ goto error_powerdown; ++ } ++ ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->width, fmt->height); ++ ++ ret = cci_multi_reg_write(sensor->regmap, mode->default_mode_regs, ++ mode->default_mode_regs_length, NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set mode: %d\n", ret); ++ goto error_powerdown; ++ } ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_DPGA_USE_GLOBAL_GAIN, 1, ++ NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = __v4l2_ctrl_handler_setup(&sensor->ctrl_handler); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STREAMING, NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ __v4l2_ctrl_grab(sensor->vflip, true); ++ __v4l2_ctrl_grab(sensor->hflip, true); ++ ++ return ret; ++ ++error_powerdown: ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static int imx471_disable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STANDBY, NULL); ++ pm_runtime_put(sensor->dev); ++ ++ if (ret) ++ dev_err(sensor->dev, ++ "failed to disable stream with return value: %d\n", ++ ret); ++ ++ __v4l2_ctrl_grab(sensor->vflip, false); ++ __v4l2_ctrl_grab(sensor->hflip, false); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops imx471_video_ops = { ++ .s_stream = v4l2_subdev_s_stream_helper, ++}; ++ ++static const struct v4l2_subdev_pad_ops imx471_pad_ops = { ++ .enum_mbus_code = imx471_enum_mbus_code, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .set_fmt = imx471_set_pad_format, ++ .get_selection = imx471_get_selection, ++ .enum_frame_size = imx471_enum_frame_size, ++ .enable_streams = imx471_enable_stream, ++ .disable_streams = imx471_disable_stream, ++}; ++ ++static const struct v4l2_subdev_ops imx471_subdev_ops = { ++ .video = &imx471_video_ops, ++ .pad = &imx471_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops imx471_internal_ops = { ++ .init_state = imx471_init_state, ++}; ++ ++static int imx471_init_controls(struct imx471 *sensor) ++{ ++ const struct imx471_mode *mode = &imx471_modes[0]; ++ struct v4l2_fwnode_device_properties props; ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ struct v4l2_ctrl *link_freq; ++ s64 exposure_max, hblank; ++ u64 pixel_rate; ++ int ret; ++ ++ ret = v4l2_fwnode_device_parse(sensor->dev, &props); ++ if (ret) { ++ dev_err(sensor->dev, "failed to parse fwnode: %d\n", ret); ++ return ret; ++ } ++ ++ ctrl_hdlr = &sensor->ctrl_handler; ++ v4l2_ctrl_handler_init(ctrl_hdlr, 12); ++ ++ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx471_ctrl_ops, &props); ++ ++ link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_LINK_FREQ, ++ ARRAY_SIZE(link_freq_menu_items) - 1, ++ 0, ++ link_freq_menu_items); ++ ++ /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ ++ pixel_rate = div_u64(IMX471_LINK_FREQ_DEFAULT * 2 * 4, 10); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_PIXEL_RATE, pixel_rate, ++ pixel_rate, 1, pixel_rate); ++ ++ sensor->vblank = v4l2_ctrl_new_std(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_VBLANK, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ ++ hblank = mode->llp - mode->width; ++ sensor->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HBLANK, hblank, hblank, ++ 1, hblank); ++ ++ /* fll >= exposure time + adjust parameter (default value is 18) */ ++ exposure_max = mode->fll_def - IMX471_EXPOSURE_MARGIN; ++ sensor->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_EXPOSURE, ++ IMX471_EXPOSURE_MIN, exposure_max, ++ IMX471_EXPOSURE_STEP, ++ IMX471_EXPOSURE_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ IMX471_ANA_GAIN_MIN, IMX471_ANA_GAIN_MAX, ++ IMX471_ANA_GAIN_STEP, IMX471_ANA_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ IMX471_DGTL_GAIN_MIN, IMX471_DGTL_GAIN_MAX, ++ IMX471_DGTL_GAIN_STEP, IMX471_DGTL_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(imx471_test_pattern_menu) - 1, ++ 0, 0, imx471_test_pattern_menu); ++ ++ sensor->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ++ sensor->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ if (ctrl_hdlr->error) { ++ dev_err(sensor->dev, "%s control init failed: %d\n", ++ __func__, ctrl_hdlr->error); ++ goto error; ++ } ++ ++ link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ ++ sensor->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++ ++error: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ ++ return ctrl_hdlr->error; ++} ++ ++static int imx471_check_hwcfg(struct imx471 *sensor) ++{ ++ struct v4l2_fwnode_endpoint bus_cfg = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY, ++ }; ++ struct fwnode_handle *ep, *fwnode = dev_fwnode(sensor->dev); ++ unsigned long link_freq_bitmap; ++ struct clk *clk; ++ int ret; ++ ++ clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(clk), ++ "can't get clock frequency\n"); ++ ++ if (clk_get_rate(clk) != IMX471_EXT_CLK) ++ return dev_err_probe(sensor->dev, -EINVAL, ++ "external clock %lu is not supported\n", ++ clk_get_rate(clk)); ++ ++ ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0); ++ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); ++ fwnode_handle_put(ep); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "parsing endpoint failed\n"); ++ ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { ++ ret = dev_err_probe(sensor->dev, -EINVAL, ++ "number of CSI2 data lanes %u is not supported\n", ++ bus_cfg.bus.mipi_csi2.num_data_lanes); ++ goto done_endpoint_free; ++ } ++ ++ ret = v4l2_link_freq_to_bitmap(sensor->dev, bus_cfg.link_frequencies, ++ bus_cfg.nr_of_link_frequencies, ++ link_freq_menu_items, ++ ARRAY_SIZE(link_freq_menu_items), ++ &link_freq_bitmap); ++ ++done_endpoint_free: ++ v4l2_fwnode_endpoint_free(&bus_cfg); ++ ++ return ret; ++} ++ ++static int imx471_probe(struct i2c_client *client) ++{ ++ struct imx471 *sensor; ++ int ret; ++ ++ sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return dev_err_probe(&client->dev, -ENOMEM, ++ "failed to allocate memory\n"); ++ ++ sensor->dev = &client->dev; ++ ++ ret = imx471_check_hwcfg(sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to check hwcfg: %d\n", ret); ++ ++ ret = imx471_get_regulators(sensor->dev, sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to get regulators\n"); ++ ++ sensor->reset_gpio = devm_gpiod_get_optional(sensor->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset_gpio), ++ "failed to get reset gpio\n"); ++ ++ sensor->img_clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(sensor->img_clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->img_clk), ++ "failed to get imaging clock\n"); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &imx471_subdev_ops); ++ ++ sensor->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(sensor->regmap)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->regmap), ++ "failed to initialize CCI\n"); ++ ++ ret = imx471_power_on(sensor->dev); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to power on\n"); ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to find sensor: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ ret = imx471_init_controls(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to init controls: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ sensor->sd.internal_ops = &imx471_internal_ops; ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, ++ "failed to init entity pads: %d\n", ret); ++ goto error_v4l2_ctrl_handler_free; ++ } ++ ++ sensor->sd.state_lock = sensor->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&sensor->sd); ++ if (ret < 0) { ++ dev_err_probe(sensor->dev, ret, "failed to init subdev: %d\n", ++ ret); ++ goto error_media_entity_pm; ++ } ++ ++ pm_runtime_set_active(sensor->dev); ++ pm_runtime_enable(sensor->dev); ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret < 0) ++ goto error_v4l2_subdev_cleanup; ++ ++ pm_runtime_idle(sensor->dev); ++ ++ return 0; ++ ++error_v4l2_subdev_cleanup: ++ pm_runtime_disable(sensor->dev); ++ pm_runtime_set_suspended(sensor->dev); ++ v4l2_subdev_cleanup(&sensor->sd); ++ ++error_media_entity_pm: ++ media_entity_cleanup(&sensor->sd.entity); ++ ++error_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); ++ ++error_power_off: ++ imx471_power_off(sensor->dev); ++ ++ return ret; ++} ++ ++static void imx471_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ ++ v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_ctrl_handler_free(sd->ctrl_handler); ++ ++ pm_runtime_disable(&client->dev); ++ ++ if (!pm_runtime_status_suspended(&client->dev)) { ++ imx471_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++ } ++} ++ ++static DEFINE_RUNTIME_DEV_PM_OPS(imx471_pm_ops, imx471_power_off, ++ imx471_power_on, NULL); ++ ++static const struct acpi_device_id imx471_acpi_ids[] __maybe_unused = { ++ { "SONY471A" }, ++ { "TBE20A0" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(acpi, imx471_acpi_ids); ++ ++static struct i2c_driver imx471_i2c_driver = { ++ .driver = { ++ .name = "imx471", ++ .acpi_match_table = ACPI_PTR(imx471_acpi_ids), ++ .pm = pm_sleep_ptr(&imx471_pm_ops), ++ }, ++ .probe = imx471_probe, ++ .remove = imx471_remove, ++}; ++module_i2c_driver(imx471_i2c_driver); ++ ++MODULE_AUTHOR("Jimmy Su "); ++MODULE_AUTHOR("Serin Yeh "); ++MODULE_AUTHOR("Kate Hsuan "); ++MODULE_DESCRIPTION("Sony imx471 sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c +--- a/drivers/media/i2c/ov2740.c ++++ b/drivers/media/i2c/ov2740.c +@@ -1241,41 +1241,42 @@ static int ov2740_nvmem_read(void *priv, unsigned int off, void *val, + return ret; + } + ++static const struct regmap_config regmap_config = { ++ .val_bits = 8, ++ .reg_bits = 16, ++ .disable_locking = true, ++}; ++ + static int ov2740_register_nvmem(struct i2c_client *client, + struct ov2740 *ov2740) + { + struct nvm_data *nvm; +- struct regmap_config regmap_config = { }; +- struct nvmem_config nvmem_config = { }; + struct regmap *regmap; + struct device *dev = ov2740->dev; ++ struct nvmem_config nvmem_config = { ++ .name = dev_name(dev), ++ .dev = dev, ++ .read_only = true, ++ .root_only = true, ++ .owner = THIS_MODULE, ++ .compat = true, ++ .base_dev = dev, ++ .reg_read = ov2740_nvmem_read, ++ .stride = 1, ++ .word_size = 1, ++ .size = CUSTOMER_USE_OTP_SIZE, ++ }; + + nvm = devm_kzalloc(dev, sizeof(*nvm), GFP_KERNEL); + if (!nvm) + return -ENOMEM; + +- regmap_config.val_bits = 8; +- regmap_config.reg_bits = 16; +- regmap_config.disable_locking = true; + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvm->regmap = regmap; +- +- nvmem_config.name = dev_name(dev); +- nvmem_config.dev = dev; +- nvmem_config.read_only = true; +- nvmem_config.root_only = true; +- nvmem_config.owner = THIS_MODULE; +- nvmem_config.compat = true; +- nvmem_config.base_dev = dev; +- nvmem_config.reg_read = ov2740_nvmem_read; +- nvmem_config.reg_write = NULL; + nvmem_config.priv = nvm; +- nvmem_config.stride = 1; +- nvmem_config.word_size = 1; +- nvmem_config.size = CUSTOMER_USE_OTP_SIZE; + + nvm->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvm->nvmem)) +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -49,6 +49,8 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { ++ /* Himax HM1092 */ ++ IPU_SENSOR_CONFIG("HIMX1092", 2, 180000000, 180480000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +@@ -97,6 +99,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* Sony IMX471 */ + IPU_SENSOR_CONFIG("SONY471A", 1, 200000000), ++ /* Sony IMX471 (found on Lenovo X1 Carbon G14) */ ++ IPU_SENSOR_CONFIG("TBE20A0", 1, 200000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), + }; +@@ -194,8 +198,8 @@ static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev + for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) { + const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i]; + struct acpi_device *consumer, *ivsc_adev; +- + acpi_handle handle = acpi_device_handle(ACPI_PTR(adev)); ++ + for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1) + /* camera sensor depends on IVSC in DSDT if exist */ + for_each_acpi_consumer_dev(ivsc_adev, consumer) diff --git a/pkgbuilds/linux-omarchy-eevdf/0543-media-laptop-cameras.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0543-media-laptop-cameras.patch.sig new file mode 100644 index 0000000..2f69b33 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0543-media-laptop-cameras.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0544-ipu-bridge-lenovo-x9-upside-down.patch b/pkgbuilds/linux-omarchy-eevdf/0544-ipu-bridge-lenovo-x9-upside-down.patch new file mode 100644 index 0000000..abda19b --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0544-ipu-bridge-lenovo-x9-upside-down.patch @@ -0,0 +1,49 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -138,6 +138,45 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "OVTI02C1", + }, ++ /* ++ * The first four characters of DMI_BOARD_NAME identify the Lenovo ++ * machine type/model. For example, a DMI_BOARD_NAME starting with ++ * "21Q6" indicates a ThinkPad X9-15. ++ * ++ * Reference: https://psref.lenovo.com/ ++ */ ++ { ++ /* Lenovo X9-14 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21QA"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-14 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21QB"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-15 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21Q6"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-15 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21Q7"), ++ }, ++ .driver_data = "SONY471A", ++ }, + {} /* Terminating entry */ + }; + diff --git a/pkgbuilds/linux-omarchy-eevdf/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig new file mode 100644 index 0000000..3b2eaec Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch b/pkgbuilds/linux-omarchy-eevdf/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch new file mode 100644 index 0000000..7c15eb7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch @@ -0,0 +1,17 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -138,6 +138,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "OVTI02C1", + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 16 (Dell 16 Premium) DA16250"), ++ }, ++ .driver_data = "OVTI02C1", ++ }, + /* + * The first four characters of DMI_BOARD_NAME identify the Lenovo + * machine type/model. For example, a DMI_BOARD_NAME starting with diff --git a/pkgbuilds/linux-omarchy-eevdf/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig new file mode 100644 index 0000000..1c5aff2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch b/pkgbuilds/linux-omarchy-eevdf/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch new file mode 100644 index 0000000..a42d186 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -184,6 +184,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "SONY471A", + }, ++ { ++ /* Samsung Galaxy Book5 Pro 360 */ ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "960QHA"), ++ }, ++ .driver_data = "OVTI02E1", ++ }, + {} /* Terminating entry */ + }; + diff --git a/pkgbuilds/linux-omarchy-eevdf/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig new file mode 100644 index 0000000..5376099 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0547-int3472-imx471-vana-power-enable.patch b/pkgbuilds/linux-omarchy-eevdf/0547-int3472-imx471-vana-power-enable.patch new file mode 100644 index 0000000..db38adc --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0547-int3472-imx471-vana-power-enable.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -164,6 +164,24 @@ static const struct int3472_gpio_map int3472_gpio_map[] = { + .con_id = "dvdd", + .enable_time_us = 45 * USEC_PER_MSEC, + }, ++ { /* imx471 expects "vana" as con_id for power enable */ ++ .hid = "SONY471A", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, ++ { ++ /* ++ * imx471 (on Lenovo ThinkPads X1 G14) expects "vana" as con_id ++ * for power enable ++ */ ++ .hid = "TBE20A0", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, + }; + + static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type, diff --git a/pkgbuilds/linux-omarchy-eevdf/0547-int3472-imx471-vana-power-enable.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0547-int3472-imx471-vana-power-enable.patch.sig new file mode 100644 index 0000000..b2651bd Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0547-int3472-imx471-vana-power-enable.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0548-media-cvs-sensor-node-in-bound-callback.patch b/pkgbuilds/linux-omarchy-eevdf/0548-media-cvs-sensor-node-in-bound-callback.patch new file mode 100644 index 0000000..b041d90 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0548-media-cvs-sensor-node-in-bound-callback.patch @@ -0,0 +1,37 @@ +diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c +--- a/drivers/media/i2c/cvs/v4l2.c ++++ b/drivers/media/i2c/cvs/v4l2.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -400,7 +401,7 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_async_connection *asc) + { + struct icvs *ctx = notifier_to_csi(notifier); +- int pad; ++ int pad, ret; + + pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, + MEDIA_PAD_FL_SOURCE); +@@ -409,9 +410,13 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + + ctx->remote = &sd->entity.pads[pad]; + +- return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, +- ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | +- MEDIA_LNK_FL_IMMUTABLE); ++ ret = media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, ++ ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | ++ MEDIA_LNK_FL_IMMUTABLE); ++ if (ret) ++ return ret; ++ ++ return v4l2_device_register_subdev_nodes(sd->v4l2_dev); + } + + /** diff --git a/pkgbuilds/linux-omarchy-eevdf/0548-media-cvs-sensor-node-in-bound-callback.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0548-media-cvs-sensor-node-in-bound-callback.patch.sig new file mode 100644 index 0000000..b58a4dd Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0548-media-cvs-sensor-node-in-bound-callback.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0549-media-imx471-analog-gain-max-960.patch b/pkgbuilds/linux-omarchy-eevdf/0549-media-imx471-analog-gain-max-960.patch new file mode 100644 index 0000000..a11c4f2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0549-media-imx471-analog-gain-max-960.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +--- a/drivers/media/i2c/imx471.c ++++ b/drivers/media/i2c/imx471.c +@@ -41,7 +41,7 @@ + /* Analog gain control */ + #define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX471_ANA_GAIN_MIN 0 +-#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_MAX 960 + #define IMX471_ANA_GAIN_STEP 1 + #define IMX471_ANA_GAIN_DEFAULT 0 + diff --git a/pkgbuilds/linux-omarchy-eevdf/0549-media-imx471-analog-gain-max-960.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0549-media-imx471-analog-gain-max-960.patch.sig new file mode 100644 index 0000000..283ab9e Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0549-media-imx471-analog-gain-max-960.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0560-input-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0560-input-updates.patch new file mode 100644 index 0000000..d8548c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0560-input-updates.patch @@ -0,0 +1,3869 @@ +diff --git a/drivers/input/input.c b/drivers/input/input.c +--- a/drivers/input/input.c ++++ b/drivers/input/input.c +@@ -318,7 +318,7 @@ static int input_get_disposition(struct input_dev *dev, + static void input_event_dispose(struct input_dev *dev, int disposition, + unsigned int type, unsigned int code, int value) + { +- if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) ++ if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event && dev->ready) + dev->event(dev, type, code, value); + + if (disposition & INPUT_PASS_TO_HANDLERS) { +@@ -568,6 +568,75 @@ void input_release_device(struct input_handle *handle) + } + EXPORT_SYMBOL(input_release_device); + ++#define INPUT_DO_TOGGLE(dev, type, bits, on) \ ++ do { \ ++ int i; \ ++ bool active; \ ++ \ ++ if (!test_bit(EV_##type, dev->evbit)) \ ++ break; \ ++ \ ++ for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ ++ active = test_bit(i, dev->bits); \ ++ if (!active && !on) \ ++ continue; \ ++ \ ++ dev->event(dev, EV_##type, i, on ? active : 0); \ ++ } \ ++ } while (0) ++ ++/* ++ * Iterate through the logical state of the input device (LEDs, sounds, ++ * auto-repeat) and explicitly push that state down to the hardware ++ * via dev->event() to match the current logical state (if activate is true), ++ * or forcibly turn off all feedback like LEDs and sounds during teardown ++ * or suspend (if activate is false). ++ * ++ * Primarily used as a state-replay mechanism after a device is opened ++ * or uninhibited, as events might have been dropped by the core while the ++ * hardware was not marked as ready. ++ */ ++static void input_dev_toggle(struct input_dev *dev, bool activate) ++{ ++ if (!dev->event || !dev->ready) ++ return; ++ ++ INPUT_DO_TOGGLE(dev, LED, led, activate); ++ INPUT_DO_TOGGLE(dev, SND, snd, activate); ++ ++ if (activate && test_bit(EV_REP, dev->evbit)) { ++ dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); ++ dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); ++ } ++} ++ ++static int input_start_device(struct input_dev *dev) ++{ ++ int error; ++ ++ lockdep_assert_held(&dev->mutex); ++ ++ if (dev->users++ == 0 && !dev->inhibited) { ++ if (dev->open) { ++ error = dev->open(dev); ++ if (error) { ++ dev->users--; ++ return error; ++ } ++ } ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ dev->ready = true; ++ input_dev_toggle(dev, true); ++ } ++ ++ if (dev->poller) ++ input_dev_poller_start(dev->poller); ++ } ++ ++ return 0; ++} ++ + /** + * input_open_device - open input device + * @handle: handle through which device is being accessed +@@ -586,21 +655,9 @@ int input_open_device(struct input_handle *handle) + + handle->open++; + +- if (handle->handler->passive_observer) +- return 0; +- +- if (dev->users++ || dev->inhibited) { +- /* +- * Device is already opened and/or inhibited, +- * so we can exit immediately and report success. +- */ +- return 0; +- } +- +- if (dev->open) { +- error = dev->open(dev); ++ if (!handle->handler->passive_observer) { ++ error = input_start_device(dev); + if (error) { +- dev->users--; + handle->open--; + /* + * Make sure we are not delivering any more +@@ -611,8 +668,8 @@ int input_open_device(struct input_handle *handle) + } + } + +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ if (handle->open == 1 && handle->handler->start) ++ handle->handler->start(handle); + } + + return 0; +@@ -651,6 +708,12 @@ void input_close_device(struct input_handle *handle) + if (!--dev->users && !dev->inhibited) { + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ + if (dev->close) + dev->close(dev); + } +@@ -1702,37 +1765,6 @@ static int input_dev_uevent(const struct device *device, struct kobj_uevent_env + return 0; + } + +-#define INPUT_DO_TOGGLE(dev, type, bits, on) \ +- do { \ +- int i; \ +- bool active; \ +- \ +- if (!test_bit(EV_##type, dev->evbit)) \ +- break; \ +- \ +- for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ +- active = test_bit(i, dev->bits); \ +- if (!active && !on) \ +- continue; \ +- \ +- dev->event(dev, EV_##type, i, on ? active : 0); \ +- } \ +- } while (0) +- +-static void input_dev_toggle(struct input_dev *dev, bool activate) +-{ +- if (!dev->event) +- return; +- +- INPUT_DO_TOGGLE(dev, LED, led, activate); +- INPUT_DO_TOGGLE(dev, SND, snd, activate); +- +- if (activate && test_bit(EV_REP, dev->evbit)) { +- dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); +- dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); +- } +-} +- + /** + * input_reset_device() - reset/restore the state of input device + * @dev: input device whose state needs to be reset +@@ -1756,52 +1788,73 @@ static int input_inhibit_device(struct input_dev *dev) + { + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (dev->inhibited) + return 0; + + if (dev->users) { +- if (dev->close) +- dev->close(dev); + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ ++ if (dev->close) ++ dev->close(dev); + } + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_mt_release_slots(dev); + input_dev_release_keys(dev); + input_handle_event(dev, EV_SYN, SYN_REPORT, 1); +- input_dev_toggle(dev, false); ++ dev->inhibited = true; + } + +- dev->inhibited = true; +- + return 0; + } + + static int input_uninhibit_device(struct input_dev *dev) + { ++ struct input_handle *handle; + int error; + + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (!dev->inhibited) + return 0; + ++ dev->inhibited = false; ++ + if (dev->users) { + if (dev->open) { + error = dev->open(dev); +- if (error) ++ if (error) { ++ dev->inhibited = true; + return error; ++ } + } +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ scoped_guard(spinlock_irq, &dev->event_lock) ++ dev->ready = true; + } + +- dev->inhibited = false; +- + scoped_guard(spinlock_irq, &dev->event_lock) + input_dev_toggle(dev, true); + ++ if (dev->users && dev->poller) ++ input_dev_poller_start(dev->poller); ++ ++ list_for_each_entry(handle, &dev->h_list, d_node) { ++ if (handle->open && handle->handler->start) ++ handle->handler->start(handle); ++ } ++ + return 0; + } + +@@ -2655,9 +2708,6 @@ int input_register_handle(struct input_handle *handle) + */ + list_add_tail_rcu(&handle->h_node, &handler->h_list); + +- if (handler->start) +- handler->start(handle); +- + return 0; + } + EXPORT_SYMBOL(input_register_handle); +diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig +--- a/drivers/input/keyboard/Kconfig ++++ b/drivers/input/keyboard/Kconfig +@@ -119,44 +119,6 @@ config KEYBOARD_ATKBD + To compile this driver as a module, choose M here: the + module will be called atkbd. + +-config KEYBOARD_ATKBD_HP_KEYCODES +- bool "Use HP keyboard scancodes" +- depends on PARISC && KEYBOARD_ATKBD +- default y +- help +- Say Y here if you have a PA-RISC machine and want to use an AT or +- PS/2 keyboard, and your keyboard uses keycodes that are specific to +- PA-RISC keyboards. +- +- Say N if you use a standard keyboard. +- +-config KEYBOARD_ATKBD_RDI_KEYCODES +- bool "Use PrecisionBook keyboard scancodes" +- depends on KEYBOARD_ATKBD_HP_KEYCODES +- default n +- help +- If you have an RDI PrecisionBook, say Y here if you want to use its +- built-in keyboard (as opposed to an external keyboard). +- +- The PrecisionBook has five keys that conflict with those used by most +- AT and PS/2 keyboards. These are as follows: +- +- PrecisionBook Standard AT or PS/2 +- +- F1 F12 +- Left Ctrl Left Alt +- Caps Lock Left Ctrl +- Right Ctrl Caps Lock +- Left 102nd key (the key to the right of Left Shift) +- +- If you say N here, and use the PrecisionBook keyboard, then each key +- in the left-hand column will be interpreted as the corresponding key +- in the right-hand column. +- +- If you say Y here, and use an external keyboard, then each key in the +- right-hand column will be interpreted as the key shown in the +- left-hand column. +- + config KEYBOARD_QT1050 + tristate "Microchip AT42QT1050 Touch Sensor Chip" + depends on I2C +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -73,13 +73,6 @@ MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard conne + #define ATKBD_KEYMAP_SIZE 512 + + static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { +-#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES +- +-/* XXX: need a more general approach */ +- +-#include "hpps2atkbd.h" /* include the keyboard scancodes */ +- +-#else + 0, 67, 65, 63, 61, 59, 60, 88,183, 68, 66, 64, 62, 15, 41,117, + 184, 56, 42, 93, 29, 16, 2, 0,185, 0, 44, 31, 30, 17, 3, 0, + 186, 46, 45, 32, 18, 5, 4, 95,187, 57, 47, 33, 20, 19, 6,183, +@@ -99,7 +92,6 @@ static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + + 0, 0, 0, 65, 99, +-#endif + }; + + static const unsigned short atkbd_set3_keycode[ATKBD_KEYMAP_SIZE] = { +diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c +--- a/drivers/input/keyboard/cap11xx.c ++++ b/drivers/input/keyboard/cap11xx.c +@@ -5,6 +5,8 @@ + * (c) 2014 Daniel Mack + */ + ++#include ++#include + #include + #include + #include +@@ -16,63 +18,43 @@ + #include + #include + ++#define CAP1114_REG_BUTTON_STATUS1 0x03 ++#define CAP1114_REG_BUTTON_STATUS2 0x04 ++#define CAP1114_REG_CONFIG2 0x40 ++#define CAP1114_REG_CONFIG2_VOL_UP_DOWN BIT(1) ++#define CAP1114_REG_LED_OUTPUT_CONTROL1 0x73 ++ + #define CAP11XX_REG_MAIN_CONTROL 0x00 + #define CAP11XX_REG_MAIN_CONTROL_GAIN_SHIFT (6) + #define CAP11XX_REG_MAIN_CONTROL_GAIN_MASK (0xc0) + #define CAP11XX_REG_MAIN_CONTROL_DLSEEP BIT(4) +-#define CAP11XX_REG_GENERAL_STATUS 0x02 + #define CAP11XX_REG_SENSOR_INPUT 0x03 +-#define CAP11XX_REG_NOISE_FLAG_STATUS 0x0a ++#define CAP1114_REG_BUTTON_STATUS2 0x04 + #define CAP11XX_REG_SENOR_DELTA(X) (0x10 + (X)) + #define CAP11XX_REG_SENSITIVITY_CONTROL 0x1f + #define CAP11XX_REG_SENSITIVITY_CONTROL_DELTA_SENSE_MASK 0x70 +-#define CAP11XX_REG_CONFIG 0x20 +-#define CAP11XX_REG_SENSOR_ENABLE 0x21 +-#define CAP11XX_REG_SENSOR_CONFIG 0x22 +-#define CAP11XX_REG_SENSOR_CONFIG2 0x23 +-#define CAP11XX_REG_SAMPLING_CONFIG 0x24 +-#define CAP11XX_REG_CALIBRATION 0x26 +-#define CAP11XX_REG_INT_ENABLE 0x27 + #define CAP11XX_REG_REPEAT_RATE 0x28 + #define CAP11XX_REG_SIGNAL_GUARD_ENABLE 0x29 +-#define CAP11XX_REG_MT_CONFIG 0x2a +-#define CAP11XX_REG_MT_PATTERN_CONFIG 0x2b +-#define CAP11XX_REG_MT_PATTERN 0x2d +-#define CAP11XX_REG_RECALIB_CONFIG 0x2f + #define CAP11XX_REG_SENSOR_THRESH(X) (0x30 + (X)) +-#define CAP11XX_REG_SENSOR_NOISE_THRESH 0x38 +-#define CAP11XX_REG_STANDBY_CHANNEL 0x40 +-#define CAP11XX_REG_STANDBY_CONFIG 0x41 +-#define CAP11XX_REG_STANDBY_SENSITIVITY 0x42 +-#define CAP11XX_REG_STANDBY_THRESH 0x43 + #define CAP11XX_REG_CONFIG2 0x44 + #define CAP11XX_REG_CONFIG2_ALT_POL BIT(6) +-#define CAP11XX_REG_SENSOR_BASE_CNT(X) (0x50 + (X)) +-#define CAP11XX_REG_LED_POLARITY 0x73 + #define CAP11XX_REG_LED_OUTPUT_CONTROL 0x74 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG 0x80 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG2 0x81 +- +-#define CAP11XX_REG_LED_DUTY_CYCLE_1 0x90 +-#define CAP11XX_REG_LED_DUTY_CYCLE_2 0x91 +-#define CAP11XX_REG_LED_DUTY_CYCLE_3 0x92 + #define CAP11XX_REG_LED_DUTY_CYCLE_4 0x93 + +-#define CAP11XX_REG_LED_DUTY_MIN_MASK (0x0f) +-#define CAP11XX_REG_LED_DUTY_MIN_MASK_SHIFT (0) + #define CAP11XX_REG_LED_DUTY_MAX_MASK (0xf0) +-#define CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT (4) + #define CAP11XX_REG_LED_DUTY_MAX_VALUE (15) + +-#define CAP11XX_REG_SENSOR_CALIB (0xb1 + (X)) +-#define CAP11XX_REG_SENSOR_CALIB_LSB1 0xb9 +-#define CAP11XX_REG_SENSOR_CALIB_LSB2 0xba + #define CAP11XX_REG_PRODUCT_ID 0xfd + #define CAP11XX_REG_MANUFACTURER_ID 0xfe + #define CAP11XX_REG_REVISION 0xff + + #define CAP11XX_MANUFACTURER_ID 0x5d + ++#define CAP11XX_T_RST_FILT_MIN_US 10000 ++#define CAP11XX_T_RST_ON_MIN_MS 400 ++ + #ifdef CONFIG_LEDS_CLASS + struct cap11xx_led { + struct cap11xx_priv *priv; +@@ -85,6 +67,7 @@ struct cap11xx_priv { + struct regmap *regmap; + struct device *dev; + struct input_dev *idev; ++ struct gpio_desc *reset_gpio; + const struct cap11xx_hw_model *model; + + struct cap11xx_led *leds; +@@ -101,47 +84,32 @@ struct cap11xx_priv { + + struct cap11xx_hw_model { + u8 product_id; ++ u8 led_output_control_reg_base; ++ u8 sensor_input_reg_base; + unsigned int num_channels; + unsigned int num_leds; ++ unsigned int num_sensor_thresholds; + bool has_gain; ++ bool has_grouped_sensors; + bool has_irq_config; ++ bool has_repeat_en; + bool has_sensitivity_control; + bool has_signal_guard; + }; + + static const struct reg_default cap11xx_reg_defaults[] = { + { CAP11XX_REG_MAIN_CONTROL, 0x00 }, +- { CAP11XX_REG_GENERAL_STATUS, 0x00 }, +- { CAP11XX_REG_SENSOR_INPUT, 0x00 }, +- { CAP11XX_REG_NOISE_FLAG_STATUS, 0x00 }, + { CAP11XX_REG_SENSITIVITY_CONTROL, 0x2f }, +- { CAP11XX_REG_CONFIG, 0x20 }, +- { CAP11XX_REG_SENSOR_ENABLE, 0x3f }, +- { CAP11XX_REG_SENSOR_CONFIG, 0xa4 }, +- { CAP11XX_REG_SENSOR_CONFIG2, 0x07 }, +- { CAP11XX_REG_SAMPLING_CONFIG, 0x39 }, +- { CAP11XX_REG_CALIBRATION, 0x00 }, +- { CAP11XX_REG_INT_ENABLE, 0x3f }, + { CAP11XX_REG_REPEAT_RATE, 0x3f }, +- { CAP11XX_REG_MT_CONFIG, 0x80 }, +- { CAP11XX_REG_MT_PATTERN_CONFIG, 0x00 }, +- { CAP11XX_REG_MT_PATTERN, 0x3f }, +- { CAP11XX_REG_RECALIB_CONFIG, 0x8a }, + { CAP11XX_REG_SENSOR_THRESH(0), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(1), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(2), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(3), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(4), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(5), 0x40 }, +- { CAP11XX_REG_SENSOR_NOISE_THRESH, 0x01 }, +- { CAP11XX_REG_STANDBY_CHANNEL, 0x00 }, +- { CAP11XX_REG_STANDBY_CONFIG, 0x39 }, +- { CAP11XX_REG_STANDBY_SENSITIVITY, 0x02 }, +- { CAP11XX_REG_STANDBY_THRESH, 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(6), 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(7), 0x40 }, + { CAP11XX_REG_CONFIG2, 0x40 }, +- { CAP11XX_REG_LED_POLARITY, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB1, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB2, 0x00 }, + }; + + static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) +@@ -149,6 +117,12 @@ static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) + switch (reg) { + case CAP11XX_REG_MAIN_CONTROL: + case CAP11XX_REG_SENSOR_INPUT: ++ /* ++ * CAP1114_REG_BUTTON_STATUS1 (CAP11XX_REG_SENSOR_INPUT) and ++ * CAP1114_REG_BUTTON_STATUS2 is volatile for the CAP1114, ++ * which supports more than 8 touch channels. ++ */ ++ case CAP1114_REG_BUTTON_STATUS2: + case CAP11XX_REG_SENOR_DELTA(0): + case CAP11XX_REG_SENOR_DELTA(1): + case CAP11XX_REG_SENOR_DELTA(2): +@@ -257,8 +231,8 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + + if (!of_property_read_u32_array(node, "microchip,input-threshold", +- priv->thresholds, priv->model->num_channels)) { +- for (i = 0; i < priv->model->num_channels; i++) { ++ priv->thresholds, priv->model->num_sensor_thresholds)) { ++ for (i = 0; i < priv->model->num_sensor_thresholds; i++) { + if (priv->thresholds[i] > 127) { + dev_err(dev, "Invalid input-threshold value %u\n", + priv->thresholds[i]); +@@ -273,10 +247,13 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + } + +- if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", +- priv->calib_sensitivities, +- priv->model->num_channels)) { +- if (priv->model->has_sensitivity_control) { ++ if (of_property_present(node, "microchip,calib-sensitivity")) { ++ if (!priv->model->has_sensitivity_control) { ++ dev_warn(dev, ++ "This model doesn't support 'calib-sensitivity'\n"); ++ } else if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", ++ priv->calib_sensitivities, ++ priv->model->num_channels)) { + for (i = 0; i < priv->model->num_channels; i++) { + if (!is_power_of_2(priv->calib_sensitivities[i]) || + priv->calib_sensitivities[i] > 4) { +@@ -296,32 +273,31 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + if (error) + return error; + } +- } else { +- dev_warn(dev, +- "This model doesn't support 'calib-sensitivity'\n"); + } + } + +- for (i = 0; i < priv->model->num_channels; i++) { +- if (!of_property_read_u32_index(node, "microchip,signal-guard", +- i, &u32_val)) { +- if (u32_val > 1) +- return -EINVAL; +- if (u32_val) +- priv->signal_guard_inputs_mask |= 0x01 << i; +- } +- } +- +- if (priv->signal_guard_inputs_mask) { +- if (priv->model->has_signal_guard) { +- error = regmap_write(priv->regmap, +- CAP11XX_REG_SIGNAL_GUARD_ENABLE, +- priv->signal_guard_inputs_mask); +- if (error) +- return error; +- } else { ++ if (of_property_present(node, "microchip,signal-guard")) { ++ if (!priv->model->has_signal_guard) { + dev_warn(dev, + "This model doesn't support 'signal-guard'\n"); ++ } else { ++ for (i = 0; i < priv->model->num_channels; i++) { ++ if (!of_property_read_u32_index(node, "microchip,signal-guard", ++ i, &u32_val)) { ++ if (u32_val > 1) ++ return -EINVAL; ++ if (u32_val) ++ priv->signal_guard_inputs_mask |= 0x01 << i; ++ } ++ } ++ ++ if (priv->signal_guard_inputs_mask) { ++ error = regmap_write(priv->regmap, ++ CAP11XX_REG_SIGNAL_GUARD_ENABLE, ++ priv->signal_guard_inputs_mask); ++ if (error) ++ return error; ++ } + } + } + +@@ -332,10 +308,23 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + of_property_read_u32_array(node, "linux,keycodes", + priv->keycodes, priv->model->num_channels); + +- /* Disable autorepeat. The Linux input system has its own handling. */ +- error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); +- if (error) +- return error; ++ /* ++ * CAP1114 needs dedicated configuration to split ++ * grouped sensors into independent inputs. ++ */ ++ if (priv->model->has_grouped_sensors) { ++ error = regmap_set_bits(priv->regmap, CAP1114_REG_CONFIG2, ++ CAP1114_REG_CONFIG2_VOL_UP_DOWN); ++ if (error) ++ return error; ++ } ++ ++ if (priv->model->has_repeat_en) { ++ /* Disable autorepeat. The Linux input system has its own handling. */ ++ error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); ++ if (error) ++ return error; ++ } + + return 0; + } +@@ -354,10 +343,25 @@ static irqreturn_t cap11xx_thread_func(int irq_num, void *data) + if (ret < 0) + goto out; + +- ret = regmap_read(priv->regmap, CAP11XX_REG_SENSOR_INPUT, &status); ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base, &status); + if (ret < 0) + goto out; + ++ if (priv->model->num_channels > 8) { ++ unsigned int status2; ++ ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base + 1, &status2); ++ if (ret < 0) ++ goto out; ++ ++ /* ++ * CAP1114 STATUS1 register only contains data for the first 6 channels. ++ * the remaining channels is stored in STATUS2. ++ */ ++ status &= GENMASK(5, 0); ++ status |= FIELD_PREP(GENMASK(13, 6), status2); ++ } ++ + for (i = 0; i < priv->idev->keycodemax; i++) + input_report_key(priv->idev, priv->keycodes[i], + status & (1 << i)); +@@ -407,10 +411,16 @@ static int cap11xx_led_set(struct led_classdev *cdev, + * limitation. Brightness levels per LED are either + * 0 (OFF) and 1 (ON). + */ +- return regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, +- BIT(led->reg), +- value ? BIT(led->reg) : 0); ++ if (led->reg >= 8) ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ BIT(led->reg - 8), ++ value ? BIT(led->reg - 8) : 0); ++ else ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base, ++ BIT(led->reg), ++ value ? BIT(led->reg) : 0); + } + + static int cap11xx_init_leds(struct device *dev, +@@ -420,6 +430,7 @@ static int cap11xx_init_leds(struct device *dev, + struct cap11xx_led *led; + int cnt = of_get_child_count(node); + int error; ++ u32 duty_val; + + if (!num_leds || !cnt) + return 0; +@@ -433,15 +444,26 @@ static int cap11xx_init_leds(struct device *dev, + + priv->leds = led; + ++ /* Set all LEDs to off */ + error = regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, 0xff, 0); ++ priv->model->led_output_control_reg_base, ++ GENMASK(min(num_leds, 8) - 1, 0), 0); + if (error) + return error; + ++ if (num_leds > 8) { ++ error = regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ GENMASK(num_leds - 8 - 1, 0), 0); ++ if (error) ++ return error; ++ } ++ ++ duty_val = FIELD_PREP(CAP11XX_REG_LED_DUTY_MAX_MASK, ++ CAP11XX_REG_LED_DUTY_MAX_VALUE); ++ + error = regmap_update_bits(priv->regmap, CAP11XX_REG_LED_DUTY_CYCLE_4, +- CAP11XX_REG_LED_DUTY_MAX_MASK, +- CAP11XX_REG_LED_DUTY_MAX_VALUE << +- CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT); ++ CAP11XX_REG_LED_DUTY_MAX_MASK, duty_val); + if (error) + return error; + +@@ -510,9 +532,20 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + ++ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(priv->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), ++ "Failed to get 'reset' GPIO\n"); ++ ++ if (priv->reset_gpio) { ++ usleep_range(CAP11XX_T_RST_FILT_MIN_US, CAP11XX_T_RST_FILT_MIN_US * 2); ++ gpiod_set_value_cansleep(priv->reset_gpio, 0); ++ msleep(CAP11XX_T_RST_ON_MIN_MS); ++ } ++ + error = regmap_read(priv->regmap, CAP11XX_REG_PRODUCT_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read product ID\n"); + + if (val != cap->product_id) { + dev_err(dev, "Product ID: Got 0x%02x, expected 0x%02x\n", +@@ -522,7 +555,7 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + + error = regmap_read(priv->regmap, CAP11XX_REG_MANUFACTURER_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read manufacturer ID\n"); + + if (val != CAP11XX_MANUFACTURER_ID) { + dev_err(dev, "Manufacturer ID: Got 0x%02x, expected 0x%02x\n", +@@ -531,11 +564,8 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + error = regmap_read(priv->regmap, CAP11XX_REG_REVISION, &rev); +- if (error < 0) +- return error; +- +- dev_info(dev, "CAP11XX detected, model %s, revision 0x%02x\n", +- id->name, rev); ++ if (error) ++ return dev_err_probe(dev, error, "Failed to read revision\n"); + + priv->model = cap; + +@@ -599,47 +629,79 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + static const struct cap11xx_hw_model cap1106_model = { +- .product_id = 0x55, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x55, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, ++}; ++ ++static const struct cap11xx_hw_model cap1114_model = { ++ .product_id = 0x3a, ++ .num_channels = 14, .num_leds = 11, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP1114_REG_LED_OUTPUT_CONTROL1, ++ .sensor_input_reg_base = CAP1114_REG_BUTTON_STATUS1, ++ .has_grouped_sensors = true, + }; + + static const struct cap11xx_hw_model cap1126_model = { +- .product_id = 0x53, .num_channels = 6, .num_leds = 2, ++ .product_id = 0x53, ++ .num_channels = 6, .num_leds = 2, .num_sensor_thresholds = 6, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1188_model = { +- .product_id = 0x50, .num_channels = 8, .num_leds = 8, ++ .product_id = 0x50, ++ .num_channels = 8, .num_leds = 8, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1203_model = { +- .product_id = 0x6d, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6d, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1206_model = { +- .product_id = 0x67, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x67, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1293_model = { +- .product_id = 0x6f, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6f, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct cap11xx_hw_model cap1298_model = { +- .product_id = 0x71, .num_channels = 8, .num_leds = 0, ++ .product_id = 0x71, ++ .num_channels = 8, .num_leds = 0, .num_sensor_thresholds = 8, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct of_device_id cap11xx_dt_ids[] = { + { .compatible = "microchip,cap1106", .data = &cap1106_model }, ++ { .compatible = "microchip,cap1114", .data = &cap1114_model }, + { .compatible = "microchip,cap1126", .data = &cap1126_model }, + { .compatible = "microchip,cap1188", .data = &cap1188_model }, + { .compatible = "microchip,cap1203", .data = &cap1203_model }, +@@ -652,6 +714,7 @@ MODULE_DEVICE_TABLE(of, cap11xx_dt_ids); + + static const struct i2c_device_id cap11xx_i2c_ids[] = { + { .name = "cap1106", .driver_data = (kernel_ulong_t)&cap1106_model }, ++ { .name = "cap1114", .driver_data = (kernel_ulong_t)&cap1114_model }, + { .name = "cap1126", .driver_data = (kernel_ulong_t)&cap1126_model }, + { .name = "cap1188", .driver_data = (kernel_ulong_t)&cap1188_model }, + { .name = "cap1203", .driver_data = (kernel_ulong_t)&cap1203_model }, +diff --git a/drivers/input/keyboard/charlieplex_keypad.c b/drivers/input/keyboard/charlieplex_keypad.c +--- a/drivers/input/keyboard/charlieplex_keypad.c ++++ b/drivers/input/keyboard/charlieplex_keypad.c +@@ -77,7 +77,9 @@ static int charlieplex_keypad_scan_line(struct charlieplex_keypad *keypad, + int err; + + /* Activate only one line as output at a time. */ +- gpiod_direction_output(line_gpios->desc[oline], 1); ++ err = gpiod_direction_output(line_gpios->desc[oline], 1); ++ if (err) ++ return err; + + if (keypad->settling_time_us) + fsleep(keypad->settling_time_us); +diff --git a/drivers/input/keyboard/hpps2atkbd.h b/drivers/input/keyboard/hpps2atkbd.h +deleted file mode 100644 +--- a/drivers/input/keyboard/hpps2atkbd.h ++++ /dev/null +@@ -1,110 +0,0 @@ +-/* +- * drivers/input/keyboard/hpps2atkbd.h +- * +- * Copyright (c) 2004 Helge Deller +- * Copyright (c) 2002 Laurent Canet +- * Copyright (c) 2002 Thibaut Varene +- * Copyright (c) 2000 Xavier Debacker +- * +- * HP PS/2 AT-compatible Keyboard, found in PA/RISC Workstations & Laptops +- * +- * This file is subject to the terms and conditions of the GNU General Public +- * License. See the file "COPYING" in the main directory of this archive +- * for more details. +- */ +- +- +-/* Is the keyboard an RDI PrecisionBook? */ +-#ifndef CONFIG_KEYBOARD_ATKBD_RDI_KEYCODES +-# define CONFLICT(x,y) x +-#else +-# define CONFLICT(x,y) y +-#endif +- +-/* sadly RDI (Tadpole) decided to ship a different keyboard layout +- than HP for their PS/2 laptop keyboard which leads to conflicting +- keycodes between a normal HP PS/2 keyboard and a RDI Precisionbook. +- HP: RDI: */ +-#define C_07 CONFLICT( KEY_F12, KEY_F1 ) +-#define C_11 CONFLICT( KEY_LEFTALT, KEY_LEFTCTRL ) +-#define C_14 CONFLICT( KEY_LEFTCTRL, KEY_CAPSLOCK ) +-#define C_58 CONFLICT( KEY_CAPSLOCK, KEY_RIGHTCTRL ) +-#define C_61 CONFLICT( KEY_102ND, KEY_LEFT ) +- +-/* Raw SET 2 scancode table */ +- +-/* 00 */ KEY_RESERVED, KEY_F9, KEY_RESERVED, KEY_F5, KEY_F3, KEY_F1, KEY_F2, C_07, +-/* 08 */ KEY_ESC, KEY_F10, KEY_F8, KEY_F6, KEY_F4, KEY_TAB, KEY_GRAVE, KEY_F2, +-/* 10 */ KEY_RESERVED, C_11, KEY_LEFTSHIFT, KEY_RESERVED, C_14, KEY_Q, KEY_1, KEY_F3, +-/* 18 */ KEY_RESERVED, KEY_LEFTALT, KEY_Z, KEY_S, KEY_A, KEY_W, KEY_2, KEY_F4, +-/* 20 */ KEY_RESERVED, KEY_C, KEY_X, KEY_D, KEY_E, KEY_4, KEY_3, KEY_F5, +-/* 28 */ KEY_RESERVED, KEY_SPACE, KEY_V, KEY_F, KEY_T, KEY_R, KEY_5, KEY_F6, +-/* 30 */ KEY_RESERVED, KEY_N, KEY_B, KEY_H, KEY_G, KEY_Y, KEY_6, KEY_F7, +-/* 38 */ KEY_RESERVED, KEY_RIGHTALT, KEY_M, KEY_J, KEY_U, KEY_7, KEY_8, KEY_F8, +-/* 40 */ KEY_RESERVED, KEY_COMMA, KEY_K, KEY_I, KEY_O, KEY_0, KEY_9, KEY_F9, +-/* 48 */ KEY_RESERVED, KEY_DOT, KEY_SLASH, KEY_L, KEY_SEMICOLON, KEY_P, KEY_MINUS, KEY_F10, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_APOSTROPHE,KEY_RESERVED, KEY_LEFTBRACE, KEY_EQUAL, KEY_F11, KEY_SYSRQ, +-/* 58 */ C_58, KEY_RIGHTSHIFT,KEY_ENTER, KEY_RIGHTBRACE,KEY_BACKSLASH, KEY_BACKSLASH,KEY_F12, KEY_SCROLLLOCK, +-/* 60 */ KEY_DOWN, C_61, KEY_PAUSE, KEY_UP, KEY_DELETE, KEY_END, KEY_BACKSPACE, KEY_INSERT, +-/* 68 */ KEY_RESERVED, KEY_KP1, KEY_RIGHT, KEY_KP4, KEY_KP7, KEY_PAGEDOWN, KEY_HOME, KEY_PAGEUP, +-/* 70 */ KEY_KP0, KEY_KPDOT, KEY_KP2, KEY_KP5, KEY_KP6, KEY_KP8, KEY_ESC, KEY_NUMLOCK, +-/* 78 */ KEY_F11, KEY_KPPLUS, KEY_KP3, KEY_KPMINUS, KEY_KPASTERISK,KEY_KP9, KEY_SCROLLLOCK,KEY_102ND, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RIGHTALT, 255, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_CAPSLOCK, KEY_RESERVED, KEY_LEFTMETA, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTMETA, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_COMPOSE, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPSLASH, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPENTER, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_END, KEY_RESERVED, KEY_LEFT, KEY_HOME, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_INSERT, KEY_DELETE, KEY_DOWN, KEY_RESERVED, KEY_RIGHT, KEY_UP, KEY_RESERVED, KEY_PAUSE, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED, KEY_SYSRQ, KEY_PAGEUP, KEY_RESERVED, KEY_RESERVED, +- +-/* These are offset for escaped keycodes: */ +- +-/* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_F7, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 08 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 10 */ KEY_RESERVED, KEY_RIGHTALT, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 18 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 20 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 28 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 30 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 38 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 40 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 48 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 58 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 60 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +- +-#undef CONFLICT +-#undef C_07 +-#undef C_11 +-#undef C_14 +-#undef C_58 +-#undef C_61 +- +diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c +--- a/drivers/input/keyboard/matrix_keypad.c ++++ b/drivers/input/keyboard/matrix_keypad.c +@@ -16,7 +16,6 @@ + #include + #include + #include +-#include + #include + #include + #include +diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c +--- a/drivers/input/keyboard/pmic8xxx-keypad.c ++++ b/drivers/input/keyboard/pmic8xxx-keypad.c +@@ -462,15 +462,9 @@ static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp) + + static int pmic8xxx_kp_disable(struct pmic8xxx_kp *kp) + { +- int rc; +- + kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN; + +- rc = regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); +- if (rc < 0) +- return rc; +- +- return rc; ++ return regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); + } + + static int pmic8xxx_kp_open(struct input_dev *dev) +diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c +--- a/drivers/input/keyboard/samsung-keypad.c ++++ b/drivers/input/keyboard/samsung-keypad.c +@@ -142,7 +142,7 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + unsigned int row_state[SAMSUNG_MAX_COLS]; + bool key_down; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + do { + readl(keypad->base + SAMSUNG_KEYIFSTSCLR); +@@ -158,8 +158,6 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + + } while (key_down && !keypad->stopped); + +- pm_runtime_put(&keypad->pdev->dev); +- + return IRQ_HANDLED; + } + +@@ -167,7 +165,7 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Tell IRQ thread that it may poll the device. */ + keypad->stopped = false; +@@ -182,14 +180,14 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + /* KEYIFCOL reg clear. */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); + +- pm_runtime_put(&keypad->pdev->dev); ++ enable_irq(keypad->irq); + } + + static void samsung_keypad_stop(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Signal IRQ thread to stop polling and disable the handler. */ + keypad->stopped = true; +@@ -205,14 +203,6 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); +- +- /* +- * Now that chip should not generate interrupts we can safely +- * re-enable the handler. +- */ +- enable_irq(keypad->irq); +- +- pm_runtime_put(&keypad->pdev->dev); + } + + static int samsung_keypad_open(struct input_dev *input_dev) +@@ -412,7 +402,8 @@ static int samsung_keypad_probe(struct platform_device *pdev) + } + + error = devm_request_threaded_irq(&pdev->dev, keypad->irq, NULL, +- samsung_keypad_irq, IRQF_ONESHOT, ++ samsung_keypad_irq, ++ IRQF_ONESHOT | IRQF_NO_AUTOEN, + dev_name(&pdev->dev), keypad); + if (error) { + dev_err(&pdev->dev, "failed to register keypad interrupt\n"); +@@ -492,15 +483,17 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, + + val = readl(keypad->base + SAMSUNG_KEYIFCON); + if (enable) { ++ enable_irq_wake(keypad->irq); + val |= SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- enable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); + } else { + val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- disable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); ++ disable_irq_wake(keypad->irq); ++ ++ if (!input_device_enabled(keypad->input_dev)) ++ writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); + } +- writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); + } +@@ -516,7 +509,8 @@ static int samsung_keypad_suspend(struct device *dev) + if (input_device_enabled(input_dev)) + samsung_keypad_stop(keypad); + +- samsung_keypad_toggle_wakeup(keypad, true); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, true); + + return 0; + } +@@ -529,7 +523,8 @@ static int samsung_keypad_resume(struct device *dev) + + guard(mutex)(&input_dev->mutex); + +- samsung_keypad_toggle_wakeup(keypad, false); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, false); + + if (input_device_enabled(input_dev)) + samsung_keypad_start(keypad); +diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c +--- a/drivers/input/keyboard/snvs_pwrkey.c ++++ b/drivers/input/keyboard/snvs_pwrkey.c +@@ -39,6 +39,9 @@ struct pwrkey_drv_data { + int keycode; + int keystate; /* 1:pressed */ + int wakeup; ++ bool suspended; /* Track suspend state */ ++ bool pending_press; /* Key pressed during suspend, report from timer callback */ ++ spinlock_t lock; /* Protects keystate, suspended and pending_press */ + struct timer_list check_timer; + struct input_dev *input; + u8 minor_rev; +@@ -49,14 +52,38 @@ static void imx_imx_snvs_check_for_events(struct timer_list *t) + struct pwrkey_drv_data *pdata = timer_container_of(pdata, t, + check_timer); + struct input_dev *input = pdata->input; ++ bool state_changed = false; ++ bool pending_press; + u32 state; + + regmap_read(pdata->snvs, SNVS_HPSR_REG, &state); + state = state & SNVS_HPSR_BTN ? 1 : 0; + +- /* only report new event if status changed */ +- if (state ^ pdata->keystate) { +- pdata->keystate = state; ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ pending_press = pdata->pending_press; ++ if (pending_press) { ++ pdata->pending_press = false; ++ pdata->keystate = 1; ++ } ++ /* only report new event if status changed */ ++ if (state ^ pdata->keystate) { ++ pdata->keystate = state; ++ state_changed = true; ++ } ++ } ++ ++ /* ++ * Report a press event latched during suspend. If the key is still ++ * held, state_changed will be 0 (keystate already set to 1 above), ++ * so no duplicate press is reported. If already released, ++ * state_changed will fire next to report the release. ++ */ ++ if (pending_press) { ++ input_report_key(input, pdata->keycode, 1); ++ input_sync(input); ++ } ++ ++ if (state_changed) { + input_event(input, EV_KEY, pdata->keycode, state); + input_sync(input); + pm_relax(pdata->input->dev.parent); +@@ -92,8 +119,17 @@ static irqreturn_t imx_snvs_pwrkey_interrupt(int irq, void *dev_id) + input_sync(input); + pm_relax(input->dev.parent); + } else { ++ /* ++ * If the key is pressed during suspend, latch it so ++ * the timer callback can report the press event in ++ * softirq context, avoiding out-of-order events. ++ */ ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ if (pdata->suspended) ++ pdata->pending_press = true; ++ } + mod_timer(&pdata->check_timer, +- jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); ++ jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); + } + } + +@@ -112,6 +148,7 @@ static void imx_snvs_pwrkey_act(void *pdata) + + static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; + struct pwrkey_drv_data *pdata; + struct input_dev *input; + struct device_node *np; +@@ -122,37 +159,35 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + u32 vid; + + /* Get SNVS register Page */ +- np = pdev->dev.of_node; ++ np = dev->of_node; + if (!np) +- return -ENODEV; ++ return dev_err_probe(dev, -ENODEV, "Device tree node not found\n"); + +- pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->snvs = syscon_regmap_lookup_by_phandle(np, "regmap"); +- if (IS_ERR(pdata->snvs)) { +- dev_err(&pdev->dev, "Can't get snvs syscon\n"); +- return PTR_ERR(pdata->snvs); +- } ++ if (IS_ERR(pdata->snvs)) ++ return dev_err_probe(dev, PTR_ERR(pdata->snvs), "Can't get snvs syscon\n"); + + if (of_property_read_u32(np, "linux,keycode", &pdata->keycode)) { + pdata->keycode = KEY_POWER; +- dev_warn(&pdev->dev, "KEY_POWER without setting in dts\n"); ++ dev_warn(dev, "KEY_POWER without setting in dts\n"); + } + +- clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); +- if (IS_ERR(clk)) { +- dev_err(&pdev->dev, "Failed to get snvs clock (%pe)\n", clk); +- return PTR_ERR(clk); +- } ++ clk = devm_clk_get_optional_enabled(dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(dev, PTR_ERR(clk), ++ "Failed to get snvs clock (%pe)\n", clk); + + pdata->wakeup = of_property_read_bool(np, "wakeup-source"); + + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq < 0) +- return -EINVAL; ++ return pdata->irq; + ++ spin_lock_init(&pdata->lock); + error = of_property_read_u32(np, "power-off-time-sec", &val); + if (!error) { + switch (val) { +@@ -165,9 +200,8 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + bpt = (val / 5) - 1; + break; + default: +- dev_err(&pdev->dev, +- "power-off-time-sec %d out of range\n", val); +- return -EINVAL; ++ return dev_err_probe(dev, -EINVAL, ++ "power-off-time-sec %d out of range\n", val); + } + + regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, SNVS_LPCR_BPT_MASK, +@@ -184,11 +218,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + + timer_setup(&pdata->check_timer, imx_imx_snvs_check_for_events, 0); + +- input = devm_input_allocate_device(&pdev->dev); +- if (!input) { +- dev_err(&pdev->dev, "failed to allocate the input device\n"); +- return -ENOMEM; +- } ++ input = devm_input_allocate_device(dev); ++ if (!input) ++ return dev_err_probe(dev, -ENOMEM, "failed to allocate the input device\n"); + + input->name = pdev->name; + input->phys = "snvs-pwrkey/input0"; +@@ -197,37 +229,57 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + input_set_capability(input, EV_KEY, pdata->keycode); + + /* input customer action to cancel release timer */ +- error = devm_add_action(&pdev->dev, imx_snvs_pwrkey_act, pdata); +- if (error) { +- dev_err(&pdev->dev, "failed to register remove action\n"); +- return error; +- } ++ error = devm_add_action(dev, imx_snvs_pwrkey_act, pdata); ++ if (error) ++ return dev_err_probe(dev, error, "failed to register remove action\n"); + + pdata->input = input; + platform_set_drvdata(pdev, pdata); + +- error = devm_request_irq(&pdev->dev, pdata->irq, +- imx_snvs_pwrkey_interrupt, +- 0, pdev->name, pdev); +- if (error) { +- dev_err(&pdev->dev, "interrupt not available.\n"); +- return error; +- } ++ error = devm_request_irq(dev, pdata->irq, ++ imx_snvs_pwrkey_interrupt, ++ 0, pdev->name, pdev); ++ if (error) ++ return dev_err_probe(dev, error, "interrupt not available.\n"); + + error = input_register_device(input); +- if (error < 0) { +- dev_err(&pdev->dev, "failed to register input device\n"); +- return error; +- } ++ if (error < 0) ++ return dev_err_probe(dev, error, "failed to register input device\n"); + +- device_init_wakeup(&pdev->dev, pdata->wakeup); +- error = dev_pm_set_wake_irq(&pdev->dev, pdata->irq); ++ device_init_wakeup(dev, pdata->wakeup); ++ error = dev_pm_set_wake_irq(dev, pdata->irq); + if (error) +- dev_err(&pdev->dev, "irq wake enable failed.\n"); ++ dev_err(dev, "irq wake enable failed.\n"); + + return 0; + } + ++static int imx_snvs_pwrkey_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = true; ++ ++ return 0; ++} ++ ++static int imx_snvs_pwrkey_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = false; ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(imx_snvs_pwrkey_pm_ops, ++ imx_snvs_pwrkey_suspend, ++ imx_snvs_pwrkey_resume); ++ + static const struct of_device_id imx_snvs_pwrkey_ids[] = { + { .compatible = "fsl,sec-v4.0-pwrkey" }, + { /* sentinel */ } +@@ -238,6 +290,7 @@ static struct platform_driver imx_snvs_pwrkey_driver = { + .driver = { + .name = "snvs_pwrkey", + .of_match_table = imx_snvs_pwrkey_ids, ++ .pm = pm_ptr(&imx_snvs_pwrkey_pm_ops), + }, + .probe = imx_snvs_pwrkey_probe, + }; +diff --git a/drivers/input/keyboard/tca8418_keypad.c b/drivers/input/keyboard/tca8418_keypad.c +--- a/drivers/input/keyboard/tca8418_keypad.c ++++ b/drivers/input/keyboard/tca8418_keypad.c +@@ -254,7 +254,8 @@ static int tca8418_configure(struct tca8418_keypad *keypad_data, + return error; + + error = tca8418_write_byte(keypad_data, REG_CFG, +- CFG_INT_CFG | CFG_OVR_FLOW_IEN | CFG_KE_IEN); ++ CFG_INT_CFG | CFG_OVR_FLOW_IEN | ++ CFG_OVR_FLOW_M | CFG_KE_IEN); + + return error; + } +diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig +--- a/drivers/input/misc/Kconfig ++++ b/drivers/input/misc/Kconfig +@@ -842,6 +842,18 @@ config INPUT_IQS7222 + To compile this driver as a module, choose M here: the + module will be called iqs7222. + ++config INPUT_ISA1200_HAPTIC ++ tristate "Imagis ISA1200 haptic feedback unit" ++ depends on I2C ++ select INPUT_FF_MEMLESS ++ select REGMAP_I2C ++ help ++ Say Y to enable support for the Imagis ISA1200 haptic ++ feedback unit. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called isa1200. ++ + config INPUT_CMA3000 + tristate "VTI CMA3000 Tri-axis accelerometer" + help +diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile +--- a/drivers/input/misc/Makefile ++++ b/drivers/input/misc/Makefile +@@ -49,6 +49,7 @@ obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o + obj-$(CONFIG_INPUT_IQS269A) += iqs269a.o + obj-$(CONFIG_INPUT_IQS626A) += iqs626a.o + obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o ++obj-$(CONFIG_INPUT_ISA1200_HAPTIC) += isa1200.o + obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o + obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o + obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -2234,6 +2234,7 @@ static const struct usb_device_id ims_pcu_id_table[] = { + }, + { } + }; ++MODULE_DEVICE_TABLE(usb, ims_pcu_id_table); + + static const struct attribute_group *ims_pcu_sysfs_groups[] = { + &ims_pcu_attr_group, +diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c +new file mode 100644 +--- /dev/null ++++ b/drivers/input/misc/isa1200.c +@@ -0,0 +1,533 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * System control (LDO regulator) ++ * ++ * LDO voltage to register mapping is linear, but it is split in two parts: ++ * 2.3V - 3.0V map to 0x08 - 0x0f; 3.1V - 3.8V map to 0x00 - 0x7 ++ */ ++ ++#define ISA1200_SCTRL 0x00 ++#define ISA1200_LDO_VOLTAGE_BASE 0x08 ++#define ISA1200_LDO_VOLTAGE_STEP 100000 ++#define ISA1200_LDO_VOLTAGE_2V3 23 ++#define ISA1200_LDO_VOLTAGE_3V1 31 ++#define ISA1200_LDO_VOLTAGE_MIN 2300000 ++#define ISA1200_LDO_VOLTAGE_MAX 3800000 ++ ++/* ++ * The output frequency is calculated with this formula: ++ * ++ * base clock frequency ++ * fout = ----------------------------------------- ++ * (128 - PWM_FREQ) * 2 * PLLDIV * PWM_PERIOD ++ * ++ * The base clock frequency is the clock frequency provided on the ++ * clock input to the chip, divided by the value in HCTRL0 ++ * ++ * PWM_FREQ is configured in register HCTRL4, it is common to set this ++ * to 0 to get only two variables to calculate. ++ * ++ * PLLDIV is configured in register HCTRL3 (bits 7..4, so 0..15) ++ * PWM_PERIOD is configured in register HCTRL6 ++ * Further the duty cycle can be configured in HCTRL5 ++ */ ++ ++/* ++ * HCTRL0 configures clock or PWM input and selects the divider for ++ * the clock input. ++ */ ++#define ISA1200_HCTRL0 0x30 ++#define ISA1200_HCTRL0_HAP_ENABLE BIT(7) ++#define ISA1200_HCTRL0_PWM_GEN_MODE BIT(4) ++#define ISA1200_HCTRL0_PWM_INPUT_MODE BIT(3) ++#define ISA1200_HCTRL0_CLKDIV_128 128 ++ ++/* ++ * HCTRL1 configures the motor type and clock sourse ++ */ ++#define ISA1200_HCTRL1 0x31 ++#define ISA1200_HCTRL1_EXT_CLOCK BIT(7) ++#define ISA1200_HCTRL1_DAC_INVERT BIT(6) ++#define ISA1200_HCTRL1_MODE(n) (((n) & 1) << 5) ++ ++/* HCTRL2 controls software reset of the chip */ ++#define ISA1200_HCTRL2 0x32 ++#define ISA1200_HCTRL2_SW_RESET BIT(0) ++ ++/* ++ * HCTRL3 controls the PLL divisor ++ * ++ * Bits [0,1] are always set to 1 (we don't know what they are ++ * used for) and bit 4 and upward control the PLL divisor. ++ */ ++#define ISA1200_HCTRL3 0x33 ++#define ISA1200_HCTRL3_DEFAULT 0x03 ++#define ISA1200_HCTRL3_PLLDIV(n) (((n) & 0xf) << 4) ++ ++/* HCTRL4 controls the PWM frequency of external channel */ ++#define ISA1200_HCTRL4 0x34 ++ ++/* HCTRL5 controls the PWM high duty cycle of internal channel */ ++#define ISA1200_HCTRL5 0x35 ++ ++/* HCTRL6 controls the PWM period of internal channel */ ++#define ISA1200_HCTRL6 0x36 ++#define ISA1200_HCTRL6_PERIOD_SCALE 100 ++ ++/* The use for these registers is unknown but they exist */ ++#define ISA1200_HCTRL7 0x37 ++#define ISA1200_HCTRL8 0x38 ++#define ISA1200_HCTRL9 0x39 ++#define ISA1200_HCTRLA 0x3a ++#define ISA1200_HCTRLB 0x3b ++#define ISA1200_HCTRLC 0x3c ++#define ISA1200_HCTRLD 0x3d ++ ++#define ISA1200_EN_PINS_MAX 2 ++ ++static const struct regulator_bulk_data isa1200_supplies[] = { ++ { .supply = "vdd" }, { .supply = "vddp" }, ++}; ++ ++struct isa1200_config { ++ u32 ldo_voltage; ++ u32 mode; ++ u32 clkdiv; ++ u32 plldiv; ++ u32 freq; ++ u32 period; ++ u32 duty; ++}; ++ ++struct isa1200 { ++ struct input_dev *input; ++ struct regmap *map; ++ ++ struct clk *clk; ++ struct pwm_device *pwm; ++ struct gpio_descs *enable_gpios; ++ struct regulator_bulk_data *supplies; ++ ++ struct work_struct play_work; ++ struct isa1200_config config; ++ ++ int level; ++ bool suspended; ++ bool active; ++}; ++ ++static const struct regmap_config isa1200_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = ISA1200_HCTRLD, ++}; ++ ++static void isa1200_start(struct isa1200 *isa) ++{ ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &isa->input->dev; ++ struct pwm_state state; ++ u8 hctrl0 = 0, hctrl1 = 0; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ int err; ++ ++ if (!isa->active) { ++ err = regulator_bulk_enable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ if (err) { ++ dev_err(dev, "failed to enable supplies (%d)\n", err); ++ return; ++ } ++ ++ err = clk_prepare_enable(isa->clk); ++ if (err) { ++ dev_err(dev, "failed to enable clock (%d)\n", err); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ return; ++ } ++ ++ bitmap_fill(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ usleep_range(200, 300); ++ } ++ ++ regmap_write(isa->map, ISA1200_SCTRL, config->ldo_voltage); ++ ++ if (isa->clk) { ++ hctrl0 = ISA1200_HCTRL0_PWM_GEN_MODE; ++ hctrl1 = ISA1200_HCTRL1_EXT_CLOCK; ++ } ++ ++ if (isa->pwm) { ++ hctrl0 = ISA1200_HCTRL0_PWM_INPUT_MODE; ++ hctrl1 = 0; ++ } ++ ++ hctrl0 |= __ffs(config->clkdiv / ISA1200_HCTRL0_CLKDIV_128); ++ hctrl1 |= ISA1200_HCTRL1_DAC_INVERT; ++ hctrl1 |= ISA1200_HCTRL1_MODE(config->mode); ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ regmap_write(isa->map, ISA1200_HCTRL1, hctrl1); ++ ++ /* Make sure to de-assert software reset */ ++ regmap_write(isa->map, ISA1200_HCTRL2, 0x00); ++ ++ /* PLL divisor */ ++ regmap_write(isa->map, ISA1200_HCTRL3, ++ ISA1200_HCTRL3_PLLDIV(config->plldiv) | ++ ISA1200_HCTRL3_DEFAULT); ++ ++ /* Frequency */ ++ regmap_write(isa->map, ISA1200_HCTRL4, config->freq); ++ /* Duty cycle */ ++ regmap_write(isa->map, ISA1200_HCTRL5, config->period >> 1); ++ /* Period */ ++ regmap_write(isa->map, ISA1200_HCTRL6, config->period); ++ ++ hctrl0 |= ISA1200_HCTRL0_HAP_ENABLE; ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ ++ if (isa->clk) ++ regmap_write(isa->map, ISA1200_HCTRL5, config->duty); ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = config->duty; ++ state.enabled = true; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ isa->active = true; ++} ++ ++static void isa1200_stop(struct isa1200 *isa) ++{ ++ struct pwm_state state; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ ++ if (!isa->active) ++ return; ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = 0; ++ state.enabled = false; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, 0x00); ++ ++ bitmap_zero(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ clk_disable_unprepare(isa->clk); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ ++ isa->active = false; ++} ++ ++static void isa1200_play_work(struct work_struct *work) ++{ ++ struct isa1200 *isa = container_of(work, struct isa1200, play_work); ++ ++ if (!READ_ONCE(isa->suspended)) { ++ if (isa->level) ++ isa1200_start(isa); ++ else ++ isa1200_stop(isa); ++ } ++} ++ ++static int isa1200_vibrator_play_effect(struct input_dev *input, void *data, ++ struct ff_effect *effect) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ int level; ++ ++ /* ++ * TODO: we currently only support rumble. ++ * The ISA1200 can control two motors and some devices ++ * also have two motors mounted. ++ */ ++ level = effect->u.rumble.strong_magnitude; ++ if (!level) ++ level = effect->u.rumble.weak_magnitude; ++ ++ dev_dbg(&input->dev, "FF effect type %d level %d\n", ++ effect->type, level); ++ ++ if (isa->level != level) { ++ isa->level = level; ++ if (!READ_ONCE(isa->suspended)) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static void isa1200_vibrator_close(struct input_dev *input) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ isa->level = 0; ++} ++ ++static int isa1200_of_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa = i2c_get_clientdata(client); ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &client->dev; ++ struct fwnode_handle *ldo_node; ++ int err; ++ ++ isa->clk = devm_clk_get_optional(dev, NULL); ++ if (IS_ERR(isa->clk)) ++ return dev_err_probe(dev, PTR_ERR(isa->clk), ++ "failed to get clock\n"); ++ ++ isa->pwm = devm_pwm_get(dev, NULL); ++ if (IS_ERR(isa->pwm)) { ++ err = PTR_ERR(isa->pwm); ++ if (err == -ENODEV || err == -EINVAL) ++ isa->pwm = NULL; ++ else ++ return dev_err_probe(dev, err, "getting PWM\n"); ++ } ++ ++ if (!isa->clk && !isa->pwm) ++ return dev_err_probe(dev, -EINVAL, ++ "clock or PWM are required, none were provided\n"); ++ ++ err = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(isa1200_supplies), ++ isa1200_supplies, &isa->supplies); ++ if (err) ++ return dev_err_probe(dev, err, "failed to get supplies\n"); ++ ++ isa->enable_gpios = devm_gpiod_get_array_optional(dev, "control", ++ GPIOD_OUT_LOW); ++ if (IS_ERR(isa->enable_gpios)) ++ return dev_err_probe(dev, PTR_ERR(isa->enable_gpios), ++ "failed to get enable gpios\n"); ++ ++ if (isa->enable_gpios && isa->enable_gpios->ndescs > ISA1200_EN_PINS_MAX) ++ return dev_err_probe(dev, -EINVAL, "too many enable gpios\n"); ++ ++ ldo_node = device_get_named_child_node(dev, "ldo"); ++ if (!ldo_node) ++ return dev_err_probe(dev, -ENODEV, ++ "failed to get embedded LDO node\n"); ++ ++ err = fwnode_property_read_u32(ldo_node, "regulator-min-microvolt", ++ &config->ldo_voltage); ++ fwnode_handle_put(ldo_node); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get ldo voltage\n"); ++ ++ config->ldo_voltage = clamp(config->ldo_voltage, ++ ISA1200_LDO_VOLTAGE_MIN, ++ ISA1200_LDO_VOLTAGE_MAX); ++ ++ config->ldo_voltage /= ISA1200_LDO_VOLTAGE_STEP; ++ if (config->ldo_voltage < ISA1200_LDO_VOLTAGE_3V1) ++ config->ldo_voltage = config->ldo_voltage - ++ ISA1200_LDO_VOLTAGE_2V3 + ++ ISA1200_LDO_VOLTAGE_BASE; ++ else ++ config->ldo_voltage -= ISA1200_LDO_VOLTAGE_3V1; ++ ++ config->mode = 0; /* LRA_MODE */ ++ device_property_read_u32(dev, "imagis,mode", &config->mode); ++ ++ config->clkdiv = ISA1200_HCTRL0_CLKDIV_128; ++ device_property_read_u32(dev, "imagis,clk-div", &config->clkdiv); ++ if (!config->clkdiv) ++ return dev_err_probe(dev, -EINVAL, "clk-div cannot be zero\n"); ++ ++ config->clkdiv = clamp(config->clkdiv, ISA1200_HCTRL0_CLKDIV_128, ++ ISA1200_HCTRL0_CLKDIV_128 << 3); ++ ++ err = device_property_read_u32(dev, "imagis,pll-div", &config->plldiv); ++ if (err || !config->plldiv) ++ config->plldiv = 1; ++ ++ config->period = 0; ++ config->freq = 0; ++ config->duty = 0; ++ ++ if (isa->clk) { ++ err = device_property_read_u32(dev, "imagis,period-ns", ++ &config->period); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get period\n"); ++ ++ /* ++ * TODO: The scale value is arbitrary, but it fits observations ++ * quite well, and the exact conversion method is unknown. ++ * The period property value returned above is the HCTRL6 ++ * register value set by the vendor code, multiplied by 100. ++ */ ++ config->period /= ISA1200_HCTRL6_PERIOD_SCALE; ++ config->duty = config->period >> 1; ++ } ++ ++ if (isa->pwm) { ++ struct pwm_state state; ++ ++ pwm_init_state(isa->pwm, &state); ++ ++ if (!state.period) ++ return dev_err_probe(dev, -EINVAL, ++ "PWM period cannot be zero\n"); ++ ++ config->freq = div64_u64(NANO, state.period * config->clkdiv); ++ config->duty = state.period >> 1; ++ ++ err = pwm_apply_might_sleep(isa->pwm, &state); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to apply initial PWM state\n"); ++ } ++ ++ /* ++ * TODO: If device is using a clock, this property should return the ++ * value written to the HCTRL5 register by downstrem code. It likely ++ * needs to be converted into a meaningful duty cycle value, though ++ * unfortunately the exact conversion mechanism is unknown. If the ++ * device uses PWM, this property will return the correct duty cycle ++ * in nanoseconds. ++ */ ++ device_property_read_u32(dev, "imagis,duty-cycle-ns", &config->duty); ++ ++ return 0; ++} ++ ++static int isa1200_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa; ++ struct device *dev = &client->dev; ++ int err; ++ ++ isa = devm_kzalloc(dev, sizeof(*isa), GFP_KERNEL); ++ if (!isa) ++ return -ENOMEM; ++ ++ isa->input = devm_input_allocate_device(dev); ++ if (!isa->input) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, isa); ++ ++ err = isa1200_of_probe(client); ++ if (err) ++ return err; ++ ++ isa->map = devm_regmap_init_i2c(client, &isa1200_regmap_config); ++ if (IS_ERR(isa->map)) ++ return dev_err_probe(dev, PTR_ERR(isa->map), ++ "failed to initialize register map\n"); ++ ++ INIT_WORK(&isa->play_work, isa1200_play_work); ++ ++ isa->input->name = "isa1200-haptic"; ++ isa->input->id.bustype = BUS_I2C; ++ isa->input->close = isa1200_vibrator_close; ++ ++ isa->active = false; ++ ++ input_set_drvdata(isa->input, isa); ++ ++ /* TODO: this hardware can likely support more than rumble */ ++ input_set_capability(isa->input, EV_FF, FF_RUMBLE); ++ ++ err = input_ff_create_memless(isa->input, NULL, ++ isa1200_vibrator_play_effect); ++ if (err) ++ return dev_err_probe(dev, err, "failed to create FF dev\n"); ++ ++ err = input_register_device(isa->input); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register input dev\n"); ++ ++ return 0; ++} ++ ++static int isa1200_suspend(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, true); ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ } ++ ++ return 0; ++} ++ ++static int isa1200_resume(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, false); ++ if (isa->level) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); ++ ++static const struct of_device_id isa1200_of_match[] = { ++ { .compatible = "imagis,isa1200" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, isa1200_of_match); ++ ++static struct i2c_driver isa1200_i2c_driver = { ++ .driver = { ++ .name = "isa1200", ++ .of_match_table = isa1200_of_match, ++ .pm = pm_sleep_ptr(&isa1200_pm_ops), ++ }, ++ .probe = isa1200_probe, ++}; ++module_i2c_driver(isa1200_i2c_driver); ++ ++MODULE_AUTHOR("Linus Walleij "); ++MODULE_AUTHOR("Svyatoslav Ryhel "); ++MODULE_DESCRIPTION("Imagis ISA1200 haptic feedback unit"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/mouse/elan_i2c.h b/drivers/input/mouse/elan_i2c.h +--- a/drivers/input/mouse/elan_i2c.h ++++ b/drivers/input/mouse/elan_i2c.h +@@ -102,7 +102,8 @@ struct elan_transport_ops { + int (*prepare_fw_update)(struct i2c_client *client, u16 ic_type, + u8 iap_version, u16 fw_page_size); + int (*write_fw_block)(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx); ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx); + int (*finish_fw_update)(struct i2c_client *client, + struct completion *reset_done); + +diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c +--- a/drivers/input/mouse/elan_i2c_core.c ++++ b/drivers/input/mouse/elan_i2c_core.c +@@ -16,27 +16,27 @@ + */ + + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +-#include +-#include +-#include +-#include +-#include +-#include +-#include + #include +-#include ++#include ++#include + #include + #include + #include + #include ++#include ++#include ++#include ++#include + #include + + #include "elan_i2c.h" +@@ -86,6 +86,7 @@ struct elan_tp_data { + u16 ic_type; + u16 fw_validpage_count; + u16 fw_page_size; ++ u16 fw_page_delay; + u32 fw_signature_address; + + u8 min_baseline; +@@ -127,8 +128,11 @@ static u32 elan_i2c_lookup_quirks(u16 ic_type, u16 product_id) + } + + static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, +- u32 *signature_address, u16 *page_size) ++ u32 *signature_address, u16 *page_size, ++ u16 *page_delay) + { ++ *page_delay = 30; ++ + switch (ic_type) { + case 0x00: + case 0x06: +@@ -164,6 +168,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + break; + case 0x19: + *validpage_count = 2032; ++ *page_delay = 10; + break; + default: + /* unknown ic type clear value */ +@@ -179,6 +184,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + if ((ic_type == 0x14 || ic_type == 0x15) && iap_version >= 2) { + *validpage_count /= 8; + *page_size = ETP_FW_PAGE_SIZE_512; ++ *page_delay = 50; + } else if (ic_type >= 0x0D && iap_version >= 1) { + *validpage_count /= 2; + *page_size = ETP_FW_PAGE_SIZE_128; +@@ -368,7 +374,8 @@ static int elan_query_device_info(struct elan_tp_data *data) + error = elan_get_fwinfo(data->ic_type, data->iap_version, + &data->fw_validpage_count, + &data->fw_signature_address, +- &data->fw_page_size); ++ &data->fw_page_size, ++ &data->fw_page_delay); + if (error) + dev_warn(&data->client->dev, + "unexpected iap version %#04x (ic type: %#04x), firmware update will not work\n", +@@ -479,14 +486,16 @@ static int elan_query_device_parameters(struct elan_tp_data *data) + ********************************************************** + */ + static int elan_write_fw_block(struct elan_tp_data *data, u16 page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 page_delay, const u8 *page, u16 checksum, ++ int idx) + { + int retry = ETP_RETRY_COUNT; + int error; + + do { + error = data->ops->write_fw_block(data->client, page_size, +- page, checksum, idx); ++ page_delay, page, checksum, ++ idx); + if (!error) + return 0; + +@@ -525,7 +534,8 @@ static int __elan_update_firmware(struct elan_tp_data *data, + checksum += ((page[j + 1] << 8) | page[j]); + + error = elan_write_fw_block(data, data->fw_page_size, +- page, checksum, i); ++ data->fw_page_delay, page, checksum, ++ i); + if (error) { + dev_err(dev, "write page %d fail: %d\n", i, error); + return error; +diff --git a/drivers/input/mouse/elan_i2c_i2c.c b/drivers/input/mouse/elan_i2c_i2c.c +--- a/drivers/input/mouse/elan_i2c_i2c.c ++++ b/drivers/input/mouse/elan_i2c_i2c.c +@@ -625,7 +625,8 @@ static int elan_i2c_prepare_fw_update(struct i2c_client *client, u16 ic_type, + } + + static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + u8 val[3]; +@@ -650,7 +651,7 @@ static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, + } + + /* Wait for F/W to update one page ROM data. */ +- msleep(fw_page_size == ETP_FW_PAGE_SIZE_512 ? 50 : 35); ++ msleep(fw_page_delay); + + error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val); + if (error) { +diff --git a/drivers/input/mouse/elan_i2c_smbus.c b/drivers/input/mouse/elan_i2c_smbus.c +--- a/drivers/input/mouse/elan_i2c_smbus.c ++++ b/drivers/input/mouse/elan_i2c_smbus.c +@@ -416,7 +416,8 @@ static int elan_smbus_prepare_fw_update(struct i2c_client *client, u16 ic_type, + + + static int elan_smbus_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + int error; +diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c +--- a/drivers/input/mouse/focaltech.c ++++ b/drivers/input/mouse/focaltech.c +@@ -78,8 +78,8 @@ struct focaltech_finger_state { + * Absolute position (from the bottom left corner) of the + * finger. + */ +- unsigned int x; +- unsigned int y; ++ int x; ++ int y; + }; + + /* +@@ -108,7 +108,7 @@ struct focaltech_hw_state { + }; + + struct focaltech_data { +- unsigned int x_max, y_max; ++ int x_max, y_max; + struct focaltech_hw_state state; + }; + +@@ -126,17 +126,16 @@ static void focaltech_report_state(struct psmouse *psmouse) + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); + if (active) { +- unsigned int clamped_x, clamped_y; + /* + * The touchpad might report invalid data, so we clamp + * the resulting values so that we do not confuse +- * userspace. ++ * userspace or accumulate coordinate wind-up. + */ +- clamped_x = clamp(finger->x, 0U, priv->x_max); +- clamped_y = clamp(finger->y, 0U, priv->y_max); +- input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); ++ finger->x = clamp(finger->x, 0, priv->x_max); ++ finger->y = clamp(finger->y, 0, priv->y_max); ++ input_report_abs(dev, ABS_MT_POSITION_X, finger->x); + input_report_abs(dev, ABS_MT_POSITION_Y, +- priv->y_max - clamped_y); ++ priv->y_max - finger->y); + input_report_abs(dev, ABS_TOOL_WIDTH, state->width); + } + } +diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c +--- a/drivers/input/mouse/psmouse-base.c ++++ b/drivers/input/mouse/psmouse-base.c +@@ -256,9 +256,8 @@ static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_sta + */ + void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + { +- serio_pause_rx(psmouse->ps2dev.serio); ++ guard(serio_pause_rx)(psmouse->ps2dev.serio); + __psmouse_set_state(psmouse, new_state); +- serio_continue_rx(psmouse->ps2dev.serio); + } + + /* +@@ -267,7 +266,15 @@ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + */ + static int psmouse_handle_byte(struct psmouse *psmouse) + { +- psmouse_ret_t rc = psmouse->protocol_handler(psmouse); ++ psmouse_ret_t rc; ++ ++ /* protocol_handler is NULL when device is being disconnected */ ++ if (unlikely(!psmouse->protocol_handler)) { ++ psmouse->pktcnt = 0; ++ return 0; ++ } ++ ++ rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: +@@ -484,13 +491,16 @@ static int psmouse_poll(struct psmouse *psmouse) + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)); + } + +-static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) ++static bool psmouse_check_pnp_id(const char *p, const char * const ids[]) + { +- int i; ++ const char * const *id; ++ size_t len; + +- for (i = 0; ids[i]; i++) +- if (!strcasecmp(id, ids[i])) ++ for (id = ids; *id; id++) { ++ len = strlen(*id); ++ if (!strncasecmp(p, *id, len) && (p[len] == ' ' || p[len] == '\0')) + return true; ++ } + + return false; + } +@@ -501,28 +511,26 @@ static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) + bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]) + { + struct serio *serio = psmouse->ps2dev.serio; +- char *p, *fw_id_copy, *save_ptr; +- bool found = false; ++ const char *p = serio->firmware_id; + +- if (strncmp(serio->firmware_id, "PNP: ", 5)) ++ if (!strstarts(p, "PNP: ")) + return false; + +- fw_id_copy = kstrndup(&serio->firmware_id[5], +- sizeof(serio->firmware_id) - 5, +- GFP_KERNEL); +- if (!fw_id_copy) +- return false; ++ p += 5; ++ while (*p) { ++ p = skip_spaces(p); ++ if (!*p) ++ break; + +- save_ptr = fw_id_copy; +- while ((p = strsep(&fw_id_copy, " ")) != NULL) { +- if (psmouse_check_pnp_id(p, ids)) { +- found = true; ++ if (psmouse_check_pnp_id(p, ids)) ++ return true; ++ ++ p = strchr(p, ' '); ++ if (!p) + break; +- } + } + +- kfree(save_ptr); +- return found; ++ return false; + } + + /* +@@ -1311,10 +1319,10 @@ static void psmouse_resync(struct work_struct *work) + bool failed = false, enabled = false; + int i; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (psmouse->state != PSMOUSE_RESYNCING) +- goto out; ++ return; + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1392,8 +1400,6 @@ static void psmouse_resync(struct work_struct *work) + + if (parent) + psmouse_activate(parent); +- out: +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1404,7 +1410,7 @@ static void psmouse_cleanup(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1440,8 +1446,6 @@ static void psmouse_cleanup(struct serio *serio) + + psmouse_activate(parent); + } +- +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1452,20 +1456,20 @@ static void psmouse_disconnect(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ disable_work_sync(&psmouse->resync_work); ++ ++ guard(mutex)(&psmouse_mutex); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + +- /* make sure we don't have a resync in progress */ +- mutex_unlock(&psmouse_mutex); +- disable_work_sync(&psmouse->resync_work); +- mutex_lock(&psmouse_mutex); +- + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + +@@ -1484,8 +1488,6 @@ static void psmouse_disconnect(struct serio *serio) + + if (parent) + psmouse_activate(parent); +- +- mutex_unlock(&psmouse_mutex); + } + + static int psmouse_switch_protocol(struct psmouse *psmouse, +@@ -1654,14 +1656,12 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + enum psmouse_type type; + int rc = -1; + +- mutex_lock(&psmouse_mutex); ++ lockdep_assert_held(&psmouse_mutex); + + if (fast_reconnect) { + reconnect_handler = psmouse->fast_reconnect; +- if (!reconnect_handler) { +- rc = -ENOENT; +- goto out_unlock; +- } ++ if (!reconnect_handler) ++ return -ENOENT; + } else { + reconnect_handler = psmouse->reconnect; + } +@@ -1713,18 +1713,20 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + if (parent) + psmouse_activate(parent); + +-out_unlock: +- mutex_unlock(&psmouse_mutex); + return rc; + } + + static int psmouse_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, false); + } + + static int psmouse_fast_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, true); + } + +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -164,6 +164,7 @@ static const char * const topbuttonpad_pnp_ids[] = { + #ifdef CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS + static const char * const smbus_pnp_ids[] = { + /* all of the topbuttonpad_pnp_ids are valid, we just add some extras */ ++ "DLL0597", /* Dell Inspiron 3521 */ + "DLL060d", /* Dell Precision M3800 */ + "LEN0048", /* X1 Carbon 3 */ + "LEN0046", /* X250 */ +diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c +--- a/drivers/input/mouse/synaptics_i2c.c ++++ b/drivers/input/mouse/synaptics_i2c.c +@@ -261,7 +261,7 @@ static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8 val) + if (error) + return error; + +- return error; ++ return 0; + } + + static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg) +diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c +--- a/drivers/input/rmi4/rmi_f12.c ++++ b/drivers/input/rmi4/rmi_f12.c +@@ -155,6 +155,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + offset += 4; + } + ++ /* When platform data are provided, we're done */ ++ if (sensor->x_mm && sensor->y_mm) ++ return 0; ++ + /* + * Use the Query DPM feature when the resolution query register + * exists. +@@ -171,8 +175,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + } + dpm_resolution = buf[0]; + +- sensor->x_mm = sensor->max_x / dpm_resolution; +- sensor->y_mm = sensor->max_y / dpm_resolution; ++ if (!sensor->x_mm) ++ sensor->x_mm = sensor->max_x / dpm_resolution; ++ if (!sensor->y_mm) ++ sensor->y_mm = sensor->max_y / dpm_resolution; + } else { + if (rmi_register_desc_has_subpacket(item, 3)) { + rx_receivers = buf[offset]; +@@ -184,8 +190,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + if (rmi_register_desc_has_subpacket(item, 4)) + offset += 1; + +- sensor->x_mm = (pitch_x * rx_receivers) >> 12; +- sensor->y_mm = (pitch_y * tx_receivers) >> 12; ++ if (!sensor->x_mm) ++ sensor->x_mm = (pitch_x * rx_receivers) >> 12; ++ if (!sensor->y_mm) ++ sensor->y_mm = (pitch_y * tx_receivers) >> 12; + } + + rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: x_mm: %d y_mm: %d\n", __func__, +diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c +--- a/drivers/input/rmi4/rmi_smbus.c ++++ b/drivers/input/rmi4/rmi_smbus.c +@@ -177,13 +177,8 @@ static int smb_block_read(struct rmi_transport_dev *xport, + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + struct i2c_client *client = rmi_smb->client; +- int retval; + +- retval = i2c_smbus_read_block_data(client, commandcode, buf); +- if (retval < 0) +- return retval; +- +- return retval; ++ return i2c_smbus_read_block_data(client, commandcode, buf); + } + + static int rmi_smb_read_block(struct rmi_transport_dev *xport, u16 rmiaddr, +diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig +--- a/drivers/input/serio/Kconfig ++++ b/drivers/input/serio/Kconfig +@@ -106,6 +106,33 @@ config SERIO_GSCPS2 + To compile this driver as a module, choose M here: the + module will be called gscps2. + ++config SERIO_GSCPS2_RDI_KEYCODES ++ bool "Use PrecisionBook keyboard scancodes" ++ depends on SERIO_GSCPS2 ++ default n ++ help ++ If you have an RDI PrecisionBook, say Y here if you want to use its ++ built-in keyboard (as opposed to an external keyboard). ++ ++ The PrecisionBook has five keys that conflict with those used by most ++ AT and PS/2 keyboards. These are as follows: ++ ++ PrecisionBook Standard AT or PS/2 ++ ++ F1 F12 ++ Left Ctrl Left Alt ++ Caps Lock Left Ctrl ++ Right Ctrl Caps Lock ++ Left 102nd key (the key to the right of Left Shift) ++ ++ If you say N here, and use the PrecisionBook keyboard, then each key ++ in the left-hand column will be interpreted as the corresponding key ++ in the right-hand column. ++ ++ If you say Y here, and use an external keyboard, then each key in the ++ right-hand column will be interpreted as the key shown in the ++ left-hand column. ++ + config HP_SDC + tristate "HP System Device Controller i8042 Support" + depends on (GSC || HP300) && SERIO +diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c +--- a/drivers/input/serio/gscps2.c ++++ b/drivers/input/serio/gscps2.c +@@ -22,18 +22,15 @@ + * was usable/enabled ?) + */ + +-#include +-#include +-#include +-#include ++#include + #include + #include +-#include +-#include ++#include + #include ++#include ++#include + + #include +-#include + #include + + MODULE_AUTHOR("Laurent Canet , Thibaut Varene , Helge Deller "); +@@ -80,6 +77,116 @@ MODULE_LICENSE("GPL"); + #define GSC_ID_KEYBOARD 0 /* device ID values */ + #define GSC_ID_MOUSE 1 + ++#ifndef CONFIG_SERIO_GSCPS2_RDI_KEYCODES ++# define CONFLICT(x, y) x ++#else ++# define CONFLICT(x, y) y ++#endif ++ ++/* ++ * Sadly RDI (Tadpole) decided to ship a different keyboard layout ++ * than HP for their PS/2 laptop keyboard which leads to conflicting ++ * keycodes between a normal HP PS/2 keyboard and a RDI PrecisionBook. ++ * HP: RDI: ++ */ ++#define C_07 CONFLICT(KEY_F12, KEY_F1) ++#define C_11 CONFLICT(KEY_LEFTALT, KEY_LEFTCTRL) ++#define C_14 CONFLICT(KEY_LEFTCTRL, KEY_CAPSLOCK) ++#define C_58 CONFLICT(KEY_CAPSLOCK, KEY_RIGHTCTRL) ++#define C_61 CONFLICT(KEY_102ND, KEY_LEFT) ++ ++/* ++ * Special keycode value recognized by atkbd (ATKBD_KEY_NULL) to silently ++ * discard scancodes without generating input events or "unknown key" warnings. ++ */ ++#define KEY_NULL 255 ++ ++#define KEYMAP_ENTRY(scancode, keycode) (((scancode) << 16) | (keycode)) ++ ++static const u32 gscps2_keymap[] = { ++ KEYMAP_ENTRY(0x01, KEY_F9), KEYMAP_ENTRY(0x03, KEY_F5), ++ KEYMAP_ENTRY(0x04, KEY_F3), KEYMAP_ENTRY(0x05, KEY_F1), ++ KEYMAP_ENTRY(0x06, KEY_F2), KEYMAP_ENTRY(0x07, C_07), ++ KEYMAP_ENTRY(0x08, KEY_ESC), KEYMAP_ENTRY(0x09, KEY_F10), ++ KEYMAP_ENTRY(0x0a, KEY_F8), KEYMAP_ENTRY(0x0b, KEY_F6), ++ KEYMAP_ENTRY(0x0c, KEY_F4), KEYMAP_ENTRY(0x0d, KEY_TAB), ++ KEYMAP_ENTRY(0x0e, KEY_GRAVE), KEYMAP_ENTRY(0x0f, KEY_F2), ++ KEYMAP_ENTRY(0x11, C_11), KEYMAP_ENTRY(0x12, KEY_LEFTSHIFT), ++ KEYMAP_ENTRY(0x14, C_14), KEYMAP_ENTRY(0x15, KEY_Q), ++ KEYMAP_ENTRY(0x16, KEY_1), KEYMAP_ENTRY(0x17, KEY_F3), ++ KEYMAP_ENTRY(0x19, KEY_LEFTALT), KEYMAP_ENTRY(0x1a, KEY_Z), ++ KEYMAP_ENTRY(0x1b, KEY_S), KEYMAP_ENTRY(0x1c, KEY_A), ++ KEYMAP_ENTRY(0x1d, KEY_W), KEYMAP_ENTRY(0x1e, KEY_2), ++ KEYMAP_ENTRY(0x1f, KEY_F4), KEYMAP_ENTRY(0x21, KEY_C), ++ KEYMAP_ENTRY(0x22, KEY_X), KEYMAP_ENTRY(0x23, KEY_D), ++ KEYMAP_ENTRY(0x24, KEY_E), KEYMAP_ENTRY(0x25, KEY_4), ++ KEYMAP_ENTRY(0x26, KEY_3), KEYMAP_ENTRY(0x27, KEY_F5), ++ KEYMAP_ENTRY(0x29, KEY_SPACE), KEYMAP_ENTRY(0x2a, KEY_V), ++ KEYMAP_ENTRY(0x2b, KEY_F), KEYMAP_ENTRY(0x2c, KEY_T), ++ KEYMAP_ENTRY(0x2d, KEY_R), KEYMAP_ENTRY(0x2e, KEY_5), ++ KEYMAP_ENTRY(0x2f, KEY_F6), KEYMAP_ENTRY(0x31, KEY_N), ++ KEYMAP_ENTRY(0x32, KEY_B), KEYMAP_ENTRY(0x33, KEY_H), ++ KEYMAP_ENTRY(0x34, KEY_G), KEYMAP_ENTRY(0x35, KEY_Y), ++ KEYMAP_ENTRY(0x36, KEY_6), KEYMAP_ENTRY(0x37, KEY_F7), ++ KEYMAP_ENTRY(0x39, KEY_RIGHTALT), KEYMAP_ENTRY(0x3a, KEY_M), ++ KEYMAP_ENTRY(0x3b, KEY_J), KEYMAP_ENTRY(0x3c, KEY_U), ++ KEYMAP_ENTRY(0x3d, KEY_7), KEYMAP_ENTRY(0x3e, KEY_8), ++ KEYMAP_ENTRY(0x3f, KEY_F8), KEYMAP_ENTRY(0x41, KEY_COMMA), ++ KEYMAP_ENTRY(0x42, KEY_K), KEYMAP_ENTRY(0x43, KEY_I), ++ KEYMAP_ENTRY(0x44, KEY_O), KEYMAP_ENTRY(0x45, KEY_0), ++ KEYMAP_ENTRY(0x46, KEY_9), KEYMAP_ENTRY(0x47, KEY_F9), ++ KEYMAP_ENTRY(0x49, KEY_DOT), KEYMAP_ENTRY(0x4a, KEY_SLASH), ++ KEYMAP_ENTRY(0x4b, KEY_L), KEYMAP_ENTRY(0x4c, KEY_SEMICOLON), ++ KEYMAP_ENTRY(0x4d, KEY_P), KEYMAP_ENTRY(0x4e, KEY_MINUS), ++ KEYMAP_ENTRY(0x4f, KEY_F10), KEYMAP_ENTRY(0x52, KEY_APOSTROPHE), ++ KEYMAP_ENTRY(0x54, KEY_LEFTBRACE), KEYMAP_ENTRY(0x55, KEY_EQUAL), ++ KEYMAP_ENTRY(0x56, KEY_F11), KEYMAP_ENTRY(0x57, KEY_SYSRQ), ++ KEYMAP_ENTRY(0x58, C_58), KEYMAP_ENTRY(0x59, KEY_RIGHTSHIFT), ++ KEYMAP_ENTRY(0x5a, KEY_ENTER), KEYMAP_ENTRY(0x5b, KEY_RIGHTBRACE), ++ KEYMAP_ENTRY(0x5c, KEY_BACKSLASH), KEYMAP_ENTRY(0x5d, KEY_BACKSLASH), ++ KEYMAP_ENTRY(0x5e, KEY_F12), KEYMAP_ENTRY(0x5f, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x60, KEY_DOWN), KEYMAP_ENTRY(0x61, C_61), ++ KEYMAP_ENTRY(0x62, KEY_PAUSE), KEYMAP_ENTRY(0x63, KEY_UP), ++ KEYMAP_ENTRY(0x64, KEY_DELETE), KEYMAP_ENTRY(0x65, KEY_END), ++ KEYMAP_ENTRY(0x66, KEY_BACKSPACE), KEYMAP_ENTRY(0x67, KEY_INSERT), ++ KEYMAP_ENTRY(0x69, KEY_KP1), KEYMAP_ENTRY(0x6a, KEY_RIGHT), ++ KEYMAP_ENTRY(0x6b, KEY_KP4), KEYMAP_ENTRY(0x6c, KEY_KP7), ++ KEYMAP_ENTRY(0x6d, KEY_PAGEDOWN), KEYMAP_ENTRY(0x6e, KEY_HOME), ++ KEYMAP_ENTRY(0x6f, KEY_PAGEUP), KEYMAP_ENTRY(0x70, KEY_KP0), ++ KEYMAP_ENTRY(0x71, KEY_KPDOT), KEYMAP_ENTRY(0x72, KEY_KP2), ++ KEYMAP_ENTRY(0x73, KEY_KP5), KEYMAP_ENTRY(0x74, KEY_KP6), ++ KEYMAP_ENTRY(0x75, KEY_KP8), KEYMAP_ENTRY(0x76, KEY_ESC), ++ KEYMAP_ENTRY(0x77, KEY_NUMLOCK), KEYMAP_ENTRY(0x78, KEY_F11), ++ KEYMAP_ENTRY(0x79, KEY_KPPLUS), KEYMAP_ENTRY(0x7a, KEY_KP3), ++ KEYMAP_ENTRY(0x7b, KEY_KPMINUS), KEYMAP_ENTRY(0x7c, KEY_KPASTERISK), ++ KEYMAP_ENTRY(0x7d, KEY_KP9), KEYMAP_ENTRY(0x7e, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x7f, KEY_102ND), KEYMAP_ENTRY(0x91, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x92, KEY_NULL), KEYMAP_ENTRY(0x94, KEY_RIGHTCTRL), ++ KEYMAP_ENTRY(0x9d, KEY_CAPSLOCK), KEYMAP_ENTRY(0x9f, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0xa7, KEY_RIGHTMETA), KEYMAP_ENTRY(0xaf, KEY_COMPOSE), ++ KEYMAP_ENTRY(0xca, KEY_KPSLASH), KEYMAP_ENTRY(0xda, KEY_KPENTER), ++ KEYMAP_ENTRY(0xe9, KEY_END), KEYMAP_ENTRY(0xeb, KEY_LEFT), ++ KEYMAP_ENTRY(0xec, KEY_HOME), KEYMAP_ENTRY(0xf0, KEY_INSERT), ++ KEYMAP_ENTRY(0xf1, KEY_DELETE), KEYMAP_ENTRY(0xf2, KEY_DOWN), ++ KEYMAP_ENTRY(0xf4, KEY_RIGHT), KEYMAP_ENTRY(0xf5, KEY_UP), ++ KEYMAP_ENTRY(0xf7, KEY_PAUSE), KEYMAP_ENTRY(0xfa, KEY_PAGEDOWN), ++ KEYMAP_ENTRY(0xfc, KEY_SYSRQ), KEYMAP_ENTRY(0xfd, KEY_PAGEUP), ++ ++ /* Escaped keycodes */ ++ KEYMAP_ENTRY(0x103, KEY_F7), KEYMAP_ENTRY(0x10b, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0x10c, KEY_RIGHTMETA), KEYMAP_ENTRY(0x111, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x114, KEY_RIGHTCTRL), ++}; ++ ++static const struct property_entry gscps2_props[] = { ++ PROPERTY_ENTRY_U32_ARRAY("linux,keymap", gscps2_keymap), ++ { } ++}; ++ ++static const struct software_node gscps2_keyboard_node = { ++ .name = "gscps2-keyboard", ++ .properties = gscps2_props, ++}; + + static irqreturn_t gscps2_interrupt(int irq, void *dev); + +@@ -398,6 +505,17 @@ static int __init gscps2_probe(struct parisc_device *dev) + goto fail; + #endif + ++ if (ps2port->id == GSC_ID_KEYBOARD) { ++ ret = device_add_software_node(&serio->dev, ++ &gscps2_keyboard_node); ++ if (ret) { ++ dev_err(&dev->dev, ++ "failed to add software node for keyboard: %d\n", ++ ret); ++ goto fail; ++ } ++ } ++ + pr_info("serio: %s port at 0x%08lx irq %d @ %s\n", + ps2port->port->name, + hpa, +@@ -411,11 +529,16 @@ static int __init gscps2_probe(struct parisc_device *dev) + return 0; + + fail: ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&serio->dev); ++ + free_irq(dev->irq, ps2port); + + fail_miserably: + iounmap(ps2port->addr); ++#if 0 + release_mem_region(dev->hpa.start, GSC_STATUS + 4); ++#endif + + fail_nomem: + kfree(ps2port); +@@ -434,6 +557,9 @@ static void __exit gscps2_remove(struct parisc_device *dev) + { + struct gscps2port *ps2port = dev_get_drvdata(&dev->dev); + ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&ps2port->port->dev); ++ + serio_unregister_port(ps2port->port); + free_irq(dev->irq, ps2port); + gscps2_flush(ps2port); +@@ -465,16 +591,25 @@ static struct parisc_driver parisc_ps2_driver __refdata = { + + static int __init gscps2_init(void) + { +- register_parisc_driver(&parisc_ps2_driver); +- return 0; ++ int error; ++ ++ error = software_node_register(&gscps2_keyboard_node); ++ if (error) ++ return error; ++ ++ error = register_parisc_driver(&parisc_ps2_driver); ++ if (error) ++ software_node_unregister(&gscps2_keyboard_node); ++ ++ return error; + } + + static void __exit gscps2_exit(void) + { + unregister_parisc_driver(&parisc_ps2_driver); ++ software_node_unregister(&gscps2_keyboard_node); + } + + + module_init(gscps2_init); + module_exit(gscps2_exit); +- +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -3,6 +3,7 @@ + #define _I8042_ACPIPNPIO_H + + #include ++#include + + #ifdef CONFIG_X86 + #include +@@ -1486,17 +1487,21 @@ static char i8042_pnp_aux_name[32]; + + static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size) + { +- strscpy(dst, "PNP:", dst_size); ++ struct seq_buf sb; ++ ++ seq_buf_init(&sb, dst, dst_size); ++ seq_buf_printf(&sb, "PNP:"); + + while (id) { +- strlcat(dst, " ", dst_size); +- strlcat(dst, id->id, dst_size); ++ seq_buf_printf(&sb, " %s", id->id); + id = id->next; + } + } + + static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1506,11 +1511,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev,0)) + i8042_pnp_kbd_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); +- strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); +- } ++ scnprintf(i8042_pnp_kbd_name, sizeof(i8042_pnp_kbd_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id, + sizeof(i8042_kbd_firmware_id)); + i8042_kbd_fwnode = dev_fwnode(&dev->dev); +@@ -1524,6 +1526,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + + static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1533,11 +1537,8 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev, 0)) + i8042_pnp_aux_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); +- strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); +- } ++ scnprintf(i8042_pnp_aux_name, sizeof(i8042_pnp_aux_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id, + sizeof(i8042_aux_firmware_id)); + +diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c +--- a/drivers/input/touchscreen/elo.c ++++ b/drivers/input/touchscreen/elo.c +@@ -257,7 +257,7 @@ static int elo_command_10(struct elo *elo, unsigned char *packet) + + static int elo_setup_10(struct elo *elo) + { +- static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; ++ static const char * const elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; + struct input_dev *dev = elo->dev; + unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD }; + +@@ -273,7 +273,7 @@ static int elo_setup_10(struct elo *elo) + + dev_info(&elo->serio->dev, + "%sTouch touchscreen, fw: %02x.%02x, features: 0x%02x, controller: 0x%02x\n", +- elo_types[(packet[1] -'0') & 0x03], ++ elo_types[(packet[1] - '0') & 0x03], + packet[5], packet[4], packet[3], packet[7]); + + return 0; +diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c +--- a/drivers/input/touchscreen/inexio.c ++++ b/drivers/input/touchscreen/inexio.c +@@ -81,7 +81,9 @@ static irqreturn_t inexio_interrupt(struct serio *serio, + if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0]) + inexio_process_data(pinexio); + else +- printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]); ++ dev_dbg(&serio->dev, ++ "unknown/unsynchronized data from device, byte %x\n", ++ pinexio->data[0]); + + return IRQ_HANDLED; + } +diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c +--- a/drivers/input/touchscreen/iqs5xx.c ++++ b/drivers/input/touchscreen/iqs5xx.c +@@ -785,7 +785,8 @@ static int iqs5xx_fw_file_parse(struct i2c_client *client, + switch (rec_type) { + case IQS5XX_REC_TYPE_DATA: + if (rec_addr < IQS5XX_CHKSM || +- rec_addr > IQS5XX_PMAP_END) { ++ rec_addr > IQS5XX_PMAP_END || ++ rec_len > IQS5XX_PMAP_END + 1 - rec_addr) { + dev_err(&client->dev, + "Invalid address at record %u\n", + rec_num); +diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c +--- a/drivers/input/touchscreen/mms114.c ++++ b/drivers/input/touchscreen/mms114.c +@@ -4,6 +4,8 @@ + // Copyright (c) 2012 Samsung Electronics Co., Ltd. + // Author: Joonyoung Shim + ++#include ++#include + #include + #include + #include +@@ -52,21 +54,13 @@ + #define MMS114_TYPE_TOUCHSCREEN 1 + #define MMS114_TYPE_TOUCHKEY 2 + +-enum mms_type { +- TYPE_MMS114 = 114, +- TYPE_MMS134S = 134, +- TYPE_MMS136 = 136, +- TYPE_MMS152 = 152, +- TYPE_MMS345L = 345, +-}; +- + struct mms114_data { ++ const struct mms_chip *chip; + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *core_reg; + struct regulator *io_reg; + struct touchscreen_properties props; +- enum mms_type type; + unsigned int contact_threshold; + unsigned int moving_threshold; + +@@ -77,9 +71,23 @@ struct mms114_data { + u8 cache_mode_control; + }; + ++struct mms_chip { ++ const char *name; ++ int event_size; ++ bool has_config_regs; ++ int (*get_version)(struct mms114_data *data); ++}; ++ ++#define MMS114_FLAGS_ID_MASK GENMASK(3, 0) ++#define MMS114_FLAGS_TYPE_MASK GENMASK(6, 5) ++#define MMS114_FLAGS_PRESSED_MASK BIT(7) ++ ++#define MMS114_XY_HI_X_MASK GENMASK(3, 0) ++#define MMS114_XY_HI_Y_MASK GENMASK(7, 4) ++ + struct mms114_touch { +- u8 id:4, reserved_bit4:1, type:2, pressed:1; +- u8 x_hi:4, y_hi:4; ++ u8 flags; ++ u8 xy_hi; + u8 x_lo; + u8 y_lo; + u8 width; +@@ -87,16 +95,16 @@ struct mms114_touch { + u8 reserved[2]; + } __packed; + +-static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, +- unsigned int len, u8 *val) ++static int __mms114_read_reg(struct mms114_data *data, u8 reg, ++ unsigned int len, void *val) + { + struct i2c_client *client = data->client; + struct i2c_msg xfer[2]; +- u8 buf = reg & 0xff; ++ u8 buf = reg; + int error; + +- if (reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL) +- BUG(); ++ if (WARN_ON(reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL)) ++ return -EINVAL; + + /* Write register */ + xfer[0].addr = client->addr; +@@ -116,12 +124,12 @@ static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c transfer failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + return 0; + } + +-static int mms114_read_reg(struct mms114_data *data, unsigned int reg) ++static int mms114_read_reg(struct mms114_data *data, u8 reg) + { + u8 val; + int error; +@@ -133,15 +141,14 @@ static int mms114_read_reg(struct mms114_data *data, unsigned int reg) + return error < 0 ? error : val; + } + +-static int mms114_write_reg(struct mms114_data *data, unsigned int reg, +- unsigned int val) ++static int mms114_write_reg(struct mms114_data *data, u8 reg, u8 val) + { + struct i2c_client *client = data->client; + u8 buf[2]; + int error; + +- buf[0] = reg & 0xff; +- buf[1] = val & 0xff; ++ buf[0] = reg; ++ buf[1] = val; + + error = i2c_master_send(client, buf, 2); + if (error != 2) { +@@ -149,7 +156,7 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c send failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + if (reg == MMS114_MODE_CONTROL) + data->cache_mode_control = val; +@@ -157,32 +164,119 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + return 0; + } + ++static int mms114_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[6]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", ++ buf[0], buf[1], buf[3]); ++ return 0; ++} ++ ++static int mms152_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int group; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ group = i2c_smbus_read_byte_data(data->client, MMS152_COMPAT_GROUP); ++ if (group < 0) ++ return group; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", ++ buf[0], buf[1], buf[2], group); ++ return 0; ++} ++ ++static int mms345l_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", ++ buf[0], buf[1], buf[2]); ++ return 0; ++} ++ ++static const struct mms_chip mms114_descriptor = { ++ .name = "MMS114", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms134s_descriptor = { ++ .name = "MMS134S", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms136_descriptor = { ++ .name = "MMS136", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms152_descriptor = { ++ .name = "MMS152", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms152_get_version, ++}; ++ ++static const struct mms_chip mms345l_descriptor = { ++ .name = "MMS345L", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms345l_get_version, ++}; ++ + static void mms114_process_mt(struct mms114_data *data, struct mms114_touch *touch) + { + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; +- unsigned int id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + unsigned int x; + unsigned int y; + +- if (touch->id == 0 || touch->id > MMS114_MAX_TOUCH) { +- dev_err(&client->dev, "Wrong touch id (%d)\n", touch->id); ++ if (id == 0 || id > MMS114_MAX_TOUCH) { ++ dev_err(&client->dev, "Wrong touch id (%d)\n", id); + return; + } + +- id = touch->id - 1; +- x = touch->x_lo | touch->x_hi << 8; +- y = touch->y_lo | touch->y_hi << 8; ++ id--; ++ x = touch->x_lo | FIELD_GET(MMS114_XY_HI_X_MASK, touch->xy_hi) << 8; ++ y = touch->y_lo | FIELD_GET(MMS114_XY_HI_Y_MASK, touch->xy_hi) << 8; + + dev_dbg(&client->dev, + "id: %d, type: %d, pressed: %d, x: %d, y: %d, width: %d, strength: %d\n", +- id, touch->type, touch->pressed, ++ id, type, pressed, + x, y, touch->width, touch->strength); + + input_mt_slot(input_dev, id); +- input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, touch->pressed); ++ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, pressed); + +- if (touch->pressed) { ++ if (pressed) { + touchscreen_report_pos(input_dev, &data->props, x, y, true); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch->width); + input_report_abs(input_dev, ABS_MT_PRESSURE, touch->strength); +@@ -195,21 +289,23 @@ static void mms114_process_touchkey(struct mms114_data *data, + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; + unsigned int keycode_id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + +- if (touch->id == 0) ++ if (id == 0) + return; + +- if (touch->id > data->num_keycodes) { ++ if (id > data->num_keycodes) { + dev_err(&client->dev, "Wrong touch id for touchkey (%d)\n", +- touch->id); ++ id); + return; + } + +- keycode_id = touch->id - 1; ++ keycode_id = id - 1; + dev_dbg(&client->dev, "keycode id: %d, pressed: %d\n", keycode_id, +- touch->pressed); ++ pressed); + +- input_report_key(input_dev, data->keycodes[keycode_id], touch->pressed); ++ input_report_key(input_dev, data->keycodes[keycode_id], pressed); + } + + static irqreturn_t mms114_interrupt(int irq, void *dev_id) +@@ -218,8 +314,8 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + struct i2c_client *client = data->client; + struct mms114_touch touch[MMS114_MAX_TOUCH]; + struct mms114_touch *t; ++ int event_size = data->chip->event_size; + int packet_size; +- int event_size; + int touch_size; + int index; + int error; +@@ -234,23 +330,17 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + goto out; + } + +- /* MMS136 has slightly different event size */ +- if (data->type == TYPE_MMS134S || data->type == TYPE_MMS136) +- event_size = MMS136_EVENT_SIZE; +- else +- event_size = MMS114_EVENT_SIZE; +- + touch_size = packet_size / event_size; + +- error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, +- (u8 *)touch); +- if (error < 0) ++ error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, touch); ++ if (error) + goto out; + + for (index = 0; index < touch_size; index++) { + t = (struct mms114_touch *)((u8 *)touch + index * event_size); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, t->flags); + +- switch (t->type) { ++ switch (type) { + case MMS114_TYPE_TOUCHSCREEN: + mms114_process_mt(data, t); + break; +@@ -261,7 +351,7 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + + default: + dev_err(&client->dev, "Wrong touch type (%d)\n", +- t->type); ++ type); + break; + } + } +@@ -290,65 +380,17 @@ static int mms114_set_active(struct mms114_data *data, bool active) + return mms114_write_reg(data, MMS114_MODE_CONTROL, val); + } + +-static int mms114_get_version(struct mms114_data *data) +-{ +- struct device *dev = &data->client->dev; +- u8 buf[6]; +- int group; +- int error; +- +- switch (data->type) { +- case TYPE_MMS345L: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", +- buf[0], buf[1], buf[2]); +- break; +- +- case TYPE_MMS152: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- group = i2c_smbus_read_byte_data(data->client, +- MMS152_COMPAT_GROUP); +- if (group < 0) +- return group; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", +- buf[0], buf[1], buf[2], group); +- break; +- +- case TYPE_MMS114: +- case TYPE_MMS134S: +- case TYPE_MMS136: +- error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", +- buf[0], buf[1], buf[3]); +- break; +- } +- +- return 0; +-} +- + static int mms114_setup_regs(struct mms114_data *data) + { + const struct touchscreen_properties *props = &data->props; + int val; + int error; + +- error = mms114_get_version(data); +- if (error < 0) ++ error = data->chip->get_version(data); ++ if (error) + return error; + +- /* MMS114, MMS134S and MMS136 have configuration and power on registers */ +- if (data->type != TYPE_MMS114 && data->type != TYPE_MMS134S && +- data->type != TYPE_MMS136) ++ if (!data->chip->has_config_regs) + return 0; + + error = mms114_set_active(data, true); +@@ -366,21 +408,21 @@ static int mms114_setup_regs(struct mms114_data *data) + if (error < 0) + return error; + +- val = props->max_x & 0xff; ++ val = props->max_y & 0xff; + error = mms114_write_reg(data, MMS114_Y_RESOLUTION, val); + if (error < 0) + return error; + + if (data->contact_threshold) { + error = mms114_write_reg(data, MMS114_CONTACT_THRESHOLD, +- data->contact_threshold); ++ data->contact_threshold); + if (error < 0) + return error; + } + + if (data->moving_threshold) { + error = mms114_write_reg(data, MMS114_MOVING_THRESHOLD, +- data->moving_threshold); ++ data->moving_threshold); + if (error < 0) + return error; + } +@@ -466,9 +508,9 @@ static int mms114_parse_legacy_bindings(struct mms114_data *data) + } + + device_property_read_u32(dev, "contact-threshold", +- &data->contact_threshold); ++ &data->contact_threshold); + device_property_read_u32(dev, "moving-threshold", +- &data->moving_threshold); ++ &data->moving_threshold); + + if (device_property_read_bool(dev, "x-invert")) + props->invert_x = true; +@@ -484,7 +526,6 @@ static int mms114_probe(struct i2c_client *client) + { + struct mms114_data *data; + struct input_dev *input_dev; +- const void *match_data; + int error; + int i; + +@@ -504,12 +545,10 @@ static int mms114_probe(struct i2c_client *client) + data->client = client; + data->input_dev = input_dev; + +- match_data = device_get_match_data(&client->dev); +- if (!match_data) ++ data->chip = i2c_get_match_data(client); ++ if (!data->chip) + return -EINVAL; + +- data->type = (enum mms_type)match_data; +- + data->num_keycodes = device_property_count_u32(&client->dev, + "linux,keycodes"); + if (data->num_keycodes == -EINVAL) { +@@ -521,7 +560,7 @@ static int mms114_probe(struct i2c_client *client) + return data->num_keycodes; + } else if (data->num_keycodes > MMS114_MAX_TOUCHKEYS) { + dev_warn(&client->dev, +- "Found %d linux,keycodes but max is %d, ignoring the rest\n", ++ "Found %d linux,keycodes but max is %d, ignoring the rest\n", + data->num_keycodes, MMS114_MAX_TOUCHKEYS); + data->num_keycodes = MMS114_MAX_TOUCHKEYS; + } +@@ -566,8 +605,7 @@ static int mms114_probe(struct i2c_client *client) + 0, data->props.max_y, 0, 0); + } + +- if (data->type == TYPE_MMS114 || data->type == TYPE_MMS134S || +- data->type == TYPE_MMS136) { ++ if (data->chip->has_config_regs) { + /* + * The firmware handles movement and pressure fuzz, so + * don't duplicate that in software. +@@ -582,8 +620,8 @@ static int mms114_probe(struct i2c_client *client) + } + + input_dev->name = devm_kasprintf(&client->dev, GFP_KERNEL, +- "MELFAS MMS%d Touchscreen", +- data->type); ++ "MELFAS %s Touchscreen", ++ data->chip->name); + if (!input_dev->name) + return -ENOMEM; + +@@ -679,29 +717,22 @@ static int mms114_resume(struct device *dev) + static DEFINE_SIMPLE_DEV_PM_OPS(mms114_pm_ops, mms114_suspend, mms114_resume); + + static const struct i2c_device_id mms114_id[] = { +- { .name = "mms114" }, ++ { .name = "mms114", .driver_data = (kernel_ulong_t)&mms114_descriptor }, ++ { .name = "mms134s", .driver_data = (kernel_ulong_t)&mms134s_descriptor }, ++ { .name = "mms136", .driver_data = (kernel_ulong_t)&mms136_descriptor }, ++ { .name = "mms152", .driver_data = (kernel_ulong_t)&mms152_descriptor }, ++ { .name = "mms345l", .driver_data = (kernel_ulong_t)&mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(i2c, mms114_id); + + #ifdef CONFIG_OF + static const struct of_device_id mms114_dt_match[] = { +- { +- .compatible = "melfas,mms114", +- .data = (void *)TYPE_MMS114, +- }, { +- .compatible = "melfas,mms134s", +- .data = (void *)TYPE_MMS134S, +- }, { +- .compatible = "melfas,mms136", +- .data = (void *)TYPE_MMS136, +- }, { +- .compatible = "melfas,mms152", +- .data = (void *)TYPE_MMS152, +- }, { +- .compatible = "melfas,mms345l", +- .data = (void *)TYPE_MMS345L, +- }, ++ { .compatible = "melfas,mms114", .data = &mms114_descriptor }, ++ { .compatible = "melfas,mms134s", .data = &mms134s_descriptor }, ++ { .compatible = "melfas,mms136", .data = &mms136_descriptor }, ++ { .compatible = "melfas,mms152", .data = &mms152_descriptor }, ++ { .compatible = "melfas,mms345l", .data = &mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(of, mms114_dt_match); +@@ -722,4 +753,4 @@ module_i2c_driver(mms114_driver); + /* Module information */ + MODULE_AUTHOR("Joonyoung Shim "); + MODULE_DESCRIPTION("MELFAS mms114 Touchscreen driver"); +-MODULE_LICENSE("GPL v2"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/touchscreen/sur40.c b/drivers/input/touchscreen/sur40.c +--- a/drivers/input/touchscreen/sur40.c ++++ b/drivers/input/touchscreen/sur40.c +@@ -128,8 +128,8 @@ struct sur40_image_header { + /* polling interval (ms) */ + #define POLL_INTERVAL 1 + +-/* maximum number of contacts FIXME: this is a guess? */ +-#define MAX_CONTACTS 64 ++/* maximum number of contacts */ ++#define MAX_CONTACTS 52 + + /* control commands */ + #define SUR40_GET_VERSION 0xb0 /* 12 bytes string */ +diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c +--- a/drivers/input/touchscreen/wacom_w8001.c ++++ b/drivers/input/touchscreen/wacom_w8001.c +@@ -417,6 +417,13 @@ static int w8001_detect(struct w8001 *w8001) + return 0; + } + ++static void w8001_append_suffix(char *dest, const char *suffix, size_t dest_sz) ++{ ++ size_t used = strnlen(dest, dest_sz); ++ ++ strscpy(dest + used, suffix, dest_sz - used); ++} ++ + static int w8001_setup_pen(struct w8001 *w8001, char *basename, + size_t basename_sz) + { +@@ -453,7 +460,7 @@ static int w8001_setup_pen(struct w8001 *w8001, char *basename, + } + + w8001->id = 0x90; +- strlcat(basename, " Penabled", basename_sz); ++ w8001_append_suffix(basename, " Penabled", basename_sz); + + return 0; + } +@@ -503,14 +510,14 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + case 2: + w8001->pktlen = W8001_PKTLEN_TOUCH93; + w8001->id = 0x93; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + break; + + case 1: + case 3: + case 4: + w8001->pktlen = W8001_PKTLEN_TOUCH9A; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + w8001->id = 0x9a; + break; + +@@ -534,7 +541,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + input_abs_set_res(dev, ABS_MT_POSITION_X, touch.panel_res); + input_abs_set_res(dev, ABS_MT_POSITION_Y, touch.panel_res); + +- strlcat(basename, " 2FG", basename_sz); ++ w8001_append_suffix(basename, " 2FG", basename_sz); + if (w8001->max_pen_x && w8001->max_pen_y) + w8001->id = 0xE3; + else +@@ -542,7 +549,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + break; + } + +- strlcat(basename, " Touchscreen", basename_sz); ++ w8001_append_suffix(basename, " Touchscreen", basename_sz); + + return 0; + } +diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c +--- a/drivers/mfd/rohm-bd71828.c ++++ b/drivers/mfd/rohm-bd71828.c +@@ -5,7 +5,8 @@ + * ROHM BD718[15/28/79] and BD72720 PMIC driver + */ + +-#include ++#include ++#include + #include + #include + #include +@@ -18,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -37,19 +39,6 @@ + }, \ + } + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +- .wakeup = 1, +-}; +- +-static const struct gpio_keys_platform_data bd71828_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd71828-pwrkey", +-}; +- + static const struct resource bd71815_rtc_irqs[] = { + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC0, "bd70528-rtc-alm-0"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC1, "bd70528-rtc-alm-1"), +@@ -174,10 +163,6 @@ static struct mfd_cell bd71828_mfd_cells[] = { + .name = "bd71828-rtc", + .resources = bd71828_rtc_irqs, + .num_resources = ARRAY_SIZE(bd71828_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, + }; + +@@ -242,11 +227,8 @@ static const struct mfd_cell bd72720_mfd_cells[] = { + .name = "bd72720-rtc", + .resources = bd72720_rtc_irqs, + .num_resources = ARRAY_SIZE(bd72720_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, ++ /* Power button is registered separately */ + }; + + static const struct regmap_range bd71815_volatile_ranges[] = { +@@ -877,6 +859,84 @@ static int set_clk_mode(struct device *dev, struct regmap *regmap, + OUT32K_MODE_CMOS); + } + ++static const struct property_entry bd71828_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd71828-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd71828_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ PROPERTY_ENTRY_BOOL("wakeup-source"), ++ { } ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd71828_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd71828_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd71828_i2c_register_pwrbutton(struct device *dev, int button_irq, ++ struct irq_domain *irq_domain) ++{ ++ const struct resource res[] = { ++ DEFINE_RES_IRQ_NAMED(button_irq, "bd71828-pwrkey"), ++ }; ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = res, ++ .num_resources = ARRAY_SIZE(res), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd71828_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd71828_powerkey_props; ++ ++ ret = bd71828_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd71828_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static struct i2c_client *bd71828_dev; + static void bd71828_power_off(void) + { +@@ -929,6 +989,7 @@ static struct regmap *bd72720_do_regmaps(struct i2c_client *i2c) + static int bd71828_i2c_probe(struct i2c_client *i2c) + { + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + struct regmap *regmap = NULL; + const struct regmap_config *regmap_config; +@@ -1022,23 +1083,23 @@ static int bd71828_i2c_probe(struct i2c_client *i2c) + "Failed to enable main level IRQs\n"); + } + } +- if (button_irq) { +- ret = regmap_irq_get_virq(irq_data, button_irq); +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, +- "Failed to get the power-key IRQ\n"); +- +- button.irq = ret; +- } + + ret = set_clk_mode(&i2c->dev, regmap, clkmode_reg); + if (ret) + return ret; + ++ irq_domain = regmap_irq_get_domain(irq_data); ++ + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, mfd, cells, +- NULL, 0, regmap_irq_get_domain(irq_data)); ++ NULL, 0, irq_domain); + if (ret) +- return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ ++ if (button_irq) { ++ ret = bd71828_i2c_register_pwrbutton(&i2c->dev, button_irq, irq_domain); ++ if (ret) ++ return ret; ++ } + + if (of_device_is_system_power_controller(i2c->dev.of_node) && + chip_type == ROHM_CHIP_TYPE_BD71828) { +diff --git a/drivers/mfd/rohm-bd718x7.c b/drivers/mfd/rohm-bd718x7.c +--- a/drivers/mfd/rohm-bd718x7.c ++++ b/drivers/mfd/rohm-bd718x7.c +@@ -7,7 +7,8 @@ + // Datasheet for BD71837MWV available from + // https://www.rohm.com/datasheet/BD71837MWV/bd71837mwv-e + +-#include ++#include ++#include + #include + #include + #include +@@ -15,37 +16,16 @@ + #include + #include + #include ++#include + #include + #include + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +-}; +- +-static struct gpio_keys_platform_data bd718xx_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd718xx-pwrkey", +-}; +- + static struct mfd_cell bd71837_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71837-clk", }, + { .name = "bd71837-pmic", }, + }; + + static struct mfd_cell bd71847_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71847-clk", }, + { .name = "bd71847-pmic", }, + }; +@@ -125,10 +105,89 @@ static int bd718xx_init_press_duration(struct regmap *regmap, + return 0; + } + ++static const struct property_entry bd718xx_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd718xx-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd718xx_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ { } ++}; ++ ++static const struct resource bd718xx_powerkey_resources[] = { ++ DEFINE_RES_IRQ_NAMED(BD718XX_INT_PWRBTN_S, "bd718xx-pwrkey"), ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd718xx_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd718xx_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd718xx_i2c_register_pwrbutton(struct device *dev, ++ struct irq_domain *irq_domain) ++{ ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = bd718xx_powerkey_resources, ++ .num_resources = ARRAY_SIZE(bd718xx_powerkey_resources), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd718xx_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd718xx_powerkey_props; ++ ++ ret = bd718xx_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd718xx_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static int bd718xx_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + unsigned int chip_type; + struct mfd_cell *mfd; +@@ -169,20 +228,18 @@ static int bd718xx_i2c_probe(struct i2c_client *i2c) + if (ret) + return ret; + +- ret = regmap_irq_get_virq(irq_data, BD718XX_INT_PWRBTN_S); +- +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, "Failed to get the IRQ\n"); +- +- button.irq = ret; ++ irq_domain = regmap_irq_get_domain(irq_data); + + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, +- mfd, cells, NULL, 0, +- regmap_irq_get_domain(irq_data)); ++ mfd, cells, NULL, 0, irq_domain); + if (ret) +- dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); + +- return ret; ++ ret = bd718xx_i2c_register_pwrbutton(&i2c->dev, irq_domain); ++ if (ret) ++ return ret; ++ ++ return 0; + } + + static const struct of_device_id bd718xx_of_match[] = { +diff --git a/include/linux/input.h b/include/linux/input.h +--- a/include/linux/input.h ++++ b/include/linux/input.h +@@ -117,7 +117,8 @@ enum input_clock_type { + * user opens device and dev->close() is called when the very + * last user closes the device + * @going_away: marks devices that are in a middle of unregistering and +- * causes input_open_device*() fail with -ENODEV. ++ * causes input_open_device() and input_inhibit/uninhibit_device() ++ * to fail with -ENODEV. + * @dev: driver model's view of this device + * @h_list: list of input handles associated with the device. When + * accessing the list dev->mutex must be held +@@ -128,11 +129,14 @@ enum input_clock_type { + * @devres_managed: indicates that devices is managed with devres framework + * and needs not be explicitly unregistered or freed. + * @timestamp: storage for a timestamp set by input_set_timestamp called +- * by a driver ++ * by a driver + * @inhibited: indicates that the input device is inhibited. If that is +- * the case then input core ignores any events generated by the device. +- * Device's close() is called when it is being inhibited and its open() +- * is called when it is being uninhibited. ++ * the case then input core ignores any events generated by the device. ++ * Device's close() is called when it is being inhibited and its open() ++ * is called when it is being uninhibited. ++ * @ready: indicates that the device has been successfully opened and is ++ * prepared to process events (like LEDs or sounds) sent from the ++ * input core. + */ + struct input_dev { + const char *name; +@@ -209,6 +213,7 @@ struct input_dev { + ktime_t timestamp[INPUT_CLK_MAX]; + + bool inhibited; ++ bool ready; + }; + #define to_input_dev(d) container_of(d, struct input_dev, dev) + +@@ -284,8 +289,9 @@ struct input_handle; + * @connect: called when attaching a handler to an input device + * @disconnect: disconnects a handler from input device + * @start: starts handler for given handle. This function is called by +- * input core right after connect() method and also when a process +- * that "grabbed" a device releases it ++ * input core when device is open and ready to process events, ++ * and also when device is uninhibited or when a process that "grabbed" ++ * a device releases it + * @passive_observer: set to %true by drivers only interested in observing + * data stream from devices if there are other users present. Such + * drivers will not result in starting underlying hardware device +diff --git a/include/linux/input/elan-i2c-ids.h b/include/linux/input/elan-i2c-ids.h +--- a/include/linux/input/elan-i2c-ids.h ++++ b/include/linux/input/elan-i2c-ids.h +@@ -18,7 +18,7 @@ + #ifndef __ELAN_I2C_IDS_H + #define __ELAN_I2C_IDS_H + +-#include ++#include + + static const struct acpi_device_id elan_acpi_id[] = { + { "ELAN0000", 0 }, diff --git a/pkgbuilds/linux-omarchy-eevdf/0560-input-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0560-input-updates.patch.sig new file mode 100644 index 0000000..9e9a221 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0560-input-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0561-input-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0561-input-fixes.patch new file mode 100644 index 0000000..b067aaa --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0561-input-fixes.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c +--- a/drivers/input/touchscreen/tsc2007_core.c ++++ b/drivers/input/touchscreen/tsc2007_core.c +@@ -221,7 +221,6 @@ static int tsc2007_get_pendown_state_gpio(struct device *dev) + static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + { + u32 val32; +- u64 val64; + + if (!device_property_read_u32(dev, "ti,max-rt", &val32)) + ts->max_rt = val32; +@@ -237,8 +236,8 @@ static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + if (!device_property_read_u32(dev, "ti,fuzzz", &val32)) + ts->fuzzz = val32; + +- if (!device_property_read_u64(dev, "ti,poll-period", &val64)) +- ts->poll_period = msecs_to_jiffies(val64); ++ if (!device_property_read_u32(dev, "ti,poll-period", &val32)) ++ ts->poll_period = msecs_to_jiffies(val32); + else + ts->poll_period = msecs_to_jiffies(1); + diff --git a/pkgbuilds/linux-omarchy-eevdf/0561-input-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0561-input-fixes.patch.sig new file mode 100644 index 0000000..27751f1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0561-input-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0562-hid-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0562-hid-fixes.patch new file mode 100644 index 0000000..47e03fa --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0562-hid-fixes.patch @@ -0,0 +1,483 @@ +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h +--- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h +@@ -12,11 +12,15 @@ + + #include + #include ++#include + #include "amd_sfh_hid.h" + + #define PCI_DEVICE_ID_AMD_MP2 0x15E4 + #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A + ++/* The BAR 2 size must cover the highest register offset (0x10958) */ ++#define AMD_SFH_MIN_BAR_SIZE SZ_128K ++ + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) + #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) + +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +@@ -451,6 +451,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i + if (rc) + return rc; + ++ if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { ++ dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); ++ return -ENODEV; ++ } ++ ++ if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { ++ dev_err(&pdev->dev, "BAR 2 is too small\n"); ++ return -EINVAL; ++ } ++ + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); + if (rc) + return rc; +diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c +--- a/drivers/hid/bpf/hid_bpf_dispatch.c ++++ b/drivers/hid/bpf/hid_bpf_dispatch.c +@@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) + + static int + __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, +- enum hid_report_type rtype) ++ enum hid_report_type rtype, bool hw_request) + { + struct hid_report_enum *report_enum; + struct hid_report *report; +@@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, + + report_len = hid_report_len(report); + ++ /* unnumbered reports need to have a report ID reserved in the first byte */ ++ if (hw_request && report_enum->numbered == 0) ++ report_len += 1; ++ + if (*buf__sz > report_len) + *buf__sz = report_len; + +@@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); + if (ret) + return ret; + +@@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); + if (ret) + return ret; + +@@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); + if (ret) + return ret; + +diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c +--- a/drivers/hid/hid-alps.c ++++ b/drivers/hid/hid-alps.c +@@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) + return 1; + + case U1_SP_ABSOLUTE_REPORT_ID: ++ if (!hdata->input2) ++ return 0; + sp_x = get_unaligned_le16(data+2); + sp_y = get_unaligned_le16(data+4); + +@@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + goto exit; + } + +- data->input2 = input2; + input2->phys = input->phys; + input2->name = "DualPoint Stick"; + input2->id.bustype = BUS_I2C; +@@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + __set_bit(INPUT_PROP_POINTER, input2->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); + +- if (input_register_device(data->input2)) { ++ if (input_register_device(input2)) { + input_free_device(input2); + ret = -ENOENT; + goto exit; + } ++ data->input2 = input2; + } + + exit: +@@ -823,6 +825,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) + return 0; + } + ++static void alps_remove(struct hid_device *hdev) ++{ ++ struct alps_dev *data = hid_get_drvdata(hdev); ++ ++ /* ++ * input2 ("DualPoint Stick") is allocated separately and is not ++ * tracked in hdev->inputs, so the default remove path ++ * (hid_hw_stop -> hidinput_disconnect) does not unregister it. ++ * ++ * Stop the device first so that no URB callback can touch input2 ++ * while it is being unregistered, then drop it explicitly. ++ */ ++ hid_hw_stop(hdev); ++ ++ if (data->input2) ++ input_unregister_device(data->input2); ++} ++ + static const struct hid_device_id alps_id[] = { + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, +@@ -845,6 +865,7 @@ static struct hid_driver alps_driver = { + .input_configured = alps_input_configured, + .resume = pm_ptr(alps_post_resume), + .reset_resume = pm_ptr(alps_post_reset), ++ .remove = alps_remove, + }; + + module_hid_driver(alps_driver); +diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c +--- a/drivers/hid/hid-corsair-void.c ++++ b/drivers/hid/hid-corsair-void.c +@@ -51,20 +51,23 @@ + /* Receiver report information: (ID 102) */ + /* -------------------------------------------------------------------------- */ + /* +- * When queried, the recevier responds with 4 bytes to describe the firmware +- * The first 2 bytes are for the receiver, the second 2 are the headset ++ * When queried, the receiver responds with 5 bytes to describe the firmware ++ * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset + * The headset firmware version will be 0 if no headset is connected + * +- * 0: Recevier firmware major version ++ * 0: Report ID ++ * 102 for the firmware packet ++ * ++ * 1: Receiver firmware major version + * Major version of the receiver's firmware + * +- * 1: Recevier firmware minor version ++ * 2: Receiver firmware minor version + * Minor version of the receiver's firmware + * +- * 2: Headset firmware major version ++ * 3: Headset firmware major version + * Major version of the headset's firmware + * +- * 3: Headset firmware minor version ++ * 4: Headset firmware minor version + * Minor version of the headset's firmware + */ + /* -------------------------------------------------------------------------- */ +diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c +--- a/drivers/hid/hid-elecom.c ++++ b/drivers/hid/hid-elecom.c +@@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: + case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: +- case USB_DEVICE_ID_ELECOM_M_XT4DRBK: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: + /* + * Report descriptor format: + * 12: button bit count +@@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); + break; + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: + /* + * Report descriptor format: + * 22: button bit count +@@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -472,7 +472,8 @@ + #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c +-#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e + #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe + #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff + #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d +@@ -759,6 +760,7 @@ + #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a + #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 + #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 ++#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db + #define USB_DEVICE_ID_ITE8595 0x8595 + #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 + +@@ -1283,6 +1285,9 @@ + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 + ++#define USB_VENDOR_ID_SDINNOVATION 0x36ae ++#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab ++ + #define USB_VENDOR_ID_SEMICO 0x1a2c + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 +diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c +--- a/drivers/hid/hid-logitech-hidpp.c ++++ b/drivers/hid/hid-logitech-hidpp.c +@@ -4656,6 +4656,8 @@ static const struct hid_device_id hidpp_devices[] = { + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, + { /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, ++ { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ ++ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, + { /* Logitech G703 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, + { /* Logitech G703 Hero Gaming Mouse over USB */ +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_YOGABOOK9I BIT(24) + #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) ++#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_APPLE_TOUCHBAR 0x0114 + #define MT_CLS_YOGABOOK9I 0x0115 + #define MT_CLS_EGALAX_P80H84 0x0116 ++#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_ASUS_CUSTOM_UP }, ++ { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ .quirks = MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS | ++ MT_QUIRK_CONFIDENCE | ++ MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, ++ .export_all_inputs = true }, + { .name = MT_CLS_VTL, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | +@@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { + + static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + { ++ struct mt_device *td = hid_get_drvdata(hdev); + int ret; + u32 size = hid_report_len(report); + u8 *buf; +@@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + dev_warn(&hdev->dev, "failed to fetch feature %d\n", + report->id); + } else { +- /* The report ID in the request and the response should match */ +- if (report->id != buf[0]) { ++ /* ++ * The report ID in the request and the response should match. ++ * Some firmware (e.g. the ASUS ROG Z13 Folio ++ * touchpad) returns a mismatched ID on this specific fetch; ++ * tolerate it only for devices explicitly flagged as such. ++ */ ++ if (report->id != buf[0] && ++ !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { + hid_err(hdev, "Returned feature report did not match the request\n"); + goto free; + } +@@ -2726,6 +2745,12 @@ static const struct hid_device_id mt_devices[] = { + HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, + I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + ++ /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ ++ { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, ++ USB_VENDOR_ID_ASUSTEK, ++ USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, ++ + /* Generic MT device */ + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, + +diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c +--- a/drivers/hid/hid-oxp.c ++++ b/drivers/hid/hid-oxp.c +@@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, + + static void oxp_hid_remove(struct hid_device *hdev) + { +- cancel_delayed_work(&drvdata.oxp_rgb_queue); +- cancel_delayed_work(&drvdata.oxp_btn_queue); +- cancel_delayed_work(&drvdata.oxp_mcu_init); ++ cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_btn_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_mcu_init); + hid_hw_close(hdev); + hid_hw_stop(hdev); + } +diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c +--- a/drivers/hid/hid-quirks.c ++++ b/drivers/hid/hid-quirks.c +@@ -183,6 +183,7 @@ static const struct hid_device_id hid_quirks[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, +@@ -422,13 +423,14 @@ static const struct hid_device_id hid_have_special_driver[] = { + #endif + #if IS_ENABLED(CONFIG_HID_ELECOM) + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, +- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c +--- a/drivers/hid/hid-roccat.c ++++ b/drivers/hid/hid-roccat.c +@@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + return temp; + } + +- mutex_unlock(&devices_lock); +- + init_waitqueue_head(&device->wait); + INIT_LIST_HEAD(&device->readers); + mutex_init(&device->readers_lock); +@@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + device->cbuf_end = 0; + device->report_size = report_size; + ++ mutex_unlock(&devices_lock); + return minor; + } + EXPORT_SYMBOL_GPL(roccat_connect); +@@ -369,15 +368,12 @@ void roccat_disconnect(int minor) + + mutex_lock(&devices_lock); + device = devices[minor]; +- mutex_unlock(&devices_lock); + + device->exist = 0; /* TODO exist maybe not needed */ + + device_destroy(device->dev->class, MKDEV(roccat_major, minor)); + +- mutex_lock(&devices_lock); + devices[minor] = NULL; +- mutex_unlock(&devices_lock); + + if (device->open) { + hid_hw_close(device->hid); +@@ -385,6 +381,8 @@ void roccat_disconnect(int minor) + } else { + roccat_free_device(device); + } ++ ++ mutex_unlock(&devices_lock); + } + EXPORT_SYMBOL_GPL(roccat_disconnect); + +diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c +--- a/drivers/hid/hid-winwing.c ++++ b/drivers/hid/hid-winwing.c +@@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) + static int winwing_play_effect(struct input_dev *dev, void *context, + struct ff_effect *effect) + { +- struct winwing_drv_data *data = (struct winwing_drv_data *) context; ++ struct hid_device *hdev = input_get_drvdata(dev); ++ struct winwing_drv_data *data = hid_get_drvdata(hdev); + + if (effect->type != FF_RUMBLE) + return 0; +@@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) + + input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); + +- return input_ff_create_memless(hidinput->input, data, ++ /* ++ * input_ff_create_memless() takes ownership of the context pointer ++ * and frees it on teardown; do not hand it the devm-managed drvdata. ++ * winwing_play_effect() fetches it from the input device instead. ++ */ ++ return input_ff_create_memless(hidinput->input, NULL, + winwing_play_effect); + } + +diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c +--- a/drivers/hid/i2c-hid/i2c-hid-core.c ++++ b/drivers/hid/i2c-hid/i2c-hid-core.c +@@ -123,9 +123,11 @@ static const struct i2c_hid_quirks { + __u32 quirks; + } i2c_hid_quirks[] = { + { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, +- I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, + { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, +diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c +--- a/drivers/hid/wacom_sys.c ++++ b/drivers/hid/wacom_sys.c +@@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, + + /* Queue events which have invalid tool type or serial number */ + for (i = 0; i < report->maxfield; i++) { +- for (j = 0; j < report->field[i]->maxusage; j++) { +- struct hid_field *field = report->field[i]; ++ struct hid_field *field = report->field[i]; ++ ++ for (j = 0; j < field->report_count; j++) { + struct hid_usage *usage = &field->usage[j]; + unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); + unsigned int offset; diff --git a/pkgbuilds/linux-omarchy-eevdf/0562-hid-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0562-hid-fixes.patch.sig new file mode 100644 index 0000000..03c4b82 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0562-hid-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0563-input-fixes-2.patch b/pkgbuilds/linux-omarchy-eevdf/0563-input-fixes-2.patch new file mode 100644 index 0000000..1682da2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0563-input-fixes-2.patch @@ -0,0 +1,88 @@ +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -1938,6 +1938,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { + }, + .callback = atkbd_deactivate_fixup, + }, ++ { ++ /* Lenovo IdeaPad Slim 3 15IWC11 (83RR) */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"), ++ }, ++ .callback = atkbd_deactivate_fixup, ++ }, + { + /* Xiaomi Book Pro 14 (TM2424) */ + .matches = { +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -192,6 +192,7 @@ static const char * const smbus_pnp_ids[] = { + "LEN2054", /* E480 */ + "LEN2055", /* E580 */ + "LEN2058", /* E490 */ ++ "LEN205b", /* T490 */ + "LEN2068", /* T14 Gen 1 */ + "SYN1221", /* TUXEDO InfinityBook Pro 14 v5 */ + "SYN3003", /* HP EliteBook 850 G1 */ +@@ -1839,8 +1840,10 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, + return -ENXIO; + } + +- /* Disable intertouch on known-broken board revisions */ +- if (info->board_id == 2722) { ++ /* Disable intertouch on known-broken T440p board revisions */ ++ if (info->board_id == 2722 && ++ psmouse_matches_pnp_id(psmouse, ++ (const char * const []){"LEN0036", NULL})) { + psmouse_info(psmouse, + "Disabling intertouch for board id %u\n", + info->board_id); +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -671,6 +671,15 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { + }, + .driver_data = (void *)(SERIO_QUIRK_NOAUX) + }, ++ { ++ /* Fujitsu LIFEBOOK U7410 */ ++ /* Mux mode also disturbs the EC keyboard, causing spurious ACKs */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU CLIENT COMPUTING LIMITED"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U7410"), ++ }, ++ .driver_data = (void *)(SERIO_QUIRK_NOMUX) ++ }, + { + /* Gigabyte M912 */ + .matches = { +diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c +--- a/drivers/input/touchscreen/s6sy761.c ++++ b/drivers/input/touchscreen/s6sy761.c +@@ -500,10 +500,22 @@ static int s6sy761_suspend(struct device *dev) + static int s6sy761_resume(struct device *dev) + { + struct s6sy761_data *sdata = dev_get_drvdata(dev); ++ int err; ++ ++ err = s6sy761_power_on(sdata); ++ if (err) ++ return err; + + enable_irq(sdata->client->irq); + +- return s6sy761_power_on(sdata); ++ guard(mutex)(&sdata->input->mutex); ++ ++ if (!input_device_enabled(sdata->input)) ++ return 0; ++ ++ err = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON); ++ ++ return err; + } + + static const struct dev_pm_ops s6sy761_pm_ops = { diff --git a/pkgbuilds/linux-omarchy-eevdf/0563-input-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0563-input-fixes-2.patch.sig new file mode 100644 index 0000000..5059200 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0563-input-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch b/pkgbuilds/linux-omarchy-eevdf/0565-i2c-asue140d-touchpad-100khz.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch rename to pkgbuilds/linux-omarchy-eevdf/0565-i2c-asue140d-touchpad-100khz.patch diff --git a/pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0565-i2c-asue140d-touchpad-100khz.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0565-i2c-asue140d-touchpad-100khz.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0565-i2c-asue140d-touchpad-100khz.patch.sig diff --git a/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch b/pkgbuilds/linux-omarchy-eevdf/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch similarity index 92% rename from pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch rename to pkgbuilds/linux-omarchy-eevdf/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch index e569f4c..d7870a2 100644 --- a/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch @@ -1,7 +1,7 @@ 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) +@@ -1480,12 +1480,14 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } diff --git a/pkgbuilds/linux-omarchy-eevdf/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig new file mode 100644 index 0000000..0d7e5e4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0601-thunderbolt-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0601-thunderbolt-updates.patch new file mode 100644 index 0000000..7a0d9ca --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0601-thunderbolt-updates.patch @@ -0,0 +1,1131 @@ +diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c +--- a/drivers/net/thunderbolt/main.c ++++ b/drivers/net/thunderbolt/main.c +@@ -1362,7 +1362,7 @@ static void tbnet_generate_mac(struct net_device *dev) + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + } + +-static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbnet_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct net_device *dev; +@@ -1482,7 +1482,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume); + + static const struct tb_service_id tbnet_ids[] = { + { TB_SERVICE("network", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbnet_ids); + +diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c +--- a/drivers/thunderbolt/debugfs.c ++++ b/drivers/thunderbolt/debugfs.c +@@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf, + if (!access_ok(user_buf, *count)) + return ERR_PTR(-EFAULT); + +- buf = (void *)get_zeroed_page(GFP_KERNEL); ++ buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + nbytes = min_t(size_t, *count, PAGE_SIZE); + if (copy_from_user(buf, user_buf, nbytes)) { +- free_page((unsigned long)buf); ++ kfree(buf); + return ERR_PTR(-EFAULT); + } + +@@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file, + out: + pm_runtime_mark_last_busy(&rt->dev); + pm_runtime_put_autosuspend(&rt->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf, + margining->ber_level = val; + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + out_unlock: + mutex_unlock(&tb->lock); + +@@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf, + } + } + +- free_page((unsigned long)buf); ++ kfree(buf); + + if (lane == -1) + return -EINVAL; +@@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + else + goto err_free; + +- free_page((unsigned long)buf); ++ kfree(buf); + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) { + if (!margining->software) +@@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + return count; + + err_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return -EINVAL; + } + +@@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file, + ret = -EINVAL; + } + +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c +--- a/drivers/thunderbolt/dma_test.c ++++ b/drivers/thunderbolt/dma_test.c +@@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc) + debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops); + } + +-static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int dma_test_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct dma_test *dt; +@@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = { + + static const struct tb_service_id dma_test_ids[] = { + { TB_SERVICE("dma_test", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, dma_test_ids); + +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev) + { + struct tb_service *svc = tb_to_service(dev); + struct tb_service_driver *driver; +- const struct tb_service_id *id; + + driver = container_of(dev->driver, struct tb_service_driver, driver); +- id = __tb_service_match(dev, &driver->driver); + +- return driver->probe(svc, id); ++ return driver->probe(svc); + } + + static void tb_service_remove(struct device *dev) +@@ -790,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + transmit_ring, receive_path, receive_ring); + } + ++static void tb_domain_reset_interface(struct tb *tb) ++{ ++ struct tb_nhi *nhi = tb->nhi; ++ ++ if (!nhi->ops->reset_interface) ++ return; ++ ++ guard(mutex)(&tb->lock); ++ ++ /* The reset clears the ring state so stop the control channel */ ++ tb_ctl_stop(tb->ctl); ++ nhi->ops->reset_interface(nhi); ++ tb_ctl_start(tb->ctl); ++} ++ + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -812,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { ++ int ret; ++ + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); ++ if (ret) ++ return ret; ++ ++ if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) ++ tb_domain_reset_interface(tb); ++ ++ return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) + { + struct ring_frame *frame, *n; + struct ring_desc *descriptor; ++ u32 flags; ++ ++ flags = RING_DESC_POSTED; ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ flags |= RING_DESC_INTERRUPT; ++ + list_for_each_entry_safe(frame, n, &ring->queue, list) { + if (ring_full(ring)) + break; +@@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) + descriptor = &ring->descriptors[ring->head]; + descriptor->phys = frame->buffer_phy; + descriptor->time = 0; +- descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; ++ descriptor->flags = flags; + if (ring->is_tx) { + descriptor->length = frame->size; + descriptor->eof = frame->eof; +@@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); + * @ring: Ring to poll + * + * This function can be called when @start_poll callback of the @ring +- * has been called. It will read one completed frame from the ring and +- * return it to the caller. ++ * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. ++ * It will read one completed frame from the ring and return it to the ++ * caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. +@@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", + transmit ? "TX" : "RX", hop, size); + ++ if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { ++ dev_WARN(nhi->dev, ++ "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); ++ return NULL; ++ } ++ + ring = kzalloc_obj(*ring); + if (!ring) + return NULL; +@@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + if (!ring->descriptors) + goto err_free_ring; + +- if (nhi->ops->request_ring_irq) { ++ if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } +@@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) + ring_iowrite32options(ring, flags, 0); + } + +- ring_interrupt_active(ring, true); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, true); + ring->running = true; + err: + spin_unlock(&ring->lock); +@@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) + RING_TYPE(ring), ring->hop); + goto err; + } +- ring_interrupt_active(ring, false); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, false); + + ring_iowrite32options(ring, 0, 0); + ring_iowrite64desc(ring, 0, 0); +@@ -1160,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + ++/** ++ * nhi_reset_interface() - Reset the host interface ++ * @nhi: Host interface to reset ++ * ++ * Brings the registers in the memory BAR back to their default state and ++ * clears the End-to-End Flow Control state. The caller is responsible for ++ * stopping the control channel over the reset because it clears the ring ++ * state as well. ++ */ ++void nhi_reset_interface(struct tb_nhi *nhi) ++{ ++ u32 val; ++ ++ val = ioread32(nhi->iobase + REG_CAPS); ++ /* Only v1 host interfaces implement the reset */ ++ if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) ++ return; ++ ++ dev_dbg(nhi->dev, "issuing host interface reset\n"); ++ ++ iowrite32(REG_HOST_INTERFACE_RESET_RST, ++ nhi->iobase + REG_HOST_INTERFACE_RESET); ++ /* Wait for tHIReset (10 ms) to complete */ ++ usleep_range(10000, 20000); ++} ++ + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +@@ -1235,6 +1276,8 @@ int nhi_probe(struct tb_nhi *nhi) + + dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); + ++ nhi->host_reset = host_reset; ++ + res = tb_domain_add(tb, host_reset); + if (res) { + /* +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); ++void nhi_reset_interface(struct tb_nhi *nhi); ++ + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook ++ * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -66,6 +69,7 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); ++ void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -116,11 +120,24 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e ++ + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) ++#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,6 +115,10 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + ++/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ ++#define REG_HOST_INTERFACE_RESET 0x39858 ++#define REG_HOST_INTERFACE_RESET_RST BIT(0) ++ + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } ++ } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { ++ switch (pdev->device) { ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: ++ /* ++ * These AMD hosts may hang the Tx ring when the ++ * DMA paths are torn down so they need the host ++ * interface reset after each teardown. ++ */ ++ nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; ++ break; ++ } + } + } + +@@ -112,7 +133,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +- struct device *dev = &pdev->dev; + int res, irq, nvec; + + ida_init(&nhi_pci->msix_ida); +@@ -139,7 +159,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + res = devm_request_irq(&pdev->dev, irq, nhi_msi, + IRQF_NO_SUSPEND, "thunderbolt", nhi); + if (res) +- return dev_err_probe(dev, res, "request_irq failed, aborting\n"); ++ return res; + } + + return 0; +@@ -230,7 +250,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring) + ring->irq = 0; + } + +-static void nhi_pci_shutdown(struct tb_nhi *nhi) ++static void nhi_pci_release_irq(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +@@ -256,9 +276,10 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, +- .shutdown = nhi_pci_shutdown, ++ .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -424,7 +445,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi) + + static void icl_nhi_shutdown(struct tb_nhi *nhi) + { +- nhi_pci_shutdown(nhi); ++ nhi_pci_release_irq(nhi); + + icl_nhi_force_power(nhi, false); + } +@@ -442,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +@@ -479,11 +501,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) + return nhi_probe(&nhi_pci->nhi); + } + +-static void nhi_pci_remove(struct pci_dev *pdev) ++static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset) + { + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + ++ /* ++ * On system shutdown/reboot force a host router reset so the ++ * connection manager asserts DPR on connected Thunderbolt 3 devices ++ * before the router tree is removed (see tb_stop()). ++ */ ++ if (reset) ++ nhi->host_reset = true; ++ + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); +@@ -493,6 +523,16 @@ static void nhi_pci_remove(struct pci_dev *pdev) + nhi_shutdown(nhi); + } + ++static void nhi_pci_remove(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, false); ++} ++ ++static void nhi_pci_shutdown(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, true); ++} ++ + static struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and +@@ -593,7 +633,7 @@ static struct pci_driver nhi_driver = { + .id_table = nhi_ids, + .probe = nhi_pci_probe, + .remove = nhi_pci_remove, +- .shutdown = nhi_pci_remove, ++ .shutdown = nhi_pci_shutdown, + .driver.pm = &nhi_pm_ops, + }; + +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -9,10 +9,12 @@ + + #define pr_fmt(fmt) "tbstream: " fmt + ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -128,6 +130,7 @@ struct tbstream_ring { + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns ++ * @busy_poll: Instead of interrupts, busy poll the rings + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. +@@ -147,6 +150,7 @@ struct tbstream_dev { + int out_hopid; + unsigned int ring_size; + unsigned int throttling; ++ bool busy_poll; + int users; + bool closed; + bool removed; +@@ -536,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); + } + ++static void ++tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) ++{ ++ struct ring_frame *frame; ++ ++ if (!sdev->busy_poll) ++ return; ++ ++ while ((frame = tb_ring_poll(ring->ring))) ++ frame->callback(ring->ring, frame, false); ++} ++ + static int tbstream_dev_send_close(struct tbstream_dev *sdev) + { + struct tbstream_frame *sf; + ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling it's the write(2) path that ++ * advances the completions so it is possible that the ++ * ring is full at this point. Advance the ring here so ++ * that there is room for the CLOSE packet to be sent. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ } ++ + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); +@@ -549,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) + static int tbstream_dev_start(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); ++ unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; + u16 sof_mask, eof_mask; + struct tb_ring *ring; + int ret, e2e_tx_hop; + +- ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E); ++ if (sdev->busy_poll) ++ flags |= RING_FLAG_NO_INTERRUPT; ++ ++ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); + if (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; +@@ -567,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + sof_mask = BIT(TBSTREAM_FRAME_START); + eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); + +- ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, +- sof_mask, eof_mask, NULL, NULL); ++ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, ++ e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; +@@ -607,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + return ret; + } + ++static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) ++{ ++ const struct tbstream_ring *ring = &sdev->tx_ring; ++ ++ /* ++ * Everything is completed when number of free TX slots is back ++ * to the maximum. ++ */ ++ return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; ++} ++ + static void tbstream_dev_stop(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd; + +- /* Wait for the ring to complete any outstanding frames */ +- tb_ring_flush(sdev->tx_ring.ring, 500); +- tb_ring_stop(sdev->tx_ring.ring); +- tb_ring_flush(sdev->rx_ring.ring, 500); +- tb_ring_stop(sdev->rx_ring.ring); ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling we must advance the ring ourselves ++ * to push all outstanding frames on the wire. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_dev_tx_drained(sdev)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } else { ++ tb_ring_flush(sdev->tx_ring.ring, 500); ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_flush(sdev->rx_ring.ring, 500); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } + + xd = tbstream_dev_xdomain(sdev); + if (xd) { +@@ -633,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) + sdev->tx_ring.ring = NULL; + } + ++/* Use only with read_iter/write_iter() to handle nowait */ ++static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) ++{ ++ if (nowait) { ++ if (!mutex_trylock(&sdev->lock)) ++ return -EAGAIN; ++ } else { ++ if (mutex_lock_interruptible(&sdev->lock)) ++ return -ERESTARTSYS; ++ } ++ return 0; ++} ++ + static ssize_t + tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -645,31 +722,50 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->rx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->rx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ /* When busy polling, advance any completions manually */ ++ tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return 0; ++ } ++ ++ if (tbstream_ring_available(&sdev->rx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->rx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return 0; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -729,6 +825,7 @@ static ssize_t + tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -737,31 +834,49 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->tx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->tx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return -ENXIO; ++ } ++ ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->tx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return -ENXIO; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -795,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + __poll_t mask = 0; + ++ /* ++ * Without interrupts there is nothing that can wake us up so ++ * return failure instead. ++ */ ++ if (sdev->busy_poll) ++ return EPOLLERR; ++ + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { +@@ -904,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) + return container_of(group, struct tbstream_dev, group); + } + ++static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ ++ return sysfs_emit(buf, "%u\n", sdev->busy_poll); ++} ++ ++static ssize_t ++tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, ++ size_t count) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ bool busy_poll; ++ int ret; ++ ++ ret = kstrtobool(buf, &busy_poll); ++ if (ret) ++ return ret; ++ ++ guard(mutex)(&sdev->lock); ++ if (sdev->users) ++ return -EBUSY; ++ sdev->busy_poll = busy_poll; ++ return count; ++} ++CONFIGFS_ATTR(tbstream_dev_, busy_poll); ++ + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) + { + struct config_group *group = to_config_group(item); +@@ -1210,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, + CONFIGFS_ATTR(tbstream_dev_, throttling); + + static struct configfs_attribute *tbstream_dev_attrs[] = { ++ &tbstream_dev_attr_busy_poll, + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, +@@ -1542,7 +1694,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream) + config_group_put(&sg->group); + } + +-static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbstream_probe(struct tb_service *svc) + { + struct tbstream *stream; + +@@ -1632,7 +1784,7 @@ static const struct dev_pm_ops tbstream_pm_ops = { + + static const struct tb_service_id tbstream_ids[] = { + { TB_SERVICE("stream", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); + +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port) + return __tb_port_enable(port, false); + } + +-static int tb_port_reset(struct tb_port *port) ++/** ++ * tb_port_reset() - Reset the port ++ * @port: Port to reset ++ * ++ * Resets @port. For USB4 ports this issues a USB4 port reset and for ++ * legacy ports the link controller port is reset. ++ * ++ * Return: %0 on success, negative errno otherwise. ++ */ ++int tb_port_reset(struct tb_port *port) + { + if (tb_switch_is_usb4(port->sw)) + return port->cap_usb4 ? usb4_port_reset(port) : 0; +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, + static void tb_stop(struct tb *tb) + { + struct tb_cm *tcm = tb_priv(tb); ++ struct tb_nhi *nhi = tb->nhi; + struct tb_tunnel *tunnel; ++ struct tb_port *port; + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); +@@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } ++ /* ++ * Signal disconnect to connected devices before the router tree is ++ * removed below. A Thunderbolt 3 device directly connected to a USB4 ++ * host otherwise never receives a disconnect indication, leaving ++ * firmware to poll the dead link for up to ~60 s which on some ++ * platforms turns the shutdown into a warm reset. Asserting ++ * PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device ++ * detects SBRX low and goes to Uninitialized Unplugged immediately. ++ */ ++ if (nhi->host_reset) { ++ tb_switch_for_each_port(tb->root_switch, port) { ++ if (!tb_port_is_null(port) || !tb_port_has_remote(port)) ++ continue; ++ if (tb_switch_is_usb4(port->remote->sw)) ++ continue; ++ if (tb_port_reset(port)) ++ tb_port_dbg(port, "downstream port reset failed, continuing\n"); ++ } ++ } + tb_switch_remove(tb->root_switch); + tb->root_switch = NULL; + tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter); + int tb_port_unlock(struct tb_port *port); + int tb_port_enable(struct tb_port *port); + int tb_port_disable(struct tb_port *port); ++int tb_port_reset(struct tb_port *port); + int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid); + void tb_port_release_in_hopid(struct tb_port *port, int hopid); + int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid); +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -48,6 +48,9 @@ + #define TB_DP_AUX_PRIORITY 2 + #define TB_DP_AUX_WEIGHT 1 + ++/* struct tb_regs_hop::initial_credits is 7 bits wide */ ++#define TB_MAX_CREDITS 127 ++ + /* Minimum number of credits needed for PCIe path */ + #define TB_MIN_PCIE_CREDITS 6U + /* +@@ -1778,8 +1781,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits) + if (available < TB_MIN_DMA_CREDITS) + return -ENOSPC; + +- while (credits > available) +- credits--; ++ credits = min(credits, available); + + tb_port_dbg(port, "reserving %u credits for DMA path\n", + credits); +@@ -1908,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + struct tb_tunnel *tunnel; + size_t npaths = 0, i = 0; + struct tb_path *path; +- int credits; ++ unsigned int credits; + + /* Ring 0 is reserved for control channel */ + if (WARN_ON(!receive_ring || !transmit_ring)) +@@ -1931,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + tunnel->destroy = tb_dma_destroy; + + credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits); ++ if (credits > TB_MAX_CREDITS) { ++ tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n", ++ credits, TB_MAX_CREDITS); ++ credits = TB_MAX_CREDITS; ++ } + + if (receive_ring > 0) { + path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, + */ + int tb_register_service_driver(struct tb_service_driver *drv) + { ++ if (!drv->probe) ++ return -EINVAL; ++ + drv->driver.bus = &tb_bus_type; + return driver_register(&drv->driver); + } +@@ -1811,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; ++ tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -465,7 +465,7 @@ static inline struct tb_service *tb_to_service(struct device *dev) + */ + struct tb_service_driver { + struct device_driver driver; +- int (*probe)(struct tb_service *svc, const struct tb_service_id *id); ++ int (*probe)(struct tb_service *svc); + void (*remove)(struct tb_service *svc); + void (*shutdown)(struct tb_service *svc); + const struct tb_service_id *id_table; +@@ -514,6 +514,11 @@ void tb_service_properties_changed(struct tb_service *svc); + * @hop_count: Number of rings (end point hops) supported by NHI. + * @quirks: NHI specific quirks if any + * @domain_released: Completed when domain has been fully released ++ * @host_reset: Host router was reset on driver load, or forced on system ++ * shutdown/reboot. When set, tb_stop() asserts DPR on connected ++ * downstream ports to signal disconnect before tearing down the ++ * router tree. Only Thunderbolt 3 devices are reset; USB4 ++ * routers are skipped. + */ + struct tb_nhi { + spinlock_t lock; +@@ -528,6 +533,7 @@ struct tb_nhi { + u32 hop_count; + unsigned long quirks; + struct completion domain_released; ++ bool host_reset; + }; + + /** +@@ -592,6 +598,8 @@ struct tb_ring { + #define RING_FLAG_FRAME BIT(1) + /* Enable end-to-end flow control */ + #define RING_FLAG_E2E BIT(2) ++/* Do not enable interrupt for the ring */ ++#define RING_FLAG_NO_INTERRUPT BIT(3) + + struct ring_frame; + typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); diff --git a/pkgbuilds/linux-omarchy-eevdf/0601-thunderbolt-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0601-thunderbolt-updates.patch.sig new file mode 100644 index 0000000..f52f6aa Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0601-thunderbolt-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0602-thunderbolt-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0602-thunderbolt-fixes.patch new file mode 100644 index 0000000..f2e51c0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0602-thunderbolt-fixes.patch @@ -0,0 +1,557 @@ +diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c +--- a/drivers/thunderbolt/ctl.c ++++ b/drivers/thunderbolt/ctl.c +@@ -73,7 +73,6 @@ struct tb_ctl { + #define tb_ctl_dbg_once(ctl, format, arg...) \ + dev_dbg_once((ctl)->nhi->dev, format, ## arg) + +-static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); + /* Serializes access to request kref_get/put */ + static DEFINE_MUTEX(tb_cfg_request_lock); + +@@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req) + static int tb_cfg_request_enqueue(struct tb_ctl *ctl, + struct tb_cfg_request *req) + { ++ tb_cfg_request_get(req); ++ + WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)); + WARN_ON(req->ctl); + +- mutex_lock(&ctl->request_queue_lock); ++ guard(mutex)(&ctl->request_queue_lock); + if (!ctl->running) { +- mutex_unlock(&ctl->request_queue_lock); ++ tb_cfg_request_put(req); + return -ENOTCONN; + } + req->ctl = ctl; + list_add_tail(&req->list, &ctl->request_queue); + set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- mutex_unlock(&ctl->request_queue_lock); + return 0; + } + ++static bool tb_cfg_request_is_active(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++} ++ ++static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++} ++ + static void tb_cfg_request_dequeue(struct tb_cfg_request *req) + { + struct tb_ctl *ctl = req->ctl; + +- mutex_lock(&ctl->request_queue_lock); +- if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { +- mutex_unlock(&ctl->request_queue_lock); +- return; ++ guard(mutex)(&ctl->request_queue_lock); ++ if (tb_cfg_request_is_active(req)) { ++ list_del(&req->list); ++ clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++ tb_cfg_request_put(req); + } +- +- list_del(&req->list); +- clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) +- wake_up(&tb_cfg_request_cancel_queue); +- mutex_unlock(&ctl->request_queue_lock); +-} +- +-static bool tb_cfg_request_is_active(struct tb_cfg_request *req) +-{ +- return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + } + + static struct tb_cfg_request * +@@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) + mutex_lock(&pkg->ctl->request_queue_lock); + list_for_each_entry(iter, &pkg->ctl->request_queue, list) { + tb_cfg_request_get(iter); +- if (iter->match(iter, pkg)) { ++ if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) { + req = iter; + break; + } +@@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req); + + if (req) { +- if (req->copy(req, pkg)) +- schedule_work(&req->work); ++ scoped_guard(mutex, &pkg->ctl->request_queue_lock) { ++ if (!tb_cfg_request_is_canceled(req) && ++ req->copy(req, pkg)) ++ schedule_work(&req->work); ++ } + tb_cfg_request_put(req); + } + +@@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work) + { + struct tb_cfg_request *req = container_of(work, typeof(*req), work); + +- if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) ++ if (!tb_cfg_request_is_canceled(req)) + req->callback(req->callback_data); + + tb_cfg_request_dequeue(req); +- tb_cfg_request_put(req); + } + + /** +@@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + INIT_WORK(&req->work, tb_cfg_request_work); + INIT_LIST_HEAD(&req->list); + +- tb_cfg_request_get(req); + ret = tb_cfg_request_enqueue(ctl, req); + if (ret) +- goto err_put; ++ return ret; + + ret = tb_ctl_tx(ctl, req->request, req->request_size, + req->request_type); +@@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + + err_dequeue: + tb_cfg_request_dequeue(req); +-err_put: +- tb_cfg_request_put(req); +- + return ret; + } + +@@ -588,9 +586,10 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + */ + void tb_cfg_request_cancel(struct tb_cfg_request *req, int err) + { +- set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); +- schedule_work(&req->work); +- wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req)); ++ scoped_guard(mutex, &req->ctl->request_queue_lock) ++ set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++ cancel_work_sync(&req->work); ++ tb_cfg_request_dequeue(req); + req->result.err = err; + } + +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + INIT_LIST_HEAD(&ring->in_flight); + INIT_WORK(&ring->work, ring_work); + init_waitqueue_head(&ring->wait); ++ lockdep_register_key(&ring->lock_key); ++ lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0); + + ring->nhi = nhi; + ring->hop = hop; +@@ -599,6 +602,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + ring->size * sizeof(*ring->descriptors), + ring->descriptors, ring->descriptors_dma); + err_free_ring: ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + + return NULL; +@@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring) + * to finish before freeing the ring. + */ + flush_work(&ring->work); ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + } + EXPORT_SYMBOL_GPL(tb_ring_free); +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + { + int port_max_hopid; + struct ida *ida; ++ int ret; + + if (in) { + port_max_hopid = port->config.max_in_hop_id; +@@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + if (max_hopid < 0 || max_hopid > port_max_hopid) + max_hopid = port_max_hopid; + +- return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ if (ret >= 0) ++ tb_switch_get(port->sw); ++ ++ return ret; + } + + /** +@@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid) + void tb_port_release_in_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->in_hopids, hopid); ++ tb_switch_put(port->sw); + } + + /** +@@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid) + void tb_port_release_out_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->out_hopids, hopid); ++ tb_switch_put(port->sw); + } + + static inline bool tb_switch_is_reachable(const struct tb_switch *parent, +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, + const char *reason); + static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port, + int retry, unsigned long delay); ++static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data); + + static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) + { +@@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, + + switch (port->config.type) { + case TB_TYPE_DP_HDMI_IN: +- tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); ++ tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids, ++ tb_dp_tunnel_active, tb); + tb_increase_tmu_accuracy(tunnel); + break; + +@@ -1910,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + struct tb *tb = data; + + mutex_lock(&tb->lock); ++ ++ /* ++ * If the DPRX read was canceled the tunnel is already being torn ++ * down by whoever canceled it. Do not touch the adapters here ++ * because the routers may be gone by now. ++ */ ++ if (tunnel->dprx_canceled) { ++ tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n"); ++ mutex_unlock(&tb->lock); ++ return; ++ } ++ + if (tb_tunnel_is_active(tunnel)) { + int consumed_up, consumed_down, ret; + +@@ -1964,8 +1978,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); + } + mutex_unlock(&tb->lock); +- +- tb_domain_put(tb); + } + + static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, +@@ -2026,8 +2038,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + available_up, available_down); + + tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, +- available_down, tb_dp_tunnel_active, +- tb_domain_get(tb)); ++ available_down, tb_dp_tunnel_active, tb); + if (!tunnel) { + tb_port_dbg(out, "could not allocate DP tunnel\n"); + goto err_reclaim_usb; +@@ -2048,7 +2059,6 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + tb_tunnel_put(tunnel); + err_reclaim_usb: + tb_reclaim_usb3_bandwidth(tb, in, out); +- tb_domain_put(tb); + err_detach_group: + tb_detach_bandwidth_group(in); + err_dealloc_dp: +@@ -2950,11 +2960,12 @@ static void tb_stop(struct tb *tb) + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +- * DMA tunnels require the driver to be functional so we +- * tear them down. Other protocol tunnels can be left +- * intact. ++ * DMA tunnels and DP tunnels which are not yet active require ++ * the driver to be functional so we tear them down. ++ * Other protocol tunnels can be left intact. + */ +- if (tb_tunnel_is_dma(tunnel)) ++ if (tb_tunnel_is_dma(tunnel) || ++ (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel))) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } +@@ -3274,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work) + struct tb *tb = tcm_to_tb(tcm); + + mutex_lock(&tb->lock); +- if (tb->root_switch) ++ if (tb->root_switch) { + tb_free_unplugged_children(tb->root_switch); ++ tb_free_unplugged_xdomains(tb->root_switch); ++ } + mutex_unlock(&tb->lock); +- +- tb_free_unplugged_xdomains(tb->root_switch); + } + + static int tb_runtime_resume(struct tb *tb) +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, + goto err_deactivate; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1093,8 +1094,14 @@ static void tb_dp_dprx_work(struct work_struct *work) + struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work); + struct tb *tb = tunnel->tb; + ++ /* ++ * The DPRX read can be canceled while this work is waiting for ++ * tb->lock. Check the flag only once it is held: while the lock is ++ * held the tunnel cannot be torn down under us and the adapters are ++ * safe to access. ++ */ ++ mutex_lock(&tb->lock); + if (!tunnel->dprx_canceled) { +- mutex_lock(&tb->lock); + if (tb_dp_is_usb4(tunnel->src_port->sw) && + tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { + if (ktime_before(ktime_get(), tunnel->dprx_timeout)) { +@@ -1106,41 +1113,42 @@ static void tb_dp_dprx_work(struct work_struct *work) + } else { + tb_tunnel_set_active(tunnel, true); + } +- mutex_unlock(&tb->lock); + } ++ mutex_unlock(&tb->lock); + +- if (tunnel->callback) +- tunnel->callback(tunnel, tunnel->callback_data); ++ tunnel->callback(tunnel, tunnel->callback_data); + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); + } + + static int tb_dp_dprx_start(struct tb_tunnel *tunnel) + { + /* +- * Bump up the reference to keep the tunnel around. It will be +- * dropped in tb_dp_dprx_stop() once the tunnel is deactivated. ++ * Bump up the references to keep the tunnel and the domain around ++ * until the work has run or has been canceled. + */ + tb_tunnel_get(tunnel); ++ tb_domain_get(tunnel->tb); + + tunnel->dprx_started = true; ++ tunnel->dprx_canceled = false; ++ tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); ++ queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); + +- if (tunnel->callback) { +- tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); +- queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); +- return -EINPROGRESS; +- } +- +- return tb_dp_is_usb4(tunnel->src_port->sw) ? +- tb_dp_wait_dprx(tunnel, dprx_timeout) : 0; ++ return -EINPROGRESS; + } + + static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) + { ++ struct tb *tb = tunnel->tb; ++ + if (tunnel->dprx_started) { + tunnel->dprx_started = false; + tunnel->dprx_canceled = true; +- if (cancel_delayed_work(&tunnel->dprx_work)) ++ if (cancel_delayed_work(&tunnel->dprx_work)) { + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); ++ } + } + } + +@@ -1582,20 +1590,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) + * @tb: Pointer to the domain structure + * @in: DP in adapter + * @alloc_hopid: Allocate HopIDs from visited ports ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * If @in adapter is active, follows the tunnel to the DP out adapter + * and back. Returns the discovered tunnel or %NULL if there was no +- * tunnel. ++ * tunnel. See tb_tunnel_alloc_dp() for @callback. + * + * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. + */ + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid) ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data) + { + struct tb_tunnel *tunnel; + struct tb_port *port; + struct tb_path *path; + ++ if (WARN_ON(!callback)) ++ return NULL; ++ + if (!tb_dp_port_is_enabled(in)) + return NULL; + +@@ -1611,6 +1627,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; + tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; + tunnel->src_port = in; ++ tunnel->callback = callback; ++ tunnel->callback_data = callback_data; ++ INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work); + + path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1, + &tunnel->dst_port, "Video", alloc_hopid); +@@ -1656,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + + tb_dp_dump(tunnel); + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1677,16 +1697,16 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + * %0 if no available bandwidth. + * @max_down: Maximum available downstream bandwidth for the DP tunnel. + * %0 if no available bandwidth. +- * @callback: Optional callback that is called when the DP tunnel is +- * fully activated (or there is an error) +- * @callback_data: Optional data for @callback ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * Allocates a tunnel between @in and @out that is capable of tunneling +- * Display Port traffic. If @callback is not %NULL it will be called +- * after tb_tunnel_activate() once the tunnel has been fully activated. +- * It can call tb_tunnel_is_active() to check if activation was +- * successful (or if it returns %false there was some sort of issue). +- * The @callback is called without @tb->lock held. ++ * Display Port traffic. The @callback is called after tb_tunnel_activate() ++ * once the tunnel has been fully activated. It can call ++ * tb_tunnel_is_active() to check if activation was successful (or if it ++ * returns %false there was some sort of issue). The @callback is called ++ * without @tb->lock held. + * + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. + */ +@@ -1701,7 +1721,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_path *path; + bool pm_support; + +- if (WARN_ON(!in->cap_adap || !out->cap_adap)) ++ if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback)) + return NULL; + + tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); +@@ -2288,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, + tb_usb3_reclaim_available_bandwidth; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h +--- a/drivers/thunderbolt/tunnel.h ++++ b/drivers/thunderbolt/tunnel.h +@@ -66,8 +66,8 @@ enum tb_tunnel_state { + * @dprx_canceled: Was DPRX capabilities read poll canceled + * @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes + * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read +- * @callback: Optional callback called when DP tunnel is fully activated +- * @callback_data: Optional data for @callback ++ * @callback: Callback called when DP tunnel is fully activated ++ * @callback_data: Data for @callback + * @paths: All paths required by the tunnel + */ + struct tb_tunnel { +@@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, + bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, + int *reserved_down); + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid); ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data); + struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_port *out, int link_nr, + int max_up, int max_down, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1814,7 +1814,6 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; +- tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -22,6 +22,7 @@ struct device; + #include + #include + #include ++#include + #include + #include + #include +@@ -565,6 +566,7 @@ struct tb_nhi { + * @interval_nsec: Interval counter if interrupt throttling is to be + * used with this ring (in ns) + * @wait: Used to signal that the ring may be empty now ++ * @lock_key: Lock validator class key per-ring + */ + struct tb_ring { + spinlock_t lock; +@@ -590,6 +592,7 @@ struct tb_ring { + void *poll_data; + unsigned int interval_nsec; + wait_queue_head_t wait; ++ struct lock_class_key lock_key; + }; + + /* Leave ring interrupt enabled on suspend */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0602-thunderbolt-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0602-thunderbolt-fixes.patch.sig new file mode 100644 index 0000000..2c4aa9c Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0602-thunderbolt-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0603-usb-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0603-usb-updates.patch new file mode 100644 index 0000000..171a4b4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0603-usb-updates.patch @@ -0,0 +1,5629 @@ +diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c +--- a/drivers/power/supply/power_supply_core.c ++++ b/drivers/power/supply/power_supply_core.c +@@ -477,6 +477,159 @@ struct power_supply *power_supply_get_by_name(const char *name) + } + EXPORT_SYMBOL_GPL(power_supply_get_by_name); + ++static bool power_supply_is_system_battery(struct power_supply *psy) ++{ ++ union power_supply_propval val; ++ ++ if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) ++ return false; ++ ++ if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE, ++ &val)) ++ if (val.intval == POWER_SUPPLY_SCOPE_DEVICE) ++ return false; ++ ++ return true; ++} ++ ++static int __power_supply_get_num_system_batteries(struct power_supply *epsy, ++ void *data) ++{ ++ int *count = data; ++ ++ if (power_supply_is_system_battery(epsy)) ++ (*count)++; ++ ++ return 0; ++} ++ ++static int power_supply_get_num_system_batteries(struct device *dev) ++{ ++ int ret, count = 0; ++ ++ ret = power_supply_for_each_psy(&count, ++ __power_supply_get_num_system_batteries); ++ ++ dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret); ++ ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++struct psy_get_supplies_data { ++ int cnt; ++ int size; ++ struct power_supply **psys; ++}; ++ ++static int ++__power_supply_populate_system_batteries_array(struct power_supply *epsy, ++ void *_data) ++{ ++ struct psy_get_supplies_data *data = _data; ++ ++ if (power_supply_is_system_battery(epsy)) { ++ if (data->size <= data->cnt) ++ return -EOVERFLOW; ++ ++ get_device(&epsy->dev); ++ data->psys[data->cnt] = epsy; ++ atomic_inc(&epsy->use_cnt); ++ data->cnt++; ++ } ++ ++ return 0; ++} ++ ++static int ++power_supply_populate_system_batteries_array(struct device *dev, int size, ++ struct power_supply **batteries) ++{ ++ int ret; ++ ++ struct psy_get_supplies_data data = { ++ .cnt = 0, ++ .size = size, ++ .psys = batteries, ++ }; ++ ++ ret = power_supply_for_each_psy(&data, ++ __power_supply_populate_system_batteries_array); ++ ++ dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n", ++ __func__, data.cnt, data.size, ret); ++ ++ if (ret < 0 || !data.cnt) { ++ power_supply_put_system_batteries(batteries, data.cnt); ++ return ret; ++ } ++ ++ return data.cnt; ++} ++ ++/** ++ * power_supply_get_system_batteries() - Fetches references to battery type ++ * power supplies in the system. ++ * @dev: Pointer to device requesting the power supply refs. ++ * @psys: Pointer to an array of power supply refs. ++ * ++ * Helper function to get handles to battery type power supplies in the system. ++ * If acquiring a ref to a power supply fails, then the search for battery type ++ * power supplies will abort and the acquired power supply references will be ++ * released. ++ * ++ * Return: Indicates the number of battery type power supplies returned on ++ * success or a negative error code on failure. ++ * ++ * Call power_supply_put_system_batteries() after use to cleanup resources. ++ */ ++int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ int ret; ++ ++ if (!psys) ++ return -EINVAL; ++ ++ ret = power_supply_get_num_system_batteries(dev); ++ if (ret <= 0) { ++ *psys = NULL; ++ return ret; ++ } ++ ++ *psys = kzalloc_objs(**psys, ret); ++ if (!*psys) ++ return -ENOMEM; ++ ++ ret = power_supply_populate_system_batteries_array(dev, ret, *psys); ++ if (ret <= 0) ++ *psys = NULL; ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(power_supply_get_system_batteries); ++ ++/** ++ * power_supply_put_system_batteries() - Cleanup resources allocated by ++ * power_supply_get_system_batteries() ++ * @psys: Array of power supply references to release and free. ++ * @count: Number of elements in the array. ++ */ ++void power_supply_put_system_batteries(struct power_supply **psys, int count) ++{ ++ int i; ++ ++ for (i = 0; i < count; i++) { ++ if (psys[i]) ++ power_supply_put(psys[i]); ++ } ++ ++ kfree(psys); ++} ++EXPORT_SYMBOL_GPL(power_supply_put_system_batteries); ++ + /** + * power_supply_put() - Drop reference obtained with power_supply_get_by_name + * @psy: Reference to put +diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c +--- a/drivers/usb/cdns3/cdns3-plat.c ++++ b/drivers/usb/cdns3/cdns3-plat.c +@@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev) + cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); + if (IS_ERR(cdns->usb2_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy), +- "Failed to get cdn3,usb2-phy\n"); ++ "Failed to get cdns3,usb2-phy\n"); + + cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); + if (IS_ERR(cdns->usb3_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy), +- "Failed to get cdn3,usb3-phy\n"); ++ "Failed to get cdns3,usb3-phy\n"); + + ret = phy_init(cdns->usb2_phy); + if (ret) +diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c +--- a/drivers/usb/core/config.c ++++ b/drivers/usb/core/config.c +@@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, + usb_endpoint_xfer_int(&ep->desc)) && + desc->bmAttributes != 0) { + dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", +- usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", ++ usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt", + desc->bmAttributes, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 0; +diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c +--- a/drivers/usb/core/devices.c ++++ b/drivers/usb/core/devices.c +@@ -37,6 +37,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + return 0; + /* allocate 2^1 pages = 8K (on i386); + * should be more than enough for one device */ +- pages_start = (char *)__get_free_pages(GFP_NOIO, 1); ++ pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO); + if (!pages_start) + return -ENOMEM; + +@@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + if (length > *nbytes) + length = *nbytes; + if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + return -EFAULT; + } + *nbytes -= length; +@@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + } else + *skip_bytes -= length; + +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + + /* Now look at all of this device's children. */ + usb_hub_for_each_child(usbdev, chix, childdev) { +diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c +--- a/drivers/usb/core/devio.c ++++ b/drivers/usb/core/devio.c +@@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + if (!connected(ps)) + return -ENODEV; + ++ if (ps->dev->state != USB_STATE_CONFIGURED) ++ return -EHOSTUNREACH; ++ ++ intf = usb_ifnum_to_if(ps->dev, ctl->ifno); ++ if (!intf) ++ return -EINVAL; ++ + /* alloc buffer */ + size = _IOC_SIZE(ctl->ioctl_code); + if (size > 0) { +@@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + } + } + +- if (ps->dev->state != USB_STATE_CONFIGURED) +- retval = -EHOSTUNREACH; +- else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno))) +- retval = -EINVAL; +- else switch (ctl->ioctl_code) { ++ switch (ctl->ioctl_code) { + + /* disconnect kernel driver from interface */ + case USBDEVFS_DISCONNECT: +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, + mutex_lock(&hub->status_mutex); + ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len); + if (ret < len) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_port_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->port.wPortStatus); + *change = le16_to_cpu(hub->status->port.wPortChange); +@@ -995,11 +995,11 @@ static int hub_hub_status(struct usb_hub *hub, + mutex_lock(&hub->status_mutex); + ret = get_hub_status(hub->hdev, &hub->status->hub); + if (ret < (int)sizeof(hub->status->hub)) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_hub_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->hub.wHubStatus); + *change = le16_to_cpu(hub->status->hub.wHubChange); +diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c +--- a/drivers/usb/dwc2/core_intr.c ++++ b/drivers/usb/dwc2/core_intr.c +@@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) + + /** + * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state +- * + * @hsotg: Programming view of DWC_otg controller +- * ++ * @remotewakeup: Whether this is a remote wakeup + */ + void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup) + { +diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c +--- a/drivers/usb/dwc3/core.c ++++ b/drivers/usb/dwc3/core.c +@@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work) + ret, dwc->current_limit); + } + +-static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) ++static int dwc3_psy_notifier(struct notifier_block *nb, ++ unsigned long event, void *data) + { +- struct power_supply *usb_psy; +- const char *usb_psy_name; ++ struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); ++ struct power_supply *psy = data; ++ unsigned long flags; ++ ++ if (dwc->usb_psy) ++ return NOTIFY_DONE; ++ ++ if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) ++ return NOTIFY_DONE; ++ ++ /* Explicitly get the reference for this psy */ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return NOTIFY_DONE; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * The USB power_supply may already be set. This can happen if notifier ++ * callbacks for the USB power_supply race, or if a previous notifier ++ * callback has already successfully fetched and associated the instance. ++ * In such cases, release the newly acquired reference and ignore ++ * subsequent notifications until the notifier is unregistered. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return NOTIFY_DONE; ++ } ++ ++ dwc->usb_psy = psy; ++ if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) ++ schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ return NOTIFY_OK; ++} ++ ++static void dwc3_get_usb_power_supply(struct dwc3 *dwc) ++{ ++ struct power_supply *psy; ++ unsigned long flags; + int ret; + +- ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name); ++ ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); + if (ret < 0) +- return NULL; +- +- usb_psy = power_supply_get_by_name(usb_psy_name); +- if (!usb_psy) +- return ERR_PTR(-EPROBE_DEFER); ++ return; + + INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); +- return usb_psy; ++ ++ dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; ++ dwc->psy_nb.notifier_call = dwc3_psy_notifier; ++ ret = power_supply_reg_notifier(&dwc->psy_nb); ++ if (ret) { ++ dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); ++ dwc->psy_nb.notifier_call = NULL; ++ return; ++ } ++ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return; ++ ++ /* Unregister the notifier now that we have the power supply */ ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ dwc->psy_nb.notifier_call = NULL; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * It is possible that the notifier callback ran before we reached here ++ * and successfully fetched the power supply. In that case we need to ++ * release the above reference. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return; ++ } ++ ++ dwc->usb_psy = psy; ++ spin_unlock_irqrestore(&dwc->lock, flags); + } + + int dwc3_core_probe(const struct dwc3_probe_data *data) +@@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + + dwc3_get_software_properties(dwc, &data->properties); + +- dwc->usb_psy = dwc3_get_usb_power_supply(dwc); +- if (IS_ERR(dwc->usb_psy)) +- return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); ++ spin_lock_init(&dwc->lock); ++ ++ dwc3_get_usb_power_supply(dwc); + + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); +@@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + dwc->num_usb3_ports = 1; + } + +- spin_lock_init(&dwc->lock); + mutex_init(&dwc->mutex); + + pm_runtime_get_noresume(dev); +@@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + err_assert_reset: + reset_control_assert(dwc->reset); + err_put_psy: ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) + power_supply_put(dwc->usb_psy); + +@@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc) + + dwc3_free_event_buffers(dwc); + ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ + if (dwc->usb_psy) { + cancel_work_sync(&dwc->vbus_draw_work); + power_supply_put(dwc->usb_psy); +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -722,7 +722,6 @@ struct dwc3_event_buffer { + * @cancelled_list: list of cancelled requests for this endpoint + * @pending_list: list of pending requests for this endpoint + * @started_list: list of started requests on this endpoint +- * @regs: pointer to first endpoint register + * @trb_pool: array of transaction buffers + * @trb_pool_dma: dma address of @trb_pool + * @trb_enqueue: enqueue 'pointer' into TRB array +@@ -1059,6 +1058,8 @@ struct dwc3_glue_ops { + * @role_switch_default_mode: default operation mode of controller while + * usb role is USB_ROLE_NONE. + * @usb_psy: pointer to power supply interface. ++ * @usb_psy_name: name of the USB power supply ++ * @psy_nb: power supply notifier block + * @vbus_draw_work: Work to set the vbus drawing limit + * @current_limit: How much current to draw from vbus, in milliAmperes. + * @usb2_phy: pointer to USB2 PHY +@@ -1251,9 +1252,13 @@ struct dwc3 { + enum usb_dr_mode role_switch_default_mode; + + struct power_supply *usb_psy; ++ const char *usb_psy_name; ++ struct notifier_block psy_nb; + struct work_struct vbus_draw_work; + unsigned int current_limit; + ++#define DWC3_CURRENT_UNSPECIFIED UINT_MAX ++ + u32 fladj; + u32 ref_clk_per; + u32 irq_gadget; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62) + + dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); + +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + + /* Set mode valid bit to indicate role is valid */ + reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); +@@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev) + { + struct dwc3_am62 *am62 = dev_get_drvdata(dev); + u32 reg; ++ int ret; + + reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); + if (reg != USBSS_DEBUG_CFG_DISABLED) { + /* lost power/context */ +- dwc3_ti_init(am62); ++ ret = dwc3_ti_init(am62); ++ if (ret) ++ return ret; + } else { + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + } + + if (device_may_wakeup(dev)) { +diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c +--- a/drivers/usb/dwc3/dwc3-qcom.c ++++ b/drivers/usb/dwc3/dwc3-qcom.c +@@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) + pm_runtime_mark_last_busy(qcom->dev); + } + +-struct dwc3_glue_ops dwc3_qcom_glue_ops = { ++static struct dwc3_glue_ops dwc3_qcom_glue_ops = { + .pre_set_role = dwc3_qcom_set_role_notifier, + .pre_run_stop = dwc3_qcom_run_stop_notifier, + }; +diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c +--- a/drivers/usb/dwc3/dwc3-xilinx.c ++++ b/drivers/usb/dwc3/dwc3-xilinx.c +@@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) + + dwc3_xlnx_mask_phy_rst(priv_data, false); + +- /* Assert and De-assert reset */ +- ret = reset_control_assert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to assert Reset\n"); +- return ret; +- } +- +- ret = reset_control_deassert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to De-assert Reset\n"); +- return ret; +- } ++ /* assert and deassert reset */ ++ ret = reset_control_reset(crst); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to assert and deassert reset\n"); + + dwc3_xlnx_mask_phy_rst(priv_data, true); + dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG); +@@ -194,7 +186,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) + } + + if (priv_data->usb3_phy) { +- /* Set PIPE Power Present signal in FPD Power Present Register*/ ++ /* Set PIPE Power Present signal in FPD Power Present Register */ + writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT); + /* Set the PIPE Clock Select bit in FPD PIPE Clock register */ + writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -3129,15 +3129,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, + static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) + { + struct dwc3 *dwc = gadget_to_dwc(g); ++ unsigned long flags; + + if (dwc->usb2_phy) + return usb_phy_set_power(dwc->usb2_phy, mA); + +- if (!dwc->usb_psy) +- return -EOPNOTSUPP; ++ spin_lock_irqsave(&dwc->lock, flags); ++ if (!dwc->usb_psy) { ++ if (!dwc->psy_nb.notifier_call) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -EOPNOTSUPP; ++ } ++ dwc->current_limit = mA; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); ++ return 0; ++ } + + dwc->current_limit = mA; + schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; + } +diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c +--- a/drivers/usb/dwc3/host.c ++++ b/drivers/usb/dwc3/host.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + #include "../host/xhci-port.h" + #include "../host/xhci-ext-caps.h" +@@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) + return; + } + +- op_regs_base = HC_LENGTH(readl(xhci_regs)); ++ op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs)); + reg = readl(xhci_regs + XHCI_HCSPARAMS1); +- port_num = HCS_MAX_PORTS(reg); ++ port_num = FIELD_GET(HCS_MAX_PORTS, reg); + + for (i = 1; i <= port_num; i++) { + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); +diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c +--- a/drivers/usb/gadget/configfs.c ++++ b/drivers/usb/gadget/configfs.c +@@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy) + if (copy) { + str = copy; + } else { +- str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); ++ str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); + if (!str) + return -ENOMEM; + } +- strcpy(str, s); ++ memcpy(str, s, ret + 1); + if (str[ret - 1] == '\n') + str[ret - 1] = '\0'; + *s_copy = str; +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -159,7 +159,9 @@ struct ffs_epfile { + struct mutex mutex; + + struct ffs_data *ffs; +- struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ + + /* + * Buffer for holding data from partial reads which may happen since +@@ -219,12 +221,13 @@ struct ffs_epfile { + struct ffs_buffer *read_buffer; + #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) + +- char name[5]; ++ char name[8]; + + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + +- unsigned char _pad; ++ u8 zlp_enabled; /* P: ffs->eps_lock */ ++ bool is_rw_proxy; + + /* Protects dmabufs */ + struct mutex dmabufs_mutex; +@@ -991,9 +994,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, + return ret; + } + +-static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) ++static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) + { +- struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; + +@@ -1020,17 +1022,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + char *data = NULL; + ssize_t ret, data_len = -EINVAL; + int halt; ++ bool is_rw_proxy = epfile->is_rw_proxy; + + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + +- ep = ffs_epfile_wait_ep(file); ++ /* Proxy to base endpoint if rw_proxy */ ++ if (is_rw_proxy) ++ epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; ++ ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + /* Do we halt? */ +- halt = (!io_data->read == !epfile->in); ++ halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); + if (halt && epfile->isoc) + return -EINVAL; + +@@ -1127,6 +1134,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1178,6 +1187,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1666,7 +1677,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + priv = attach->importer_priv; + +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + goto err_attachment_put; +@@ -1732,6 +1743,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + /* Now that the dma_fence is in place, queue the transfer. */ + ++ usb_req->zero = epfile->zlp_enabled; + usb_req->length = req->length; + usb_req->buf = NULL; + usb_req->sg = priv->sgt->sgl; +@@ -1779,10 +1791,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; ++ __u32 enable_zlp = 0; + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + ++ if (epfile->is_rw_proxy) ++ return -ENOTTY; ++ + switch (code) { + case FUNCTIONFS_DMABUF_ATTACH: + { +@@ -1811,12 +1827,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + + return ffs_dmabuf_transfer(file, &req); + } ++ /* ++ * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace ++ * to configure ZLP behavior immediately without blocking indefinitely ++ * while waiting for the USB host to connect and enable the endpoint. ++ */ ++ case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: ++ if (!epfile->in) ++ return -EINVAL; ++ ++ if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) ++ return -EFAULT; ++ ++ spin_lock_irq(&epfile->ffs->eps_lock); ++ epfile->zlp_enabled = !!enable_zlp; ++ spin_unlock_irq(&epfile->ffs->eps_lock); ++ ++ return 0; + default: + break; + } + + /* Wait for endpoint to be enabled */ +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + +@@ -2214,7 +2247,7 @@ static void ffs_data_closed(struct ffs_data *ffs) + + if (epfiles) + ffs_epfiles_destroy(ffs->sb, epfiles, +- ffs->eps_count); ++ ffs->epfiles_count); + + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); +@@ -2273,7 +2306,7 @@ static void ffs_data_clear(struct ffs_data *ffs) + * copy of epfile will save us from use-after-free. + */ + if (epfiles) { +- ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); ++ ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); + ffs->epfiles = NULL; + } + +@@ -2371,11 +2404,16 @@ static void functionfs_unbind(struct ffs_data *ffs) + static int ffs_epfiles_create(struct ffs_data *ffs) + { + struct ffs_epfile *epfile, *epfiles; +- unsigned i, count; ++ unsigned int i, count, epfiles_count; + int err; + + count = ffs->eps_count; +- epfiles = kzalloc_objs(*epfiles, count); ++ epfiles_count = count; ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) ++ epfiles_count += count / 2; ++ ffs->epfiles_count = epfiles_count; ++ ++ epfiles = kzalloc_objs(*epfiles, epfiles_count); + if (!epfiles) + return -ENOMEM; + +@@ -2398,6 +2436,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs) + } + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ struct ffs_epfile *comp = epfiles + count; ++ ++ for (i = 0; i < count; i += 2, ++comp) { ++ struct ffs_epfile *ep1 = &epfiles[i]; ++ struct ffs_epfile *ep2 = &epfiles[i + 1]; ++ bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; ++ ++ comp->ffs = ffs; ++ comp->is_rw_proxy = true; ++ comp->epfile_in = ep1_in ? ep1 : ep2; ++ comp->epfile_out = ep1_in ? ep2 : ep1; ++ mutex_init(&comp->mutex); ++ mutex_init(&comp->dmabufs_mutex); ++ INIT_LIST_HEAD(&comp->dmabufs); ++ snprintf(comp->name, sizeof(comp->name), "%s_rw", ++ epfiles[i].name); ++ err = ffs_sb_create_file(ffs->sb, comp->name, ++ comp, &ffs_epfile_operations); ++ if (err) { ++ ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); ++ return err; ++ } ++ } ++ } ++ + ffs->epfiles = epfiles; + return 0; + } +@@ -2975,7 +3039,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + FUNCTIONFS_VIRTUAL_ADDR | + FUNCTIONFS_EVENTFD | + FUNCTIONFS_ALL_CTRL_RECIP | +- FUNCTIONFS_CONFIG0_SETUP)) { ++ FUNCTIONFS_CONFIG0_SETUP | ++ FUNCTIONFS_RW_PROXY_EPS)) { + ret = -ENOSYS; + goto error; + } +@@ -3063,6 +3128,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + goto error; + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ if (ffs->eps_count % 2) { ++ ret = -EINVAL; ++ goto error; ++ } ++ ++ for (i = 1; i < ffs->eps_count; i += 2) { ++ if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == ++ (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { ++ ret = -EINVAL; ++ goto error; ++ } ++ } ++ } ++ + ffs->raw_descs_data = _data; + ffs->raw_descs = raw_descs; + ffs->raw_descs_length = data - raw_descs; +@@ -3358,7 +3438,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, + struct usb_request *req; + struct usb_ep *ep; + u8 bEndpointAddress; +- u16 wMaxPacketSize; ++ __le16 wMaxPacketSize; + + /* + * We back up bEndpointAddress because autoconfig overwrites +diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c +--- a/drivers/usb/gadget/function/f_mass_storage.c ++++ b/drivers/usb/gadget/function/f_mass_storage.c +@@ -203,7 +203,6 @@ + /*------------------------------------------------------------------------*/ + + #define FSG_DRIVER_DESC "Mass Storage Function" +-#define FSG_DRIVER_VERSION "2009/09/11" + + static const char fsg_string_interface[] = "Mass Storage"; + +@@ -3516,8 +3515,6 @@ static struct usb_function_instance *fsg_alloc_inst(void) + if (rc) + goto release_common; + +- pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); +- + memset(&config, 0, sizeof(config)); + config.removable = true; + rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0", +diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c +--- a/drivers/usb/gadget/function/u_audio.c ++++ b/drivers/usb/gadget/function/u_audio.c +@@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev) + ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } + +- prm->fb_ep_enabled = true; + ret = usb_ep_enable(ep_fback); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } ++ prm->fb_ep_enabled = true; + req_len = ep_fback->maxpacket; + + req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); +@@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + + return 0; ++ ++err_out_ep: ++ set_active(prm, false); ++ free_ep(prm, ep); ++ ++ return ret; + } + EXPORT_SYMBOL_GPL(u_audio_start_capture); + +diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h +--- a/drivers/usb/gadget/function/u_fs.h ++++ b/drivers/usb/gadget/function/u_fs.h +@@ -252,8 +252,14 @@ struct ffs_data { + + unsigned short strings_count; + unsigned short interfaces_count; ++ ++ /* ++ * eps_count tracks the number of underlying hardware endpoints. ++ * epfiles_count tracks the total number of VFS endpoint files. ++ * When companion endpoints are active, epfiles_count > eps_count. ++ */ + unsigned short eps_count; +- unsigned short _pad1; ++ unsigned short epfiles_count; + + /* filled by __ffs_data_got_strings() */ + /* ids in stringtabs are set in functionfs_bind() */ +diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c +--- a/drivers/usb/gadget/legacy/inode.c ++++ b/drivers/usb/gadget/legacy/inode.c +@@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) + struct kiocb *iocb = priv->iocb; + size_t ret; + +- kthread_use_mm(mm); +- ret = copy_to_iter(priv->buf, priv->actual, &priv->to); +- kthread_unuse_mm(mm); +- if (!ret) ++ if (mmget_not_zero(mm)) { ++ kthread_use_mm(mm); ++ ret = copy_to_iter(priv->buf, priv->actual, &priv->to); ++ kthread_unuse_mm(mm); ++ mmput(mm); ++ if (!ret) ++ ret = -EFAULT; ++ } else { + ret = -EFAULT; ++ } ++ mmdrop(mm); + + /* completing the iocb can drop the ctx and mm, don't touch mm after */ + iocb->ki_complete(iocb, ret); +@@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) + * complete the aio request immediately. + */ + if (priv->to_free == NULL || unlikely(req->actual == 0)) { ++ mmdrop(priv->mm); + kfree(req->buf); + kfree(priv->to_free); + kfree(priv); +@@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + priv->epdata = epdata; + priv->actual = 0; + priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ ++ mmgrab(priv->mm); + + /* each kiocb is coupled to one usb_request, but we can't + * allocate or submit those if the host disconnected. +@@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + + fail: + spin_unlock_irq(&epdata->dev->lock); ++ mmdrop(priv->mm); + kfree(priv->to_free); + kfree(priv); + put_ep(epdata); +diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c +--- a/drivers/usb/gadget/legacy/nokia.c ++++ b/drivers/usb/gadget/legacy/nokia.c +@@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; + */ + #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS + +-#endif /* CONFIG_USB_DEBUG */ ++#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + + FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig +--- a/drivers/usb/gadget/udc/Kconfig ++++ b/drivers/usb/gadget/udc/Kconfig +@@ -90,7 +90,7 @@ config USB_BCM63XX_UDC + + config USB_FSL_USB2 + tristate "Freescale Highspeed USB DR Peripheral Controller" +- depends on FSL_SOC ++ depends on FSL_SOC || COMPILE_TEST + help + Some of Freescale PowerPC and i.MX processors have a High Speed + Dual-Role(DR) USB controller, which supports device mode. +diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c +--- a/drivers/usb/gadget/udc/fsl_qe_udc.c ++++ b/drivers/usb/gadget/udc/fsl_qe_udc.c +@@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) + { + u16 usb_status = 0; ++ struct usb_request *usb_req; + struct qe_req *req; + struct qe_ep *ep; + int status = 0; +@@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + } + } + +- req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL), +- struct qe_req, req); ++ usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL); ++ if (!usb_req) ++ goto stall; ++ ++ req = container_of(usb_req, struct qe_req, req); + req->req.length = 2; + req->req.buf = udc->statusbuf; + *(u16 *)req->req.buf = cpu_to_le16(usb_status); +diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c +--- a/drivers/usb/gadget/udc/pch_udc.c ++++ b/drivers/usb/gadget/udc/pch_udc.c +@@ -320,7 +320,6 @@ struct pch_vbus_gpio_data { + * @lock: protects all state + * @stall: stall requested + * @prot_stall: protcol stall requested +- * @registered: driver registered with system + * @suspended: driver in suspended state + * @connected: gadget driver associated + * @vbus_session: required vbus_session state +diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c +--- a/drivers/usb/host/fsl-mph-dr-of.c ++++ b/drivers/usb/host/fsl-mph-dr-of.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + struct fsl_usb2_dev_data { + char *dr_mode; /* controller mode */ +@@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + const struct of_device_id *match; + const unsigned char *prop; + static unsigned int idx; +- int i; ++ int i, err; + + if (!of_device_is_available(np)) + return -ENODEV; +@@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + } + } + ++ err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus"); ++ if (err) ++ return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n"); ++ + for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { + if (!dev_data->drivers[i]) + continue; +diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c +--- a/drivers/usb/host/ohci-dbg.c ++++ b/drivers/usb/host/ohci-dbg.c +@@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf) + int ret; + + if (!buf->page) +- buf->page = (char *)get_zeroed_page(GFP_KERNEL); ++ buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL); + + if (!buf->page) { + ret = -ENOMEM; +@@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file) + { + struct debug_buffer *buf = file->private_data; + +- if (buf) { +- if (buf->page) +- free_page((unsigned long)buf->page); +- kfree(buf); +- } ++ kfree(buf->page); ++ kfree(buf); + + return 0; + } +diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h +--- a/drivers/usb/host/xhci-caps.h ++++ b/drivers/usb/host/xhci-caps.h +@@ -5,22 +5,23 @@ + */ + + #include ++#include + + /* hc_capbase - bitmasks */ + /* bits 7:0 - Capability Registers Length */ +-#define HC_LENGTH(p) ((p) & 0xff) ++#define HC_LENGTH GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - Host Controller Interface Version Number */ +-#define HC_VERSION(p) (((p) >> 16) & 0xffff) ++#define HC_VERSION GENMASK(31, 16) + + /* HCSPARAMS1 - hcs_params1 - bitmasks */ + /* bits 7:0 - Number of Device Slots */ +-#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +-#define HCS_SLOTS_MASK 0xff +-/* bits 18:8 - Number of Interrupters, max values is 1024 */ +-#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +-/* bits 31:24, Max Ports - max value is 255 */ +-#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff) ++#define HCS_SLOTS_MASK GENMASK(7, 0) ++/* bits 18:8 - Number of Interrupters, max values is 1024 */ ++#define HCS_MAX_INTRS GENMASK(18, 8) ++/* bits 23:19 - Rsvd */ ++/* bits 31:24 - Max Ports, max values is 255 */ ++#define HCS_MAX_PORTS GENMASK(31, 24) + + /* HCSPARAMS2 - hcs_params2 - bitmasks */ + /* +@@ -33,24 +34,25 @@ + * Note: 1 Frame = 8 Microframes + * xHCI specification section 5.3.4. + */ +-#define HCS_IST_VALUE(p) ((p) & 0x7) ++#define HCS_IST_VALUE GENMASK(2, 0) + #define HCS_IST_UNIT BIT(3) + /* bits 7:4 - Event Ring Segment Table Max, 2^(n) */ +-#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) ++#define HCS_ERST_MAX GENMASK(7, 4) + /* bits 20:8 - Rsvd */ + /* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */ +-#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f) ++#define HCS_MAX_SP_HI GENMASK(25, 21) + /* bit 26 - Scratchpad restore, for save/restore HW state */ + /* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */ +-#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f) +-#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p)) ++#define HCS_MAX_SP_LO GENMASK(31, 27) ++#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ ++ FIELD_GET(HCS_MAX_SP_LO, (p))) + + /* HCSPARAMS3 - hcs_params3 - bitmasks */ + /* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */ +-#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) ++#define HCS_U1_LATENCY GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */ +-#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) ++#define HCS_U2_LATENCY GENMASK(31, 16) + + /* HCCPARAMS1 - hcc_params - bitmasks */ + /* bit 0 - 64-bit Addressing Capability */ +@@ -77,19 +79,20 @@ + /* bit 11 - Contiguous Frame ID Capability */ + #define HCC_CFC BIT(11) + /* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */ +-#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) ++#define HCC_MAX_PSA GENMASK(15, 12) ++#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1)) + /* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */ +-#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff) ++#define HCC_EXT_CAPS GENMASK(31, 16) + + /* DBOFF - db_off - bitmasks */ + /* bits 1:0 - Rsvd */ + /* bits 31:2 - Doorbell Array Offset */ +-#define DBOFF_MASK (0xfffffffc) ++#define DBOFF_MASK GENMASK(31, 2) + + /* RTSOFF - run_regs_off - bitmasks */ + /* bits 4:0 - Rsvd */ + /* bits 31:5 - Runtime Register Space Offse */ +-#define RTSOFF_MASK (~0x1f) ++#define RTSOFF_MASK GENMASK(31, 5) + + /* HCCPARAMS2 - hcc_params2 - bitmasks */ + /* bit 0 - U3 Entry Capability */ +diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c +--- a/drivers/usb/host/xhci-dbgtty.c ++++ b/drivers/usb/host/xhci-dbgtty.c +@@ -665,11 +665,11 @@ int dbc_tty_init(void) + + void dbc_tty_exit(void) + { +- if (dbc_tty_driver) { +- tty_unregister_driver(dbc_tty_driver); +- tty_driver_kref_put(dbc_tty_driver); +- dbc_tty_driver = NULL; +- } ++ if (IS_ERR_OR_NULL(dbc_tty_driver)) ++ return; + ++ tty_unregister_driver(dbc_tty_driver); ++ tty_driver_kref_put(dbc_tty_driver); + idr_destroy(&dbc_tty_minors); ++ dbc_tty_driver = NULL; + } +diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c +--- a/drivers/usb/host/xhci-debugfs.c ++++ b/drivers/usb/host/xhci-debugfs.c +@@ -9,6 +9,7 @@ + + #include + #include ++#include + + #include "xhci.h" + #include "xhci-debugfs.h" +@@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) + xhci->debugfs_root, "reg-cap"); + + xhci_debugfs_regset(xhci, +- HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)), ++ FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), + xhci_op_regs, ARRAY_SIZE(xhci_op_regs), + xhci->debugfs_root, "reg-op"); + +diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c +--- a/drivers/usb/host/xhci-histb.c ++++ b/drivers/usb/host/xhci-histb.c +@@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev) + if (ret) + goto put_usb3_hcd; + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c +--- a/drivers/usb/host/xhci-hub.c ++++ b/drivers/usb/host/xhci-hub.c +@@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, + + if ((xhci->quirks & XHCI_LPM_SUPPORT)) { + reg = readl(&xhci->cap_regs->hcs_params3); +- ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg); +- ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg)); ++ ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg); ++ ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg)); + } + + if (wLength < le16_to_cpu(bos->wTotalLength)) +@@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + } + /* In spec software should not attempt to suspend + * a port unless the port reports that it is in the +- * enabled (PED = ‘1’,PLS < ‘3’) state. ++ * enabled (PED = '1',PLS < '3') state. + */ + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || +diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1001,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, + for (i = 0; i < 31; i++) { + dev->eps[i].ep_index = i; + dev->eps[i].vdev = dev; +- dev->eps[i].xhci = xhci; + INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); + INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); + } +@@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, + return -ENOMEM; + + virt_dev->eps[ep_index].skip = false; ++ virt_dev->eps[ep_index].next_uframe = -1; + ep_ring = virt_dev->eps[ep_index].new_ring; + xhci_ring_init(xhci, ep_ring); + +@@ -2083,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, + addr, port_offset, port_count, major_revision); + /* Port count includes the current port offset */ + if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) +- /* WTF? "Valid values are ‘1’ to MaxPorts" */ ++ /* WTF? "Valid values are '1' to MaxPorts" */ + return; + + port_cap = &xhci->port_caps[xhci->num_port_caps++]; +@@ -2300,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) + if (!segs) + segs = ERST_DEFAULT_SEGS; + +- max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2)); ++ max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c +--- a/drivers/usb/host/xhci-mtk.c ++++ b/drivers/usb/host/xhci-mtk.c +@@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) + * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, + * and it's 3 when support it. + */ +- if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4) ++ if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4) + xhci->quirks |= XHCI_BROKEN_STREAMS; + } + +@@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c +--- a/drivers/usb/host/xhci-pci.c ++++ b/drivers/usb/host/xhci-pci.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && ++ GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; + + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ +diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c +--- a/drivers/usb/host/xhci-plat.c ++++ b/drivers/usb/host/xhci-plat.c +@@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -57,6 +57,8 @@ + #include + #include + #include ++#include ++ + #include "xhci.h" + #include "xhci-trace.h" + +@@ -561,8 +563,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + * pointer command pending because the device can choose to start any + * stream once the endpoint is on the HW schedule. + */ +- if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) || +- (ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT)) ++ if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED | ++ EP_CLEARING_TT | EP_DROP_PENDING)) + return; + + trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); +@@ -573,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + } + + /* Ring the doorbell for any rings with pending URBs */ +-static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) ++void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id, ++ unsigned int ep_index) + { + unsigned int stream_id; + struct xhci_virt_ep *ep; +@@ -598,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, + } + } + +-void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) +-{ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); +-} +- + static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +@@ -686,112 +680,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, + return le64_to_cpu(ep_ctx->deq); + } + +-static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, +- unsigned int slot_id, unsigned int ep_index, +- unsigned int stream_id, struct xhci_td *td) ++/* ++ * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or ++ * to enqueue if no TDs are queued (empty ring) ++ * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list ++ * before calling this function ++ */ ++static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci, ++ struct xhci_virt_ep *ep, ++ unsigned int stream_id) + { +- struct xhci_virt_device *dev = xhci->devs[slot_id]; +- struct xhci_virt_ep *ep = &dev->eps[ep_index]; +- struct xhci_ring *ep_ring; + struct xhci_command *cmd; +- struct xhci_segment *new_seg; +- union xhci_trb *new_deq; +- int new_cycle; ++ struct xhci_ring *ring; ++ struct xhci_td *td; + dma_addr_t addr; +- u64 hw_dequeue; +- bool hw_dequeue_found = false; +- bool td_last_trb_found = false; ++ int new_cycle; + u32 trb_sct = 0; +- int ret; ++ int ret = 0; + +- ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, +- ep_index, stream_id); +- if (!ep_ring) { ++ ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); ++ if (!ring) { + xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", + stream_id); + return -ENODEV; + } + +- hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; +- new_seg = ep_ring->deq_seg; +- new_deq = ep_ring->dequeue; +- new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; +- +- /* +- * Walk the ring until both the next TRB and hw_dequeue are found (don't +- * move hw_dequeue back if it went forward due to a HW bug). Cycle state +- * is loaded from a known good TRB, track later toggles to maintain it. +- */ +- do { +- if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) +- == (dma_addr_t)hw_dequeue) { +- hw_dequeue_found = true; +- if (td_last_trb_found) +- break; +- } +- if (new_deq == td->end_trb) +- td_last_trb_found = true; +- +- if (td_last_trb_found && trb_is_link(new_deq) && +- link_trb_toggles_cycle(new_deq)) +- new_cycle ^= 0x1; +- +- next_trb(&new_seg, &new_deq); +- +- /* Search wrapped around, bail out */ +- if (new_deq == ep->ring->dequeue) { +- xhci_err(xhci, "Error: Failed finding new dequeue state\n"); +- return -EINVAL; +- } +- +- } while (!hw_dequeue_found || !td_last_trb_found); +- +- /* Don't update the ring cycle state for the producer (us). */ +- addr = xhci_trb_virt_to_dma(new_seg, new_deq); +- if (addr == 0) { +- xhci_warn(xhci, "Can't find dma of new dequeue ptr\n"); +- xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq); +- return -EINVAL; +- } +- + if ((ep->ep_state & SET_DEQ_PENDING)) { +- xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n", +- &addr); ++ xhci_warn(xhci, "Set TR Deq already pending\n"); + return -EBUSY; + } + + /* This function gets called from contexts where it cannot sleep */ + cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!cmd) { +- xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr); ++ xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n"); + return -ENOMEM; + } + ++ /* ++ * Move dequeue to the beginning of next td, or to enqueue if ring is ++ * empty. Avoid moving dequeue to a link trb (empty ring) as it causes ++ * issues on some hosts. In that case advance the enqueue to next segment ++ * before moving dequeue to it ++ */ ++ ++ if (list_empty(&ring->td_list)) { ++ if (trb_is_link(ring->enqueue)) ++ inc_enq_past_link(xhci, ring, 0); ++ ep->queued_deq_seg = ring->enq_seg; ++ ep->queued_deq_ptr = ring->enqueue; ++ new_cycle = ring->cycle_state; ++ } else { ++ td = list_first_entry(&ring->td_list, struct xhci_td, td_list); ++ ep->queued_deq_seg = td->start_seg; ++ ep->queued_deq_ptr = td->start_trb; ++ new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE; ++ } ++ ++ addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr); ++ if (addr == 0) { ++ xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n", ++ ep->queued_deq_seg, ep->queued_deq_ptr); ++ ret = -EINVAL; ++ goto err_out; ++ } ++ + if (stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + ret = queue_command(xhci, cmd, + lower_32_bits(addr) | trb_sct | new_cycle, + upper_32_bits(addr), +- STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) | +- EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false); +- if (ret < 0) { +- xhci_free_command(xhci, cmd); +- return ret; +- } +- ep->queued_deq_seg = new_seg; +- ep->queued_deq_ptr = new_deq; ++ STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) | ++ EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false); ++ if (ret < 0) ++ goto err_out; + + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); + +- /* Stop the TD queueing code from ringing the doorbell until +- * this command completes. The HC won't set the dequeue pointer +- * if the ring is running, and ringing the doorbell starts the +- * ring running. ++ /* ++ * Stop the TD queueing code from ringing the doorbell until this ++ * command completes. The HC won't set the dequeue pointer if the ring ++ * is running, and ringing the doorbell starts the ring. + */ + ep->ep_state |= SET_DEQ_PENDING; + xhci_ring_cmd_db(xhci); ++ + return 0; ++ ++err_out: ++ xhci_free_command(xhci, cmd); ++ ep->queued_deq_seg = NULL; ++ ep->queued_deq_ptr = NULL; ++ ++ return ret; + } + + /* flip_cycle means flip the cycle bit of all but the first and last TRB. +@@ -950,7 +932,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh + } + + /* Complete the cancelled URBs we unlinked from td_list. */ +-static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) ++static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_ring *ring; + struct xhci_td *td, *tmp_td; +@@ -958,17 +940,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + +- ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ring = xhci_urb_to_transfer_ring(xhci, td->urb); + + if (td->cancel_status == TD_CLEARED) { +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ring, td->status); ++ xhci_td_cleanup(xhci, td, ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } +- if (ep->xhci->xhc_state & XHCI_STATE_DYING) ++ if (xhci->xhc_state & XHCI_STATE_DYING) + return; + } + } +@@ -1002,15 +984,19 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + struct xhci_td *td, + enum xhci_ep_reset_type reset_type) + { ++ struct xhci_port *rhub_port = ep->vdev->rhub_port; + unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +- * Avoid resetting endpoint if link is inactive. Can cause host hang. +- * Device will be reset soon to recover the link so don't do anything ++ * Avoid resetting endpoint if link is inactive or device disonnected. ++ * Can cause host hang. ++ * Device will be reset to recover an inactive link, so don't do anything + */ +- if (ep->vdev->flags & VDEV_PORT_ERROR) ++ if (rhub_port->link_inactive || !rhub_port->connected) { ++ ep->ep_state |= EP_DROP_PENDING; + return -ENODEV; ++ } + + /* add td to cancelled list and let reset ep handler take care of it */ + if (reset_type == EP_HARD_RESET) { +@@ -1047,15 +1033,13 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + * only call this when ring is not in a running state + */ + +-static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) ++static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- struct xhci_hcd *xhci; + struct xhci_td *td = NULL; + struct xhci_td *tmp_td = NULL; + struct xhci_td *cached_td = NULL; + struct xhci_ring *ring; + u64 hw_deq; +- unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +@@ -1065,8 +1049,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (ep->ep_state & SET_DEQ_PENDING) + return 0; + +- xhci = ep->xhci; +- + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", +@@ -1080,6 +1062,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + td->urb, td->urb->stream_id); + continue; + } ++ ++ /* device disconnected or link error, ep will be dropped */ ++ if (ep->ep_state & EP_DROP_PENDING) { ++ td->cancel_status = TD_CLEARED; ++ continue; ++ } ++ + /* + * If a ring stopped on the TD we need to cancel then we have to + * move the xHC endpoint ring dequeue pointer past this TD. +@@ -1131,9 +1120,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (!cached_td) + return 0; + +- err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, +- cached_td->urb->stream_id, +- cached_td); ++ err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id); ++ + if (err) { + /* Failed to move past cached td, just set cached TDs to no-op */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { +@@ -1161,23 +1149,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + * + * Call under xhci->lock on a stopped endpoint. + */ +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep) ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- xhci_invalidate_cancelled_tds(ep); +- xhci_giveback_invalidated_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } + + /* + * Returns the TD the endpoint ring halted on. + * Only call for non-running rings without streams. + */ +-static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) ++static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_td *td; + u64 hw_deq; + + if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ +- hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); ++ hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0); + hw_deq &= TR_DEQ_PTR_MASK; + td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); + if (trb_in_td(td, hw_deq)) +@@ -1252,7 +1240,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + reset_type = EP_SOFT_RESET; + } else { + reset_type = EP_HARD_RESET; +- td = find_halted_td(ep); ++ td = find_halted_td(xhci, ep); + if (td) + td->status = -EPROTO; + } +@@ -1310,13 +1298,17 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + } + } + ++ /* link is inactive or disconnected, ep is not running and shouldn't be restarted */ ++ if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected) ++ ep->ep_state |= EP_DROP_PENDING; ++ + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + ep->ep_state &= ~EP_STOP_CMD_PENDING; + + /* Otherwise ring the doorbell(s) to restart queued transfers */ +- xhci_giveback_invalidated_tds(ep); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_giveback_invalidated_tds(xhci, ep); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) +@@ -1537,14 +1529,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + /* HW cached TDs cleared from cache, give them back */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { +- ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb); + if (td->cancel_status == TD_CLEARING_CACHE) { + td->cancel_status = TD_CLEARED; +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); ++ xhci_td_cleanup(xhci, td, ep_ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } + } +@@ -1555,17 +1547,17 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + + /* Check for deferred or newly cancelled TDs */ + if (!list_empty(&ep->cancelled_td_list)) { +- xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n", ++ xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n", + __func__); +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + /* Try to restart the endpoint if all is done */ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + /* Start giving back any TDs invalidated above */ +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } else { + /* Restart any rings with pending URBs */ +- xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + } + +@@ -1591,16 +1583,16 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, + "Ignoring reset ep completion code of %u", cmd_comp_code); + + /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + + /* Clear our internal halted state */ + ep->ep_state &= ~EP_HALTED; + +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + + /* if this was a soft reset, then restart */ + if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command, +@@ -2008,13 +2000,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) + static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + { + struct xhci_virt_device *vdev = NULL; +- struct usb_hcd *hcd; +- u32 port_id; +- u32 portsc, cmd_reg; +- unsigned int hcd_portnum; + struct xhci_bus_state *bus_state; +- bool bogus_port_status = false; + struct xhci_port *port; ++ struct usb_hcd *hcd; ++ bool bogus_port_status = false; ++ unsigned int hcd_portnum; ++ u32 cmd_reg; ++ u32 port_id; ++ u32 portsc; ++ u32 pls; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) +@@ -2051,6 +2045,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + bus_state = &port->rhub->bus_state; + hcd_portnum = port->hcd_portnum; + portsc = xhci_portsc_readl(port); ++ pls = portsc & PORT_PLS_MASK; + + xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n", + hcd->self.busnum, hcd_portnum + 1, port_id, portsc); +@@ -2062,11 +2057,13 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + usb_hcd_resume_root_hub(hcd); + } + +- if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) { +- if (!(portsc & PORT_RESET)) +- vdev->flags |= VDEV_PORT_ERROR; +- } else if (vdev && portsc & PORT_RC) { +- vdev->flags &= ~VDEV_PORT_ERROR; ++ /* ++ * Tag broken links to avoid retries while hub driver sorts it out. ++ * Link status is not relible while port is in reset. ++ */ ++ if (!(portsc & PORT_RESET)) { ++ port->link_inactive = (pls == XDEV_INACTIVE); ++ port->connected = !!(portsc & PORT_CONNECT); + } + + if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { +@@ -3978,88 +3975,102 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci, + /* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */ + static int xhci_ist_microframes(struct xhci_hcd *xhci) + { +- int ist = HCS_IST_VALUE(xhci->hcs_params2); ++ int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2); + + if (xhci->hcs_params2 & HCS_IST_UNIT) + ist *= 8; + return ist; + } + +-/* +- * Calculates Frame ID field of the isochronous TRB identifies the +- * target frame that the Interval associated with this Isochronous +- * Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec. +- * +- * Returns actual frame id on success, negative value on error. +- */ +-static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, +- struct urb *urb, int index) ++ ++static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep, int i) + { +- int start_frame, ist, ret = 0; +- int start_frame_id, end_frame_id, current_frame_id; ++ if (urb->transfer_flags & URB_ISO_ASAP) ++ return false; + +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- start_frame = urb->start_frame + index * urb->interval; +- else +- start_frame = (urb->start_frame + index * urb->interval) >> 3; ++ if (xhci->hcc_params & HCC_CFC) ++ return true; + ++ /* set frame id for first TD of first URB in stream */ ++ if (ep->next_uframe == -1 && i == 0) ++ return true; ++ ++ return false; ++} ++ ++/* ++ * Check if frame is in the valid frame window, including start and end. ++ * If start > end then assume window wrapped around at a limit the frame ++ * value won't exceed. ++ */ ++static bool xhci_frame_in_range(u32 frame, u32 start, u32 end) ++{ ++ /* frame window end wrapped around */ ++ if (start > end) ++ return frame >= start || frame <= end; ++ ++ return frame >= start && frame <= end; ++} ++ ++/* ++ * Set the urb->start_frame of the URB. ++ * ++ * Returns microframe index of first TD ++ */ ++static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep) ++{ ++ u32 curr_frame, start_uframe; ++ u32 urb_start, urb_end; ++ u32 win_start, win_end; ++ bool frame_unit; ++ int uinterval; ++ u32 mfindex; ++ int ist; ++ ++ /* check if urb uses frame units instead of microframes */ ++ frame_unit = (urb->dev->speed == USB_SPEED_FULL || ++ urb->dev->speed == USB_SPEED_LOW); ++ ++ uinterval = urb->interval; ++ if (frame_unit) ++ uinterval *= 8; ++ ++ /* get current microframe index and isoc scheduling threshold */ ++ mfindex = readl(&xhci->run_regs->microframe_index); + ist = xhci_ist_microframes(xhci); + +- /* Software shall not schedule an Isoch TD with a Frame ID value that +- * is less than the Start Frame ID or greater than the End Frame ID, +- * where: +- * +- * End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048 +- * Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048 +- * +- * Both the End Frame ID and Start Frame ID values are calculated +- * in microframes. When software determines the valid Frame ID value; +- * The End Frame ID value should be rounded down to the nearest Frame +- * boundary, and the Start Frame ID value should be rounded up to the +- * nearest Frame boundary. +- */ +- current_frame_id = readl(&xhci->run_regs->microframe_index); +- start_frame_id = roundup(current_frame_id + ist + 1, 8); +- end_frame_id = rounddown(current_frame_id + 895 * 8, 8); ++ /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */ ++ curr_frame = MFINDEX_TO_FRAME(mfindex); ++ win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES; ++ win_end = (curr_frame + 895) % MAX_FRAMES; + +- start_frame &= 0x7ff; +- start_frame_id = (start_frame_id >> 3) & 0x7ff; +- end_frame_id = (end_frame_id >> 3) & 0x7ff; +- +- if (start_frame_id < end_frame_id) { +- if (start_frame > end_frame_id || +- start_frame < start_frame_id) +- ret = -EINVAL; +- } else if (start_frame_id > end_frame_id) { +- if ((start_frame > end_frame_id && +- start_frame < start_frame_id)) +- ret = -EINVAL; ++ /* Is this the first URB starting the whole isoc data flow? */ ++ if (ep->next_uframe < 0) { ++ /* align first URB to next interval boundary, or at last to full frame */ ++ start_uframe = mfindex + ist + XHCI_CFC_DELAY; ++ start_uframe = roundup(start_uframe, 8); ++ start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES; + } else { +- ret = -EINVAL; +- } ++ /* URB is mid stream and expected to handle the next frame */ ++ start_uframe = ep->next_uframe; ++ urb_start = start_uframe / 8; ++ urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8; ++ urb_end %= MAX_FRAMES; + +- if (index == 0) { +- if (ret == -EINVAL || start_frame == start_frame_id) { +- start_frame = start_frame_id + 1; +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- urb->start_frame = start_frame; +- else +- urb->start_frame = start_frame << 3; +- ret = 0; +- } +- } ++ if (!xhci_frame_in_range(urb_start, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + +- if (ret) { +- xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n", +- start_frame, current_frame_id, index, +- start_frame_id, end_frame_id); +- xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n"); +- return ret; ++ if (!xhci_frame_in_range(urb_end, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + } ++ /* set urb->start_frame */ ++ urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe; + +- return start_frame; ++ return start_uframe; + } + + /* Check if we should generate event interrupt for a TD in an isoc URB */ +@@ -4099,7 +4110,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + int i, j; + bool more_trbs_coming; + struct xhci_virt_ep *xep; +- int frame_id; ++ int uinterval = urb->interval; ++ int start_uframe; + + xep = &xhci->devs[slot_id]->eps[ep_index]; + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; +@@ -4115,6 +4127,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + start_cycle = ep_ring->cycle_state; + + urb_priv = urb->hcpriv; ++ ++ if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW) ++ uinterval = urb->interval * 8; ++ ++ start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep); ++ + /* Queue the TRBs for each TD, even if they are zero-length */ + for (i = 0; i < num_tds; i++) { + unsigned int total_pkt_count, max_pkt; +@@ -4146,14 +4164,16 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + goto cleanup; + } + td = &urb_priv->td[i]; +- /* use SIA as default, if frame id is used overwrite it */ +- sia_frame_id = TRB_SIA; +- if (!(urb->transfer_flags & URB_ISO_ASAP) && +- (xhci->hcc_params & HCC_CFC)) { +- frame_id = xhci_get_isoc_frame_id(xhci, urb, i); +- if (frame_id >= 0) +- sia_frame_id = TRB_FRAME_ID(frame_id); ++ ++ ++ /* Choose SIA or frame ID based scheduling for this TD */ ++ if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) { ++ sia_frame_id = (start_uframe + i * uinterval) / 8; ++ sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES); ++ } else { ++ sia_frame_id = TRB_SIA; + } ++ + /* + * Set isoc specific data for the first TRB in a TD. + * Prevent HW from getting the TRBs by keeping the cycle state +@@ -4232,9 +4252,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + } + } + +- /* store the next frame id */ +- if (xhci->hcc_params & HCC_CFC) +- xep->next_frame_id = urb->start_frame + num_tds * urb->interval; ++ xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES; + + if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { + if (xhci->quirks & XHCI_AMD_PLL_FIX) +@@ -4281,11 +4299,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + struct xhci_ep_ctx *ep_ctx; +- int start_frame; ++ struct xhci_virt_ep *xep; + int num_tds, num_trbs, i; + int ret; +- struct xhci_virt_ep *xep; +- int ist; + + xdev = xhci->devs[slot_id]; + xep = &xhci->devs[slot_id]->eps[ep_index]; +@@ -4311,38 +4327,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + */ + check_interval(urb, ep_ctx); + +- /* Calculate the start frame and put it in urb->start_frame. */ +- if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) { +- if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) { +- urb->start_frame = xep->next_frame_id; +- goto skip_start_over; +- } +- } +- +- start_frame = readl(&xhci->run_regs->microframe_index); +- start_frame &= 0x3fff; + /* +- * Round up to the next frame and consider the time before trb really +- * gets scheduled by hardare. ++ * Check if this starts the isoc data flow. Relies on hw setting ep ctx ++ * state after doorbell ring. Consider adding list_empty(td_list) check + */ +- ist = xhci_ist_microframes(xhci); +- start_frame += ist + XHCI_CFC_DELAY; +- start_frame = roundup(start_frame, 8); +- +- /* +- * Round up to the next ESIT (Endpoint Service Interval Time) if ESIT +- * is greate than 8 microframes. +- */ +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) { +- start_frame = roundup(start_frame, urb->interval << 3); +- urb->start_frame = start_frame >> 3; +- } else { +- start_frame = roundup(start_frame, urb->interval); +- urb->start_frame = start_frame; +- } +- +-skip_start_over: ++ if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ xep->next_uframe = -1; + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } +diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c +--- a/drivers/usb/host/xhci-tegra.c ++++ b/drivers/usb/host/xhci-tegra.c +@@ -6,6 +6,7 @@ + * Copyright (C) 2014 Google, Inc. + */ + ++#include + #include + #include + #include +@@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) + u32 value; + + cap_regs = tegra->regs; +- op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); ++ op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)), + + ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); + +@@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) + goto remove_usb2; + } + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1664,7 +1665,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag + goto free_priv; + } + +- if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) { ++ if (xhci->devs[slot_id]->rhub_port->link_inactive) { + xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); + ret = -ENODEV; + goto free_priv; +@@ -1852,11 +1853,11 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) + } + + /* In this case no commands are pending but the endpoint is stopped */ +- if (ep->ep_state & EP_CLEARING_TT) { ++ if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) { + /* and cancelled TDs can be given back right away */ + xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", + urb->dev->slot_id, ep_index, ep->ep_state); +- xhci_process_cancelled_tds(ep); ++ xhci_process_cancelled_tds(xhci, ep); + } else { + /* Otherwise, queue a new Stop Endpoint command */ + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); +@@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci, + * level page entries), but that's an optional feature for xHCI host + * controllers. xHCs must support at least 4 stream IDs. + */ +- max_streams = HCC_MAX_PSA(xhci->hcc_params); ++ max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params); + if (*num_stream_ctxs > max_streams) { + xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n", + max_streams); +@@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + + /* MaxPSASize value 0 (2 streams) means streams are not supported */ + if ((xhci->quirks & XHCI_BROKEN_STREAMS) || +- HCC_MAX_PSA(xhci->hcc_params) < 4) { ++ GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) { + xhci_dbg(xhci, "xHCI controller does not support streams.\n"); + return -ENOSYS; + } +@@ -4035,7 +4036,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + xhci_get_slot_state(xhci, virt_dev->out_ctx)); + xhci_dbg(xhci, "Not freeing device rings.\n"); + /* Don't treat this as an error. May change my mind later. */ +- virt_dev->flags = 0; + ret = 0; + goto command_cleanup; + case COMP_SUCCESS: +@@ -4087,10 +4087,12 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + } + /* If necessary, update the number of active TTs on this root port */ + xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); +- virt_dev->flags = 0; + ret = 0; + + command_cleanup: ++ for (i = 0; i < EP_CTX_PER_DEV; i++) ++ virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING; ++ + xhci_free_command(xhci, reset_device_cmd); + return ret; + } +@@ -4607,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, + int besl_device = 0; + u32 field; + +- u2del = HCS_U2_LATENCY(xhci->hcs_params3); ++ u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3); + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + if (field & USB_BESL_SUPPORT) { +@@ -5459,26 +5461,28 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + xhci_warn(xhci, "Host controller not accessible, removed?\n"); + return -ENODEV; + } +- xhci->op_regs = hcd->regs + HC_LENGTH(hc_capbase); ++ xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase); ++ + xhci->run_regs = hcd->regs + + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + hcs_params1 = readl(&xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); +- xhci->hci_version = HC_VERSION(hc_capbase); ++ xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase); + xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); + if (xhci->hci_version > 0x100) + xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + + xhci->dma_mask_bits = 64; +- xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); +- xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); ++ xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS); ++ xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS); ++ + /* xhci-plat or xhci-pci might have set max_interrupters already */ + if (!xhci->max_interrupters) +- xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS); +- else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1)) +- xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1); ++ xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS); ++ else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1)) ++ xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1); + + xhci->quirks |= quirks; + +@@ -5519,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + * DMA_BIT_MASK(32)) in this xhci_gen_setup(). + */ + if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) +- xhci->hcc_params &= ~BIT(0); ++ xhci->hcc_params &= ~HCC_64BIT_ADDR; + + /* + * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports +diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h +--- a/drivers/usb/host/xhci.h ++++ b/drivers/usb/host/xhci.h +@@ -290,6 +290,11 @@ struct xhci_run_regs { + struct xhci_intr_reg ir_set[1024]; + }; + ++/* Bits [13:3] of the microframe index equals the 1ms frame index */ ++#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff) ++#define MAX_FRAMES 2048 ++#define MAX_UFRAMES (MAX_FRAMES * 8) ++ + /** + * struct doorbell_array + * +@@ -677,9 +682,9 @@ struct xhci_virt_ep { + #define EP_SOFT_CLEAR_TOGGLE BIT(7) + /* usb_hub_clear_tt_buffer is in progress */ + #define EP_CLEARING_TT BIT(8) ++#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ + /* ---- Related to URB cancellation ---- */ + struct list_head cancelled_td_list; +- struct xhci_hcd *xhci; + /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue + * command. We'll need to update the ring's dequeue segment and dequeue + * pointer after the command completes. +@@ -699,7 +704,7 @@ struct xhci_virt_ep { + struct list_head bw_endpoint_list; + unsigned long stop_time; + /* Isoch Frame ID checking storage */ +- int next_frame_id; ++ int next_uframe; + /* Use new Isoch TRB layout needed for extended TBC support */ + bool use_extended_tbc; + /* set if this endpoint is controlled via sideband access*/ +@@ -753,14 +758,6 @@ struct xhci_virt_device { + struct xhci_port *rhub_port; + struct xhci_interval_bw_table *bw_table; + struct xhci_tt_bw_info *tt_info; +- /* +- * flags for state tracking based on events and issued commands. +- * Software can not rely on states from output contexts because of +- * latency between events and xHC updating output context values. +- * See xhci 1.1 section 4.8.3 for more details +- */ +- unsigned long flags; +-#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */ + + /* The current max exit latency for the enabled USB3 link states. */ + u16 current_mel; +@@ -1480,6 +1477,8 @@ struct xhci_port { + int hcd_portnum; + struct xhci_hub *rhub; + struct xhci_port_cap *port_cap; ++ unsigned int link_inactive:1; ++ unsigned int connected:1; + unsigned int lpm_incapable:1; + unsigned long resume_timestamp; + bool rexit_active; +@@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len); + unsigned int xhci_num_trbs_free(struct xhci_ring *ring); + int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + int suspend, gfp_t gfp_flags); +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep); + void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb); +diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c +--- a/drivers/usb/misc/cypress_cy7c63.c ++++ b/drivers/usb/misc/cypress_cy7c63.c +@@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request, + address, data, iobuf, CYPRESS_MAX_REQSIZE, + USB_CTRL_GET_TIMEOUT); + /* we must not process garbage */ +- if (retval < 2) ++ if (retval < 2) { ++ if (retval >= 0) ++ retval = -EIO; + goto err_buf; ++ } + + /* store returned data (more READs to be added) */ + switch (request) { +@@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr, + dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); + + result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); +- + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); ++ if (result < 0) ++ return result; + + return sprintf(buf, "%d", cyp->port[port_num]); + } +diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c +--- a/drivers/usb/misc/uss720.c ++++ b/drivers/usb/misc/uss720.c +@@ -223,6 +223,7 @@ static int get_1284_register(struct parport *pp, unsigned char reg, unsigned cha + } + printk(KERN_WARNING "get_1284_register timeout\n"); + kill_all_async_requests_priv(priv); ++ kref_put(&rq->ref_count, destroy_async); + return -EIO; + } + +diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c +--- a/drivers/usb/mtu3/mtu3_host.c ++++ b/drivers/usb/mtu3/mtu3_host.c +@@ -46,6 +46,14 @@ + #define WC1_IS_P_95 BIT(12) + #define WC1_IS_EN_P0_95 BIT(6) + ++/* mt8196 */ ++#define PERI_WK_CTRL0_8196 0x08 ++#define WC0_IS_EN_P0_96 BIT(0) ++#define WC0_IS_EN_P1_96 BIT(7) ++ ++#define PERI_WK_CTRL1_8196 0x10 ++#define WC1_IS_EN_P2_96 BIT(0) ++ + /* mt2712 etc */ + #define PERI_SSUSB_SPM_CTRL 0x0 + #define SSC_IP_SLEEP_EN BIT(4) +@@ -59,6 +67,9 @@ enum ssusb_uwk_vers { + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ ++ SSUSB_UWK_V1_7, /* mt8196 IP0 */ ++ SSUSB_UWK_V1_8, /* mt8196 IP1 */ ++ SSUSB_UWK_V1_9, /* mt8196 IP2 */ + }; + + /* +@@ -100,6 +111,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; ++ case SSUSB_UWK_V1_7: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P0_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_8: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P1_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_9: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196; ++ msk = WC1_IS_EN_P2_96; ++ val = enable ? msk : 0; ++ break; + case SSUSB_UWK_V2: + reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; + msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; +diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c +--- a/drivers/usb/mtu3/mtu3_plat.c ++++ b/drivers/usb/mtu3/mtu3_plat.c +@@ -544,7 +544,8 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) + + ssusb_phy_power_off(ssusb); + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); +- ssusb_wakeup_set(ssusb, true); ++ if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg))) ++ ssusb_wakeup_set(ssusb, true); + return 0; + + sleep_err: +diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig +--- a/drivers/usb/musb/Kconfig ++++ b/drivers/usb/musb/Kconfig +@@ -116,7 +116,6 @@ config USB_MUSB_POLARFIRE_SOC + tristate "Microchip PolarFire SoC platforms" + depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on NOP_USB_XCEIV +- select USB_MUSB_DUAL_ROLE + help + Say Y here to enable support for USB on Microchip's PolarFire SoC. + +diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c +--- a/drivers/usb/musb/musb_host.c ++++ b/drivers/usb/musb/musb_host.c +@@ -1774,7 +1774,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) + status = -EPIPE; + + } else if (rx_csr & MUSB_RXCSR_H_ERROR) { +- dev_err(musb->controller, "ep%d RX three-strikes error", epnum); ++ dev_err_ratelimited(musb->controller, ++ "ep%d RX three-strikes error\n", epnum); + + /* + * The three-strikes error could only happen when the USB +@@ -1788,6 +1789,17 @@ void musb_host_rx(struct musb *musb, u8 epnum) + rx_csr &= ~MUSB_RXCSR_H_ERROR; + musb_writew(epio, MUSB_RXCSR, rx_csr); + ++ /* ++ * Unplugging a USB-Ethernet adapter while it is busy can make ++ * the controller keep re-asserting the three-strikes error for ++ * this endpoint before the disconnect is processed. That floods ++ * the log and can wedge the host port until reboot. Drop the ++ * stale pending RX interrupt on platforms that support it (e.g. ++ * AM335x/DSPS) to break the storm; the transfer is still ++ * aborted below via the fault path. ++ */ ++ musb_platform_clear_ep_rxintr(musb, epnum); ++ + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { + + if (USB_ENDPOINT_XFER_ISOC != qh->type) { +diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c +--- a/drivers/usb/serial/digi_acceleport.c ++++ b/drivers/usb/serial/digi_acceleport.c +@@ -32,10 +32,6 @@ + /* so we can be sure to send the full buffer in one urb */ + #define DIGI_OUT_BUF_SIZE 8 + +-/* port input buffer length -- must be >= transfer buffer length - 3 */ +-/* so we can be sure to hold at least one full buffer from one urb */ +-#define DIGI_IN_BUF_SIZE 64 +- + /* retry timeout while sleeping */ + #define DIGI_RETRY_TIMEOUT (HZ/10) + +@@ -176,10 +172,10 @@ + /* Structures */ + + struct digi_serial { +- spinlock_t ds_serial_lock; ++ struct mutex open_mutex; + struct usb_serial_port *ds_oob_port; /* out-of-band port */ + int ds_oob_port_num; /* index of out-of-band port */ +- int ds_device_started; ++ int open_count; + }; + + struct digi_port { +@@ -194,7 +190,6 @@ struct digi_port { + int dp_throttled; + int dp_throttle_restart; + wait_queue_head_t dp_flush_wait; +- wait_queue_head_t dp_close_wait; /* wait queue for close */ + wait_queue_head_t write_wait; + struct usb_serial_port *dp_port; + }; +@@ -227,9 +222,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty); + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); + static void digi_close(struct usb_serial_port *port); + static void digi_dtr_rts(struct usb_serial_port *port, int on); +-static int digi_startup_device(struct usb_serial *serial); + static int digi_startup(struct usb_serial *serial); +-static void digi_disconnect(struct usb_serial *serial); + static void digi_release(struct usb_serial *serial); + static int digi_port_probe(struct usb_serial_port *port); + static void digi_port_remove(struct usb_serial_port *port); +@@ -272,7 +265,7 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .dtr_rts = digi_dtr_rts, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -282,7 +275,6 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -301,7 +293,7 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .close = digi_close, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -311,7 +303,6 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -335,7 +326,6 @@ static struct usb_serial_driver * const serial_drivers[] = { + * interruptible_sleep_on_timeout is deprecated and has been replaced + * with the equivalent code. + */ +- + static long cond_wait_interruptible_timeout_irqrestore( + wait_queue_head_t *q, long timeout, + spinlock_t *lock, unsigned long flags) +@@ -351,6 +341,13 @@ __releases(lock) + return timeout; + } + ++static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ ++ return serial_priv->ds_oob_port; ++} ++ + /* + * Digi Write OOB Command + * +@@ -361,15 +358,14 @@ __releases(lock) + * the interruptible flag is true, or a negative error + * returned by usb_submit_urb. + */ +- + static int digi_write_oob_command(struct usb_serial_port *port, + unsigned char *buf, int count, int interruptible) + { +- int ret = 0; +- int len; +- struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, + "digi_write_oob_command: TOP: port=%d, count=%d\n", +@@ -406,10 +402,8 @@ static int digi_write_oob_command(struct usb_serial_port *port, + dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", + __func__, ret); + return ret; +- + } + +- + /* + * Digi Write In Band Command + * +@@ -421,16 +415,15 @@ static int digi_write_oob_command(struct usb_serial_port *port, + * timeout ticks. Returns 0 if successful, or a negative + * error returned by digi_write. + */ +- + static int digi_write_inb_command(struct usb_serial_port *port, + unsigned char *buf, int count, unsigned long timeout) + { +- int ret = 0; +- int len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + unsigned long expire; + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); +@@ -459,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, + /* len must be a multiple of 4 and small enough to */ + /* guarantee the write will send buffered data first, */ + /* so commands are in order with data and not split */ +- len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + if (len > 4) + len &= ~3; + +@@ -467,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port, + if (priv->dp_out_buf_len > 0) { + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; +- memcpy(data + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); + memcpy(data + 2 + priv->dp_out_buf_len, buf, len); +- port->write_urb->transfer_buffer_length +- = priv->dp_out_buf_len + 2 + len; ++ port->write_urb->transfer_buffer_length = ++ priv->dp_out_buf_len + 2 + len; + } else { + memcpy(data, buf, len); + port->write_urb->transfer_buffer_length = len; +@@ -484,7 +476,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + count -= len; + buf += len; + } +- + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + +@@ -495,7 +486,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + return ret; + } + +- + /* + * Digi Set Modem Signals + * +@@ -505,17 +495,15 @@ static int digi_write_inb_command(struct usb_serial_port *port, + * -EINTR if interrupted while sleeping, or a non-zero error + * returned by usb_submit_urb. + */ +- + static int digi_set_modem_signals(struct usb_serial_port *port, + unsigned int modem_signals, int interruptible) + { +- +- int ret; + struct digi_port *port_priv = usb_get_serial_port_data(port); +- struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned char *data = oob_port->write_urb->transfer_buffer; + unsigned long flags; ++ int ret; + + dev_dbg(&port->dev, + "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", +@@ -573,14 +561,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port, + * is only called from close, and only one process can be in close on a + * port at a time, so its ok. + */ +- + static int digi_transmit_idle(struct usb_serial_port *port, + unsigned long timeout) + { +- int ret; +- unsigned char buf[2]; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[2]; + unsigned long flags; ++ int ret; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_transmit_idle = 0; +@@ -607,16 +594,15 @@ static int digi_transmit_idle(struct usb_serial_port *port, + } + priv->dp_transmit_idle = 0; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return 0; +- + } + +- + static void digi_rx_throttle(struct tty_struct *tty) + { +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; + + /* stop receiving characters by not resubmitting the read urb */ + spin_lock_irqsave(&priv->dp_port_lock, flags); +@@ -625,13 +611,12 @@ static void digi_rx_throttle(struct tty_struct *tty) + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + } + +- + static void digi_rx_unthrottle(struct tty_struct *tty) + { +- int ret = 0; +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; ++ int ret = 0; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + +@@ -651,7 +636,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty) + __func__, ret, priv->dp_port_num); + } + +- + static void digi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +@@ -762,7 +746,6 @@ static void digi_set_termios(struct tty_struct *tty, + + /* set stop bits */ + if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { +- + if ((cflag & CSTOPB)) + arg = DIGI_STOP_BITS_2; + else +@@ -772,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty, + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; +- + } + + /* set input flow control */ +@@ -841,7 +823,6 @@ static void digi_set_termios(struct tty_struct *tty, + tty_encode_baud_rate(tty, baud, baud); + } + +- + static int digi_break_ctl(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; +@@ -855,50 +836,48 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state) + return digi_write_inb_command(port, buf, 4, 0); + } + +- + static int digi_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = priv->dp_modem_signals; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return val; + } + +- + static int digi_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = (priv->dp_modem_signals & ~clear) | set; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return digi_set_modem_signals(port, val, 1); + } + +- + static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) + { +- +- int ret, data_len, new_len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; ++ int ret, data_len, new_len; + unsigned long flags; + + dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); + + /* copy user data (which can sleep) before getting spin lock */ +- count = min(count, port->bulk_out_size-2); ++ count = min(count, port->bulk_out_size - 2); + count = min(64, count); + + /* be sure only one write proceeds at a time */ +@@ -920,7 +899,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + + /* allow space for any buffered data and for new data, up to */ + /* transfer buffer size - 2 (for command and length bytes) */ +- new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + data_len = new_len + priv->dp_out_buf_len; + + if (data_len == 0) { +@@ -928,7 +907,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + return 0; + } + +- port->write_urb->transfer_buffer_length = data_len+2; ++ port->write_urb->transfer_buffer_length = data_len + 2; + + *data++ = DIGI_CMD_SEND_DATA; + *data++ = data_len; +@@ -954,39 +933,22 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + dev_dbg(&port->dev, "digi_write: returning %d\n", ret); +- return ret; + ++ return ret; + } + + static void digi_write_bulk_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; +- struct usb_serial *serial; +- struct digi_port *priv; +- struct digi_serial *serial_priv; ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char *data = urb->transfer_buffer; + unsigned long flags; +- int ret = 0; +- int status = urb->status; + bool wakeup; +- +- /* port and serial sanity check */ +- if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { +- pr_err("%s: port or port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- serial = port->serial; +- if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { +- dev_err(&port->dev, +- "%s: serial or serial->private is NULL, status=%d\n", +- __func__, status); +- return; +- } ++ int ret = 0; + + /* handle oob callback */ + if (priv->dp_port_num == serial_priv->ds_oob_port_num) { +- dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + wake_up_interruptible(&priv->write_wait); +@@ -999,15 +961,13 @@ static void digi_write_bulk_callback(struct urb *urb) + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + if (priv->dp_out_buf_len > 0) { +- *((unsigned char *)(port->write_urb->transfer_buffer)) +- = (unsigned char)DIGI_CMD_SEND_DATA; +- *((unsigned char *)(port->write_urb->transfer_buffer) + 1) +- = (unsigned char)priv->dp_out_buf_len; +- port->write_urb->transfer_buffer_length = +- priv->dp_out_buf_len + 2; +- memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); +- ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); ++ data[0] = DIGI_CMD_SEND_DATA; ++ data[1] = priv->dp_out_buf_len; ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); ++ ++ urb->transfer_buffer_length = priv->dp_out_buf_len + 2; ++ ++ ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + priv->dp_out_buf_len = 0; +@@ -1041,8 +1001,8 @@ static unsigned int digi_write_room(struct tty_struct *tty) + + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room); +- return room; + ++ return room; + } + + static unsigned int digi_chars_in_buffer(struct tty_struct *tty) +@@ -1070,17 +1030,53 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) + digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1); + } + ++static int digi_open_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ int ret = 0; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count++ == 0) { ++ ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ++ ret); ++ serial_priv->open_count--; ++ } ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++ ++ return ret; ++} ++ ++static void digi_close_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count-- == 1) { ++ usb_kill_urb(oob_port->read_urb); ++ usb_kill_urb(oob_port->write_urb); ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++} ++ + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + { +- int ret; +- unsigned char buf[32]; + struct digi_port *priv = usb_get_serial_port_data(port); + struct ktermios not_termios; +- int throttled; ++ unsigned char buf[32]; ++ int ret; + +- /* be sure the device is started up */ +- if (digi_startup_device(port->serial) != 0) +- return -ENXIO; ++ ret = digi_open_oob_port(port->serial); ++ if (ret) ++ return ret; + + /* read modem signals automatically whenever they change */ + buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; +@@ -1106,32 +1102,34 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + } + + spin_lock_irq(&priv->dp_port_lock); +- throttled = priv->dp_throttle_restart; + priv->dp_throttled = 0; + priv->dp_throttle_restart = 0; + spin_unlock_irq(&priv->dp_port_lock); + +- if (throttled) { +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret) { +- dev_err(&port->dev, "failed to submit read urb: %d\n", ret); +- return ret; +- } ++ ret = usb_submit_urb(port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&port->dev, "failed to submit read urb: %d\n", ret); ++ goto err_close_oob; + } + + return 0; +-} + ++err_close_oob: ++ digi_close_oob_port(port->serial); ++ ++ return ret; ++} + + static void digi_close(struct usb_serial_port *port) + { ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[32]; + DEFINE_WAIT(wait); + int ret; +- unsigned char buf[32]; +- struct digi_port *priv = usb_get_serial_port_data(port); ++ ++ usb_kill_urb(port->read_urb); + + mutex_lock(&port->serial->disc_mutex); +- /* if disconnected, just clear flags */ + if (port->serial->disconnected) + goto exit; + +@@ -1177,57 +1175,18 @@ static void digi_close(struct usb_serial_port *port) + TASK_INTERRUPTIBLE); + schedule_timeout(DIGI_CLOSE_TIMEOUT); + finish_wait(&priv->dp_flush_wait, &wait); ++exit: ++ mutex_unlock(&port->serial->disc_mutex); + + /* shutdown any outstanding bulk writes */ + usb_kill_urb(port->write_urb); +-exit: +- spin_lock_irq(&priv->dp_port_lock); +- priv->dp_write_urb_in_use = 0; +- wake_up_interruptible(&priv->dp_close_wait); +- spin_unlock_irq(&priv->dp_port_lock); +- mutex_unlock(&port->serial->disc_mutex); +-} + +- +-/* +- * Digi Startup Device +- * +- * Starts reads on all ports. Must be called AFTER startup, with +- * urbs initialized. Returns 0 if successful, non-zero error otherwise. +- */ +- +-static int digi_startup_device(struct usb_serial *serial) +-{ +- int i, ret = 0; +- struct digi_serial *serial_priv = usb_get_serial_data(serial); +- struct usb_serial_port *port; +- +- /* be sure this happens exactly once */ +- spin_lock(&serial_priv->ds_serial_lock); +- if (serial_priv->ds_device_started) { +- spin_unlock(&serial_priv->ds_serial_lock); +- return 0; +- } +- serial_priv->ds_device_started = 1; +- spin_unlock(&serial_priv->ds_serial_lock); +- +- /* start reading from each bulk in endpoint for the device */ +- /* set USB_DISABLE_SPD flag for write bulk urbs */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- port = serial->port[i]; +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret != 0) { +- dev_err(&port->dev, +- "%s: usb_submit_urb failed, ret=%d, port=%d\n", +- __func__, ret, i); +- break; +- } +- } +- return ret; ++ digi_close_oob_port(port->serial); + } + + static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + { ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv; + + priv = kzalloc_obj(*priv); +@@ -1235,10 +1194,13 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + return -ENOMEM; + + spin_lock_init(&priv->dp_port_lock); ++ ++ if (port == serial_priv->ds_oob_port) ++ lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING); ++ + priv->dp_port_num = port_num; + init_waitqueue_head(&priv->dp_transmit_idle_wait); + init_waitqueue_head(&priv->dp_flush_wait); +- init_waitqueue_head(&priv->dp_close_wait); + init_waitqueue_head(&priv->write_wait); + priv->dp_port = port; + +@@ -1275,10 +1237,13 @@ static int digi_startup(struct usb_serial *serial) + if (!serial_priv) + return -ENOMEM; + +- spin_lock_init(&serial_priv->ds_serial_lock); ++ mutex_init(&serial_priv->open_mutex); ++ + serial_priv->ds_oob_port_num = oob_port_num; + serial_priv->ds_oob_port = serial->port[oob_port_num]; + ++ usb_set_serial_data(serial, serial_priv); ++ + ret = digi_port_init(serial_priv->ds_oob_port, + serial_priv->ds_oob_port_num); + if (ret) { +@@ -1286,24 +1251,9 @@ static int digi_startup(struct usb_serial *serial) + return ret; + } + +- usb_set_serial_data(serial, serial_priv); +- + return 0; + } + +- +-static void digi_disconnect(struct usb_serial *serial) +-{ +- int i; +- +- /* stop reads and writes on all ports */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- usb_kill_urb(serial->port[i]->read_urb); +- usb_kill_urb(serial->port[i]->write_urb); +- } +-} +- +- + static void digi_release(struct usb_serial *serial) + { + struct digi_serial *serial_priv; +@@ -1333,30 +1283,25 @@ static void digi_port_remove(struct usb_serial_port *port) + static void digi_read_bulk_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +- struct digi_port *priv; +- struct digi_serial *serial_priv; +- int ret; ++ struct usb_serial *serial = port->serial; ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); + int status = urb->status; +- +- /* port sanity check, do not resubmit if port is not valid */ +- if (port == NULL) +- return; +- priv = usb_get_serial_port_data(port); +- if (priv == NULL) { +- dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- if (port->serial == NULL || +- (serial_priv = usb_get_serial_data(port->serial)) == NULL) { +- dev_err(&port->dev, "%s: serial is bad or serial->private " +- "is NULL, status=%d\n", __func__, status); +- return; +- } ++ int ret; + + /* do not resubmit urb if it has any status error */ +- if (status) { +- dev_err(&port->dev, ++ switch (status) { ++ case 0: ++ break; ++ case -ENOENT: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ dev_err(&serial->interface->dev, ++ "%s: nonzero read bulk status: status=%d, port=%d\n", ++ __func__, status, priv->dp_port_num); ++ return; ++ default: ++ dev_err(&serial->interface->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; +@@ -1374,11 +1319,10 @@ static void digi_read_bulk_callback(struct urb *urb) + /* continue read */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) { +- dev_err(&port->dev, ++ dev_err(&serial->interface->dev, + "%s: failed resubmitting urb, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + } +- + } + + /* +@@ -1390,7 +1334,6 @@ static void digi_read_bulk_callback(struct urb *urb) + * It returns 0 if successful, 1 if successful but the port is + * throttled, and -1 if the sanity checks failed. + */ +- + static int digi_read_inb_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +@@ -1468,10 +1411,8 @@ static int digi_read_inb_callback(struct urb *urb) + dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); + + return throttled ? 1 : 0; +- + } + +- + /* + * Digi Read OOB Callback + * +@@ -1480,10 +1421,8 @@ static int digi_read_inb_callback(struct urb *urb) + * the port->serial is valid. It returns 0 if successful, and + * -1 if the sanity checks failed. + */ +- + static int digi_read_oob_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + struct tty_struct *tty; +@@ -1491,8 +1430,8 @@ static int digi_read_oob_callback(struct urb *urb) + unsigned char *buf = urb->transfer_buffer; + int opcode, line, status, val; + unsigned long flags; +- int i; + unsigned int rts; ++ int i; + + if (urb->actual_length < 4) + return -1; +@@ -1504,7 +1443,8 @@ static int digi_read_oob_callback(struct urb *urb) + status = buf[i + 2]; + val = buf[i + 3]; + +- dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", ++ dev_dbg(&serial->interface->dev, ++ "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + opcode, line, status, val); + + if (status != 0 || line >= serial->type->num_ports) +@@ -1562,8 +1502,8 @@ static int digi_read_oob_callback(struct urb *urb) + } + tty_kref_put(tty); + } +- return 0; + ++ return 0; + } + + module_usb_serial_driver(serial_drivers, id_table_combined); +diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c +--- a/drivers/usb/serial/keyspan_pda.c ++++ b/drivers/usb/serial/keyspan_pda.c +@@ -31,7 +31,6 @@ + struct keyspan_pda_private { + int tx_room; + struct work_struct unthrottle_work; +- struct usb_serial *serial; + struct usb_serial_port *port; + bool throttled; + bool throttle_req; +diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c +--- a/drivers/usb/serial/metro-usb.c ++++ b/drivers/usb/serial/metro-usb.c +@@ -36,6 +36,7 @@ + struct metrousb_private { + spinlock_t lock; + int throttled; ++ int throttle_req; + unsigned long control_state; + }; + +@@ -108,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb) + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + int throttled = 0; +- int result = 0; ++ int result; + + dev_dbg(&port->dev, "%s\n", __func__); + +@@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb) + + /* Set any port variables. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- throttled = metro_priv->throttled; ++ if (metro_priv->throttle_req) { ++ metro_priv->throttled = 1; ++ throttled = 1; ++ } + spin_unlock_irqrestore(&metro_priv->lock, flags); + + if (throttled) +@@ -169,12 +173,13 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) + struct usb_serial *serial = port->serial; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int result; + + /* Set the private data information for the port. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->control_state = 0; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + /* Clear the urb pipe. */ +@@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty) + + /* Set the private information for the port to stop reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- metro_priv->throttled = 1; ++ metro_priv->throttle_req = 1; + spin_unlock_irqrestore(&metro_priv->lock, flags); + } + +@@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty) + struct usb_serial_port *port = tty->driver_data; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int throttled; ++ int result; + + /* Set the private information for the port to resume reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); ++ throttled = metro_priv->throttled; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + +- /* Submit the urb to read from the port. */ +- result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); +- if (result) +- dev_err(&port->dev, +- "failed submitting interrupt in urb error code=%d\n", +- result); ++ if (throttled) { ++ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); ++ if (result) { ++ dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", ++ result); ++ } ++ } + } + + static struct usb_serial_driver metrousb_device = { +diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c +--- a/drivers/usb/serial/pl2303.c ++++ b/drivers/usb/serial/pl2303.c +@@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, ++ { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), +@@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define VENDOR_WRITE_REQUEST_TYPE 0x40 + #define VENDOR_WRITE_REQUEST 0x01 + #define VENDOR_WRITE_NREQUEST 0x80 ++#define VENDOR_WRITE_MPREQUEST 0x80 + + #define VENDOR_READ_REQUEST_TYPE 0xc0 + #define VENDOR_READ_REQUEST 0x01 + #define VENDOR_READ_NREQUEST 0x81 ++#define VENDOR_READ_MPREQUEST 0x80 ++ ++#define PL256X_RESET_REQUEST_TYPE 0x40 ++#define PL256X_RESET_REQUEST 0x96 + + #define UART_STATE_INDEX 8 + #define UART_STATE_MSR_MASK 0x8b +@@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 + #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + ++#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 ++#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 ++#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 ++#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 ++ ++#define PL256X_FLOWCTRL_MASK 0x43 ++#define PL256X_FLOWCTRL_XON_XOFF 0x40 ++#define PL256X_FLOWCTRL_RTS_CTS 0x03 ++#define PL256X_FLOWCTRL_NONE 0x00 ++ + static int pl2303_set_break(struct usb_serial_port *port, bool enable); + + enum pl2303_type { +@@ -181,6 +197,7 @@ enum pl2303_type { + TYPE_TB, + TYPE_HXD, + TYPE_HXN, ++ TYPE_MP, + TYPE_COUNT + }; + +@@ -196,6 +213,8 @@ struct pl2303_type_data { + struct pl2303_serial_private { + const struct pl2303_type_data *type; + unsigned long quirks; ++ u16 interface_num; ++ u16 flowctrl_reg; + }; + + struct pl2303_private { +@@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { + .max_baud_rate = 12000000, + .no_divisors = true, + }, ++ [TYPE_MP] = { ++ .name = "MP", ++ .max_baud_rate = 24000000, ++ .no_divisors = true, ++ }, + }; + ++static int pl256x_uart_reset(struct usb_serial *serial) ++{ ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); ++ struct device *dev = &serial->interface->dev; ++ int res; ++ ++ res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), ++ PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, ++ 0, spriv->interface_num, NULL, 0, 100); ++ if (res) { ++ dev_err(dev, "failed to reset device: %d\n", res); ++ return res; ++ } ++ ++ return 0; ++} ++ + static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + unsigned char buf[1]) + { +@@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_READ_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_READ_MPREQUEST; + else + request = VENDOR_READ_REQUEST; + +@@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_WRITE_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_WRITE_MPREQUEST; + else + request = VENDOR_WRITE_REQUEST; + +@@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) + if (!buf) + return -ENOMEM; + +- if (spriv->type == &pl2303_type_data[TYPE_HXN]) ++ if (spriv->type == &pl2303_type_data[TYPE_HXN] || ++ spriv->type == &pl2303_type_data[TYPE_MP]) { + ret = pl2303_vendor_read(serial, reg, buf); +- else ++ } else { + ret = pl2303_vendor_read(serial, reg | 0x80, buf); ++ } + + if (ret) + goto out_free; +@@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial) + case 0x905: /* GT-2AB */ + case 0x1005: /* GC-Q20 */ + return TYPE_HXN; ++ case 0x3302: /* PL2533 VC 2 Port */ ++ case 0x3304: /* PL2533 VC 4 Port */ ++ case 0x4302: /* PL2543 VC 2 Port */ ++ case 0x4304: /* PL2543 VC 4 Port */ ++ case 0x6502: /* PL2565 VC 2 Port */ ++ case 0x6504: /* PL2565 VC 4 Port */ ++ case 0x6506: /* PL2565 VC 4 Port QFN64 package */ ++ return TYPE_MP; + } + break; + } +@@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial) + struct pl2303_serial_private *spriv; + enum pl2303_type type; + unsigned char *buf; ++ unsigned int ifnum; + int ret; + + ret = pl2303_detect_type(serial); +@@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial) + type = ret; + dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + ++ ifnum = serial->interface->altsetting->desc.bInterfaceNumber; ++ + spriv = kzalloc_obj(*spriv); + if (!spriv) + return -ENOMEM; + ++ if (type == TYPE_MP) { ++ switch (ifnum) { ++ case 0: ++ spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; ++ break; ++ case 1: ++ spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; ++ break; ++ case 2: ++ spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; ++ break; ++ case 3: ++ spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; ++ break; ++ default: ++ kfree(spriv); ++ return -ENODEV; ++ } ++ } ++ + spriv->type = &pl2303_type_data[type]; + spriv->quirks = (unsigned long)usb_get_serial_data(serial); + spriv->quirks |= spriv->type->quirks; ++ spriv->interface_num = ifnum; + + if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) + spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; + + usb_set_serial_data(serial, spriv); + +- if (type != TYPE_HXN) { ++ if (type != TYPE_HXN && type != TYPE_MP) { + buf = kmalloc(1, GFP_KERNEL); + if (!buf) { + kfree(spriv); +@@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port) + static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) + { + struct usb_device *dev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int retval; + + dev_dbg(&port->dev, "%s - %02x\n", __func__, value); + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, +- value, 0, NULL, 0, 100); ++ value, spriv->interface_num, NULL, 0, 100); + if (retval) + dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); + +@@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port, + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret != 7) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + +@@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port, + unsigned char buf[7]) + { + struct usb_device *udev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int ret; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret < 0) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + return ret; +@@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_RTS_CTS); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_RTS_CTS); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); + } +@@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_XON_XOFF); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_XON_XOFF); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); + } +@@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_NONE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_NONE); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); + } +@@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) + pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, + PL2303_HXN_RESET_UPSTREAM_PIPE | + PL2303_HXN_RESET_DOWNSTREAM_PIPE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl256x_uart_reset(serial); + } else { + pl2303_vendor_write(serial, 8, 0); + pl2303_vendor_write(serial, 9, 0); +@@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable) + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + BREAK_REQUEST, BREAK_REQUEST_TYPE, state, +- 0, NULL, 0, 100); ++ spriv->interface_num, NULL, 0, 100); + if (result) { + dev_err(&port->dev, "error sending break = %d\n", result); + return result; +diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h +--- a/drivers/usb/serial/pl2303.h ++++ b/drivers/usb/serial/pl2303.h +@@ -26,7 +26,7 @@ + #define PL2303_PRODUCT_ID_HCR331 0x331a + #define PL2303_PRODUCT_ID_MOTOROLA 0x0307 + #define PL2303_PRODUCT_ID_ZTEK 0xe1f1 +- ++#define PL256X_PRODUCT_ID_4P 0x2533 + + #define ATEN_VENDOR_ID 0x0557 + #define ATEN_VENDOR_ID2 0x0547 +diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig +--- a/drivers/usb/typec/altmodes/Kconfig ++++ b/drivers/usb/typec/altmodes/Kconfig +@@ -5,6 +5,7 @@ menu "USB Type-C Alternate Mode drivers" + config TYPEC_DP_ALTMODE + tristate "DisplayPort Alternate Mode driver" + depends on DRM ++ select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + DisplayPort USB Type-C Alternate Mode allows DisplayPort + displays and adapters to be attached to the USB Type-C +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -764,6 +764,7 @@ int dp_altmode_probe(struct typec_altmode *alt) + struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + struct fwnode_handle *fwnode; + struct dp_altmode *dp; ++ u32 cap = DP_CAP_CAPABILITY(alt->vdo); + + /* Port can only be DFP_U. */ + if (typec_altmode_get_data_role(alt) != TYPEC_HOST) +@@ -778,6 +779,18 @@ int dp_altmode_probe(struct typec_altmode *alt) + return -ENODEV; + } + ++ /* ++ * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section ++ * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a ++ * USB-C receptacle must advertise at least one pin assignment for the ++ * capability it claims, otherwise Alt Mode can never be configured. ++ */ ++ if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) || ++ (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) { ++ typec_altmode_put_plug(plug); ++ return -ENODEV; ++ } ++ + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) { + typec_altmode_put_plug(plug); +@@ -790,7 +803,6 @@ int dp_altmode_probe(struct typec_altmode *alt) + dp->alt = alt; + + alt->desc = "DisplayPort"; +- typec_altmode_set_ops(alt, &dp_altmode_ops); + + if (plug) { + plug->desc = "Displayport"; +@@ -811,6 +823,10 @@ int dp_altmode_probe(struct typec_altmode *alt) + if (plug) + typec_altmode_set_drvdata(plug, dp); + ++ if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) ++ return 0; ++ ++ typec_altmode_set_ops(alt, &dp_altmode_ops); + if (!alt->mode_selection) { + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; + schedule_work(&dp->work); +diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c +--- a/drivers/usb/typec/altmodes/thunderbolt.c ++++ b/drivers/usb/typec/altmodes/thunderbolt.c +@@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); ++ ++ if (typec_cable_altmode_unsupported(alt)) ++ return 0; ++ + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (!alt->mode_selection && tbt_ready(alt)) { +diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c +--- a/drivers/usb/typec/class.c ++++ b/drivers/usb/typec/class.c +@@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) + } + EXPORT_SYMBOL_GPL(typec_cable_is_active); + ++enum typec_cable_altmode_support { ++ CABLE_SUPPORT_UNKNOWN, ++ CABLE_SUPPORTED, ++ CABLE_NOT_SUPPORTED, ++}; ++ ++static enum typec_cable_altmode_support ++typec_cable_check_altmode_support(struct typec_cable *cable, ++ struct typec_altmode *alt) ++{ ++ struct typec_altmode *plug; ++ u32 speed; ++ ++ /* ++ * Check if the cable has an e-marker, supports modal operation, and the ++ * SOP' altmode nodes are created. ++ */ ++ plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); ++ if (plug) { ++ typec_altmode_put_plug(plug); ++ return CABLE_SUPPORTED; ++ } ++ ++ /* The identity is not specified */ ++ if (!cable->identity) ++ return CABLE_SUPPORT_UNKNOWN; ++ ++ /* Non-e-marked cable */ ++ if (!cable->identity->id_header) ++ return CABLE_NOT_SUPPORTED; ++ ++ switch (PD_IDH_PTYPE(cable->identity->id_header)) { ++ case IDH_PTYPE_PCABLE: ++ speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); ++ if (speed == CABLE_USB2_ONLY) ++ return CABLE_NOT_SUPPORTED; ++ return CABLE_SUPPORTED; ++ case IDH_PTYPE_ACABLE: ++ /* ++ * Active cables must establish an SOP' communication ++ * node. Since that check failed at the beginning of ++ * this function, this active cable does not support ++ * this specific altmode. ++ */ ++ return CABLE_NOT_SUPPORTED; ++ } ++ ++ return CABLE_SUPPORT_UNKNOWN; ++} ++ ++/** ++ * typec_cable_altmode_unsupported - Check if a cable restricts altmode ++ * @alt: The Alternate Mode to evaluate ++ * ++ * Returns true if the connected cable is incapable of handling the altmode. ++ */ ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt) ++{ ++ enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; ++ struct typec_cable *cable; ++ ++ cable = typec_cable_get(typec_altmode2port(alt)); ++ if (cable) { ++ support = typec_cable_check_altmode_support(cable, alt); ++ typec_cable_put(cable); ++ } ++ ++ return support == CABLE_NOT_SUPPORTED; ++} ++EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); ++ + /** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values +diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c +--- a/drivers/usb/typec/mux/tusb1046.c ++++ b/drivers/usb/typec/mux/tusb1046.c +@@ -178,6 +178,7 @@ static const struct of_device_id tusb1046_match_table[] = { + {.compatible = "ti,tusb1046"}, + {}, + }; ++MODULE_DEVICE_TABLE(of, tusb1046_match_table); + + static struct i2c_driver tusb1046_driver = { + .driver = { +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -212,6 +213,24 @@ static const char * const tcpm_ams_str[] = { + FOREACH_AMS(GENERATE_STRING) + }; + ++#define FOREACH_VDM_DISCOVERY(S) \ ++ S(VDM_DISCOVERY_UNKNOWN), \ ++ S(VDM_DISCOVERY_PARTNER_IDENT), \ ++ S(VDM_DISCOVERY_CABLE_IDENT), \ ++ S(VDM_DISCOVERY_PARTNER_SVIDS), \ ++ S(VDM_DISCOVERY_PARTNER_MODES), \ ++ S(VDM_DISCOVERY_CABLE_SVIDS), \ ++ S(VDM_DISCOVERY_CABLE_MODES), \ ++ S(VDM_DISCOVERY_COMPLETE) ++ ++enum vdm_discovery_states { ++ FOREACH_VDM_DISCOVERY(GENERATE_ENUM) ++}; ++ ++static const char * const vdm_discovery_state_strings[] = { ++ FOREACH_VDM_DISCOVERY(GENERATE_STRING) ++}; ++ + enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, +@@ -232,7 +251,9 @@ enum pd_msg_request { + PD_MSG_DATA_SINK_CAP, + PD_MSG_DATA_SOURCE_CAP, + PD_MSG_DATA_REV, +- PD_MSG_EXT_SINK_CAP_EXT ++ PD_MSG_EXT_SINK_CAP_EXT, ++ PD_MSG_DATA_BATT_STATUS, ++ PD_MSG_EXT_BATT_CAP, + }; + + enum adev_actions { +@@ -274,7 +295,7 @@ enum frs_typec_current { + #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) + + #define GET_SINK_CAP_RETRY_MS 100 +-#define SEND_DISCOVER_RETRY_MS 100 ++#define SEND_DISCOVERY_VDM_RETRY_MS 100 + + struct pd_mode_data { + int svid_index; /* current SVID index */ +@@ -387,7 +408,15 @@ struct pd_timings { + }; + + /* Convert microwatt to watt */ +-#define UW_TO_W(pow) ((pow) / 1000000) ++#define UW_TO_W(pow) (div_u64((pow), 1000000)) ++ ++/* ++ * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries ++ * that a port can be queried is restricted to 4. ++ */ ++#define MAX_NUM_FIXED_BATT 4 ++ ++#define BATTERY_PROPERTY_UNKNOWN 0xffff + + /* + * struct pd_identifier - Contains info about PD identifiers +@@ -483,8 +512,6 @@ struct tcpm_port { + bool vbus_source; + bool vbus_charge; + +- /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */ +- bool send_discover; + bool op_vsafe5v; + + int try_role; +@@ -510,8 +537,8 @@ struct tcpm_port { + struct kthread_work vdm_state_machine; + struct hrtimer enable_frs_timer; + struct kthread_work enable_frs; +- struct hrtimer send_discover_timer; +- struct kthread_work send_discover_work; ++ struct hrtimer vdm_discovery_timer; ++ struct kthread_work vdm_discovery_work; + bool state_machine_running; + /* Set to true when VDM State Machine has following actions. */ + bool vdm_sm_running; +@@ -578,6 +605,9 @@ struct tcpm_port { + + u32 bist_request; + ++ /* VDM Discovery State to determine message sent */ ++ enum vdm_discovery_states vdm_discovery_state; ++ + /* PD state for Vendor Defined Messages */ + enum vdm_states vdm_state; + u32 vdm_retries; +@@ -641,12 +671,6 @@ struct tcpm_port { + bool potential_contaminant; + + /* SOP* Related Fields */ +- /* +- * Flag to determine if SOP' Discover Identity is available. The flag +- * is set if Discover Identity on SOP' does not immediately follow +- * Discover Identity on SOP. +- */ +- bool send_discover_prime; + /* + * tx_sop_type determines which SOP* a message is being sent on. + * For messages that are queued and not sent immediately such as in +@@ -683,6 +707,9 @@ struct tcpm_port { + + struct pd_identifier pd_ident; + struct sink_caps_ext_data sink_caps_ext; ++ struct power_supply **fixed_batt; ++ u32 fixed_batt_cnt; ++ u32 batt_request_id; + #ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + struct mutex logbuffer_lock; /* log buffer access lock */ +@@ -771,6 +798,9 @@ static const char * const pd_rev[] = { + #define tcpm_wait_for_discharge(port) \ + (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0) + ++#define tcpm_can_send_vdm(state) \ ++ ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST)) ++ + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) + { + if (port->port_type == TYPEC_PORT_DRP) { +@@ -1470,6 +1500,20 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + ++static void tcpm_get_fixed_batt(struct tcpm_port *port) ++{ ++ int ret; ++ ++ if (!port->self_powered || port->fixed_batt_cnt > 0) ++ return; ++ ++ ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt); ++ if (ret < 0) ++ tcpm_log(port, "Failed to get battery array, ret=%d", ret); ++ else ++ port->fixed_batt_cnt = ret; ++} ++ + static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + { + u16 operating_snk_watt = port->operating_snk_mw / 1000; +@@ -1482,6 +1526,8 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + if (!port->self_powered) + data->spr_op_pdp = operating_snk_watt; + ++ tcpm_get_fixed_batt(port); ++ + /* + * SPR Sink Minimum PDP indicates the minimum power required to operate + * a sink device in its lowest level of functionality without requiring +@@ -1507,6 +1553,7 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + skedb.load_step = data->load_step; + skedb.load_char = cpu_to_le16(data->load_char); + skedb.compliance = data->compliance; ++ skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT); + skedb.modes = data->modes; + skedb.spr_min_pdp = data->spr_min_pdp; + skedb.spr_op_pdp = data->spr_op_pdp; +@@ -1525,6 +1572,153 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + port->message_id, + data_obj_cnt, + 1 /* Denotes if ext header */)); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static u16 tcpm_charge_to_energy(int charge, int voltage) ++{ ++ u64 energy = div_u64((u64)charge * voltage, 1000000); ++ ++ /* Battery telemetry is reported in increments of 0.1Wh */ ++ return (u16)UW_TO_W(energy * 10); ++} ++ ++static int tcpm_pd_send_batt_status(struct tcpm_port *port) ++{ ++ u16 present_charge = BATTERY_PROPERTY_UNKNOWN; ++ bool batt_present = false, invalid_ref = true; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct power_supply *batt; ++ u8 charging_status = 0; ++ struct pd_message msg; ++ int ret, charge_now; ++ u32 bsdo; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_status; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val); ++ if (ret) ++ tcpm_log(port, ++ "Failed to fetch power_supply_prop_present ret %d", ++ ret); ++ else ++ batt_present = val.intval > 0; ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, ++ &val); ++ if (!ret) { ++ charge_now = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) ++ present_charge = tcpm_charge_to_energy(charge_now, ++ val.intval); ++ } ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); ++ if (!ret) { ++ switch (val.intval) { ++ case POWER_SUPPLY_STATUS_CHARGING: ++ charging_status = BSDO_BATTERY_INFO_CHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_DISCHARGING: ++ charging_status = BSDO_BATTERY_INFO_DISCHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_NOT_CHARGING: ++ case POWER_SUPPLY_STATUS_FULL: ++ charging_status = BSDO_BATTERY_INFO_IDLE; ++ break; ++ default: ++ charging_status = BSDO_BATTERY_INFO_RSVD; ++ break; ++ } ++ } ++ ++send_status: ++ ++ bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref); ++ msg.payload[0] = cpu_to_le32(bsdo); ++ msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS, ++ port->pwr_role, ++ port->data_role, ++ port->negotiated_rev, ++ port->message_id, ++ 1); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static int tcpm_pd_send_batt_cap(struct tcpm_port *port) ++{ ++ u16 design_cap = BATTERY_PROPERTY_UNKNOWN; ++ u16 charge_cap = BATTERY_PROPERTY_UNKNOWN; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct batt_cap_ext_msg bcdb; ++ struct power_supply *batt; ++ bool invalid_ref = true; ++ struct pd_message msg; ++ u8 data_obj_cnt; ++ int ret, vol; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_cap; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) { ++ vol = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, ++ &val); ++ if (!ret) ++ design_cap = tcpm_charge_to_energy(val.intval, vol); ++ ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ &val); ++ if (!ret) ++ charge_cap = tcpm_charge_to_energy(val.intval, vol); ++ } ++ ++send_cap: ++ ++ /* ++ * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the ++ * USB-IF) does not exist or an invalid battery reference is made by the ++ * requestor, then set the VID field to 0xffff. If the VID field is ++ * 0xffff, set the PID field to 0. ++ */ ++ bcdb.vid = BATTERY_PROPERTY_UNKNOWN; ++ bcdb.pid = 0; ++ bcdb.batt_design_cap = cpu_to_le16(design_cap); ++ bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap); ++ bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0; ++ memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb)); ++ msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb), ++ 0, /* Denotes if request chunk */ ++ 0, /* Chunk number */ ++ 1 /* Chunked */); ++ ++ data_obj_cnt = count_chunked_data_objs(sizeof(bcdb)); ++ msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role, ++ port->data_role, port->negotiated_rev, ++ port->message_id, data_obj_cnt); ++ + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + +@@ -1559,13 +1753,19 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + } + } + +-static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms) ++static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) ++{ ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_cancel_work_sync(&port->vdm_discovery_work); ++} ++ ++static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) + { + if (delay_ms) { +- hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); ++ hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { +- hrtimer_cancel(&port->send_discover_timer); +- kthread_queue_work(port->wq, &port->send_discover_work); ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + } + } + +@@ -1773,16 +1973,11 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, + WARN_ON(!mutex_is_locked(&port->lock)); + + /* If is sending discover_identity, handle received message first */ +- if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { +- if (tx_sop_type == TCPC_TX_SOP_PRIME) +- port->send_discover_prime = true; +- else +- port->send_discover = true; +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); +- } else { ++ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); ++ else + /* Make sure we are not still processing a previous VDM packet */ + WARN_ON(port->vdm_state > VDM_STATE_DONE); +- } + + port->vdo_count = cnt + 1; + port->vdo_data[0] = header; +@@ -1805,8 +2000,7 @@ static void tcpm_queue_vdm_work(struct kthread_work *work) + struct tcpm_port *port = event->port; + + mutex_lock(&port->lock); +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + tcpm_log_force(port, "dropping altmode_vdm_event"); + goto port_unlock; + } +@@ -2034,7 +2228,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) + if (!port->partner) + return; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_partner_register_altmode(port->partner, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2052,9 +2246,10 @@ static void tcpm_register_plug_altmodes(struct tcpm_port *port) + struct typec_altmode *altmode; + int i; + +- typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); ++ typec_plug_set_num_altmodes(port->plug_prime, ++ min(modep->altmodes, ALTMODE_DISCOVERY_MAX)); + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_plug_register_altmode(port->plug_prime, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2154,6 +2349,19 @@ static bool tcpm_cable_vdm_supported(struct tcpm_port *port) + tcpm_can_communicate_sop_prime(port); + } + ++static void tcpm_update_vdm_discovery_state(struct tcpm_port *port, ++ enum vdm_discovery_states new_state) ++{ ++ enum vdm_discovery_states old_state = port->vdm_discovery_state; ++ ++ if (old_state != new_state) ++ tcpm_log_force(port, "vdm discovery state changed: %s -> %s", ++ vdm_discovery_state_strings[old_state], ++ vdm_discovery_state_strings[new_state]); ++ ++ port->vdm_discovery_state = new_state; ++} ++ + static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) +@@ -2171,6 +2379,7 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + +@@ -2179,10 +2388,12 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(USB_SID_PD, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + modep = &port->mode_data_prime; + modep->svid_index++; +@@ -2193,11 +2404,13 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES); + return 1; + } + + tcpm_register_plug_altmodes(port); + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + return 0; +@@ -2376,18 +2589,18 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + typec_cable_set_svdm_version(port->cable, + svdm_version); + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT); ++ + /* 6.4.4.3.1 */ + svdm_consume_identity(port, p, cnt); + /* Attempt Vconn swap, delay SOP' discovery if necessary */ + if (tcpm_attempt_vconn_swap_discovery(port)) { +- port->send_discover_prime = true; + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (!ret) + return 0; + /* Cannot perform Vconn swap */ + port->upcoming_state = INVALID_STATE; +- port->send_discover_prime = false; + } + + /* +@@ -2398,7 +2611,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + if (IS_ERR_OR_NULL(port->cable) && + tcpm_can_communicate_sop_prime(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; +- port->send_discover_prime = true; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_IDENT); +@@ -2426,6 +2638,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + return 0; + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, +@@ -2445,16 +2658,27 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + rlen = 1; + } else { + if (rx_sop_type == TCPC_TX_SOP) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_PARTNER_SVIDS); + if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_CABLE_SVIDS); + if (modep_prime->nsvids) { + response[0] = VDO(modep_prime->svids[0], 1, + svdm_version, CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } + } +@@ -2505,8 +2729,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + case CMDT_RSP_NAK: + tcpm_ams_finish(port); + switch (cmd) { ++ /* ++ * The cable is not allowed to respond with NAK so this must've happened over SOP ++ */ + case CMD_DISCOVER_IDENT: + case CMD_DISCOVER_SVID: ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; + case CMD_DISCOVER_MODES: +@@ -2686,44 +2915,6 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, + port->vdm_sm_running = false; + } + +-static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, +- const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) +-{ +- int svdm_version; +- u32 header; +- +- switch (tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- /* +- * If the port partner is discovered, then the port partner's +- * SVDM Version will be returned +- */ +- svdm_version = typec_get_cable_svdm_version(port->typec_port); +- if (svdm_version < 0) +- svdm_version = SVDM_VER_MAX; +- break; +- case TCPC_TX_SOP: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- default: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- } +- +- if (WARN_ON(count > VDO_MAX_SIZE - 1)) +- count = VDO_MAX_SIZE - 1; +- +- /* set VDM header with VID & CMD */ +- header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? +- 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), +- svdm_version, cmd); +- tcpm_queue_vdm(port, header, data, count, tx_sop_type); +-} +- + static unsigned int vdm_ready_timeout(u32 vdm_hdr) + { + unsigned int timeout; +@@ -2769,8 +2960,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + port->vdm_sm_running = false; + break; + } +@@ -2780,22 +2970,10 @@ static void vdm_run_state_machine(struct tcpm_port *port) + switch (PD_VDO_CMD(vdo_hdr)) { + case CMD_DISCOVER_IDENT: + res = tcpm_ams_start(port, DISCOVER_IDENTITY); +- if (res == 0) { +- switch (port->tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- port->send_discover_prime = false; +- break; +- case TCPC_TX_SOP: +- port->send_discover = false; +- break; +- default: +- port->send_discover = false; +- break; +- } +- } else if (res == -EAGAIN) { ++ if (res == -EAGAIN) { + port->vdo_data[0] = 0; +- mod_send_discover_delayed_work(port, +- SEND_DISCOVER_RETRY_MS); ++ mod_vdm_discovery_delayed_work(port, ++ SEND_DISCOVERY_VDM_RETRY_MS); + } + break; + case CMD_DISCOVER_SVID: +@@ -2853,6 +3031,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + port->vdm_state = VDM_STATE_DONE; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* +@@ -2869,6 +3048,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + tcpm_ams_finish(port); + } else { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + if (port->tx_sop_type == TCPC_TX_SOP) + break; + /* Handle SOP' Transmission Errors */ +@@ -2878,11 +3058,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * discovery process on SOP only. + */ + case CMD_DISCOVER_IDENT: +- port->vdo_data[0] = 0; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version( + port->typec_port), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + tcpm_queue_vdm(port, response[0], &response[1], + 0, TCPC_TX_SOP); + break; +@@ -2892,9 +3072,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + case CMD_DISCOVER_SVID: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + case CMD_DISCOVER_MODES: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + default: + break; +@@ -3807,7 +3989,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3823,7 +4006,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3832,7 +4016,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + } + break; + case PD_CTRL_VCONN_SWAP: +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3891,6 +4076,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + { + enum pd_ext_msg_type type = pd_header_type_le(msg->header); + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); ++ const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg; + + /* stopping VDM state machine if interrupted by other Messages */ + if (tcpm_vdm_ams(port)) { +@@ -3899,7 +4085,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + mod_vdm_delayed_work(port, 0); + } + +- if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { ++ if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + tcpm_log(port, "Unchunked extended messages unsupported"); + return; +@@ -3924,9 +4110,25 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + NONE_AMS, 0); + } + break; +- case PD_EXT_SOURCE_CAP_EXT: +- case PD_EXT_GET_BATT_CAP: + case PD_EXT_GET_BATT_STATUS: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS, ++ GETTING_BATTERY_STATUS); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_GET_BATT_CAP: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP, ++ GETTING_BATTERY_CAPABILITIES); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_SOURCE_CAP_EXT: + case PD_EXT_BATT_CAP: + case PD_EXT_GET_MANUFACTURER_INFO: + case PD_EXT_MANUFACTURER_INFO: +@@ -4137,6 +4339,22 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) + ret); + tcpm_ams_finish(port); + break; ++ case PD_MSG_DATA_BATT_STATUS: ++ ret = tcpm_pd_send_batt_status(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery status ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; ++ case PD_MSG_EXT_BATT_CAP: ++ ret = tcpm_pd_send_batt_cap(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery cap ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; + default: + break; + } +@@ -4853,8 +5071,7 @@ static int tcpm_src_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + +@@ -4896,11 +5113,11 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) + struct pd_mode_data *modep_prime = &port->mode_data_prime; + int i; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->partner_altmode[i]); + port->partner_altmode[i] = NULL; + } +- for (i = 0; i < modep_prime->altmodes; i++) { ++ for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->plug_prime_altmode[i]); + port->plug_prime_altmode[i] = NULL; + } +@@ -4931,6 +5148,8 @@ static void tcpm_reset_port(struct tcpm_port *port) + port->in_ams = false; + port->ams = NONE_AMS; + port->vdm_sm_running = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + tcpm_unregister_altmodes(port); + tcpm_typec_disconnect(port); + port->attached = false; +@@ -5014,8 +5233,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + } +@@ -5422,16 +5640,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + /* +@@ -5814,16 +6027,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + power_supply_changed(port->psy); +@@ -5869,8 +6077,8 @@ static void run_state_machine(struct tcpm_port *port) + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_unregister_altmodes(port); + port->nr_sink_caps = 0; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, + PD_T_PS_HARD_RESET); +@@ -6017,25 +6225,15 @@ static void run_state_machine(struct tcpm_port *port) + /* DR_Swap states */ + case DR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case DR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); + break; + case DR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); +- port->send_discover = false; +- port->send_discover_prime = false; + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6047,6 +6245,8 @@ static void run_state_machine(struct tcpm_port *port) + else + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, + TYPEC_HOST); ++ if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6330,9 +6530,7 @@ static void run_state_machine(struct tcpm_port *port) + + /* Cable states */ + case SRC_VDM_IDENTITY_REQUEST: +- port->send_discover_prime = true; +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + port->upcoming_state = SRC_SEND_CAPABILITIES; + break; + +@@ -7052,8 +7250,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + goto unlock; + + /* Send when the state machine is idle */ +- if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || +- port->send_discover_prime) ++ if (port->state != SNK_READY || port->vdm_sm_running || ++ port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + goto resched; + + port->upcoming_state = GET_SINK_CAP; +@@ -7070,29 +7268,160 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + mutex_unlock(&port->lock); + } + +-static void tcpm_send_discover_work(struct kthread_work *work) ++static void tcpm_vdm_discovery_work(struct kthread_work *work) + { +- struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work); ++ struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work); ++ enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP; ++ struct typec_port *typec = port->typec_port; ++ struct pd_mode_data *modep, *modep_prime; ++ u32 msg[2] = { }; ++ int svdm_version; + + mutex_lock(&port->lock); +- /* No need to send DISCOVER_IDENTITY anymore */ +- if (!port->send_discover && !port->send_discover_prime) +- goto unlock; + +- if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_log_force(port, "%s state [%s]", __func__, ++ vdm_discovery_state_strings[port->vdm_discovery_state]); ++ ++ /* No need to perform work if Discovery process is complete */ ++ if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE) + goto unlock; +- } + + /* Retry if the port is not idle */ +- if ((port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); ++ if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) { ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + goto unlock; + } + +- tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); ++ modep = &port->mode_data; ++ modep_prime = &port->mode_data_prime; ++ ++ svdm_version = typec_get_negotiated_svdm_version(typec); ++ ++ switch (port->vdm_discovery_state) { ++ /* ++ * The port has not received a Discover Identity response from the port partner. ++ * ++ * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and ++ * SNK_READY states if there is an explicit contract ++ * 2. The port will send Discover Identity to the cable over SOP' in the ++ * SRC_VDM_IDENTITY_REQUEST state if capable of doing so. ++ */ ++ case VDM_DISCOVERY_UNKNOWN: ++ /* Can't send Discover Identity, VDM discovery is complete */ ++ if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ ++ if (port->state == SRC_VDM_IDENTITY_REQUEST) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = SVDM_VER_MAX; ++ } ++ ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the port partner. ++ * ++ * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and ++ * SNK_READY states if it did not previously discover the cable but is capable of doing ++ * so. ++ * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and ++ * SNK_READY states otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_IDENT: ++ if (tcpm_can_communicate_sop_prime(port) && !port->cable) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ } else { ++ if (tcpm_can_communicate_sop_prime(port)) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the cable. ++ * ++ * 1. The port will send Discover SVIDs to the partner over SOP. ++ */ ++ case VDM_DISCOVERY_CABLE_IDENT: ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported ++ * by the partner. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports ++ * modal operation and valid SVIDs were registered. ++ * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_SVIDS: ++ if (modep->nsvids && supports_modal(port)) { ++ msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the partner for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the port partner over SOP. ++ * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP' ++ * and the cable supports VDMs. ++ * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_MODES: ++ /* Not all modes have been discovered yet */ ++ if (modep->svid_index < modep->nsvids) { ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the cable over SOP'. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_SVIDS: ++ if (modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the cable for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the cable over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_MODES: ++ if (modep_prime->svid_index < modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ default: ++ goto unlock; ++ } ++ ++ if (svdm_version >= 0) ++ tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type); + + unlock: + mutex_unlock(&port->lock); +@@ -8508,12 +8837,12 @@ static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) + return HRTIMER_NORESTART; + } + +-static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer) ++static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer) + { +- struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer); ++ struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer); + + if (port->registered) +- kthread_queue_work(port->wq, &port->send_discover_work); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + return HRTIMER_NORESTART; + } + +@@ -8547,14 +8876,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) + kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); + kthread_init_work(&port->event_work, tcpm_pd_event_handler); + kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); +- kthread_init_work(&port->send_discover_work, tcpm_send_discover_work); ++ kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work); + hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); +- hrtimer_setup(&port->send_discover_timer, send_discover_timer_handler, CLOCK_MONOTONIC, ++ hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + spin_lock_init(&port->pd_event_lock); +@@ -8649,11 +8978,12 @@ void tcpm_unregister_port(struct tcpm_port *port) + port->registered = false; + kthread_destroy_worker(port->wq); + +- hrtimer_cancel(&port->send_discover_timer); ++ hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); + hrtimer_cancel(&port->vdm_state_machine_timer); + hrtimer_cancel(&port->state_machine_timer); + ++ power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt); + tcpm_reset_port(port); + + tcpm_port_unregister_pd(port); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -129,6 +129,7 @@ enum { + TPS_MODE_BIST, + TPS_MODE_DISC, + TPS_MODE_PTCH, ++ TPS_MODE_APP1, + }; + + static const char *const modes[] = { +@@ -137,6 +138,7 @@ static const char *const modes[] = { + [TPS_MODE_BIST] = "BIST", + [TPS_MODE_DISC] = "DISC", + [TPS_MODE_PTCH] = "PTCH", ++ [TPS_MODE_APP1] = "APP1", + }; + + /* Unrecognized commands will be replaced with "!CMD" */ +@@ -158,6 +160,7 @@ struct tipd_data { + int (*init)(struct tps6598x *tps); + int (*switch_power_state)(struct tps6598x *tps, u8 target_state); + bool (*read_data_status)(struct tps6598x *tps); ++ bool (*read_power_status)(struct tps6598x *tps); + int (*reset)(struct tps6598x *tps); + int (*connect)(struct tps6598x *tps, u32 status); + }; +@@ -629,6 +632,34 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) + return true; + } + ++/* ++ * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported ++ * and the remaining bits are redundant with STATUS register (0x1A). ++ * Synthesize pwr_status from the already-read STATUS register. ++ */ ++static bool tps66993_read_power_status(struct tps6598x *tps) ++{ ++ u16 pwr_status = 0; ++ ++ /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */ ++ if (tps->status & TPS_STATUS_PLUG_PRESENT) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1); ++ ++ /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */ ++ if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status)) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1); ++ ++ if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, ++ TPS_POWER_STATUS_TYPEC_CURRENT_PD); ++ ++ tps->pwr_status = pwr_status; ++ ++ tps->data->trace_power_status(pwr_status); ++ ++ return true; ++} ++ + static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) + { + int ret; +@@ -906,7 +937,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) + goto err_unlock; + + if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { + if (tps6598x_read_partner_identity(tps)) { +@@ -961,7 +992,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) +@@ -1034,17 +1065,25 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) + if (!tps6598x_read_status(tps, &status)) + goto err_unlock; + ++ tps->status = status; ++ + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + + if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) + if (!tps->data->read_data_status(tps)) + goto err_unlock; + +- /* Handle plug insert or removal */ +- if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) ++ /* ++ * Refresh power status before connect - needed for TPS66993 which ++ * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE. ++ */ ++ if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) { ++ if (!tps->data->read_power_status(tps)) ++ goto err_unlock; + tps6598x_handle_plug_event(tps, status); ++ } + + err_unlock: + mutex_unlock(&tps->lock); +@@ -1080,6 +1119,7 @@ static int tps6598x_check_mode(struct tps6598x *tps) + + switch (ret) { + case TPS_MODE_APP: ++ case TPS_MODE_APP1: + case TPS_MODE_PTCH: + return ret; + case TPS_MODE_BOOT: +@@ -1753,7 +1793,7 @@ static int tps6598x_probe(struct i2c_client *client) + struct tps6598x *tps; + struct fwnode_handle *fwnode; + u32 status; +- u32 vid; ++ u32 vid = 0; + int ret; + + data = i2c_get_match_data(client); +@@ -1781,8 +1821,11 @@ static int tps6598x_probe(struct i2c_client *client) + + if (!device_is_compatible(tps->dev, "ti,tps25750")) { + ret = tps6598x_read32(tps, TPS_REG_VID, &vid); +- if (ret < 0 || !vid) ++ if (ret < 0 || !vid) { ++ dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n", ++ ret, vid); + return -ENODEV; ++ } + } + + /* +@@ -1818,6 +1861,8 @@ static int tps6598x_probe(struct i2c_client *client) + goto err_clear_mask; + } + ++ tps->status = status; ++ + /* + * This fwnode has a "compatible" property, but is never populated as a + * struct device. Instead we simply parse it to read the properties. +@@ -1845,7 +1890,7 @@ static int tps6598x_probe(struct i2c_client *client) + + if (status & TPS_STATUS_PLUG_PRESENT) { + ret = -EINVAL; +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unregister_port; + if (!tps->data->read_data_status(tps)) + goto err_unregister_port; +@@ -1860,7 +1905,7 @@ static int tps6598x_probe(struct i2c_client *client) + IRQF_SHARED | IRQF_ONESHOT, + dev_name(&client->dev), tps); + } else { +- dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n"); ++ dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); + INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); + queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, + msecs_to_jiffies(POLL_INTERVAL)); +@@ -1987,6 +2032,7 @@ static const struct tipd_data cd321x_data = { + .trace_status = trace_tps6598x_status, + .init = cd321x_init, + .read_data_status = cd321x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, +@@ -2006,6 +2052,25 @@ static const struct tipd_data tps6598x_data = { + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, ++ .reset = tps6598x_reset, ++ .connect = tps6598x_connect, ++}; ++ ++static const struct tipd_data tps66993_data = { ++ .irq_handler = tps6598x_interrupt, ++ .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE | ++ TPS_REG_INT_PLUG_EVENT, ++ .tps_struct_size = sizeof(struct tps6598x), ++ .register_port = tps6598x_register_port, ++ .unregister_port = tps6598x_unregister_port, ++ .trace_data_status = trace_tps6598x_data_status, ++ .trace_power_status = trace_tps6598x_power_status, ++ .trace_status = trace_tps6598x_status, ++ .apply_patch = tps6598x_apply_patch, ++ .init = tps6598x_init, ++ .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps66993_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, + }; +@@ -2024,12 +2089,14 @@ static const struct tipd_data tps25750_data = { + .apply_patch = tps25750_apply_patch, + .init = tps25750_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = tps25750_reset, + .connect = tps6598x_connect, + }; + + static const struct of_device_id tps6598x_of_match[] = { + { .compatible = "ti,tps6598x", &tps6598x_data}, ++ { .compatible = "ti,tps66993", &tps66993_data}, + { .compatible = "apple,cd321x", &cd321x_data}, + { .compatible = "ti,tps25750", &tps25750_data}, + {} +diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h +--- a/drivers/usb/typec/tipd/tps6598x.h ++++ b/drivers/usb/typec/tipd/tps6598x.h +@@ -142,9 +142,13 @@ + #define TPS_SYSTEM_POWER_STATE_S4 0x04 + #define TPS_SYSTEM_POWER_STATE_S5 0x05 + +-/* TPS_REG_POWER_STATUS bits */ +-#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x)) +-#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x)) ++/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */ ++#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0) ++#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1) ++#define TPS_POWER_STATUS_CONNECTION(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x)) ++#define TPS_POWER_STATUS_SOURCESINK(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x)) + #define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x)) + + #define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2) +diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c +--- a/drivers/usb/typec/ucsi/debugfs.c ++++ b/drivers/usb/typec/ucsi/debugfs.c +@@ -82,8 +82,8 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) + if (ucsi->debugfs->status) + return ucsi->debugfs->status; + +- seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high, +- ucsi->debugfs->response.low); ++ seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext, ++ ucsi->debugfs->response.high, ucsi->debugfs->response.low); + return 0; + } + DEFINE_SHOW_ATTRIBUTE(ucsi_resp); +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1102,8 +1102,8 @@ static int ucsi_register_plug(struct ucsi_connector *con) + plug = typec_register_plug(con->cable, &desc); + if (IS_ERR(plug)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register plug (%ld)\n", con->num, +- PTR_ERR(plug)); ++ "con%d: failed to register plug (%pe)\n", con->num, ++ plug); + return PTR_ERR(plug); + } + +@@ -1161,8 +1161,8 @@ static int ucsi_register_cable(struct ucsi_connector *con) + cable = typec_register_cable(con->port, &desc); + if (IS_ERR(cable)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register cable (%ld)\n", con->num, +- PTR_ERR(cable)); ++ "con%d: failed to register cable (%pe)\n", con->num, ++ cable); + return PTR_ERR(cable); + } + +@@ -1291,8 +1291,8 @@ static int ucsi_register_partner(struct ucsi_connector *con) + partner = typec_register_partner(con->port, &desc); + if (IS_ERR(partner)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register partner (%ld)\n", con->num, +- PTR_ERR(partner)); ++ "con%d: failed to register partner (%pe)\n", con->num, ++ partner); + return PTR_ERR(partner); + } + +@@ -2221,7 +2221,7 @@ static void ucsi_init_work(struct work_struct *work) + return; + } + +- queue_delayed_work(system_long_wq, &ucsi->work, ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); + } + } +@@ -2345,7 +2345,7 @@ int ucsi_register(struct ucsi *ucsi) + UCSI_BCD_GET_MINOR(ucsi->version), + UCSI_BCD_GET_SUBMINOR(ucsi->version)); + +- queue_delayed_work(system_long_wq, &ucsi->work, 0); ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0); + + ucsi_debugfs_register(ucsi); + return 0; +diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h +--- a/drivers/usb/typec/ucsi/ucsi.h ++++ b/drivers/usb/typec/ucsi/ucsi.h +@@ -466,6 +466,7 @@ struct ucsi_debugfs_entry { + struct ucsi_data { + u64 low; + u64 high; ++ u64 ext; + } response; + int status; + u8 message_out[MESSAGE_OUT_MAX_LEN]; +diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h +--- a/include/linux/power_supply.h ++++ b/include/linux/power_supply.h +@@ -806,11 +806,26 @@ extern int power_supply_reg_notifier(struct notifier_block *nb); + extern void power_supply_unreg_notifier(struct notifier_block *nb); + #if IS_ENABLED(CONFIG_POWER_SUPPLY) + extern struct power_supply *power_supply_get_by_name(const char *name); ++extern int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys); ++extern void power_supply_put_system_batteries(struct power_supply **psys, int count); + extern void power_supply_put(struct power_supply *psy); + #else + static inline void power_supply_put(struct power_supply *psy) {} + static inline struct power_supply *power_supply_get_by_name(const char *name) + { return NULL; } ++static inline int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ if (psys) ++ *psys = NULL; ++ return 0; ++} ++ ++static inline void power_supply_put_system_batteries(struct power_supply **psys, ++ int count) ++{ ++} + #endif + extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, + const char *property); +diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h +--- a/include/linux/usb/pd.h ++++ b/include/linux/usb/pd.h +@@ -106,6 +106,9 @@ enum pd_ext_msg_type { + #define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) + ++#define PD_HEADER_EXT_LE(type, pwr, data, rev, id, cnt) \ ++ cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (1))) ++ + static inline unsigned int pd_header_cnt(u16 header) + { + return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; +@@ -219,6 +222,25 @@ static inline u8 count_chunked_data_objs(u32 size) + return ((size / 4) + (size % 4 ? 1 : 0)); + } + ++/** ++ * batt_cap_ext_msg - Battery capability extended PD message ++ * @vid: Battery Vendor ID (assigned by USB-IF) ++ * @pid: Battery Product ID (assigned by battery or device vendor) ++ * @batt_design_cap: Battery design capacity in 0.1Wh ++ * @batt_last_chg_cap: Battery last full charge capacity in 0.1Wh ++ * @batt_type: Battery Type. bit0 when set indicates invalid battery reference. ++ * Rest of the bits are reserved. ++ */ ++struct batt_cap_ext_msg { ++ __le16 vid; ++ __le16 pid; ++ __le16 batt_design_cap; ++ __le16 batt_last_chg_cap; ++ u8 batt_type; ++} __packed; ++ ++#define BATT_CAP_BATT_TYPE_INVALID_REF BIT(0) ++ + /* Sink Caps Extended Data Block Version */ + #define SKEDB_VER_1_0 1 + +@@ -724,4 +746,33 @@ void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct devi + + #endif /* CONFIG_TYPEC */ + ++/* Battery Status Data Object */ ++#define BSDO_PRESENT_CAPACITY GENMASK(31, 16) ++#define BSDO_CHG_STATUS GENMASK(11, 10) ++#define BSDO_BATTERY_PRESENT BIT(9) ++#define BSDO_INVALID_BATTERY_REFERENCE BIT(8) ++ ++/* ++ * Battery Charge Status: Battery Charging Status Values as defined in ++ * "USB PD Spec Rev3.1 Ver1.8", "Table 6-46 Battery Status Data Object (BSDO)". ++ */ ++#define BSDO_BATTERY_INFO_CHARGING 0x0 ++#define BSDO_BATTERY_INFO_DISCHARGING 0x1 ++#define BSDO_BATTERY_INFO_IDLE 0x2 ++#define BSDO_BATTERY_INFO_RSVD 0x3 ++ ++/** ++ * BSDO() - Pack data into Battery Status Data Object format. ++ * @batt_charge: Battery's present state of charge in 0.1WH increment. ++ * @chg_status: Battery charge status. ++ * @batt_present: Indicates that battery is present/attached when set else absent when unset. ++ * @invalid_ref: Indicates that an invalid battery reference was made in the Get_Battery_Status ++ * request. ++ */ ++#define BSDO(batt_charge, chg_status, batt_present, invalid_ref) \ ++ ((FIELD_PREP(BSDO_PRESENT_CAPACITY, batt_charge)) | \ ++ (FIELD_PREP(BSDO_CHG_STATUS, chg_status)) | \ ++ ((batt_present) ? BSDO_BATTERY_PRESENT : 0) | \ ++ ((invalid_ref) ? BSDO_INVALID_BATTERY_REFERENCE : 0)) ++ + #endif /* __LINUX_USB_PD_H */ +diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h +--- a/include/linux/usb/typec.h ++++ b/include/linux/usb/typec.h +@@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); + struct typec_cable *typec_cable_get(struct typec_port *port); + void typec_cable_put(struct typec_cable *cable); + int typec_cable_is_active(struct typec_cable *cable); ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt); + + struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc); +diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h +--- a/include/uapi/linux/usb/functionfs.h ++++ b/include/uapi/linux/usb/functionfs.h +@@ -25,6 +25,7 @@ enum functionfs_flags { + FUNCTIONFS_EVENTFD = 32, + FUNCTIONFS_ALL_CTRL_RECIP = 64, + FUNCTIONFS_CONFIG0_SETUP = 128, ++ FUNCTIONFS_RW_PROXY_EPS = 256, + }; + + /* Descriptor of an non-audio endpoint */ +@@ -414,4 +415,27 @@ struct usb_functionfs_event { + #define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \ + struct usb_ffs_dmabuf_transfer_req) + ++/* ++ * Enable or disable automatic zero-length packet (ZLP) appending for the ++ * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to ++ * enable. ++ * ++ * When enabled, the kernel will automatically append a ZLP at the end of ++ * a transfer if the payload length is an exact multiple of the endpoint's ++ * max packet size. ++ * ++ * This is useful for compatibility with legacy protocols which require ++ * automatic ZLP appending to data written from userspace. ++ * ++ * This ioctl can only be used on IN endpoints. It can be called at any time ++ * after the FunctionFS instance is active, even before the host has connected ++ * or enabled the endpoint. ++ * ++ * Returns zero on success, or a negative errno value on error: ++ * -ENODEV: The FunctionFS instance is not active. ++ * -EINVAL: The endpoint is not an IN endpoint. ++ * -EFAULT: Invalid user space pointer for the argument. ++ */ ++#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32) ++ + #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ +diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs +--- a/rust/kernel/usb.rs ++++ b/rust/kernel/usb.rs +@@ -24,11 +24,8 @@ + }; + use core::{ + marker::PhantomData, +- mem::{ +- offset_of, +- MaybeUninit, // +- }, +- ptr::NonNull, ++ mem::offset_of, ++ ptr::NonNull, // + }; + + /// An adapter for the registration of USB drivers. +@@ -130,8 +127,7 @@ pub const fn from_id(vendor: u16, product: u16) -> Self { + match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16, + idVendor: vendor, + idProduct: product, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -143,8 +139,7 @@ pub const fn from_device_ver(vendor: u16, product: u16, bcd_lo: u16, bcd_hi: u16 + idProduct: product, + bcdDevice_lo: bcd_lo, + bcdDevice_hi: bcd_hi, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -155,8 +150,7 @@ pub const fn from_device_info(class: u8, subclass: u8, protocol: u8) -> Self { + bDeviceClass: class, + bDeviceSubClass: subclass, + bDeviceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -167,8 +161,7 @@ pub const fn from_interface_info(class: u8, subclass: u8, protocol: u8) -> Self + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -180,8 +173,7 @@ pub const fn from_device_interface_class(vendor: u16, product: u16, class: u8) - + idVendor: vendor, + idProduct: product, + bInterfaceClass: class, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -193,8 +185,7 @@ pub const fn from_device_interface_protocol(vendor: u16, product: u16, protocol: + idVendor: vendor, + idProduct: product, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -206,8 +197,7 @@ pub const fn from_device_interface_number(vendor: u16, product: u16, number: u8) + idVendor: vendor, + idProduct: product, + bInterfaceNumber: number, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -227,8 +217,7 @@ pub const fn from_device_and_interface_info( + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + } +@@ -393,6 +382,7 @@ fn as_ref(&self) -> &Device { + + // SAFETY: Instances of `Interface` are always reference-counted. + unsafe impl AlwaysRefCounted for Interface { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Interface` guarantee that `self.as_raw()` + // returns a valid `struct usb_interface` pointer, for which we will +@@ -400,6 +390,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_intf(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) } +@@ -444,6 +435,7 @@ fn as_raw(&self) -> *mut bindings::usb_device { + + // SAFETY: Instances of `Device` are always reference-counted. + unsafe impl AlwaysRefCounted for Device { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Device` guarantee that `self.as_raw()` + // returns a valid `struct usb_device` pointer, for which we will +@@ -451,6 +443,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_dev(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) } diff --git a/pkgbuilds/linux-omarchy-eevdf/0603-usb-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0603-usb-updates.patch.sig new file mode 100644 index 0000000..d35c194 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0603-usb-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0604-xhci-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0604-xhci-fixes.patch new file mode 100644 index 0000000..8545f24 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0604-xhci-fixes.patch @@ -0,0 +1,35 @@ +diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -2301,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) + if (!segs) + segs = ERST_DEFAULT_SEGS; + +- max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; ++ max_segs = BIT(FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2)); + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -4328,11 +4328,16 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + check_interval(urb, ep_ctx); + + /* +- * Check if this starts the isoc data flow. Relies on hw setting ep ctx +- * state after doorbell ring. Consider adding list_empty(td_list) check ++ * Schedule the URB discontiguously if all previous URBs have completed. ++ * XXX core can't tell if completions are pending but not running yet. + */ +- if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ if (list_empty(&ep_ring->td_list) && ++ !hcd_periodic_completion_in_progress(xhci_to_hcd(xhci), urb->ep)) { ++ if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) ++ xhci_dbg(xhci, "Unexpected running ring at isoc stream start, uframe: %d\n", ++ xep->next_uframe); + xep->next_uframe = -1; ++ } + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } diff --git a/pkgbuilds/linux-omarchy-eevdf/0604-xhci-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0604-xhci-fixes.patch.sig new file mode 100644 index 0000000..d93226e Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0604-xhci-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0605-thunderbolt-pcie-rescan-after-tunnel.patch b/pkgbuilds/linux-omarchy-eevdf/0605-thunderbolt-pcie-rescan-after-tunnel.patch new file mode 100644 index 0000000..a0b7740 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0605-thunderbolt-pcie-rescan-after-tunnel.patch @@ -0,0 +1,186 @@ +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -909,6 +909,10 @@ int tb_domain_init(void) + { + int ret; + ++ ret = tb_pci_rescan_init(); ++ if (ret) ++ return ret; ++ + tb_configfs_init(); + tb_debugfs_init(); + tb_acpi_init(); +@@ -927,6 +931,7 @@ err_xdomain: + err_acpi: + tb_acpi_exit(); + tb_debugfs_exit(); ++ tb_pci_rescan_exit(); + + return ret; + } +@@ -940,4 +945,5 @@ void tb_domain_exit(void) + tb_acpi_exit(); + tb_debugfs_exit(); + tb_configfs_exit(); ++ tb_pci_rescan_exit(); + } +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -9,7 +9,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + + #include "tb.h" +@@ -18,6 +20,7 @@ + + #define TB_TIMEOUT 100 /* ms */ + #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ ++#define TB_PCI_RESCAN_DELAY 300 /* ms */ + + /* + * How many time bandwidth allocation request from graphics driver is +@@ -49,6 +52,13 @@ MODULE_PARM_DESC(asym_threshold, + "threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: " + __MODULE_STRING(TB_ASYM_THRESHOLD) ")"); + ++struct tb_pci_rescan { ++ struct delayed_work work; ++ struct kref kref; ++ struct pci_bus *bus; ++ bool stopped; ++}; ++ + /** + * struct tb_cm - Simple Thunderbolt connection manager + * @tunnel_list: List of active tunnels +@@ -67,8 +77,11 @@ struct tb_cm { + bool hotplug_active; + struct delayed_work remove_work; + struct tb_bandwidth_group groups[MAX_GROUPS]; ++ struct tb_pci_rescan *pci_rescan; + }; + ++static struct workqueue_struct *tb_pci_rescan_wq; ++ + static inline struct tb *tcm_to_tb(struct tb_cm *tcm) + { + return ((void *)tcm - sizeof(struct tb)); +@@ -2282,6 +2295,82 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw) + return 0; + } + ++static void tb_pci_rescan_release(struct kref *kref) ++{ ++ struct tb_pci_rescan *rescan = container_of(kref, typeof(*rescan), kref); ++ ++ put_device(&rescan->bus->dev); ++ kfree(rescan); ++} ++ ++static void tb_pci_rescan_work_fn(struct work_struct *work) ++{ ++ struct tb_pci_rescan *rescan = ++ container_of(to_delayed_work(work), typeof(*rescan), work); ++ ++ pci_lock_rescan_remove(); ++ if (!READ_ONCE(rescan->stopped)) ++ pci_rescan_bus(rescan->bus); ++ pci_unlock_rescan_remove(); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_schedule(struct tb *tb) ++{ ++ struct tb_cm *tcm = tb_priv(tb); ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) { ++ struct pci_bus *bus; ++ ++ if (!dev_is_pci(tb->nhi->dev)) ++ return; ++ bus = to_pci_dev(tb->nhi->dev)->bus; ++ if (bus->parent) ++ bus = bus->parent; ++ ++ rescan = kzalloc_obj(*rescan); ++ if (!rescan) ++ return; ++ INIT_DELAYED_WORK(&rescan->work, tb_pci_rescan_work_fn); ++ kref_init(&rescan->kref); ++ get_device(&bus->dev); ++ rescan->bus = bus; ++ tcm->pci_rescan = rescan; ++ } ++ ++ kref_get(&rescan->kref); ++ if (mod_delayed_work(tb_pci_rescan_wq, &rescan->work, ++ msecs_to_jiffies(TB_PCI_RESCAN_DELAY))) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_stop(struct tb_cm *tcm) ++{ ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) ++ return; ++ ++ WRITE_ONCE(rescan->stopped, true); ++ if (cancel_delayed_work(&rescan->work)) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ tcm->pci_rescan = NULL; ++} ++ ++int tb_pci_rescan_init(void) ++{ ++ tb_pci_rescan_wq = alloc_workqueue("thunderbolt_pci_rescan", ++ WQ_FREEZABLE | WQ_UNBOUND, 0); ++ return tb_pci_rescan_wq ? 0 : -ENOMEM; ++} ++ ++void tb_pci_rescan_exit(void) ++{ ++ destroy_workqueue(tb_pci_rescan_wq); ++} ++ + static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + { + struct tb_port *up, *down, *port; +@@ -2323,6 +2412,7 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + tb_sw_warn(sw, "failed to connect xHCI\n"); + + list_add_tail(&tunnel->list, &tcm->tunnel_list); ++ tb_pci_rescan_schedule(tb); + return 0; + } + +@@ -2957,6 +3047,7 @@ static void tb_stop(struct tb *tb) + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); ++ tb_pci_rescan_stop(tcm); + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -759,6 +759,8 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, + + struct tb *icm_probe(struct tb_nhi *nhi); + struct tb *tb_probe(struct tb_nhi *nhi); ++int tb_pci_rescan_init(void); ++void tb_pci_rescan_exit(void); + + extern const struct device_type tb_domain_type; + extern const struct device_type tb_retimer_type; diff --git a/pkgbuilds/linux-omarchy-eevdf/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig new file mode 100644 index 0000000..63eb3c6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0606-thunderbolt-fixes-2.patch b/pkgbuilds/linux-omarchy-eevdf/0606-thunderbolt-fixes-2.patch new file mode 100644 index 0000000..0d85afa --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0606-thunderbolt-fixes-2.patch @@ -0,0 +1,239 @@ +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -788,21 +788,6 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + transmit_ring, receive_path, receive_ring); + } + +-static void tb_domain_reset_interface(struct tb *tb) +-{ +- struct tb_nhi *nhi = tb->nhi; +- +- if (!nhi->ops->reset_interface) +- return; +- +- guard(mutex)(&tb->lock); +- +- /* The reset clears the ring state so stop the control channel */ +- tb_ctl_stop(tb->ctl); +- nhi->ops->reset_interface(nhi); +- tb_ctl_start(tb->ctl); +-} +- + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -825,20 +810,11 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { +- int ret; +- + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); +- if (ret) +- return ret; +- +- if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) +- tb_domain_reset_interface(tb); +- +- return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -1180,32 +1180,6 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + +-/** +- * nhi_reset_interface() - Reset the host interface +- * @nhi: Host interface to reset +- * +- * Brings the registers in the memory BAR back to their default state and +- * clears the End-to-End Flow Control state. The caller is responsible for +- * stopping the control channel over the reset because it clears the ring +- * state as well. +- */ +-void nhi_reset_interface(struct tb_nhi *nhi) +-{ +- u32 val; +- +- val = ioread32(nhi->iobase + REG_CAPS); +- /* Only v1 host interfaces implement the reset */ +- if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) +- return; +- +- dev_dbg(nhi->dev, "issuing host interface reset\n"); +- +- iowrite32(REG_HOST_INTERFACE_RESET_RST, +- nhi->iobase + REG_HOST_INTERFACE_RESET); +- /* Wait for tHIReset (10 ms) to complete */ +- usleep_range(10000, 20000); +-} +- + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,8 +36,6 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); +-void nhi_reset_interface(struct tb_nhi *nhi); +- + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -54,7 +52,6 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook +- * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -69,7 +66,6 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); +- void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -120,24 +116,11 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e +- + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) +-#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,10 +115,6 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + +-/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ +-#define REG_HOST_INTERFACE_RESET 0x39858 +-#define REG_HOST_INTERFACE_RESET_RST BIT(0) +- + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,27 +62,6 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } +- } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { +- switch (pdev->device) { +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: +- /* +- * These AMD hosts may hang the Tx ring when the +- * DMA paths are torn down so they need the host +- * interface reset after each teardown. +- */ +- nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; +- break; +- } + } + } + +@@ -279,7 +258,6 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -463,7 +441,6 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c +--- a/drivers/thunderbolt/quirks.c ++++ b/drivers/thunderbolt/quirks.c +@@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = { + quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, ++ /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */ ++ { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5, ++ quirk_clx_disable }, + /* + * Block Runtime PM in DP redrive mode for Intel Barlow Ridge host + * controllers. +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -980,6 +980,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) + } + + mutex_unlock(&sdev->lock); ++ ++ /* Stream handles IOCB_NOWAIT just fine */ ++ file->f_mode |= FMODE_NOWAIT; + return 0; + + err_unlock: +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, + + len += sizeof(res->hdr.xd_hdr) / 4; + len -= sizeof(*res) / 4; ++ if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) { ++ ret = -EINVAL; ++ goto err; ++ } + + if (res->offset != req.offset) { + ret = -EINVAL; diff --git a/pkgbuilds/linux-omarchy-eevdf/0606-thunderbolt-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0606-thunderbolt-fixes-2.patch.sig new file mode 100644 index 0000000..8e90b6c Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0606-thunderbolt-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0607-usb-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0607-usb-fixes.patch new file mode 100644 index 0000000..2e3ea13 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0607-usb-fixes.patch @@ -0,0 +1,1673 @@ +diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c +--- a/drivers/usb/cdns3/cdns3-gadget.c ++++ b/drivers/usb/cdns3/cdns3-gadget.c +@@ -3269,6 +3269,10 @@ static void cdns3_gadget_exit(struct cdns *cdns) + usb_del_gadget(&priv_dev->gadget); + devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + ++ /* The works can still be queued until the IRQ is freed. */ ++ cancel_work_sync(&priv_dev->pending_status_wq); ++ cancel_work_sync(&priv_dev->aligned_buf_wq); ++ + cdns3_free_all_eps(priv_dev); + + while (!list_empty(&priv_dev->aligned_buf_list)) { +diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c +--- a/drivers/usb/cdns3/cdns3-pci-wrap.c ++++ b/drivers/usb/cdns3/cdns3-pci-wrap.c +@@ -96,6 +96,11 @@ static int cdns3_pci_probe(struct pci_dev *pdev, + + if (pci_is_enabled(func)) { + wrap = pci_get_drvdata(func); ++ if (!wrap) { ++ dev_err(&pdev->dev, ++ "second function not initialized, retrying\n"); ++ return -EPROBE_DEFER; ++ } + } else { + wrap = kzalloc_obj(*wrap); + if (!wrap) +diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c +--- a/drivers/usb/chipidea/ci_hdrc_tegra.c ++++ b/drivers/usb/chipidea/ci_hdrc_tegra.c +@@ -306,8 +306,10 @@ static int tegra_usb_probe(struct platform_device *pdev) + + pm_runtime_enable(&pdev->dev); + err = pm_runtime_resume_and_get(&pdev->dev); +- if (err) ++ if (err) { ++ pm_runtime_disable(&pdev->dev); + return err; ++ } + + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) + usb->needs_double_reset = true; +diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c +--- a/drivers/usb/class/cdc-acm.c ++++ b/drivers/usb/class/cdc-acm.c +@@ -331,7 +331,6 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) + + spin_lock_irqsave(&acm->read_lock, flags); + acm->ctrlin = newctrl; +- acm->oldcount = acm->iocount; + + if (difference & USB_CDC_SERIAL_STATE_DSR) + acm->iocount.dsr++; +@@ -788,6 +787,9 @@ static void acm_port_shutdown(struct tty_port *port) + usb_autopm_put_interface_async(acm->control); + } + ++ if (acm->disconnected) ++ return; ++ + acm_unpoison_urbs(acm); + + if (acm->quirks & ALWAYS_POLL_CTRL) { +@@ -1027,11 +1029,16 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + +- do { ++ spin_lock_irq(&acm->read_lock); ++ old = acm->iocount; ++ spin_unlock_irq(&acm->read_lock); ++ ++ add_wait_queue(&acm->wioctl, &wait); ++ for (;;) { ++ set_current_state(TASK_INTERRUPTIBLE); ++ + spin_lock_irq(&acm->read_lock); +- old = acm->oldcount; + new = acm->iocount; +- acm->oldcount = new; + spin_unlock_irq(&acm->read_lock); + + if ((arg & TIOCM_DSR) && +@@ -1044,22 +1051,20 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + old.rng != new.rng) + break; + +- add_wait_queue(&acm->wioctl, &wait); +- set_current_state(TASK_INTERRUPTIBLE); +- schedule(); +- remove_wait_queue(&acm->wioctl, &wait); + if (acm->disconnected) { +- if (arg & TIOCM_CD) +- break; +- else +- rv = -ENODEV; +- } else { +- if (signal_pending(current)) +- rv = -ERESTARTSYS; ++ rv = -ENODEV; ++ break; + } +- } while (!rv); + +- ++ schedule(); ++ ++ if (signal_pending(current)) { ++ rv = -ERESTARTSYS; ++ break; ++ } ++ } ++ __set_current_state(TASK_RUNNING); ++ remove_wait_queue(&acm->wioctl, &wait); + + return rv; + } +diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h +--- a/drivers/usb/class/cdc-acm.h ++++ b/drivers/usb/class/cdc-acm.h +@@ -90,7 +90,6 @@ struct acm { + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ +- struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -73,8 +73,11 @@ + /* + * Give SS hubs 200ms time after wake to train downstream links before + * assuming no port activity and allowing hub to runtime suspend back. ++ * Root hubs have no upstream hub whose wake propagation needs to be ++ * accounted for, so they need less time, use 120ms for them. + */ + #define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ ++#define USB_SS_RH_PORT_U0_WAKE_TIME 120 /* ms */ + + /* Protect struct usb_device->state and ->children members + * Note: Both are also protected by ->dev.sem, except that ->state can +@@ -1358,7 +1361,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) + + queue_delayed_work(system_power_efficient_wq, + &hub->post_resume_work, +- msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); ++ msecs_to_jiffies(hdev->parent ? ++ USB_SS_PORT_U0_WAKE_TIME : ++ USB_SS_RH_PORT_U0_WAKE_TIME)); + return; + } + +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1337,7 +1337,8 @@ static void remove_intf_ep_devs(struct usb_interface *intf) + * + * Disables the endpoint for URB submission and nukes all pending URBs. + * If @reset_hardware is set then also deallocates hcd/hardware state +- * for the endpoint. ++ * for the endpoint (clearing both ep_in and ep_out pointers for ++ * bidirectional non-ep0 control endpoints). + */ + void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + bool reset_hardware) +@@ -1358,6 +1359,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + dev->ep_in[epnum] = NULL; + } + if (ep) { ++ if (reset_hardware && epnum != 0 && ++ usb_endpoint_xfer_control(&ep->desc)) { ++ dev->ep_out[epnum] = NULL; ++ dev->ep_in[epnum] = NULL; ++ } + ep->enabled = 0; + usb_hcd_flush_endpoint(dev, ep); + if (reset_hardware) +diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c +--- a/drivers/usb/dwc2/hcd.c ++++ b/drivers/usb/dwc2/hcd.c +@@ -5082,14 +5082,13 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) + } + + cancel_work_sync(&hsotg->phy_reset_work); +- +- timer_delete(&hsotg->wkp_timer); + } + + static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) + { + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); ++ timer_shutdown_sync(&hsotg->wkp_timer); + + dwc2_hcd_free(hsotg); + } +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -49,6 +49,7 @@ + #define DWC3_ENDPOINTS_NUM 32 + #define DWC3_XHCI_RESOURCES_NUM 2 + #define DWC3_ISOC_MAX_RETRIES 5 ++#define DWC3_ERR_RECOVERY_MAX 3 + + #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ + #define DWC3_EVENT_BUFFERS_SIZE 4096 +@@ -841,6 +842,12 @@ enum dwc3_link_state { + DWC3_LINK_STATE_MASK = 0x0f, + }; + ++enum dwc3_err_state { ++ DWC3_ERR_NONE = 0, ++ DWC3_ERR_RECOVERY, ++ DWC3_ERR_UNRECOVERABLE, ++}; ++ + /* TRB Length, PCM and Status */ + #define DWC3_TRB_SIZE_MASK (0x00ffffff) + #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) +@@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { + /** + * struct dwc3 - representation of our controller + * @drd_work: workqueue used for role swapping ++ * @err_recovery_work: workqueue used for controller error recovery + * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @setup_buf: used while precessing STD USB requests +@@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { + * @ep0_in_setup: one control transfer is completed and enter setup phase + * @lock: for synchronizing + * @mutex: for mode switching ++ * @connect_mutex: for the pull-up and err_recovery_work + * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device + * @xhci: pointer to our xHCI child +@@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { + * @ep0_next_event: hold the next expected event + * @ep0state: state of endpoint zero + * @link_state: link state ++ * @err_state: current error recovery state. ++ * @err_recovery_count: number of consecutive error recovery attempts until ++ * confirmed healthy and reset to 0 on reset event. + * @speed: device speed (super, high, full, low) + * @hwparams: copy of hwparams registers + * @regset: debugfs pointer to regdump file +@@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { + */ + struct dwc3 { + struct work_struct drd_work; ++ struct work_struct err_recovery_work; + struct dwc3_trb *ep0_trb; + void *bounce; + u8 *setup_buf; +@@ -1203,6 +1216,9 @@ struct dwc3 { + /* mode switching lock */ + struct mutex mutex; + ++ /* serializes pull-up run/stop vs error recovery */ ++ struct mutex connect_mutex; ++ + struct device *dev; + struct device *sysdev; + +@@ -1332,6 +1348,9 @@ struct dwc3 { + enum dwc3_ep0_next ep0_next_event; + enum dwc3_ep0_state ep0state; + enum dwc3_link_state link_state; ++ enum dwc3_err_state err_state; ++ ++ u32 err_recovery_count; + + u16 u2sel; + u16 u2pel; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -299,6 +299,7 @@ static int dwc3_ti_probe(struct platform_device *pdev) + + err_pm_disable: + clk_disable_unprepare(am62->usb2_refclk); ++ pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + return ret; +diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c +--- a/drivers/usb/dwc3/ep0.c ++++ b/drivers/usb/dwc3/ep0.c +@@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ ret = -ESHUTDOWN; ++ goto out; ++ } ++ + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { + dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", + dep->name); +@@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep0_set_halt(ep, value); + spin_unlock_irqrestore(&dwc->lock, flags); + +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, + return 0; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_queue(dep, req); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_set_halt(dep, value, false); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + dep->flags |= DWC3_EP_WEDGE; + + if (dep->number == 0 || dep->number == 1) +@@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + if (!dwc->gadget->wakeup_armed) { + dev_err(dwc->dev, "not armed for remote wakeup\n"); + spin_unlock_irqrestore(&dwc->lock, flags); +@@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + /* + * If the link is in U3, signal for remote wakeup and wait for the + * link to transition to U0 before sending device notification. +@@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) + + synchronize_irq(dwc->irq_gadget); + ++ /* Serialize against dwc3_err_recovery_work() */ ++ mutex_lock(&dwc->connect_mutex); + if (!is_on) + ret = dwc3_gadget_soft_disconnect(dwc); + else + ret = dwc3_gadget_soft_connect(dwc); ++ mutex_unlock(&dwc->connect_mutex); + + pm_runtime_put(dwc->dev); + +@@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) + + dwc->suspended = false; + ++ /* The controller is recovered. */ ++ if (dwc->err_state == DWC3_ERR_NONE) ++ dwc->err_recovery_count = 0; ++ + /* + * Ideally, dwc3_reset_gadget() would trigger the function + * drivers to stop any active transfers through ep disable. +@@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) + return ret; + } + ++static void dwc3_schedule_err_recovery(struct dwc3 *dwc) ++{ ++ dwc->err_state = DWC3_ERR_RECOVERY; ++ schedule_work(&dwc->err_recovery_work); ++} ++ + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + { + struct dwc3 *dwc = evt->dwc; +@@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + return IRQ_NONE; + + if (count > evt->length) { +- dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", ++ dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + count, evt->length); +- return IRQ_NONE; ++ /* ++ * This is a fatal error - the driver and controller are out of ++ * sync on which event has been consumed. Reinitializing the ++ * controller is required to recover. Write the bogus count back ++ * to GEVNTCOUNT to clear the IRQ source, consistent with the ++ * stale event clearing in dwc3_event_buffers_setup(), then ++ * schedule error recovery. ++ */ ++ spin_lock(&dwc->lock); ++ dwc3_schedule_err_recovery(dwc); ++ spin_unlock(&dwc->lock); ++ dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); ++ return IRQ_HANDLED; + } + + evt->count = count; +@@ -4735,6 +4784,57 @@ static void dwc_gadget_release(struct device *dev) + kfree(gadget); + } + ++static void dwc3_err_recovery_work(struct work_struct *work) ++{ ++ struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); ++ unsigned long flags; ++ int ret; ++ ++ /* serializes against dwc3_gadget_pullup() */ ++ mutex_lock(&dwc->connect_mutex); ++ ++ ret = dwc3_gadget_soft_disconnect(dwc); ++ ++ dwc3_disconnect_gadget_sleepable(dwc); ++ ++ if (ret) ++ goto err_unrecoverable; ++ ++ if (dwc->softconnect) { ++ u32 count; ++ /* ++ * Wait irq to finish before soft_connect resets evt->lpos and ++ * the event buffer registers to avoid racing with ++ * dwc3_process_event_buf(). ++ */ ++ synchronize_irq(dwc->irq_gadget); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ count = ++dwc->err_recovery_count; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ if (count > DWC3_ERR_RECOVERY_MAX) ++ goto err_unrecoverable; ++ ++ ret = dwc3_gadget_soft_connect(dwc); ++ if (ret) ++ goto err_unrecoverable; ++ } ++ mutex_unlock(&dwc->connect_mutex); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_NONE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return; ++ ++err_unrecoverable: ++ dev_err(dwc->dev, "Unable to recover the controller\n"); ++ mutex_unlock(&dwc->connect_mutex); ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_UNRECOVERABLE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++} ++ + /** + * dwc3_gadget_init - initializes gadget related registers + * @dwc: pointer to our controller context structure +@@ -4778,6 +4878,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) + } + + init_completion(&dwc->ep0_in_setup); ++ INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); ++ mutex_init(&dwc->connect_mutex); + dwc->gadget = kzalloc_obj(struct usb_gadget); + if (!dwc->gadget) { + ret = -ENOMEM; +@@ -4874,6 +4976,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) + if (!dwc->gadget) + return; + ++ cancel_work_sync(&dwc->err_recovery_work); ++ mutex_destroy(&dwc->connect_mutex); + dwc3_enable_susphy(dwc, true); + usb_del_gadget(dwc->gadget); + dwc3_gadget_free_endpoints(dwc); +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -1877,8 +1877,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + break; + case FUNCTIONFS_ENDPOINT_DESC: + { ++ const struct usb_endpoint_descriptor *desc; ++ struct usb_endpoint_descriptor desc1; + int desc_idx; +- struct usb_endpoint_descriptor desc1, *desc; + + switch (epfile->ffs->gadget->speed) { + case USB_SPEED_SUPER: +@@ -1892,7 +1893,17 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + desc_idx = 0; + } + +- desc = epfile->ep->descs[desc_idx]; ++ do { ++ desc = epfile->ep->descs[desc_idx]; ++ } while (!desc && --desc_idx >= 0); ++ ++ if (!desc) ++ desc = epfile->ep->ep->desc; ++ if (!desc) { ++ ret = -EINVAL; ++ break; ++ } ++ + memcpy(&desc1, desc, desc->bLength); + + spin_unlock_irq(&epfile->ffs->eps_lock); +diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c +--- a/drivers/usb/gadget/function/f_midi2.c ++++ b/drivers/usb/gadget/function/f_midi2.c +@@ -1633,8 +1633,8 @@ struct f_midi2_usb_config { + + /* MIDI 1.0 jacks */ + unsigned char jack_in, jack_out, jack_id; +- struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; +- struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; ++ struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES * 2]; ++ struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES * 2]; + }; + + static int append_config(struct f_midi2_usb_config *config, void *d) +diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c +--- a/drivers/usb/gadget/function/f_uac1_legacy.c ++++ b/drivers/usb/gadget/function/f_uac1_legacy.c +@@ -797,6 +797,9 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) + + static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + con->data[cmd] = value; + + return 0; +@@ -804,6 +807,9 @@ static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + + static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + return con->data[cmd]; + } + +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/core.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c +@@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev) + for (i = 0; i < vhub->max_ports; i++) + ast_vhub_del_dev(&vhub->ports[i].dev); + ++ /* Final drain; the worker takes vhub->lock, so stay outside of it */ ++ cancel_work_sync(&vhub->wake_work); ++ + spin_lock_irqsave(&vhub->lock, flags); + + /* Mask & ack all interrupts */ +@@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev) + return -ENOMEM; + + spin_lock_init(&vhub->lock); ++ INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + vhub->pdev = pdev; + vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, + VHUB_IRQ_DEV1_BIT); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +@@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) + int rc = -EINVAL; + + spin_lock_irqsave(&d->vhub->lock, flags); +- if (!d->wakeup_en) ++ if (!d->wakeup_en || !d->registered) + goto err; + + DDBG(d, "Device initiated wakeup\n"); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +@@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub, + ast_vhub_send_host_wakeup(vhub); + } + +-static void ast_vhub_wake_work(struct work_struct *work) ++void ast_vhub_wake_work(struct work_struct *work) + { + struct ast_vhub *vhub = container_of(work, + struct ast_vhub, +@@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work) + + void ast_vhub_hub_wake_all(struct ast_vhub *vhub) + { ++ lockdep_assert_held(&vhub->lock); ++ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain +@@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) + int ast_vhub_init_hub(struct ast_vhub *vhub) + { + vhub->speed = USB_SPEED_UNKNOWN; +- INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + + return ast_vhub_init_desc(vhub); + } +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +--- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h ++++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +@@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub); + void ast_vhub_hub_resume(struct ast_vhub *vhub); + void ast_vhub_hub_reset(struct ast_vhub *vhub); + void ast_vhub_hub_wake_all(struct ast_vhub *vhub); ++void ast_vhub_wake_work(struct work_struct *work); + + /* dev.c */ + int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); +diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c +--- a/drivers/usb/gadget/udc/dummy_hcd.c ++++ b/drivers/usb/gadget/udc/dummy_hcd.c +@@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, + { + bool fifo = req == &dum->fifo_req; + ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + usb_gadget_giveback_request(_ep, &req->req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + if (fifo) + dum->fifo_req_busy = 0; + } +@@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, + req->req.complete = fifo_complete; + + list_add_tail(&req->queue, &ep->queue); ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + _req->actual = _req->length; + _req->status = 0; + usb_gadget_giveback_request(_ep, _req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + } else + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&dum->lock, flags); +diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c +--- a/drivers/usb/host/octeon-hcd.c ++++ b/drivers/usb/host/octeon-hcd.c +@@ -378,8 +378,22 @@ struct octeon_hcd { + struct cvmx_usb_transaction *active_split; + struct cvmx_usb_tx_fifo periodic; + struct cvmx_usb_tx_fifo nonperiodic; ++ struct hrtimer sof_timer; + }; + ++static int cvmx_usb_poll(struct octeon_hcd *usb); ++ ++static enum hrtimer_restart octeon_usb_sof_timer(struct hrtimer *t) ++{ ++ struct octeon_hcd *usb = container_of(t, struct octeon_hcd, sof_timer); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&usb->lock, flags); ++ cvmx_usb_poll(usb); ++ spin_unlock_irqrestore(&usb->lock, flags); ++ return HRTIMER_NORESTART; ++} ++ + /* + * This macro logically sets a single field in a CSR. It does the sequence + * read, modify, and write +@@ -578,7 +592,7 @@ static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) + int result; + u64 address = CVMX_USBCX_GRSTCTL(usb->index); + u64 done = cvmx_get_cycle() + 100 * +- (u64)octeon_get_clock_rate / 1000000; ++ (u64)octeon_get_clock_rate() / 1000000; + union cvmx_usbcx_grstctl c; + + while (1) { +@@ -853,6 +867,14 @@ static int cvmx_usb_initialize(struct device *dev, + * USBC_GAHBCFG[PTXFEMPLVL] + * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 + */ ++ usbcx_gahbcfg.u32 = cvmx_usb_read_csr32(usb, ++ CVMX_USBCX_GHWCFG3(usb->index)); ++ if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { ++ dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", ++ usbcx_gahbcfg.u32); ++ return -ENODEV; ++ } ++ + usbcx_gahbcfg.u32 = 0; + usbcx_gahbcfg.s.dmaen = !(usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA); +@@ -1900,6 +1922,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + int channel; + struct cvmx_usb_pipe *pipe; + int need_sof; ++ u64 min_due; + enum cvmx_usb_transfer ttype; + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { +@@ -1940,15 +1963,33 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + * future that might need to be scheduled + */ + need_sof = 0; ++ min_due = ~0ull; + for (ttype = CVMX_USB_TRANSFER_CONTROL; + ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { + list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { +- if (pipe->next_tx_frame > usb->frame_number) { +- need_sof = 1; +- break; +- } ++ if (pipe->next_tx_frame > usb->frame_number && ++ pipe->next_tx_frame < min_due) ++ min_due = pipe->next_tx_frame; + } + } ++ if (min_due != ~0ull) { ++ u64 delta = min_due - usb->frame_number; ++ ++ /* ++ * frame_number is resynced from HFNUM on every poll, so a ++ * deadline that is many frames away does not need an ++ * interrupt on every SOF to count them down - sleep on the ++ * timer instead and keep SOF interrupts for deadlines within ++ * a few frames. Stay well below the 16383-frame wrap of ++ * HFNUM. One (micro)frame is 125us in high-speed mode. ++ */ ++ if (delta <= 4 || delta > 8000) ++ need_sof = 1; ++ else ++ hrtimer_start(&usb->sof_timer, ++ ns_to_ktime((delta - 2) * 125000), ++ HRTIMER_MODE_REL); ++ } + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, sofmsk, need_sof); + } +@@ -3638,6 +3679,8 @@ static int octeon_usb_probe(struct platform_device *pdev) + usb = (struct octeon_hcd *)hcd->hcd_priv; + + spin_lock_init(&usb->lock); ++ hrtimer_setup(&usb->sof_timer, octeon_usb_sof_timer, CLOCK_MONOTONIC, ++ HRTIMER_MODE_REL); + + usb->init_flags = initialize_flags; + +@@ -3688,6 +3731,7 @@ static void octeon_usb_remove(struct platform_device *pdev) + unsigned long flags; + + usb_remove_hcd(hcd); ++ hrtimer_cancel(&usb->sof_timer); + spin_lock_irqsave(&usb->lock, flags); + status = cvmx_usb_shutdown(usb); + spin_unlock_irqrestore(&usb->lock, flags); +diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c +--- a/drivers/usb/host/ohci-da8xx.c ++++ b/drivers/usb/host/ohci-da8xx.c +@@ -447,6 +447,7 @@ static int ohci_da8xx_probe(struct platform_device *pdev) + + err_remove_hcd: + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + err: + usb_put_hcd(hcd); + return error; +@@ -457,6 +458,7 @@ static void ohci_da8xx_remove(struct platform_device *pdev) + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + usb_put_hcd(hcd); + } + +diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c +--- a/drivers/usb/host/ohci-s3c2410.c ++++ b/drivers/usb/host/ohci-s3c2410.c +@@ -335,6 +335,7 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) + struct usb_hcd *hcd = platform_get_drvdata(dev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + s3c2410_stop_hc(dev); + usb_put_hcd(hcd); + } +diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c +--- a/drivers/usb/host/ohci-spear.c ++++ b/drivers/usb/host/ohci-spear.c +@@ -103,6 +103,7 @@ static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) + struct spear_ohci *sohci_p = to_spear_ohci(hcd); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + clk_disable_unprepare(sohci_p->clk); + + usb_put_hcd(hcd); +diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c +--- a/drivers/usb/host/ohci-st.c ++++ b/drivers/usb/host/ohci-st.c +@@ -235,6 +235,7 @@ static void st_ohci_platform_remove(struct platform_device *dev) + int clk; + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + + if (pdata->power_off) + pdata->power_off(dev); +diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c +--- a/drivers/usb/serial/bus.c ++++ b/drivers/usb/serial/bus.c +@@ -165,7 +165,11 @@ int usb_serial_bus_register(struct usb_serial_driver *driver) + + void usb_serial_bus_deregister(struct usb_serial_driver *driver) + { +- free_dynids(driver); + driver_unregister(&driver->driver); ++ free_dynids(driver); + } + ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver) ++{ ++ driver_remove_file(&driver->driver, &driver_attr_new_id); ++} +diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c +--- a/drivers/usb/serial/cp210x.c ++++ b/drivers/usb/serial/cp210x.c +@@ -223,6 +223,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ + { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ + { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ ++ { USB_DEVICE(0x1B1C, 0x1C02) }, /* Corsair AX1500i Power Supply */ + { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ + { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ + { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ +diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c +--- a/drivers/usb/serial/generic.c ++++ b/drivers/usb/serial/generic.c +@@ -266,6 +266,7 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); + void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + unsigned int bps; + unsigned long period; + unsigned long expire; +@@ -285,7 +286,14 @@ void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + __func__, jiffies_to_msecs(timeout), + jiffies_to_msecs(period)); + expire = jiffies + timeout; +- while (!port->serial->type->tx_empty(port)) { ++ for (;;) { ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty) || port->serial->type->tx_empty(port)) { ++ mutex_unlock(&tport->mutex); ++ break; ++ } ++ mutex_unlock(&tport->mutex); ++ + schedule_timeout_interruptible(period); + if (signal_pending(current)) + break; +diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c +--- a/drivers/usb/serial/option.c ++++ b/drivers/usb/serial/option.c +@@ -260,6 +260,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EM060K_12a 0x012a + #define QUECTEL_PRODUCT_EM060K_12b 0x012b + #define QUECTEL_PRODUCT_EM060K_12c 0x012c ++#define QUECTEL_PRODUCT_RG660QB 0x013d + #define QUECTEL_PRODUCT_EG91 0x0191 + #define QUECTEL_PRODUCT_EG95 0x0195 + #define QUECTEL_PRODUCT_BG96 0x0296 +@@ -280,6 +281,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EC200U 0x0901 + #define QUECTEL_PRODUCT_EG912Y 0x6001 + #define QUECTEL_PRODUCT_EC200S_CN 0x6002 ++#define QUECTEL_PRODUCT_EG060W 0x6004 + #define QUECTEL_PRODUCT_EC200A 0x6005 + #define QUECTEL_PRODUCT_EG916Q 0x6007 + #define QUECTEL_PRODUCT_EM061K_LWW 0x6008 +@@ -1268,12 +1270,15 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG060W, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0xff, 0x30) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, +@@ -2168,6 +2173,8 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ + .driver_info = RSVD(7) }, ++ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x902b, 0xff), ++ .driver_info = RSVD(4) }, + { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ +@@ -2429,6 +2436,13 @@ static const struct usb_device_id option_ids[] = { + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ + .driver_info = RSVD(3) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x01) }, /* Compal EXC-T1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x02) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x03) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x04) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, + { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ + .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, + { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ +diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c +--- a/drivers/usb/serial/quatech2.c ++++ b/drivers/usb/serial/quatech2.c +@@ -52,6 +52,7 @@ + #define QT2_XMIT_FLUSH 0x05 /* no following info */ + #define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 921600 + #define DEFAULT_BAUD_RATE 9600 + +@@ -156,7 +157,7 @@ static inline int calc_baud_divisor(int baudrate) + + static inline int qt2_set_port_config(struct usb_device *dev, + unsigned char port_number, +- u16 baudrate, u16 lcr) ++ speed_t baudrate, u16 lcr) + { + int divisor = calc_baud_divisor(baudrate); + u16 index = ((u16) (lcr << 8) | (u16) (port_number)); +@@ -257,9 +258,9 @@ static void qt2_set_termios(struct tty_struct *tty, + struct usb_device *dev = port->serial->dev; + struct qt2_port_private *port_priv; + struct ktermios *termios = &tty->termios; +- u16 baud; + unsigned int cflag = termios->c_cflag; + u16 new_lcr = 0; ++ speed_t baud; + int status; + + port_priv = usb_get_serial_port_data(port); +@@ -277,6 +278,18 @@ static void qt2_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + status = qt2_set_port_config(dev, port_priv->device_port, baud, + new_lcr); + if (status < 0) +@@ -373,7 +386,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) + port_priv->device_port = (u8) device_port; + + if (tty) +- qt2_set_termios(tty, port, &tty->termios); ++ qt2_set_termios(tty, port, NULL); + + return 0; + +diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c +--- a/drivers/usb/serial/ssu100.c ++++ b/drivers/usb/serial/ssu100.c +@@ -32,6 +32,7 @@ + + #define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 460800 + + #define ATC_DISABLED 0x00 +@@ -217,9 +218,10 @@ static void ssu100_set_termios(struct tty_struct *tty, + { + struct usb_device *dev = port->serial->dev; + struct ktermios *termios = &tty->termios; +- u16 baud, divisor, remainder; ++ speed_t baud, remainder; + unsigned int cflag = termios->c_cflag; + u16 urb_value = 0; /* will hold the new flags */ ++ u16 divisor; + int result; + + if (cflag & PARENB) { +@@ -235,6 +237,18 @@ static void ssu100_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); + + +@@ -310,7 +324,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) + dev_dbg(&port->dev, "%s - set uart failed\n", __func__); + + if (tty) +- ssu100_set_termios(tty, port, &tty->termios); ++ ssu100_set_termios(tty, port, NULL); + + return usb_serial_generic_open(tty, port); + } +diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c +--- a/drivers/usb/serial/usb-serial.c ++++ b/drivers/usb/serial/usb-serial.c +@@ -400,17 +400,13 @@ static unsigned int serial_chars_in_buffer(struct tty_struct *tty) + static void serial_wait_until_sent(struct tty_struct *tty, int timeout) + { + struct usb_serial_port *port = tty->driver_data; +- struct usb_serial *serial = port->serial; + + dev_dbg(&port->dev, "%s\n", __func__); + + if (!port->serial->type->wait_until_sent) + return; + +- mutex_lock(&serial->disc_mutex); +- if (!serial->disconnected) +- port->serial->type->wait_until_sent(tty, timeout); +- mutex_unlock(&serial->disc_mutex); ++ port->serial->type->wait_until_sent(tty, timeout); + } + + static void serial_throttle(struct tty_struct *tty) +@@ -438,8 +434,13 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + unsigned int close_delay, closing_wait; ++ int ret = 0; + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + close_delay = jiffies_to_msecs(tport->close_delay) / 10; + closing_wait = tport->closing_wait; +@@ -452,10 +453,10 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + + if (port->serial->type->get_serial) + port->serial->type->get_serial(tty, ss); +- ++out_unlock: + mutex_unlock(&tport->mutex); + +- return 0; ++ return ret; + } + + static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) +@@ -471,6 +472,10 @@ static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) + closing_wait = msecs_to_jiffies(closing_wait * 10); + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + if (!capable(CAP_SYS_ADMIN)) { + if (close_delay != tport->close_delay || +@@ -498,6 +503,7 @@ static int serial_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + int retval = -ENOIOCTLCMD; + + dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd); +@@ -508,8 +514,21 @@ static int serial_ioctl(struct tty_struct *tty, + retval = port->serial->type->tiocmiwait(tty, arg); + break; + default: +- if (port->serial->type->ioctl) ++ if (!port->serial->type->ioctl) ++ break; ++ ++ if (cmd == TIOCSRS485) ++ down_write(&tty->termios_rwsem); ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ retval = -EIO; ++ else + retval = port->serial->type->ioctl(tty, cmd, arg); ++ mutex_unlock(&tport->mutex); ++ ++ if (cmd == TIOCSRS485) ++ up_write(&tty->termios_rwsem); + } + + return retval; +@@ -519,25 +538,40 @@ static void serial_set_termios(struct tty_struct *tty, + const struct ktermios *old) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->set_termios) +- port->serial->type->set_termios(tty, port, old); +- else ++ if (!port->serial->type->set_termios) { + tty_termios_copy_hw(&tty->termios, old); ++ return; ++ } ++ ++ mutex_lock(&tport->mutex); ++ if (!tty_io_error(tty)) ++ port->serial->type->set_termios(tty, port, old); ++ mutex_unlock(&tport->mutex); + } + + static int serial_break(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->break_ctl) +- return port->serial->type->break_ctl(tty, break_state); ++ if (!port->serial->type->break_ctl) ++ return -ENOTTY; + +- return -ENOTTY; ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->break_ctl(tty, break_state); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_proc_show(struct seq_file *m, void *v) +@@ -578,24 +612,44 @@ static int serial_proc_show(struct seq_file *m, void *v) + static int serial_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmget) +- return port->serial->type->tiocmget(tty); +- return -ENOTTY; ++ if (!port->serial->type->tiocmget) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmget(tty); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmset) +- return port->serial->type->tiocmset(tty, set, clear); +- return -ENOTTY; ++ if (!port->serial->type->tiocmset) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmset(tty, set, clear); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_get_icount(struct tty_struct *tty, +@@ -1191,11 +1245,16 @@ static void usb_serial_disconnect(struct usb_interface *interface) + usb_serial_port_poison_urbs(port); + wake_up_interruptible(&port->port.delta_msr_wait); + cancel_work_sync(&port->work); ++ } ++ ++ if (serial->type->disconnect) ++ serial->type->disconnect(serial); ++ ++ for (i = 0; i < serial->num_ports; ++i) { ++ port = serial->port[i]; + if (device_is_registered(&port->dev)) + device_del(&port->dev); + } +- if (serial->type->disconnect) +- serial->type->disconnect(serial); + + release_sibling(serial, interface); + +@@ -1464,7 +1523,7 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + { + int rc; + struct usb_driver *udriver; +- struct usb_serial_driver * const *sd; ++ struct usb_serial_driver * const *sd, * const *s; + + /* + * udriver must be registered before any of the serial drivers, +@@ -1517,9 +1576,11 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + return 0; + + err_deregister_drivers: ++ for (s = serial_drivers; s < sd; ++s) ++ usb_serial_bus_remove_new_id(*s); ++ usb_deregister(udriver); + while (sd-- > serial_drivers) + usb_serial_deregister(*sd); +- usb_deregister(udriver); + err_free_driver: + kfree(udriver); + return rc; +@@ -1537,10 +1598,23 @@ EXPORT_SYMBOL_GPL(__usb_serial_register_drivers); + void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) + { + struct usb_driver *udriver = (*serial_drivers)->usb_driver; ++ struct usb_serial_driver * const *sd; ++ ++ /* ++ * udriver must be deregistered before the serial drivers so that ++ * I/O is stopped before unbinding the ports. ++ * ++ * Remove the new_id attributes to prevent ids from being added and ++ * triggering a probe of udriver after it has been deregistered. ++ */ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_bus_remove_new_id(*sd); + +- for (; *serial_drivers; ++serial_drivers) +- usb_serial_deregister(*serial_drivers); + usb_deregister(udriver); ++ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_deregister(*sd); ++ + kfree(udriver); + } + EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); +diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c +--- a/drivers/usb/serial/xr_serial.c ++++ b/drivers/usb/serial/xr_serial.c +@@ -850,12 +850,9 @@ static int xr_get_rs485_config(struct tty_struct *tty, + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + +- down_read(&tty->termios_rwsem); +- if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { +- up_read(&tty->termios_rwsem); ++ /* core holds port mutex */ ++ if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) + return -EFAULT; +- } +- up_read(&tty->termios_rwsem); + + return 0; + } +@@ -871,10 +868,9 @@ static int xr_set_rs485_config(struct tty_struct *tty, + return -EFAULT; + xr_sanitize_serial_rs485(&rs485); + +- down_write(&tty->termios_rwsem); ++ /* core holds termios rwsem and port mutex */ + data->rs485 = rs485; + xr_set_flow_mode(tty, port, NULL); +- up_write(&tty->termios_rwsem); + + if (copy_to_user(argp, &rs485, sizeof(rs485))) + return -EFAULT; +diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c +--- a/drivers/usb/storage/sierra_ms.c ++++ b/drivers/usb/storage/sierra_ms.c +@@ -77,6 +77,13 @@ static int sierra_get_swoc_info(struct usb_device *udev, + sizeof(struct swoc_info), /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + ++ /* ++ * A short IN transfer leaves the tail of swocInfo uninitialized; ++ * only a full transfer is valid. ++ */ ++ if (result != sizeof(struct swoc_info)) ++ return -EIO; ++ + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); + swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); + return result; +diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c +--- a/drivers/usb/typec/anx7411.c ++++ b/drivers/usb/typec/anx7411.c +@@ -1566,8 +1566,11 @@ static void anx7411_i2c_remove(struct i2c_client *client) + anx7411_partner_unregister_altmode(plat); + anx7411_unregister_partner(plat); + +- if (plat->workqueue) ++ if (plat->workqueue) { ++ disable_irq(plat->intp_irq); ++ cancel_work_sync(&plat->work); + destroy_workqueue(plat->workqueue); ++ } + + i2c_unregister_device(plat->spi_client); + +diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c +--- a/drivers/usb/typec/port-mapper.c ++++ b/drivers/usb/typec/port-mapper.c +@@ -42,6 +42,23 @@ static int usb4_port_compare(struct device *dev, void *fwnode) + return usb4_usb3_port_match(dev, fwnode); + } + ++static bool typec_has_usb4_host_interface(const struct fwnode_handle *fwnode) ++{ ++ if (!IS_REACHABLE(CONFIG_USB4)) ++ return false; ++ ++ struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = ++ fwnode_find_reference(fwnode, "usb4-host-interface", 0); ++ ++ /* ++ * The USB4 port can only appear if the host interface is enabled in ++ * the firmware and has been enumerated as a device. The latter is ++ * the same check as in usb_acpi_add_usb4_devlink(). ++ */ ++ return !IS_ERR(nhi_fwnode) && fwnode_device_is_available(nhi_fwnode) && ++ nhi_fwnode->dev; ++} ++ + static int typec_port_compare(struct device *dev, void *fwnode) + { + return device_match_fwnode(dev, fwnode); +@@ -64,11 +81,11 @@ static int typec_port_match(struct device *dev, void *data) + adev_fwnode); + + /* +- * If dev is USB 3.x port, it may have reference to the ++ * If dev is USB 3.x port, it may have reference to an available + * USB4 host interface in which case we can also link the + * Type-C port with the USB4 port. + */ +- if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) ++ if (typec_has_usb4_host_interface(adev_fwnode)) + component_match_add(&arg->port->dev, &arg->match, + usb4_port_compare, adev_fwnode); + } +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -1756,7 +1756,8 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) + { + hrtimer_cancel(&port->vdm_discovery_timer); +- kthread_cancel_work_sync(&port->vdm_discovery_work); ++ if (port->wq) ++ kthread_cancel_work_sync(&port->vdm_discovery_work); + } + + static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +@@ -7113,7 +7114,7 @@ static void tcpm_pd_event_handler(struct kthread_work *work) + port->upcoming_state = FR_SWAP_SEND; + ret = tcpm_ams_start(port, FAST_ROLE_SWAP); + if (ret == -EAGAIN) +- port->upcoming_state = INVALID_STATE; ++ tcpm_set_state(port, ERROR_RECOVERY, 0); + } else { + tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); + } +@@ -8977,6 +8978,7 @@ void tcpm_unregister_port(struct tcpm_port *port) + + port->registered = false; + kthread_destroy_worker(port->wq); ++ port->wq = NULL; + + hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -343,7 +343,7 @@ static void tps6598x_set_data_role(struct tps6598x *tps, + + static int tps6598x_connect(struct tps6598x *tps, u32 status) + { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + enum typec_pwr_opmode mode; + int ret; + +@@ -354,7 +354,6 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) + + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) { + ret = tps6598x_read_partner_identity(tps); +@@ -850,11 +849,10 @@ static void cd321x_update_work(struct work_struct *work) + + /* Set up partner if we were previously disconnected (or changed). */ + if (!tps->partner) { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + + desc.usb_pd = is_pd; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) + desc.identity = &st.partner_identity; +@@ -1792,6 +1790,7 @@ static int tps6598x_probe(struct i2c_client *client) + const struct tipd_data *data; + struct tps6598x *tps; + struct fwnode_handle *fwnode; ++ bool patch_loaded = false; + u32 status; + u32 vid = 0; + int ret; +@@ -1847,6 +1846,7 @@ static int tps6598x_probe(struct i2c_client *client) + return ret; + + if (ret == TPS_MODE_PTCH) { ++ patch_loaded = true; + ret = tps->data->init(tps); + if (ret) + return ret; +@@ -1937,7 +1937,8 @@ static int tps6598x_probe(struct i2c_client *client) + tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); + err_reset_controller: + /* Reset PD controller to remove any applied patch */ +- tps->data->reset(tps); ++ if (patch_loaded) ++ tps->data->reset(tps); + + return ret; + } +diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c +--- a/drivers/usb/typec/ucsi/displayport.c ++++ b/drivers/usb/typec/ucsi/displayport.c +@@ -71,11 +71,14 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) + if (ret < 0) { + if (ucsi->version > 0x0100) + goto err_unlock; +- cur = 0xff; + } + +- if (cur < UCSI_MAX_ALTMODES) { +- ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ if (cur != 0xff) { ++ if (cur < UCSI_MAX_ALTMODES) ++ ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ else ++ ret = -EINVAL; ++ + goto err_unlock; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1801,10 +1801,21 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) + { + struct fwnode_handle *fwnode; + int i = 1; ++ int ret; ++ u32 port; + +- device_for_each_child_node(con->ucsi->dev, fwnode) +- if (i++ == con->num) +- return fwnode; ++ device_for_each_child_node(con->ucsi->dev, fwnode) { ++ ret = fwnode_property_read_u32(fwnode, "reg", &port); ++ if (ret < 0) { ++ if (i == con->num) ++ return fwnode; ++ } else { ++ if (port == con->num - 1) ++ return fwnode; ++ } ++ ++ i++; ++ } + return NULL; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c +--- a/drivers/usb/typec/ucsi/ucsi_acpi.c ++++ b/drivers/usb/typec/ucsi/ucsi_acpi.c +@@ -121,6 +121,33 @@ static const struct ucsi_operations ucsi_acpi_ops = { + .async_control = ucsi_acpi_async_control + }; + ++static int ucsi_acer_read_version(struct ucsi *ucsi, u16 *version) ++{ ++ struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); ++ int ret; ++ ++ ret = ucsi_acpi_read_version(ucsi, version); ++ if (ret) ++ return ret; ++ ++ if (!*version) { ++ dev_warn(ua->dev, "UCSI version is zero, assuming 1.2\n"); ++ *version = UCSI_VERSION_1_2; ++ } ++ ++ return 0; ++} ++ ++static const struct ucsi_operations ucsi_acer_ops = { ++ .read_version = ucsi_acer_read_version, ++ .read_cci = ucsi_acpi_read_cci, ++ .poll_cci = ucsi_acpi_poll_cci, ++ .read_message_in = ucsi_acpi_read_message_in, ++ .write_message_out = ucsi_acpi_write_message_out, ++ .sync_control = ucsi_sync_control_common, ++ .async_control = ucsi_acpi_async_control ++}; ++ + static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, + void *val, size_t len, void *msg_out, + size_t msg_out_size) +@@ -162,6 +189,13 @@ static const struct ucsi_operations ucsi_gram_ops = { + }; + + static const struct dmi_system_id ucsi_acpi_quirks[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), ++ }, ++ .driver_data = (void *)&ucsi_acer_ops, ++ }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), +diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c +--- a/drivers/usb/typec/ucsi/ucsi_glink.c ++++ b/drivers/usb/typec/ucsi/ucsi_glink.c +@@ -362,6 +362,10 @@ static void pmic_glink_ucsi_destroy(void *data) + { + struct pmic_glink_ucsi *ucsi = data; + ++ /* Drain the work items before ucsi_destroy() frees the ucsi instance */ ++ cancel_work_sync(&ucsi->notify_work); ++ cancel_work_sync(&ucsi->register_work); ++ + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ + mutex_lock(&ucsi->lock); + ucsi_destroy(ucsi->ucsi); +@@ -467,8 +471,15 @@ static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) + { + struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); + +- /* Unregister first to stop having read & writes */ +- ucsi_unregister(ucsi->ucsi); ++ /* Callbacks can queue work until devres releases the client */ ++ disable_work_sync(&ucsi->notify_work); ++ disable_work_sync(&ucsi->register_work); ++ ++ /* register_work may have unregistered the instance already */ ++ if (ucsi->ucsi_registered) { ++ ucsi->ucsi_registered = false; ++ ucsi_unregister(ucsi->ucsi); ++ } + } + + static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { +diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h +--- a/include/linux/usb/serial.h ++++ b/include/linux/usb/serial.h +@@ -381,6 +381,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, + + int usb_serial_bus_register(struct usb_serial_driver *device); + void usb_serial_bus_deregister(struct usb_serial_driver *device); ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver); + + extern const struct bus_type usb_serial_bus_type; + extern struct tty_driver *usb_serial_tty_driver; diff --git a/pkgbuilds/linux-omarchy-eevdf/0607-usb-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0607-usb-fixes.patch.sig new file mode 100644 index 0000000..864678b Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0607-usb-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch b/pkgbuilds/linux-omarchy-eevdf/0620-usb-string-sanitize.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch rename to pkgbuilds/linux-omarchy-eevdf/0620-usb-string-sanitize.patch diff --git a/pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0620-usb-string-sanitize.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0620-usb-string-sanitize.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0620-usb-string-sanitize.patch.sig diff --git a/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch b/pkgbuilds/linux-omarchy-eevdf/0650-wireguard-tstamp-type.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch rename to pkgbuilds/linux-omarchy-eevdf/0650-wireguard-tstamp-type.patch diff --git a/pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0650-wireguard-tstamp-type.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0650-wireguard-tstamp-type.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0650-wireguard-tstamp-type.patch.sig diff --git a/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch b/pkgbuilds/linux-omarchy-eevdf/0660-btusb-mediatek-mt7922-13d3-3625.patch similarity index 89% rename from pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch rename to pkgbuilds/linux-omarchy-eevdf/0660-btusb-mediatek-mt7922-13d3-3625.patch index 8789452..3b51649 100644 --- a/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0660-btusb-mediatek-mt7922-13d3-3625.patch @@ -1,7 +1,7 @@ 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[] = { +@@ -819,6 +819,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | BTUSB_WIDEBAND_SPEECH }, diff --git a/pkgbuilds/linux-omarchy-eevdf/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig new file mode 100644 index 0000000..f497ab2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch b/pkgbuilds/linux-omarchy-eevdf/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch rename to pkgbuilds/linux-omarchy-eevdf/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch diff --git a/pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0663-btintel-pcie-serialize-reset-type.patch b/pkgbuilds/linux-omarchy-eevdf/0663-btintel-pcie-serialize-reset-type.patch new file mode 100644 index 0000000..2464f58 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0663-btintel-pcie-serialize-reset-type.patch @@ -0,0 +1,145 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -2499,8 +2499,6 @@ static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev, + } + } + +-static void btintel_pcie_reset(struct hci_dev *hdev); +- + static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data) + { + union acpi_object *obj, argv4; +@@ -2693,56 +2691,86 @@ static void btintel_pcie_reset_work(struct work_struct *wk) + pci_unlock_rescan_remove(); + } + +-static void btintel_pcie_reset(struct hci_dev *hdev) ++/* Schedule a device reset of the requested type. ++ * ++ * BTINTEL_PCIE_RECOVERY_IN_PROGRESS serializes all reset requesters ++ * (sysfs reset attribute, hci_cmd_timeout(), hw_error, resume error ++ * path, etc.) so that: ++ * ++ * - dev_data->reset_type is written by exactly one caller (the ++ * thread that wins test_and_set_bit), eliminating the race where ++ * a second hw_error could clobber an already-scheduled reset's ++ * type; ++ * - the write happens AFTER the bit is set, so reset_work observes ++ * it through schedule_work()'s memory ordering; ++ * - losers return without touching reset_type or scheduling the ++ * work, so concurrent triggers are silently coalesced into the ++ * in-flight one (whose recovery will reinitialize the device ++ * regardless of the dropped trigger's variant). ++ * ++ * The bit is cleared only by .remove() / re-probe via fresh devm ++ * allocation, which is the intended one-shot semantics: a reset ++ * tears down and re-probes 'data', so there is no "in-flight" ++ * reset to follow up after device_reprobe() succeeds. ++ */ ++static void btintel_pcie_request_reset(struct btintel_pcie_data *data, ++ enum btintel_pcie_reset_type type) + { +- struct btintel_pcie_data *data; +- +- data = hci_get_drvdata(hdev); +- + if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags)) + return; + + if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return; + ++ data->reset_type = type; ++ + pci_dev_get(data->pdev); + schedule_work(&data->reset_work); + } + ++static void btintel_pcie_hci_reset(struct hci_dev *hdev) ++{ ++ struct btintel_pcie_data *data = hci_get_drvdata(hdev); ++ ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); ++} ++ + static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) + { +- struct btintel_pcie_dev_recovery *data; ++ struct btintel_pcie_dev_recovery *rec; + struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev); + struct pci_dev *pdev = dev_data->pdev; ++ enum btintel_pcie_reset_type type; + time64_t retry_window; + ++ if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &dev_data->flags)) ++ return; ++ + btintel_pcie_dump_debug_registers(hdev); + +- data = btintel_pcie_get_recovery(pdev, &hdev->dev); +- if (!data) ++ rec = btintel_pcie_get_recovery(pdev, &hdev->dev); ++ if (!rec) + return; + +- if (code == 0x13) +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR; +- else +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; ++ type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR ++ : BTINTEL_PCIE_IOSF_PRR_FLR; + + bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code, +- dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); +- retry_window = ktime_get_boottime_seconds() - data->last_error; ++ type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); ++ retry_window = ktime_get_boottime_seconds() - rec->last_error; + + if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS && +- data->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { ++ rec->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { + bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d", +- BTINTEL_PCIE_FLR_MAX_RETRY, data->count); ++ BTINTEL_PCIE_FLR_MAX_RETRY, rec->count); + bt_dev_dbg(hdev, "Boot time: %lld seconds", + ktime_get_boottime_seconds()); + bt_dev_dbg(hdev, "last error at: %lld seconds", +- data->last_error); ++ rec->last_error); + return; + } + btintel_pcie_inc_recovery_count(pdev, &hdev->dev); +- btintel_pcie_reset(hdev); ++ btintel_pcie_request_reset(dev_data, type); + } + + static bool btintel_pcie_wakeup(struct hci_dev *hdev) +@@ -2832,7 +2860,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data) + hdev->hw_error = btintel_pcie_hw_error; + hdev->set_diag = btintel_set_diag; + hdev->set_bdaddr = btintel_set_bdaddr; +- hdev->reset = btintel_pcie_reset; ++ hdev->reset = btintel_pcie_hci_reset; + hdev->wakeup = btintel_pcie_wakeup; + hdev->hci_drv = &btintel_pcie_hci_drv; + +@@ -3124,8 +3152,7 @@ static int btintel_pcie_resume(struct device *dev) + if (data->pm_sx_event == PM_EVENT_FREEZE || + data->pm_sx_event == PM_EVENT_HIBERNATE) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; + } + +@@ -3156,7 +3183,7 @@ static int btintel_pcie_resume(struct device *dev) + queue_work(data->coredump_workqueue, &data->coredump_work); + } + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + } + return err; + } diff --git a/pkgbuilds/linux-omarchy-eevdf/0663-btintel-pcie-serialize-reset-type.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0663-btintel-pcie-serialize-reset-type.patch.sig new file mode 100644 index 0000000..590f6a4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0663-btintel-pcie-serialize-reset-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch b/pkgbuilds/linux-omarchy-eevdf/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch new file mode 100644 index 0000000..54219c0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch @@ -0,0 +1,30 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, + BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + +- /* A hardware bug may cause the alive interrupt to be missed. +- * Check if the controller reached the expected state and retry +- * the operation only if it hasn't. ++ /* A hardware bug may cause the alive interrupt to be missed. Refresh ++ * boot_stage_cache from hardware, since only the interrupt handler ++ * updates it. Finally retry only if the state check still fails. + */ ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) + return 0; +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -50,6 +50,7 @@ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_DEVICE_HALTED (BIT(14)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_MAC_ACCESS_ON (BIT(16)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_ALIVE (BIT(23)) ++/* Reflects live D-state. Updated by hardware on every D-state transition. */ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY (BIT(24)) + + /* Registers for MSI-X */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig new file mode 100644 index 0000000..c9f68b6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch b/pkgbuilds/linux-omarchy-eevdf/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch new file mode 100644 index 0000000..2ec0e80 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch @@ -0,0 +1,145 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -3046,7 +3047,8 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0, status; ++ int retry = 0; ++ long status; + u32 dx_intr_timeout_ms = 200; + + do { +@@ -3098,18 +3100,23 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg) + + data = pci_get_drvdata(pdev); + +- dxstate = (mesg.event == PM_EVENT_SUSPEND ? +- BTINTEL_PCIE_STATE_D3_HOT : BTINTEL_PCIE_STATE_D3_COLD); +- +- data->pm_sx_event = mesg.event; ++ /* S0ix (s2idle) uses D3_HOT; S3, freeze and hibernate use D3_COLD. */ ++ if (mesg.event == PM_EVENT_SUSPEND && ++ pm_suspend_target_state == PM_SUSPEND_TO_IDLE) ++ dxstate = BTINTEL_PCIE_STATE_D3_HOT; ++ else ++ dxstate = BTINTEL_PCIE_STATE_D3_COLD; + + start = ktime_get(); + + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, dxstate); + +- if (err) ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set dxstate:%u (%d)", ++ dxstate, err); + return err; ++ } + + bt_dev_dbg(data->hdev, + "device entered into d3 state from d0 in %lld us", +@@ -3132,7 +3139,7 @@ static int btintel_pcie_freeze(struct device *dev) + return btintel_pcie_suspend_late(dev, PMSG_FREEZE); + } + +-static int btintel_pcie_resume(struct device *dev) ++static int btintel_pcie_resume_event(struct device *dev, pm_message_t mesg) + { + struct pci_dev *pdev = to_pci_dev(dev); + struct btintel_pcie_data *data; +@@ -3140,19 +3147,15 @@ static int btintel_pcie_resume(struct device *dev) + int err; + + data = pci_get_drvdata(pdev); +- data->gp0_received = false; + + start = ktime_get(); + +- /* When the system enters S4 (hibernate) mode, bluetooth device loses +- * power, which results in the erasure of its loaded firmware. +- * Consequently, function level reset (flr) is required on system +- * resume to bring the controller back into an operational state by +- * initiating a new firmware download. ++ /* S3 and S4 may cut power, erasing the firmware. Force FLR to recover ++ * instead of a normal D0 transition. + */ +- +- if (data->pm_sx_event == PM_EVENT_FREEZE || +- data->pm_sx_event == PM_EVENT_HIBERNATE) { ++ if (mesg.event == PM_EVENT_RESTORE || ++ (mesg.event == PM_EVENT_RESUME && ++ pm_suspend_target_state == PM_SUSPEND_MEM)) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; +@@ -3161,7 +3164,9 @@ static int btintel_pcie_resume(struct device *dev) + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, BTINTEL_PCIE_STATE_D0); + +- if (err == 0) { ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set D0 state (%d)", err); ++ } else { + bt_dev_dbg(data->hdev, + "device entered into d0 state from d3 in %lld us", + ktime_to_us(ktime_get() - start)); +@@ -3190,13 +3195,28 @@ static int btintel_pcie_resume(struct device *dev) + return err; + } + ++static int btintel_pcie_resume(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESUME); ++} ++ ++static int btintel_pcie_restore(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESTORE); ++} ++ ++static int btintel_pcie_thaw(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_THAW); ++} ++ + static const struct dev_pm_ops btintel_pcie_pm_ops = { + .suspend = btintel_pcie_suspend, + .resume = btintel_pcie_resume, + .freeze = btintel_pcie_freeze, +- .thaw = btintel_pcie_resume, ++ .thaw = btintel_pcie_thaw, + .poweroff = btintel_pcie_hibernate, +- .restore = btintel_pcie_resume, ++ .restore = btintel_pcie_restore, + }; + + static struct pci_driver btintel_pcie_driver = { +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -478,7 +478,6 @@ struct btintel_pcie_dump_header { + * @txq: TX Queue struct + * @rxq: RX Queue struct + * @alive_intr_ctxt: Alive interrupt context +- * @pm_sx_event: PM event on which system got suspended + */ + struct btintel_pcie_data { + struct pci_dev *pdev; +@@ -533,7 +532,6 @@ struct btintel_pcie_data { + enum btintel_pcie_reset_type reset_type; + struct btintel_pcie_dbgc dbgc; + struct btintel_pcie_dump_header dmp_hdr; +- u8 pm_sx_event; + u32 debug_evt_addr; + u32 debug_evt_size; + }; diff --git a/pkgbuilds/linux-omarchy-eevdf/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig new file mode 100644 index 0000000..89af410 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0666-btintel-pcie-verify-serialize-dstate.patch b/pkgbuilds/linux-omarchy-eevdf/0666-btintel-pcie-verify-serialize-dstate.patch new file mode 100644 index 0000000..b1305d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0666-btintel-pcie-verify-serialize-dstate.patch @@ -0,0 +1,113 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1012,6 +1012,10 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + submit_rx = false; + signal_waitq = false; + ++ /* Serialize the alive_intr_ctxt claim against set_dxstate()'s ++ * register-based fallback, which can run concurrently. ++ */ ++ spin_lock(&data->irq_lock); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1067,6 +1071,7 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + data->alive_intr_ctxt); + break; + } ++ spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); +@@ -3047,7 +3052,7 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0; ++ int retry = 0, err; + long status; + u32 dx_intr_timeout_ms = 200; + +@@ -3059,30 +3064,66 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + status = wait_event_timeout(data->gp0_wait_q, data->gp0_received, + msecs_to_jiffies(dx_intr_timeout_ms)); + +- if (status) +- return 0; ++ if (!status) { ++ bt_dev_warn(data->hdev, ++ "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", ++ dx_intr_timeout_ms, dxstate, retry); + +- bt_dev_warn(data->hdev, +- "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", +- dx_intr_timeout_ms, dxstate, retry); ++ /* clear gp0 cause */ ++ btintel_pcie_clr_reg_bits(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ } + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); +- +- /* A hardware bug may cause the alive interrupt to be missed. Refresh +- * boot_stage_cache from hardware, since only the interrupt handler +- * updates it. Finally retry only if the state check still fails. ++ /* gp0_received is set at the top of the handler, before the switch on ++ * alive_intr_ctxt. Error and lockdown are filtered out above it, but a ++ * matched case can still complete without doing anything - D3 breaks ++ * unchanged while the controller has not reached D0 - and a hardware ++ * bug may drop the interrupt outright. Either way the flag says a gp0 ++ * was handled, not that the transition completed, and only the register ++ * knows. Refresh the cache here and retry only if the state check still ++ * fails. + */ + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { +- if (btintel_pcie_in_d0(data)) ++ if (btintel_pcie_in_d0(data)) { ++ bool armed; ++ ++ /* Claim the transition under irq_lock so a ++ * concurrent gp0 handler run cannot also arm RX. ++ */ ++ spin_lock(&data->irq_lock); ++ armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; ++ if (!armed) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D0; ++ spin_unlock(&data->irq_lock); ++ ++ if (armed) ++ return 0; ++ ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ if (err) ++ return err; ++ + return 0; ++ } + } else { +- if (btintel_pcie_in_d3(data)) ++ if (btintel_pcie_in_d3(data)) { ++ /* Do what the handler's D0 -> D3 branch ++ * would have done, unless it already has. ++ */ ++ spin_lock(&data->irq_lock); ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D3; ++ spin_unlock(&data->irq_lock); ++ + return 0; ++ } + } + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); diff --git a/pkgbuilds/linux-omarchy-eevdf/0666-btintel-pcie-verify-serialize-dstate.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0666-btintel-pcie-verify-serialize-dstate.patch.sig new file mode 100644 index 0000000..8fbfd35 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0666-btintel-pcie-verify-serialize-dstate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0667-btintel-pcie-gp0-cause-w1c-write.patch b/pkgbuilds/linux-omarchy-eevdf/0667-btintel-pcie-gp0-cause-w1c-write.patch new file mode 100644 index 0000000..9927412 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0667-btintel-pcie-gp0-cause-w1c-write.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", + dx_intr_timeout_ms, dxstate, retry); + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ /* MSIX_HW_INT_CAUSES is W1C. Write only GP0 so other ++ * pending causes are not acknowledged here. ++ */ ++ btintel_pcie_wr_reg32(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + } + + /* gp0_received is set at the top of the handler, before the switch on diff --git a/pkgbuilds/linux-omarchy-eevdf/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig new file mode 100644 index 0000000..817ba81 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0668-btintel-pcie-dstate-races.patch b/pkgbuilds/linux-omarchy-eevdf/0668-btintel-pcie-dstate-races.patch new file mode 100644 index 0000000..d25c602 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0668-btintel-pcie-dstate-races.patch @@ -0,0 +1,86 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1016,6 +1016,8 @@ static void btintel_pcie_msix_gp0_handle + * register-based fallback, which can run concurrently. + */ + spin_lock(&data->irq_lock); ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1071,12 +1073,12 @@ static void btintel_pcie_msix_gp0_handle + data->alive_intr_ctxt); + break; + } +- spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); + btintel_pcie_start_rx(data); + } ++ spin_unlock(&data->irq_lock); + + if (signal_waitq) { + bt_dev_dbg(data->hdev, "wake up gp0 wait_q"); +@@ -3086,40 +3088,34 @@ static int btintel_pcie_set_dxstate(stru + * knows. Refresh the cache here and retry only if the state check still + * fails. + */ ++ spin_lock(&data->irq_lock); + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) { +- bool armed; ++ err = 0; + + /* Claim the transition under irq_lock so a + * concurrent gp0 handler run cannot also arm RX. + */ +- spin_lock(&data->irq_lock); +- armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; +- if (!armed) ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D0) { + data->alive_intr_ctxt = BTINTEL_PCIE_D0; +- spin_unlock(&data->irq_lock); + +- if (armed) +- return 0; +- +- /* Do what the handler's D3 -> D0 branch +- * would have done, unless it already has. +- */ +- btintel_pcie_reset_ia(data); +- err = btintel_pcie_start_rx(data); +- if (err) +- return err; ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ } ++ spin_unlock(&data->irq_lock); + +- return 0; ++ return err; + } + } else { + if (btintel_pcie_in_d3(data)) { + /* Do what the handler's D0 -> D3 branch + * would have done, unless it already has. + */ +- spin_lock(&data->irq_lock); + if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) + data->alive_intr_ctxt = BTINTEL_PCIE_D3; + spin_unlock(&data->irq_lock); +@@ -3127,6 +3123,7 @@ static int btintel_pcie_set_dxstate(stru + return 0; + } + } ++ spin_unlock(&data->irq_lock); + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); + diff --git a/pkgbuilds/linux-omarchy-eevdf/0668-btintel-pcie-dstate-races.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0668-btintel-pcie-dstate-races.patch.sig new file mode 100644 index 0000000..93a7c8a Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0668-btintel-pcie-dstate-races.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0669-btintel-version-tlv-parse-errors.patch b/pkgbuilds/linux-omarchy-eevdf/0669-btintel-version-tlv-parse-errors.patch new file mode 100644 index 0000000..164dbb6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0669-btintel-version-tlv-parse-errors.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -720,6 +720,7 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + { + struct sk_buff *skb; + const u8 param[1] = { 0xFF }; ++ int err; + + if (!version) + return -EINVAL; +@@ -738,10 +739,10 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + return -EIO; + } + +- btintel_parse_version_tlv(hdev, version, skb); ++ err = btintel_parse_version_tlv(hdev, version, skb); + + kfree_skb(skb); +- return 0; ++ return err; + } + + /* ------- REGMAP IBT SUPPORT ------- */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0669-btintel-version-tlv-parse-errors.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0669-btintel-version-tlv-parse-errors.patch.sig new file mode 100644 index 0000000..caff3f4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0669-btintel-version-tlv-parse-errors.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0670-bluetooth-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0670-bluetooth-fixes.patch new file mode 100644 index 0000000..14623cd --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0670-bluetooth-fixes.patch @@ -0,0 +1,485 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -288,7 +288,8 @@ void btintel_hw_error(struct hci_dev *hdev, u8 code) + goto unlock; + } + +- bt_dev_err(hdev, "Exception info %s", (char *)(skb->data + 1)); ++ bt_dev_err(hdev, "Exception info %.*s", (int)(skb->len - 1), ++ (char *)(skb->data + 1)); + + kfree_skb(skb); + +@@ -407,8 +408,16 @@ int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name) + /* DDC file contains one or more DDC structure which has + * Length (1 byte), DDC ID (2 bytes), and DDC value (Length - 2). + */ +- while (fw->size > fw_ptr - fw->data) { +- u8 cmd_plen = fw_ptr[0] + sizeof(u8); ++ while (fw->size > (size_t)(fw_ptr - fw->data)) { ++ size_t remaining = fw->size - (fw_ptr - fw->data); ++ unsigned int cmd_plen = fw_ptr[0] + 1U; ++ ++ if (cmd_plen < 3 || cmd_plen > U8_MAX || cmd_plen > remaining) { ++ bt_dev_err(hdev, "Malformed DDC record (plen=%u, remaining=%zu)", ++ cmd_plen, remaining); ++ release_firmware(fw); ++ return -EINVAL; ++ } + + skb = __hci_cmd_sync(hdev, 0xfc8b, cmd_plen, fw_ptr, + HCI_INIT_TIMEOUT); +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1200,7 +1200,7 @@ static int btintel_pcie_recv_frame(struct btintel_pcie_data *data, + { + int ret; + u8 pkt_type; +- u16 plen; ++ u32 plen; + u32 pcie_pkt_type; + void *pdata; + struct hci_dev *hdev = data->hdev; +@@ -2149,6 +2149,16 @@ static int btintel_pcie_send_frame(struct hci_dev *hdev, + if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return -ENODEV; + ++ /* Account for the 4-byte PCIe type header prepended before the ++ * DMA copy. Written as a subtraction to avoid wrap-around on ++ * attacker-controlled skb->len. ++ */ ++ if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { ++ bt_dev_err(hdev, "Packet too large: %u > %u", skb->len, ++ BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); ++ return -EMSGSIZE; ++ } ++ + /* Due to the fw limitation, the type header of the packet should be + * 4 bytes unlike 1 byte for UART. In UART, the firmware can read + * the first byte to get the packet type and redirect the rest of data +diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -62,7 +62,7 @@ struct inquiry_entry { + NAME_PENDING, + NAME_KNOWN, + } name_state; +- __u32 timestamp; ++ unsigned long timestamp; + struct inquiry_data data; + }; + +@@ -78,7 +78,7 @@ struct discovery_state { + struct list_head all; /* All devices found during inquiry */ + struct list_head unknown; /* Name state not known */ + struct list_head resolve; /* Name needs to be resolved */ +- __u32 timestamp; ++ unsigned long timestamp; + bdaddr_t last_adv_addr; + u8 last_adv_addr_type; + s8 last_adv_rssi; +@@ -561,6 +561,7 @@ struct hci_dev { + struct list_head link_keys; + struct list_head long_term_keys; + struct list_head identity_resolving_keys; ++ struct mutex remote_oob_lock; + struct list_head remote_oob_data; + struct list_head le_accept_list; + struct list_head le_resolv_list; +diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -302,11 +302,13 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.tx_bandwidth = cpu_to_le32(0x00001f40); + cp.rx_bandwidth = cpu_to_le32(0x00001f40); + ++ hci_dev_lock(hdev); ++ + switch (conn->codec.id) { + case BT_CODEC_MSBC: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x05; +@@ -332,7 +334,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + case BT_CODEC_TRANSPARENT: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x03; +@@ -359,11 +361,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + if (conn->parent && lmp_esco_capable(conn->parent)) { + if (!find_next_esco_param(conn, esco_param_cvsd, + ARRAY_SIZE(esco_param_cvsd))) +- return -EINVAL; ++ goto unlock; + param = &esco_param_cvsd[conn->attempt - 1]; + } else { + if (conn->attempt > ARRAY_SIZE(sco_param_cvsd)) +- return -EINVAL; ++ goto unlock; + param = &sco_param_cvsd[conn->attempt - 1]; + } + cp.tx_coding_format.id = 2; +@@ -386,9 +388,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.out_transport_unit_size = 16; + break; + default: +- return -EINVAL; ++ goto unlock; + } + ++ hci_dev_unlock(hdev); ++ + cp.retrans_effort = param->retrans_effort; + cp.pkt_type = __cpu_to_le16(param->pkt_type); + cp.max_latency = __cpu_to_le16(param->max_latency); +@@ -397,6 +401,10 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + return -EIO; + + return 0; ++ ++unlock: ++ hci_dev_unlock(hdev); ++ return -EINVAL; + } + + static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) +diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -1486,8 +1486,10 @@ int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s removing %pMR (%u)", hdev->name, bdaddr, bdaddr_type); + ++ mutex_lock(&hdev->remote_oob_lock); + list_del(&data->list); + kfree(data); ++ mutex_unlock(&hdev->remote_oob_lock); + + return 0; + } +@@ -1496,10 +1498,12 @@ void hci_remote_oob_data_clear(struct hci_dev *hdev) + { + struct oob_data *data, *n; + ++ mutex_lock(&hdev->remote_oob_lock); + list_for_each_entry_safe(data, n, &hdev->remote_oob_data, list) { + list_del(&data->list); + kfree(data); + } ++ mutex_unlock(&hdev->remote_oob_lock); + } + + int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, +@@ -1508,11 +1512,14 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + { + struct oob_data *data; + ++ mutex_lock(&hdev->remote_oob_lock); + data = hci_find_remote_oob_data(hdev, bdaddr, bdaddr_type); + if (!data) { + data = kmalloc_obj(*data); +- if (!data) ++ if (!data) { ++ mutex_unlock(&hdev->remote_oob_lock); + return -ENOMEM; ++ } + + bacpy(&data->bdaddr, bdaddr); + data->bdaddr_type = bdaddr_type; +@@ -1545,6 +1552,8 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s for %pMR", hdev->name, bdaddr); + ++ mutex_unlock(&hdev->remote_oob_lock); ++ + return 0; + } + +@@ -2482,6 +2491,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) + mutex_init(&hdev->lock); + mutex_init(&hdev->req_lock); + mutex_init(&hdev->mgmt_pending_lock); ++ mutex_init(&hdev->remote_oob_lock); + + ida_init(&hdev->unset_handle_ida); + +@@ -2554,8 +2564,10 @@ int hci_register_dev(struct hci_dev *hdev) + return id; + + error = dev_set_name(&hdev->dev, "hci%u", id); +- if (error) ++ if (error) { ++ ida_free(&hci_index_ida, id); + return error; ++ } + + hdev->name = dev_name(&hdev->dev); + hdev->id = id; +@@ -3323,6 +3335,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + skb_shinfo(skb)->frag_list = NULL; + ++ spin_lock_bh(&queue->lock); ++ + __skb_queue_tail(queue, skb); + + do { +@@ -3337,6 +3351,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + __skb_queue_tail(queue, skb); + } while (list); ++ ++ spin_unlock_bh(&queue->lock); + } + + bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(queue)); +@@ -3400,9 +3416,6 @@ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, + struct hci_conn *conn = NULL, *c; + unsigned int num = 0, min = ~0; + +- /* We don't have to lock device here. Connections are always +- * added and removed with TX task disabled. */ +- + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +@@ -3607,13 +3620,15 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt, u8 type) + } + + /* Schedule SCO */ +-static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++static void __hci_sched_sco(struct hci_dev *hdev, __u8 type) + { + struct hci_conn *conn; + struct sk_buff *skb; + int quote, *cnt; + unsigned int pkts = hdev->sco_pkts; + ++ lockdep_assert_held(&hdev->lock); ++ + bt_dev_dbg(hdev, "type %u", type); + + if (!hci_conn_num(hdev, type) || !pkts) +@@ -3648,6 +3663,13 @@ static void hci_sched_sco(struct hci_dev *hdev, __u8 type) + queue_work(hdev->workqueue, &hdev->tx_work); + } + ++static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++{ ++ hci_dev_lock(hdev); ++ __hci_sched_sco(hdev, type); ++ hci_dev_unlock(hdev); ++} ++ + static void hci_sched_acl_pkt(struct hci_dev *hdev) + { + unsigned int cnt = hdev->acl_cnt; +@@ -3657,6 +3679,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + + __check_timeout(hdev, cnt, ACL_LINK); + ++ hci_dev_lock(hdev); ++ + while (hdev->acl_cnt && + (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3681,13 +3705,15 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (cnt != hdev->acl_cnt) + hci_prio_recalculate(hdev, ACL_LINK); ++ ++ hci_dev_unlock(hdev); + } + + static void hci_sched_acl(struct hci_dev *hdev) +@@ -3716,6 +3742,8 @@ static void hci_sched_le(struct hci_dev *hdev) + + __check_timeout(hdev, *cnt, LE_LINK); + ++ hci_dev_lock(hdev); ++ + tmp = *cnt; + while (*cnt && (chan = hci_chan_sent(hdev, LE_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3737,13 +3765,15 @@ static void hci_sched_le(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (*cnt != tmp) + hci_prio_recalculate(hdev, LE_LINK); ++ ++ hci_dev_unlock(hdev); + } + + /* Schedule iso */ +@@ -3762,6 +3792,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + + __check_timeout(hdev, *cnt, type); + ++ hci_dev_lock(hdev); ++ + while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); +@@ -3775,6 +3807,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + (*cnt)--; + } + } ++ ++ hci_dev_unlock(hdev); + } + + static void hci_tx_work(struct work_struct *work) +diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c +--- a/net/bluetooth/hci_debugfs.c ++++ b/net/bluetooth/hci_debugfs.c +@@ -364,7 +364,7 @@ static int inquiry_cache_show(struct seq_file *f, void *p) + + list_for_each_entry(e, &cache->all, all) { + struct inquiry_data *data = &e->data; +- seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n", ++ seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %lu\n", + &data->bdaddr, + data->pscan_rep_mode, data->pscan_period_mode, + data->pscan_mode, data->dev_class[2], +diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -312,6 +312,10 @@ static void hci_cmd_sync_work(struct work_struct *work) + while (1) { + struct hci_cmd_sync_work_entry *entry; + ++ /* Leave the backlog to hci_cmd_sync_clear() */ ++ if (hci_dev_test_flag(hdev, HCI_UNREGISTER)) ++ break; ++ + mutex_lock(&hdev->cmd_sync_work_lock); + entry = list_first_entry_or_null(&hdev->cmd_sync_work_list, + struct hci_cmd_sync_work_entry, +@@ -658,6 +662,8 @@ void hci_cmd_sync_clear(struct hci_dev *hdev) + { + struct hci_cmd_sync_work_entry *entry, *tmp; + ++ /* cmd_work is disabled, the pending request can only time out */ ++ hci_cmd_sync_cancel_sync(hdev, ENODEV); + cancel_work_sync(&hdev->cmd_sync_work); + cancel_work_sync(&hdev->reenable_adv_work); + +@@ -5678,6 +5684,7 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + { + struct discovery_state *d = &hdev->discovery; + struct inquiry_entry *e; ++ bdaddr_t addr; + int err; + + bt_dev_dbg(hdev, "state %u", hdev->discovery.state); +@@ -5713,15 +5720,21 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + return 0; + + if (d->state == DISCOVERY_RESOLVING || d->state == DISCOVERY_STOPPING) { ++ hci_dev_lock(hdev); + e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, + NAME_PENDING); +- if (!e) ++ if (!e) { ++ hci_dev_unlock(hdev); + return 0; ++ } ++ ++ bacpy(&addr, &e->data.bdaddr); ++ hci_dev_unlock(hdev); + + /* Ignore cancel errors since it should interfere with stopping + * of the discovery. + */ +- hci_remote_name_cancel_sync(hdev, &e->data.bdaddr); ++ hci_remote_name_cancel_sync(hdev, &addr); + } + + return 0; +@@ -7151,6 +7164,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + bacpy(&cp.bdaddr, &conn->dst); + cp.pscan_rep_mode = 0x02; + ++ hci_dev_lock(hdev); + ie = hci_inquiry_cache_lookup(hdev, &conn->dst); + if (ie) { + if (inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { +@@ -7162,6 +7176,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + + memcpy(conn->dev_class, ie->data.dev_class, 3); + } ++ hci_dev_unlock(hdev); + + cp.pkt_type = cpu_to_le16(conn->pkt_type); + if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) +diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c +--- a/net/bluetooth/rfcomm/tty.c ++++ b/net/bluetooth/rfcomm/tty.c +@@ -462,7 +462,7 @@ static int __rfcomm_release_dev(void __user *arg) + /* Shut down TTY synchronously before freeing rfcomm_dev */ + tty_port_tty_vhangup(&dev->port); + +- if (!test_bit(RFCOMM_TTY_OWNED, &dev->status)) ++ if (!test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); + + tty_port_put(&dev->port); +@@ -724,10 +724,9 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty) + * when the last process closes the tty. The behaviour is expected by + * userspace. + */ +- if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { +- set_bit(RFCOMM_TTY_OWNED, &dev->status); ++ if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags) && ++ !test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); +- } + + return 0; + } +diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c +--- a/net/bluetooth/smp.c ++++ b/net/bluetooth/smp.c +@@ -661,6 +661,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + else + bdaddr_type = BDADDR_LE_RANDOM; + ++ mutex_lock(&hdev->remote_oob_lock); + oob_data = hci_find_remote_oob_data(hdev, &hcon->dst, + bdaddr_type); + if (oob_data && oob_data->present) { +@@ -671,6 +672,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + SMP_DBG("OOB Remote Confirmation: %16phN", smp->pcnf); + SMP_DBG("OOB Remote Random: %16phN", smp->rr); + } ++ mutex_unlock(&hdev->remote_oob_lock); + + } else { + authreq &= ~SMP_AUTH_SC; diff --git a/pkgbuilds/linux-omarchy-eevdf/0670-bluetooth-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0670-bluetooth-fixes.patch.sig new file mode 100644 index 0000000..cadba43 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0670-bluetooth-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch b/pkgbuilds/linux-omarchy-eevdf/0700-pci-target-speed-quirk.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch rename to pkgbuilds/linux-omarchy-eevdf/0700-pci-target-speed-quirk.patch diff --git a/pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0700-pci-target-speed-quirk.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0700-pci-target-speed-quirk.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0700-pci-target-speed-quirk.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0701-pci-amd-enhanced-atomics.patch b/pkgbuilds/linux-omarchy-eevdf/0701-pci-amd-enhanced-atomics.patch new file mode 100644 index 0000000..c76cb96 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0701-pci-amd-enhanced-atomics.patch @@ -0,0 +1,109 @@ +diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c +--- a/arch/x86/pci/fixup.c ++++ b/arch/x86/pci/fixup.c +@@ -886,6 +886,105 @@ static void quirk_clear_strap_no_soft_reset_dev2_f0(struct pci_dev *dev) + } + } + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15b8, quirk_clear_strap_no_soft_reset_dev2_f0); ++ ++/* ++ * Enhanced atomic operations can cause corruption with 64-bit IOVA ++ * on these devices. ++ */ ++#define RX_ENH_ATOMIC_EN BIT(8) ++ ++static const u32 nbio_7_7_pcie_smn_addrs[] = { ++ 0x111401d0, ++ 0x111411d0, ++ 0x111421d0, ++ 0x111431d0, ++ 0x111441d0, ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x114401d0, ++}; ++ ++static const u32 nbio_7_11_pcie_smn_addrs[] = { ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x113411d0, ++ 0x113421d0, ++ 0x113431d0, ++ 0x113441d0, ++ 0x113451d0, ++}; ++ ++static void quirk_amd_nbio_enhanced_atomic(struct pci_dev *host_bridge, ++ const u32 *smn_addrs, ++ size_t nr_smn_addrs) ++{ ++ bool changed = false; ++ size_t i; ++ u32 data; ++ int ret; ++ ++ for (i = 0; i < nr_smn_addrs; i++) { ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (!(data & RX_ENH_ATOMIC_EN)) ++ continue; ++ data = data & ~RX_ENH_ATOMIC_EN; ++ ret = amd_smn_write(0, smn_addrs[i], data); ++ if (ret) ++ continue; ++ if (changed) ++ continue; ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (data & RX_ENH_ATOMIC_EN) ++ continue; ++ changed = true; ++ } ++ ++ if (changed) ++ pci_info(host_bridge, "enhanced atomics disabled\n"); ++} ++ ++static void quirk_amd_nbio_7_7_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_7_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_7_pcie_smn_addrs)); ++} ++ ++static void quirk_amd_nbio_7_11_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_11_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_11_pcie_smn_addrs)); ++} ++ ++/* Phoenix, Hawk Point (NBIO 7.7) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++ ++/* Strix, Krackan, Strix Halo (NBIO 7.11) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++ + #endif + + /* diff --git a/pkgbuilds/linux-omarchy-eevdf/0701-pci-amd-enhanced-atomics.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0701-pci-amd-enhanced-atomics.patch.sig new file mode 100644 index 0000000..7732219 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0701-pci-amd-enhanced-atomics.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0702-ahci-jmb585-32bit-dma-revert.patch b/pkgbuilds/linux-omarchy-eevdf/0702-ahci-jmb585-32bit-dma-revert.patch new file mode 100644 index 0000000..9794028 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0702-ahci-jmb585-32bit-dma-revert.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c +--- a/drivers/ata/ahci.c ++++ b/drivers/ata/ahci.c +@@ -68,7 +68,6 @@ enum board_ids { + /* board IDs for specific chipsets in alphabetical order */ + board_ahci_al, + board_ahci_avn, +- board_ahci_jmb585, + board_ahci_mcp65, + board_ahci_mcp77, + board_ahci_mcp89, +@@ -213,15 +212,6 @@ static const struct ata_port_info ahci_port_info[] = { + .udma_mask = ATA_UDMA6, + .port_ops = &ahci_avn_ops, + }, +- /* JMicron JMB582/585: 64-bit DMA is broken, force 32-bit */ +- [board_ahci_jmb585] = { +- AHCI_HFLAGS (AHCI_HFLAG_IGN_IRQ_IF_ERR | +- AHCI_HFLAG_32BIT_ONLY), +- .flags = AHCI_FLAG_COMMON, +- .pio_mask = ATA_PIO4, +- .udma_mask = ATA_UDMA6, +- .port_ops = &ahci_ops, +- }, + [board_ahci_mcp65] = { + AHCI_HFLAGS (AHCI_HFLAG_NO_FPDMA_AA | AHCI_HFLAG_NO_PMP | + AHCI_HFLAG_YES_NCQ), +@@ -954,14 +944,6 @@ static const struct pci_device_id ahci_pci_tbl[] = { + /* Elkhart Lake AHCI */ + PCI_VDEVICE(INTEL, 0x4b63), + .driver_data = board_ahci_pcs_quirk, +- }, { +- /* JMicron JMB582/585: force 32-bit DMA (broken 64-bit implementation) */ +- PCI_VDEVICE(JMICRON, 0x0582), +- .driver_data = board_ahci_jmb585, +- +- }, { +- PCI_VDEVICE(JMICRON, 0x0585), +- .driver_data = board_ahci_jmb585, + }, { + /* JMicron 360/1/3/5/6, match class to avoid IDE function */ + PCI_DEVICE(PCI_VENDOR_ID_JMICRON, PCI_ANY_ID), diff --git a/pkgbuilds/linux-omarchy-eevdf/0702-ahci-jmb585-32bit-dma-revert.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0702-ahci-jmb585-32bit-dma-revert.patch.sig new file mode 100644 index 0000000..58c4c7c Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0702-ahci-jmb585-32bit-dma-revert.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0703-pci-vmd-scan-bios-enumerated-devices.patch b/pkgbuilds/linux-omarchy-eevdf/0703-pci-vmd-scan-bios-enumerated-devices.patch new file mode 100644 index 0000000..0fe6c04 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0703-pci-vmd-scan-bios-enumerated-devices.patch @@ -0,0 +1,297 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -37,6 +37,12 @@ + #define MB2_SHADOW_OFFSET 0x2000 + #define MB2_SHADOW_SIZE 16 + ++/* DMR BAR4 register offsets */ ++#define SHADOW_MEMBAR1_28C1 0x2818 /* MEMBAR1 physical address */ ++#define SHADOW_MEMBAR2_28C1 0x2820 /* MEMBAR2 physical address */ ++#define BASE_ID_REG_28C1 0x2840 ++#define MEMBAR2_OFFSET_28C1 0x30d0 ++ + enum vmd_features { + /* + * Device may contain registers which hint the physical location of the +@@ -77,6 +83,15 @@ enum vmd_features { + * proper power management of the SoC. + */ + VMD_FEAT_BIOS_PM_QUIRK = (1 << 5), ++ ++ /* ++ * Newer VMD with device ID 0x28C1 has unique settings compared to its ++ * predecessor where BIOS enumerates the entire VMD device tree and ++ * stores respective configurations including bus start range and ++ * shadow registers in VMD MMIO space in VMD BAR4/BAR5, otherwise ++ * referred to as MEMBAR2 or MSI-X BAR. ++ */ ++ VMD_FEAT_USE_BIOS_INFO = (1 << 6), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -142,6 +157,7 @@ struct vmd_dev { + u8 first_vec; + char *name; + int instance; ++ unsigned long features; + }; + + static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) +@@ -366,6 +382,9 @@ static void vmd_set_msi_remapping(struct vmd_dev *vmd, bool enable) + { + u16 reg; + ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return; ++ + pci_read_config_word(vmd->dev, PCI_REG_VMCONFIG, ®); + reg = enable ? (reg & ~VMCONFIG_MSI_REMAP) : + (reg | VMCONFIG_MSI_REMAP); +@@ -389,11 +408,22 @@ static void vmd_remove_irq_domain(struct vmd_dev *vmd) + } + } + ++static unsigned int vmd_bus_to_ecam(struct vmd_dev *vmd, unsigned int busnr) ++{ ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return busnr; ++ ++ return busnr - vmd->busn_start; ++} ++ + static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, + unsigned int devfn, int reg, int len) + { +- unsigned int busnr_ecam = bus->number - vmd->busn_start; +- u32 offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); ++ unsigned int busnr_ecam; ++ u32 offset; ++ ++ busnr_ecam = vmd_bus_to_ecam(vmd, bus->number); ++ offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); + + if (offset + len >= resource_size(&vmd->dev->resource[VMD_CFGBAR])) + return NULL; +@@ -518,22 +548,37 @@ static inline void vmd_acpi_begin(void) { } + static inline void vmd_acpi_end(void) { } + #endif /* CONFIG_ACPI */ + ++static resource_size_t vmd_cfgbar_ecam_space(struct vmd_dev *vmd) ++{ ++ resource_size_t cfgbar_buses; ++ unsigned int ecam_start; ++ ++ cfgbar_buses = resource_size(&vmd->dev->resource[VMD_CFGBAR]) >> 20; ++ ecam_start = vmd_bus_to_ecam(vmd, vmd->resources[0].start); ++ if (ecam_start >= cfgbar_buses) ++ return 0; ++ ++ return cfgbar_buses - ecam_start; ++} + static void vmd_domain_reset(struct vmd_dev *vmd) + { + u16 bus, max_buses = resource_size(&vmd->resources[0]); + u8 dev, functions, fn, hdr_type; ++ unsigned int ecam_bus; + char __iomem *base; + ++ max_buses = min_t(u16, max_buses, vmd_cfgbar_ecam_space(vmd)); + for (bus = 0; bus < max_buses; bus++) { ++ ecam_bus = vmd_bus_to_ecam(vmd, vmd->resources[0].start + bus); + for (dev = 0; dev < 32; dev++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, 0), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE); + + functions = (hdr_type & PCI_HEADER_TYPE_MFD) ? 8 : 1; + for (fn = 0; fn < functions; fn++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, fn), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE) & +@@ -661,6 +706,46 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_get_bus_info_from_bar4(struct vmd_dev *vmd, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ u64 phys1, phys2, bar4_2840; ++ void __iomem *bar4; ++ u32 base_id; ++ u8 base_bus; ++ ++ bar4 = pci_ioremap_bar(vmd->dev, 4); ++ if (!bar4) ++ return -ENOMEM; ++ ++ /* Read shadow registers for MEMBAR1 and MEMBAR2 physical addresses */ ++ phys1 = readq(bar4 + SHADOW_MEMBAR1_28C1); ++ phys2 = readq(bar4 + SHADOW_MEMBAR2_28C1); ++ ++ /* ++ * Read and set bus start number from Base ID register. 24-bit Base ID ++ * register is part of 64-bit shadowed reqid hide range register and ++ * holds segment, bus, device and function. ++ */ ++ bar4_2840 = readq(bar4 + BASE_ID_REG_28C1); ++ base_id = bar4_2840 & 0xFFFFFF; ++ base_bus = base_id >> 8; ++ vmd->busn_start = base_bus; ++ ++ /* Calculate offsets like vmd_get_phys_offsets() does */ ++ if (phys1) ++ *offset1 = vmd->dev->resource[VMD_MEMBAR1].start - ++ (phys1 & PCI_BASE_ADDRESS_MEM_MASK); ++ if (phys2) ++ *offset2 = vmd->dev->resource[VMD_MEMBAR2].start - ++ (phys2 & PCI_BASE_ADDRESS_MEM_MASK); ++ ++ pci_iounmap(vmd->dev, bar4); ++ ++ return 0; ++} ++ + static irqreturn_t vmd_irq(int irq, void *data) + { + struct vmd_irq_list *irqs = data; +@@ -711,6 +796,52 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_prepare_offsets_and_bus(struct vmd_dev *vmd, ++ unsigned long features, ++ resource_size_t *membar2_offset, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ int ret; ++ ++ /* ++ * Shadow registers may exist in certain VMD device IDs which allow ++ * guests to correctly assign host physical addresses to the root ports ++ * and child devices. These registers will either return the host value ++ * or 0, depending on an enable bit in the VMD device. ++ * ++ * For certain VMD devices (i.e. 0x28C1), BIOS places device info ++ * in BAR4 shadow registers to determine the base bus number and memory ++ * offsets. ++ */ ++ if (features & VMD_FEAT_USE_BIOS_INFO) { ++ *membar2_offset = MEMBAR2_OFFSET_28C1; ++ ret = vmd_get_bus_info_from_bar4(vmd, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { ++ *membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; ++ ret = vmd_get_phys_offsets(vmd, true, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { ++ ret = vmd_get_phys_offsets(vmd, false, offset1, offset2); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Certain VMD devices may have a root port configuration option which ++ * limits the bus range to between 0-127, 128-255, or 224-255. ++ */ ++ if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { ++ ret = vmd_get_bus_number_start(vmd); ++ if (ret) ++ return ret; ++ } ++ return 0; ++} ++ + /* + * Since VMD is an aperture to regular PCIe root ports, only allow it to + * control features that the OS is allowed to control on the physical PCI bus. +@@ -780,42 +911,24 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) + LIST_HEAD(resources); + resource_size_t offset[2] = {0}; + resource_size_t membar2_offset = 0x2000; ++ resource_size_t busn_end; + struct pci_bus *child; + struct pci_dev *dev; + int ret; + +- /* +- * Shadow registers may exist in certain VMD device ids which allow +- * guests to correctly assign host physical addresses to the root ports +- * and child devices. These registers will either return the host value +- * or 0, depending on an enable bit in the VMD device. +- */ +- if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { +- membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; +- ret = vmd_get_phys_offsets(vmd, true, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { +- ret = vmd_get_phys_offsets(vmd, false, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } +- +- /* +- * Certain VMD devices may have a root port configuration option which +- * limits the bus range to between 0-127, 128-255, or 224-255 +- */ +- if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { +- ret = vmd_get_bus_number_start(vmd); +- if (ret) +- return ret; +- } ++ ret = vmd_prepare_offsets_and_bus(vmd, features, &membar2_offset, ++ &offset[0], &offset[1]); ++ if (ret) ++ return ret; + ++ /* Do not let resource[0] end go out of bounds */ + res = &vmd->dev->resource[VMD_CFGBAR]; ++ busn_end = vmd->busn_start + (resource_size(res) >> 20) - 1; ++ busn_end = min_t(resource_size_t, busn_end, 0xff); + vmd->resources[0] = (struct resource) { + .name = "VMD CFGBAR", + .start = vmd->busn_start, +- .end = vmd->busn_start + (resource_size(res) >> 20) - 1, ++ .end = busn_end, + .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, + }; + +@@ -998,6 +1111,7 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) + + vmd->dev = dev; + vmd->sysdata.domain = PCI_DOMAIN_NR_NOT_SET; ++ vmd->features = features; + vmd->instance = ida_alloc(&vmd_instance_ida, GFP_KERNEL); + if (vmd->instance < 0) + return vmd->instance; +@@ -1114,6 +1228,10 @@ static const struct pci_device_id vmd_ids[] = { + .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | + VMD_FEAT_HAS_BUS_RESTRICTIONS | + VMD_FEAT_CAN_BYPASS_MSI_REMAP,}, ++ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_28C1), ++ .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | ++ VMD_FEAT_CAN_BYPASS_MSI_REMAP | ++ VMD_FEAT_USE_BIOS_INFO,}, + {PCI_VDEVICE(INTEL, 0x467f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x4c3d), +diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h +--- a/include/linux/pci_ids.h ++++ b/include/linux/pci_ids.h +@@ -2892,6 +2892,7 @@ + #define PCI_DEVICE_ID_INTEL_HDA_ICH8 0x284b + #define PCI_DEVICE_ID_INTEL_ICH8_6 0x2850 + #define PCI_DEVICE_ID_INTEL_VMD_28C0 0x28c0 ++#define PCI_DEVICE_ID_INTEL_VMD_28C1 0x28c1 + #define PCI_DEVICE_ID_INTEL_ICH9_0 0x2910 + #define PCI_DEVICE_ID_INTEL_ICH9_2 0x2912 + #define PCI_DEVICE_ID_INTEL_ICH9_3 0x2913 diff --git a/pkgbuilds/linux-omarchy-eevdf/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig new file mode 100644 index 0000000..7e9f3d1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0704-pci-vmd-meteor-lake-mtl016-flush.patch b/pkgbuilds/linux-omarchy-eevdf/0704-pci-vmd-meteor-lake-mtl016-flush.patch new file mode 100644 index 0000000..22be8d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0704-pci-vmd-meteor-lake-mtl016-flush.patch @@ -0,0 +1,120 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -92,6 +92,22 @@ enum vmd_features { + * referred to as MEMBAR2 or MSI-X BAR. + */ + VMD_FEAT_USE_BIOS_INFO = (1 << 6), ++ ++ /* ++ * Meteor Lake VMD (device ID 0x7d0b) is affected by erratum MTL016: ++ * the VMD may signal its MSI before the posted writes that carry the ++ * child device's DMA data have landed in memory. The demuxed handler ++ * then runs against a not-yet-coherent completion queue and misses the ++ * completion entirely, so the I/O is only recovered when the block ++ * layer timeout fires and polls the queue ("timeout, completion ++ * polled"). Intel's documented workaround is to issue a dummy read ++ * to the MSI initiator (the child device) before handling the ++ * interrupt: the read completion cannot pass the device's earlier ++ * posted writes, so it pulls them into memory per PCIe ordering ++ * rules. A read that terminates at the VMD itself does not order ++ * against the child's writes and is not sufficient (measured). ++ */ ++ VMD_FEAT_INTERRUPT_QUIRK = (1 << 7), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -114,6 +130,9 @@ static DEFINE_RAW_SPINLOCK(list_lock); + * @irq: back pointer to parent. + * @enabled: true if driver enabled IRQ + * @virq: the virtual IRQ value provided to the requesting driver. ++ * @flush_addr: config space address of the initiating device, read before ++ * demuxing to flush its posted writes (MTL016); NULL if the ++ * VMD is not affected. + * + * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to + * a VMD IRQ using this structure. +@@ -123,8 +142,14 @@ struct vmd_irq { + struct vmd_irq_list *irq; + bool enabled; + unsigned int virq; ++ void __iomem *flush_addr; + }; + ++struct vmd_dev; ++ ++static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, ++ unsigned int devfn, int reg, int len); ++ + /** + * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector + * @irq_list: the list of irq's the VMD one demuxes to. +@@ -132,12 +157,15 @@ struct vmd_irq { + * @count: number of child IRQs assigned to this vector; used to track + * sharing. + * @virq: The underlying VMD Linux interrupt number ++ * @vmd: back pointer to the owning VMD device; serializes the MTL016 ++ * flush read against other config space access. + */ + struct vmd_irq_list { + struct list_head irq_list; + struct srcu_struct srcu; + unsigned int count; + unsigned int virq; ++ struct vmd_dev *vmd; + }; + + struct vmd_dev { +@@ -295,6 +323,13 @@ static int vmd_msi_alloc(struct irq_domain *domain, unsigned int virq, + INIT_LIST_HEAD(&vmdirq->node); + vmdirq->irq = vmd_next_irq(vmd, desc); + vmdirq->virq = virq + i; ++ if (vmd->features & VMD_FEAT_INTERRUPT_QUIRK) { ++ struct pci_dev *pdev = msi_desc_to_pci_dev(desc); ++ ++ vmdirq->flush_addr = vmd_cfg_addr(vmd, pdev->bus, ++ pdev->devfn, ++ PCI_VENDOR_ID, 2); ++ } + + irq_domain_set_info(domain, virq + i, vmdirq->irq->virq, + &vmd_msi_controller, vmdirq, +@@ -753,8 +788,20 @@ static irqreturn_t vmd_irq(int irq, void *data) + int idx; + + idx = srcu_read_lock(&irqs->srcu); +- list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) ++ list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) { ++ /* ++ * MTL016: the MSI may have outrun the initiating device's ++ * posted writes (e.g. its NVMe completion entry). A read ++ * that completes at the initiator flushes them, so the ++ * demuxed handler observes a coherent completion queue. ++ * The value is discarded; only the ordering matters. ++ */ ++ if (vmdirq->flush_addr) { ++ guard(raw_spinlock)(&irqs->vmd->cfg_lock); ++ readw(vmdirq->flush_addr); ++ } + generic_handle_irq(vmdirq->virq); ++ } + srcu_read_unlock(&irqs->srcu, idx); + + return IRQ_HANDLED; +@@ -785,6 +832,7 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return err; + + INIT_LIST_HEAD(&vmd->irqs[i].irq_list); ++ vmd->irqs[i].vmd = vmd; + vmd->irqs[i].virq = pci_irq_vector(dev, i); + err = devm_request_irq(&dev->dev, vmd->irqs[i].virq, + vmd_irq, IRQF_NO_THREAD, +@@ -1239,7 +1287,7 @@ static const struct pci_device_id vmd_ids[] = { + {PCI_VDEVICE(INTEL, 0xa77f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x7d0b), +- .driver_data = VMD_FEATS_CLIENT,}, ++ .driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,}, + {PCI_VDEVICE(INTEL, 0xad0b), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), diff --git a/pkgbuilds/linux-omarchy-eevdf/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig new file mode 100644 index 0000000..69bd67e Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch b/pkgbuilds/linux-omarchy-eevdf/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch new file mode 100644 index 0000000..49806cf --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -4370,6 +4370,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_JMICRON, + DECLARE_PCI_FIXUP_HEADER(0x1c28, /* Lite-On */ + 0x0122, /* Plextor M6E (Marvell 88SS9183)*/ + quirk_dma_func1_alias); ++/* Toshiba AHCI SSD controller in MacBookAir6,1 and MacBookAir7,1 */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA, 0x010b, ++ quirk_dma_func1_alias); + + /* + * Some devices DMA with the wrong devfn, not just the wrong function. diff --git a/pkgbuilds/linux-omarchy-eevdf/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig new file mode 100644 index 0000000..ab588ee Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0711-iommu-amd-perfopt-support.patch b/pkgbuilds/linux-omarchy-eevdf/0711-iommu-amd-perfopt-support.patch new file mode 100644 index 0000000..4b13fed --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0711-iommu-amd-perfopt-support.patch @@ -0,0 +1,413 @@ +diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h +--- a/drivers/iommu/amd/amd_iommu.h ++++ b/drivers/iommu/amd/amd_iommu.h +@@ -48,6 +48,9 @@ extern u8 amd_iommu_hpt_vasize; + extern unsigned long amd_iommu_pgsize_bitmap; + extern bool amd_iommu_hatdis; + ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu); ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu); ++ + /* Protection domain ops */ + void amd_iommu_init_identity_domain(void); + struct protection_domain *protection_domain_alloc(void); +diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h +--- a/drivers/iommu/amd/amd_iommu_types.h ++++ b/drivers/iommu/amd/amd_iommu_types.h +@@ -65,6 +65,7 @@ + #define MMIO_MSI_ADDR_LO_OFFSET 0x015C + #define MMIO_MSI_ADDR_HI_OFFSET 0x0160 + #define MMIO_MSI_DATA_OFFSET 0x0164 ++#define MMIO_PERF_OPT_OFFSET 0x016C + #define MMIO_INTCAPXT_EVT_OFFSET 0x0170 + #define MMIO_INTCAPXT_PPR_OFFSET 0x0178 + #define MMIO_INTCAPXT_GALOG_OFFSET 0x0180 +@@ -99,6 +100,8 @@ + #define FEATURE_GLX GENMASK_ULL(15, 14) + #define FEATURE_GAM_VAPIC BIT_ULL(21) + #define FEATURE_PASMAX GENMASK_ULL(36, 32) ++#define FEATURE_PERF_OPT BIT_ULL(45) ++#define PERF_OPT_EN BIT(13) + #define FEATURE_GIOSUP BIT_ULL(48) + #define FEATURE_HASUP BIT_ULL(49) + #define FEATURE_EPHSUP BIT_ULL(50) +@@ -663,6 +666,9 @@ struct amd_iommu { + /* Extended features 2 */ + u64 features2; + ++ /* Devices requesting PerfOpt; the shared PERF_OPT_EN bit is on while >0. Protected by @lock. */ ++ int perfopt_refcount; ++ + /* PCI device id of the IOMMU device */ + u16 devid; + +@@ -824,6 +830,7 @@ struct iommu_dev_data { + u8 ppr :1; /* Enable device PPR support */ + bool use_vapic; /* Enable device to use vapic mode */ + bool defer_attach; ++ bool perfopt; + + struct ratelimit_state rs; /* Ratelimit IOPF messages */ + }; +diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c +--- a/drivers/iommu/amd/init.c ++++ b/drivers/iommu/amd/init.c +@@ -1945,6 +1945,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h, + if (!iommu->mmio_base) + return -ENOMEM; + ++ if (amd_iommu_perfopt_clear(iommu)) ++ pr_err("IOMMU%d: failed to clear PerfOpt\n", iommu->index); ++ + return init_iommu_from_acpi(iommu, h); + } + +@@ -3035,10 +3038,46 @@ static void enable_iommus_vapic(void) + #endif + } + ++static int clear_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_clear(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ ++static int restore_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_restore(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ + static void disable_iommus(void) + { + struct amd_iommu *iommu; + ++ /* ++ * PerfOpt is an optional performance bit, so a failure to clear it must ++ * not skip the mandatory disable below. This also runs from the void ++ * amd_iommu_disable() shutdown/kexec path, which cannot report an error. ++ */ ++ if (clear_perfopt_all()) ++ pr_err("Failed to clear PerfOpt while disabling IOMMUs\n"); ++ + for_each_iommu(iommu) + iommu_disable(iommu); + +@@ -3064,6 +3103,10 @@ static void amd_iommu_resume(void *data) + for_each_iommu(iommu) + early_enable_iommu(iommu); + ++ /* early_enable_iommu() cleared PERF_OPT_EN; re-assert it from the refcount. */ ++ if (restore_perfopt_all()) ++ pr_err("Failed to restore PerfOpt after IOMMU resume\n"); ++ + iommu_enable_event_buffer(); + amd_iommu_enable_interrupts(); + } +diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c +--- a/drivers/iommu/amd/iommu.c ++++ b/drivers/iommu/amd/iommu.c +@@ -2352,6 +2352,9 @@ static int attach_device(struct device *dev, + if (ret) + goto out; + ++ if (dev_data->perfopt) ++ goto skip_caps; ++ + /* Setup GCR3 table */ + if (pdom_is_sva_capable(domain)) { + ret = init_gcr3_table(dev_data, domain); +@@ -2376,6 +2379,7 @@ static int attach_device(struct device *dev, + pdev_enable_cap_ats(pdev); + } + ++skip_caps: + /* Update data structures */ + dev_data->domain = domain; + spin_lock_irqsave(&domain->lock, flags); +@@ -2444,6 +2448,192 @@ static void detach_device(struct device *dev) + mutex_unlock(&dev_data->mutex); + } + ++/* Program the per-IOMMU PerfOpt enable bit. Caller must hold iommu->lock. */ ++static int __perfopt_write(struct amd_iommu *iommu, bool enable) ++{ ++ u32 old, val, readback; ++ ++ if (!(readq(iommu->mmio_base + MMIO_EXT_FEATURES) & FEATURE_PERF_OPT)) ++ return enable ? -ENODEV : 0; ++ ++ old = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (old == U32_MAX) ++ return -EIO; ++ ++ val = enable ? old | PERF_OPT_EN : old & ~PERF_OPT_EN; ++ if (val != old) ++ writel(val, iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ readback = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (readback == U32_MAX || ++ (readback & PERF_OPT_EN) != (val & PERF_OPT_EN)) ++ return -EIO; ++ return 0; ++} ++ ++/* ++ * PERF_OPT_EN is a single bit shared by every device behind @iommu, so it is ++ * reference counted: armed on the first requesting device, cleared on the last. ++ */ ++static int perfopt_get(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount == 0) { ++ ret = __perfopt_write(iommu, true); ++ if (ret) ++ goto out; ++ } ++ iommu->perfopt_refcount++; ++out: ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++static int perfopt_put(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount > 0 && --iommu->perfopt_refcount == 0) ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Force PERF_OPT_EN off without touching the refcount (used on init, shutdown, ++ * and suspend). The count is preserved so amd_iommu_perfopt_restore() can ++ * re-arm on resume. ++ */ ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Re-assert PERF_OPT_EN from the refcount after the hardware was reprogrammed on ++ * resume, so devices armed before suspend keep the optimization without each ++ * consumer driver re-arming. ++ */ ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, iommu->perfopt_refcount > 0); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ int ret; ++ ++ if (!iommu || !dev_data) ++ return -ENODEV; ++ ++ if (!(iommu->features & FEATURE_PERF_OPT)) ++ return -ENODEV; ++ ++ domain = dev_data->domain; ++ if (!domain) ++ return -ENODEV; ++ ++ /* Already armed for this device (e.g. re-entry on resume). */ ++ if (dev_data->perfopt) ++ return 0; ++ ++ /* ++ * The bit is only architecturally valid while the device is untranslated: ++ * identity domain with ATS/PRI/PASID off. The identity domain is ++ * SVA-capable so attach_device() enabled ATS/PRI/PASID and built a GCR3 ++ * table. Re-home the device onto the same identity domain with ++ * perfopt set, so the attach_device() skip_caps path leaves ++ * ATS/PRI/PASID off and no GCR3 table. This follows the detach/attach ++ * pattern used by amd_iommu_attach_device(). ++ * ++ * Locking: this and amd_iommu_disable_perfopt() run only from the ++ * consumer driver's bind/unbind path. group->mutex is not exposed to ++ * drivers, but a device bound to its native driver cannot have its domain ++ * changed concurrently by the core (VFIO ownership is mutually exclusive; ++ * sysfs domain changes require an unused group), so the detach/attach pair ++ * is serialized without it. ++ */ ++ dev_data->perfopt = true; ++ detach_device(&pdev->dev); ++ ret = attach_device(&pdev->dev, domain); ++ if (ret) ++ goto err_restore; ++ ++ ret = perfopt_get(iommu); ++ if (ret) ++ goto err_rearm; ++ ++ dev_info_once(&pdev->dev, "PerfOpt armed on IOMMU%d\n", iommu->index); ++ return 0; ++ ++err_rearm: ++ detach_device(&pdev->dev); ++err_restore: ++ dev_data->perfopt = false; ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore state after PerfOpt setup; device left detached\n"); ++ dev_err_once(&pdev->dev, "PerfOpt failed to arm on IOMMU%d (%d)\n", ++ iommu->index, ret); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(amd_iommu_enable_perfopt); ++ ++void amd_iommu_disable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ ++ if (!iommu || !dev_data || !dev_data->perfopt || !dev_data->domain) ++ return; ++ ++ if (WARN_ON(perfopt_put(iommu))) ++ pci_err(pdev, "failed to clear PerfOpt\n"); ++ ++ /* ++ * Restore ATS/PRI/PASID (and thus SVA) by re-homing the device onto its ++ * identity domain with the flag cleared, so a later bind without PerfOpt ++ * sees a normally-capable device. See the locking note in ++ * amd_iommu_enable_perfopt(). ++ */ ++ domain = dev_data->domain; ++ dev_data->perfopt = false; ++ detach_device(&pdev->dev); ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore caps after PerfOpt disable\n"); ++} ++EXPORT_SYMBOL_GPL(amd_iommu_disable_perfopt); + static struct iommu_device *amd_iommu_probe_device(struct device *dev) + { + struct iommu_device *iommu_dev; +@@ -2511,6 +2701,14 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) + static void amd_iommu_release_device(struct device *dev) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt on release\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + WARN_ON(dev_data->domain); + +@@ -2885,6 +3083,19 @@ static int blocked_domain_attach_device(struct iommu_domain *domain, + struct iommu_domain *old) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ /* ++ * blocked_domain is also the .release_domain, so this is the normal ++ * teardown path: drop the reference and clear the flag here too, and ++ * don't fail teardown if the WARN-guarded write doesn't stick. ++ */ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt for blocked domain\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + if (dev_data->domain) + detach_device(dev); +@@ -2953,6 +3164,9 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev, + struct amd_iommu *iommu = get_amd_iommu_from_dev(dev); + int ret; + ++ if (dev_data->perfopt && !pdom_is_in_pt_mode(domain)) ++ return -EBUSY; ++ + /* + * Skip attach device to domain if new domain is same as + * devices current domain +diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h +--- a/include/linux/amd-iommu.h ++++ b/include/linux/amd-iommu.h +@@ -11,11 +11,11 @@ + #include + + struct amd_iommu; ++struct pci_dev; + + #ifdef CONFIG_AMD_IOMMU + + struct task_struct; +-struct pci_dev; + + extern void amd_iommu_detect(void); + +@@ -76,4 +76,15 @@ static inline int amd_iommu_snp_disable(void) { return 0; } + static inline bool amd_iommu_sev_tio_supported(void) { return false; } + #endif + ++#ifdef CONFIG_AMD_IOMMU ++int amd_iommu_enable_perfopt(struct pci_dev *pdev); ++void amd_iommu_disable_perfopt(struct pci_dev *pdev); ++#else ++static inline int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ return 0; ++} ++static inline void amd_iommu_disable_perfopt(struct pci_dev *pdev) { } ++#endif ++ + #endif /* _ASM_X86_AMD_IOMMU_H */ diff --git a/pkgbuilds/linux-omarchy-eevdf/0711-iommu-amd-perfopt-support.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0711-iommu-amd-perfopt-support.patch.sig new file mode 100644 index 0000000..cd3768f Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0711-iommu-amd-perfopt-support.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0712-drm-amdgpu-perfopt-in-identity-domain.patch b/pkgbuilds/linux-omarchy-eevdf/0712-drm-amdgpu-perfopt-in-identity-domain.patch new file mode 100644 index 0000000..5c74198 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0712-drm-amdgpu-perfopt-in-identity-domain.patch @@ -0,0 +1,128 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h +@@ -160,6 +160,7 @@ struct amdgpu_watchdog_timer { + * Modules parameters. + */ + extern int amdgpu_modeset; ++extern int amdgpu_iommu_perfopt; + extern unsigned int amdgpu_vram_limit; + extern int amdgpu_vis_vram_limit; + extern int amdgpu_gart_size; +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -3842,6 +3843,28 @@ amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px) + apple_gmux_detect(NULL, NULL))); + } + ++static inline bool amdgpu_device_identity(struct amdgpu_device *adev) ++{ ++ struct pci_dev *pdev = adev->pdev; ++ struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev); ++ ++ if (!domain) ++ return false; ++ ++ return domain->type == IOMMU_DOMAIN_IDENTITY; ++} ++ ++static bool amdgpu_device_use_perfopt(struct amdgpu_device *adev) ++{ ++ if (amdgpu_iommu_perfopt == 0) ++ return false; ++ ++ if (!(adev->flags & AMD_IS_APU)) ++ return false; ++ ++ return amdgpu_device_identity(adev); ++} ++ + /** + * amdgpu_device_init - initialize the driver + * +@@ -4048,6 +4071,16 @@ int amdgpu_device_init(struct amdgpu_device *adev, + if (r) + return r; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(pdev); ++ ++ /* Optional optimization; a failure to arm it must not abort probe. */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + /* + * No need to remove conflicting FBs for non-display class devices. + * This prevents the sysfb from being freed accidently. +@@ -4416,6 +4449,9 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) + + amdgpu_gart_dummy_page_fini(adev); + ++ if (amdgpu_device_use_perfopt(adev)) ++ amd_iommu_disable_perfopt(adev->pdev); ++ + if (pci_dev_is_disconnected(adev->pdev)) + amdgpu_device_unmap_mmio(adev); + +@@ -4779,6 +4815,20 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients) + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(adev->pdev); ++ ++ /* ++ * Must not return on failure: a bare return would leak the ++ * SR-IOV VF exclusive-mode acquisition taken above (released ++ * via the exit: path). ++ */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + if (adev->in_s0ix) + amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D0Entry); + +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -185,6 +185,7 @@ char *amdgpu_disable_cu; + char *amdgpu_virtual_display; + int amdgpu_enforce_isolation = -1; + int amdgpu_modeset = -1; ++int amdgpu_iommu_perfopt = -1; + + /* Specifies the default granularity for SVM, used in buffer + * migration and restoration of backing memory when handling +@@ -393,6 +394,17 @@ module_param_named(fw_load_type, amdgpu_fw_load_type, int, 0444); + MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)"); + module_param_named(aspm, amdgpu_aspm, int, 0444); + ++/** ++ * DOC: iommu_perfopt (int) ++ * Control the AMD IOMMU PerfOpt DMA-latency optimization ++ * (0 = disable; -1 = enable on supported devices). ++ * This arms the IOMMU PerfOpt control (IOMMU spec, MMIO Offset 016Ch, EFR PerfOptSup / PerfOptEn) ++ * Arming it disables ATS, PRI, PASID and SVA for the GPU and removes IOMMU DMA containment for it, ++ * trading isolation for lower DMA latency. ++ */ ++MODULE_PARM_DESC(iommu_perfopt, "Control IOMMU PerfOpt DMA-latency optimization (-1 = enable on supported devices, 0 = disable)"); ++module_param_named(iommu_perfopt, amdgpu_iommu_perfopt, int, 0444); ++ + /** + * DOC: runpm (int) + * Override for runtime power management control for dGPUs. The amdgpu driver can dynamically power down diff --git a/pkgbuilds/linux-omarchy-eevdf/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig new file mode 100644 index 0000000..59f21e5 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0740-hwmon-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0740-hwmon-updates.patch new file mode 100644 index 0000000..a66a783 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0740-hwmon-updates.patch @@ -0,0 +1,7419 @@ +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -224,6 +224,7 @@ config SENSORS_ADT7462 + config SENSORS_ADT7470 + tristate "Analog Devices ADT7470" + depends on I2C ++ depends on PWM || PWM=n + select REGMAP_I2C + help + If you say yes here you get support for the Analog Devices +@@ -292,11 +293,11 @@ config SENSORS_ASC7621 + will be called asc7621. + + config SENSORS_ASUS_ROG_RYUJIN +- tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver" ++ tristate "ASUS ROG RYUJIN hardware monitoring driver" + depends on HID + help + If you say yes here you get support for the fans and sensors of +- the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler. ++ supported ASUS ROG RYUJIN II and III AIO CPU liquid coolers. + + This driver can also be built as a module. If so, the module + will be called asus_rog_ryujin. +@@ -335,6 +336,17 @@ config SENSORS_K10TEMP + This driver can also be built as a module. If so, the module + will be called k10temp. + ++config SENSORS_KB9002 ++ tristate "Kandou KB9002 PCIe retimer" ++ depends on I2C ++ help ++ If you say yes here you get support for the integrated ++ temperature sensor and firmware version readout on Kandou ++ KB9002 PCIe 5.0 retimers, accessed over SMBus. ++ ++ This driver can also be built as a module. If so, the module ++ will be called kb9002. ++ + config SENSORS_KBATT + tristate "KEBA battery controller support" + depends on KEBA_CP500 +@@ -403,7 +415,6 @@ config SENSORS_ARCTIC_FAN_CONTROLLER + config SENSORS_ARM_SCMI + tristate "ARM SCMI Sensors" + depends on ARM_SCMI_PROTOCOL +- depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on SCMI based platforms. The actual +@@ -415,7 +426,6 @@ config SENSORS_ARM_SCMI + config SENSORS_ARM_SCPI + tristate "ARM SCPI Sensors" + depends on ARM_SCPI_PROTOCOL +- depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on ARM Ltd's SCP based platforms. The +@@ -435,7 +445,6 @@ config SENSORS_ASB100 + config SENSORS_ASPEED + tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver" + depends on ARCH_ASPEED || COMPILE_TEST +- depends on THERMAL || THERMAL=n + select REGMAP + help + This driver provides support for ASPEED AST2400/AST2500 PWM +@@ -506,8 +515,8 @@ config SENSORS_CORSAIR_PSU + If you say yes here you get support for Corsair PSUs with a HID + interface. + Currently this driver supports the (RM/HX)550i, (RM/HX)650i, +- (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies +- by Corsair. ++ (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i, HX1200i and HX1500i power ++ supplies by Corsair. + + This driver can also be built as a module. If so, the module + will be called corsair-psu. +@@ -788,7 +797,6 @@ config SENSORS_G762 + config SENSORS_GPIO_FAN + tristate "GPIO fan" + depends on GPIOLIB || COMPILE_TEST +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to GPIO lines. + +@@ -802,7 +810,7 @@ config SENSORS_GXP_FAN_CTRL + If you say yes here you get support for GXP fan control functionality. + + The GXP controls fan function via the CPLD through the use of PWM +- registers. This driver reports status and pwm setting of the fans. ++ registers. This driver enables pwm setting of the fans. + + config SENSORS_HIH6130 + tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor" +@@ -942,6 +950,19 @@ config SENSORS_JC42 + This driver can also be built as a module. If so, the module + will be called jc42. + ++config SENSORS_POLARFIRE_SOC_TVS ++ tristate "PolarFire SoC (MPFS) temperature and voltage sensor" ++ depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST ++ depends on MFD_SYSCON ++ help ++ This driver adds support for the PolarFire SoC (MPFS) Temperature and ++ Voltage Sensor. ++ ++ To compile this driver as a module, choose M here. the ++ module will be called tvs-mpfs. ++ ++ If unsure, say N. ++ + config SENSORS_POWERZ + tristate "ChargerLAB POWER-Z USB-C tester" + depends on USB +@@ -1357,7 +1378,6 @@ config SENSORS_MAX6639 + config SENSORS_MAX6650 + tristate "Maxim MAX6650 sensor chip" + depends on I2C +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for the MAX6650 / MAX6651 + sensor chips. +@@ -1732,7 +1752,6 @@ config SENSORS_PC87427 + config SENSORS_NTC_THERMISTOR + tristate "NTC thermistor support" + depends on IIO +- depends on THERMAL || !THERMAL_OF + help + This driver supports NTC thermistors sensor reading and its + interpretation. The driver can also monitor the temperature and +@@ -1933,7 +1952,6 @@ config SENSORS_PT5161L + config SENSORS_PWM_FAN + tristate "PWM fan" + depends on PWM || COMPILE_TEST +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to PWM lines. + The driver uses the generic PWM interface, thus it will work on a +@@ -1945,7 +1963,6 @@ config SENSORS_PWM_FAN + config SENSORS_QNAP_MCU_HWMON + tristate "QNAP MCU hardware monitoring" + depends on MFD_QNAP_MCU +- depends on THERMAL || THERMAL=n + help + Say yes here to enable support for fan and temperature sensor + connected to a QNAP MCU, as found in a number of QNAP network +@@ -2010,8 +2027,9 @@ config SENSORS_SHT3x + depends on I2C + select CRC8 + help +- If you say yes here you get support for the Sensiron SHT30, SHT31 and +- SHT85 humidity and temperature sensors. ++ If you say yes here you get support for the Sensiron SHT30, SHT31, ++ SHT85 and the compatible GXCAS GXHT30 humidity and temperature ++ sensors. + + This driver can also be built as a module. If so, the module + will be called sht3x. +@@ -2070,6 +2088,17 @@ config SENSORS_DME1737 + This driver can also be built as a module. If so, the module + will be called dme1737. + ++config SENSORS_EIC7700_PVT ++ tristate "Eswin EIC7700 Voltage, Temperature sensor driver" ++ depends on ARCH_ESWIN || COMPILE_TEST ++ select POLYNOMIAL ++ help ++ If you say yes here you get support for Eswin EIC7700 PVT sensor ++ embedded into the SoC. ++ ++ This driver can also be built as a module. If so, the module will be ++ called eic7700-pvt. ++ + config SENSORS_EMC1403 + tristate "SMSC EMC1403/23 thermal sensor" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737) += dme1737.o + obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o + obj-$(CONFIG_SENSORS_DS620) += ds620.o + obj-$(CONFIG_SENSORS_DS1621) += ds1621.o ++obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o + obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o + obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o + obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o +@@ -112,6 +113,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o + obj-$(CONFIG_SENSORS_JC42) += jc42.o + obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o + obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o ++obj-$(CONFIG_SENSORS_KB9002) += kb9002.o + obj-$(CONFIG_SENSORS_KBATT) += kbatt.o + obj-$(CONFIG_SENSORS_KFAN) += kfan.o + obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o +@@ -197,6 +199,7 @@ obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o + obj-$(CONFIG_SENSORS_PC87360) += pc87360.o + obj-$(CONFIG_SENSORS_PC87427) += pc87427.o + obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o ++obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o + obj-$(CONFIG_SENSORS_POWERZ) += powerz.o + obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o + obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o +diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c +--- a/drivers/hwmon/acpi_power_meter.c ++++ b/drivers/hwmon/acpi_power_meter.c +@@ -21,8 +21,6 @@ + #include + + #define ACPI_POWER_METER_NAME "power_meter" +-#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" +-#define ACPI_POWER_METER_CLASS "pwr_meter_resource" + + #define NUM_SENSORS 17 + +@@ -877,7 +875,7 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, ++ acpi_bus_generate_netlink_event("pwr_meter_resource", + dev_name(&resource->acpi_dev->dev), + event, 0); + } +@@ -899,8 +897,6 @@ static int acpi_power_meter_probe(struct platform_device *pdev) + resource->sensors_valid = 0; + resource->acpi_dev = device; + mutex_init(&resource->lock); +- strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); + + platform_set_drvdata(pdev, resource); + +diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c +--- a/drivers/hwmon/adt7470.c ++++ b/drivers/hwmon/adt7470.c +@@ -22,6 +22,9 @@ + #include + #include + #include ++#include ++#include ++#include + + /* Addresses to scan */ + static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; +@@ -100,6 +103,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; + #define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2)) + + #define ADT7470_PWM_COUNT 4 ++#define ADT7470_PWM_MAX 255 + #define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x)) +@@ -163,6 +167,7 @@ struct adt7470_data { + char limits_valid; + unsigned long sensors_last_updated; /* In jiffies */ + unsigned long limits_last_updated; /* In jiffies */ ++ bool use_pwm_framework; + + int num_temp_sensors; /* -1 = probe */ + int temperatures_probed; +@@ -850,7 +855,7 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + + switch (attr) { + case hwmon_pwm_input: +- val = clamp_val(val, 0, 255); ++ val = clamp_val(val, 0, ADT7470_PWM_MAX); + mutex_lock(&data->lock); + err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), + val); +@@ -886,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + return err; + } + ++struct adt7470_pwm_wfhw { ++ u8 val; ++}; ++ ++static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const struct pwm_waveform *wf, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ u64 actual_period; ++ ++ if (wf->duty_length_ns == 0) { ++ wfhw->val = 0; ++ return 0; ++ } ++ ++ /* ++ * The PWM frequency (period) is a single chip-wide setting shared by ++ * all 4 channels, so it cannot be changed on a per-pwm_device basis ++ * through this API. The duty cycle is rounded against the currently ++ * configured hardware period rather than the period requested in ++ * @wf; round_waveform_fromhw() reports the actual resulting ++ * waveform back so the core/consumer can detect a mismatch. ++ */ ++ actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ ++ if (actual_period > wf->period_length_ns) ++ /* period too short */ ++ return 1; ++ ++ if (wf->duty_length_ns >= actual_period) { ++ wfhw->val = ADT7470_PWM_MAX; ++ } else { ++ wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns, ++ ADT7470_PWM_MAX, ++ actual_period); ++ } ++ ++ return 0; ++} ++ ++static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw, ++ struct pwm_waveform *wf) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ wf->duty_offset_ns = 0; ++ wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns, ++ ADT7470_PWM_MAX); ++ return 0; ++} ++ ++static int adt7470_pwm_read_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct device *dev = regmap_get_device(data->regmap); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ data = adt7470_update_device(dev); ++ if (IS_ERR(data)) ++ return PTR_ERR(data); ++ ++ /* ++ * No lock needed: like the other hwmon_ops read callbacks in this ++ * driver (e.g. adt7470_pwm_read()), this only does a single byte ++ * read from the cache populated by adt7470_update_device(). ++ */ ++ wfhw->val = data->pwm[pwm->hwpwm]; ++ ++ return 0; ++} ++ ++static int adt7470_pwm_write_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ unsigned int pwm_auto_reg_mask; ++ int err; ++ ++ if (pwm->hwpwm % 2) ++ pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; ++ else ++ pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; ++ ++ guard(mutex)(&data->lock); ++ ++ if (data->pwm[pwm->hwpwm] == wfhw->val && ++ data->pwm_automatic[pwm->hwpwm] == 0) ++ return 0; ++ ++ /* Put the PWM channel in manual mode before updating it. */ ++ err = regmap_update_bits(data->regmap, ++ ADT7470_REG_PWM_CFG(pwm->hwpwm), ++ pwm_auto_reg_mask, 0); ++ if (err < 0) ++ return err; ++ ++ data->pwm_automatic[pwm->hwpwm] = 0; ++ ++ err = regmap_write(data->regmap, ++ ADT7470_REG_PWM(pwm->hwpwm), wfhw->val); ++ if (err < 0) ++ return err; ++ ++ data->pwm[pwm->hwpwm] = wfhw->val; ++ ++ return 0; ++} ++ ++static const struct pwm_ops adt7470_pwm_ops = { ++ .sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw), ++ .round_waveform_tohw = adt7470_pwm_round_waveform_tohw, ++ .round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw, ++ .read_waveform = adt7470_pwm_read_waveform, ++ .write_waveform = adt7470_pwm_write_waveform, ++}; ++ + static ssize_t pwm_max_show(struct device *dev, + struct device_attribute *devattr, char *buf) + { +@@ -910,7 +1042,7 @@ static ssize_t pwm_max_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_max[attr->index] = temp; +@@ -945,7 +1077,7 @@ static ssize_t pwm_min_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_min[attr->index] = temp; +@@ -1106,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = { + &dev_attr_alarm_mask.attr, + &dev_attr_num_temp_sensors.attr, + &dev_attr_auto_update_interval.attr, ++ NULL ++}; ++ ++static struct attribute *adt7470_pwm_attrs[] = { + &sensor_dev_attr_force_pwm_max.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, +@@ -1130,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = { + NULL + }; + +-ATTRIBUTE_GROUPS(adt7470); ++static const struct attribute_group adt7470_group = { ++ .attrs = adt7470_attrs, ++}; ++ ++static umode_t adt7470_pwm_is_visible(struct kobject *kobj, ++ struct attribute *attr, int index) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct adt7470_data *data = dev_get_drvdata(dev); ++ ++ if (data->use_pwm_framework) ++ return 0; ++ ++ return attr->mode; ++} ++ ++static const struct attribute_group adt7470_pwm_group = { ++ .attrs = adt7470_pwm_attrs, ++ .is_visible = adt7470_pwm_is_visible, ++}; ++ ++static const struct attribute_group *adt7470_groups[] = { ++ &adt7470_group, ++ &adt7470_pwm_group, ++ NULL, ++}; + + static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +@@ -1165,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a + static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) + { ++ const struct adt7470_data *data = _data; + umode_t mode = 0; + + switch (type) { +@@ -1197,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ + } + break; + case hwmon_pwm: ++ /* Hide all pwm attributes if this device is exposed to the PWM ++ * framework ++ */ ++ if (data->use_pwm_framework) { ++ mode = 0; ++ break; ++ } ++ + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: +@@ -1226,6 +1396,8 @@ static const struct hwmon_ops adt7470_hwmon_ops = { + }; + + static const struct hwmon_channel_info * const adt7470_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, +@@ -1327,6 +1499,29 @@ static int adt7470_probe(struct i2c_client *client) + + data->pwm_freq = (u32)freq_val; + ++ data->use_pwm_framework = false; ++ ++ if (device_property_present(dev, "#pwm-cells")) { ++ if (IS_REACHABLE(CONFIG_PWM)) { ++ struct pwm_chip *chip; ++ ++ chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0); ++ if (IS_ERR(chip)) ++ return PTR_ERR(chip); ++ ++ chip->ops = &adt7470_pwm_ops; ++ pwmchip_set_drvdata(chip, data); ++ ++ err = devm_pwmchip_add(dev, chip); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register PWM chip\n"); ++ ++ data->use_pwm_framework = true; ++ } else { ++ dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n"); ++ } ++ } ++ + /* Register sysfs hooks */ + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &adt7470_chip_info, +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + /* data port used by Apple SMC */ + #define APPLESMC_DATA_PORT 0x300 +@@ -133,6 +134,7 @@ static struct applesmc_registers { + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ ++ char fan_positions[10][17]; /* cached fan position labels */ + } smcreg = { + .mutex = __MUTEX_INITIALIZER(smcreg.mutex), + }; +@@ -143,8 +145,8 @@ static s16 rest_x; + static s16 rest_y; + static u8 backlight_state[2]; + +-static struct device *hwmon_dev; + static struct input_dev *applesmc_idev; ++static struct device *hwmon_dev; + + /* + * Last index written to key_at_index sysfs file, and value to use for all other +@@ -372,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + __be32 be; + int ret = 0; + +- if (cache->valid) ++ /* Pairs with smp_store_release() to ensure cache contents are visible */ ++ if (smp_load_acquire(&cache->valid)) + return cache; + + mutex_lock(&smcreg.mutex); +@@ -391,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + cache->len = info[0]; + memcpy(cache->type, &info[1], 4); + cache->flags = info[5]; +- cache->valid = true; ++ /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ ++ smp_store_release(&cache->valid, true); + + out: + mutex_unlock(&smcreg.mutex); +@@ -566,7 +570,7 @@ static int applesmc_init_smcreg_try(void) + { + struct applesmc_registers *s = &smcreg; + bool left_light_sensor = false, right_light_sensor = false; +- unsigned int count; ++ unsigned int count, i; + u8 tmp[1]; + int ret; + +@@ -597,6 +601,16 @@ static int applesmc_init_smcreg_try(void) + if (s->fan_count > 10) + s->fan_count = 10; + ++ for (i = 0; i < s->fan_count; i++) { ++ char newkey[5]; ++ ++ scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); ++ ret = applesmc_read_key(newkey, s->fan_positions[i], 16); ++ s->fan_positions[i][16] = 0; ++ if (ret) ++ scnprintf(s->fan_positions[i], 17, " Fan %d", i); ++ } ++ + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); + if (ret) + return ret; +@@ -808,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, + return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); + } + +-/* Displays sensor key as label */ +-static ssize_t applesmc_show_sensor_label(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- +- return sysfs_emit(sysfsbuf, "%s\n", key); +-} +- +-/* Displays degree Celsius * 1000 */ +-static ssize_t applesmc_show_temperature(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- int ret; +- s16 value; +- int temp; +- +- ret = applesmc_read_s16(key, &value); +- if (ret) +- return ret; +- +- temp = 250 * (value >> 6); +- +- return sysfs_emit(sysfsbuf, "%d\n", temp); +-} +- + static ssize_t applesmc_show_fan_speed(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -854,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, + return sysfs_emit(sysfsbuf, "%u\n", speed); + } + +-static ssize_t applesmc_store_fan_speed(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- unsigned long speed; +- char newkey[5]; +- u8 buffer[2]; +- +- if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) +- return -EINVAL; /* Bigger than a 14-bit value */ +- +- scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], +- to_index(attr)); +- +- buffer[0] = (speed >> 6) & 0xff; +- buffer[1] = (speed << 2) & 0xff; +- ret = applesmc_write_key(newkey, buffer, 2); +- +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_manual(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- u16 manual = 0; +- u8 buffer[2]; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- return ret; +- +- manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; +- return sysfs_emit(sysfsbuf, "%d\n", manual); +-} +- +-static ssize_t applesmc_store_fan_manual(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- u8 buffer[2]; +- unsigned long input; +- u16 val; +- +- if (kstrtoul(sysfsbuf, 10, &input) < 0) +- return -EINVAL; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- goto out; +- +- val = (buffer[0] << 8 | buffer[1]); +- +- if (input) +- val = val | (0x01 << to_index(attr)); +- else +- val = val & ~(0x01 << to_index(attr)); +- +- buffer[0] = (val >> 8) & 0xFF; +- buffer[1] = val & 0xFF; +- +- ret = applesmc_write_key(FANS_MANUAL, buffer, 2); +- +-out: +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_position(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- char newkey[5]; +- u8 buffer[17]; +- +- scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); +- +- ret = applesmc_read_key(newkey, buffer, 16); +- buffer[16] = 0; +- +- if (ret) +- return ret; +- +- return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); +-} +- + static ssize_t applesmc_calibrate_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -1094,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { + { } + }; + +-static struct applesmc_node_group fan_group[] = { +- { "fan%d_label", applesmc_show_fan_position }, +- { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, +- { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, +- { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, +- { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, +- { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, +- { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, +- { } +-}; + +-static struct applesmc_node_group temp_group[] = { +- { "temp%d_label", applesmc_show_sensor_label }, +- { "temp%d_input", applesmc_show_temperature }, +- { } +-}; + + /* Module stuff */ + +@@ -1312,8 +1191,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { + }; + MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); + ++static struct applesmc_dev_attr *fan_safe_attrs; ++static struct attribute **fan_safe_attr_list; ++static struct attribute_group fan_safe_group; ++static const struct attribute_group *applesmc_extra_groups[2]; ++ ++static u32 *applesmc_temp_config; ++static u32 *applesmc_fan_config; ++static u32 *applesmc_pwm_config; ++static struct hwmon_channel_info *applesmc_info_temp; ++static struct hwmon_channel_info *applesmc_info_fan; ++static struct hwmon_channel_info *applesmc_info_pwm; ++static const struct hwmon_channel_info **applesmc_info_arr; ++static struct hwmon_chip_info *applesmc_chip; ++ ++static void applesmc_free_hwmon(void) ++{ ++ kfree(applesmc_temp_config); ++ kfree(applesmc_fan_config); ++ kfree(applesmc_pwm_config); ++ kfree(applesmc_info_temp); ++ kfree(applesmc_info_fan); ++ kfree(applesmc_info_pwm); ++ kfree(applesmc_info_arr); ++ kfree(applesmc_chip); ++ kfree(fan_safe_attrs); ++ kfree(fan_safe_attr_list); ++} ++ ++static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input || attr == hwmon_temp_label) ++ return 0444; ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: ++ case hwmon_fan_label: ++ case hwmon_fan_max: ++ return 0444; ++ case hwmon_fan_min: ++ case hwmon_fan_target: ++ return 0644; ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) ++ return 0644; ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input) { ++ const char *key = smcreg.index[channel]; ++ s16 value; ++ ++ ret = applesmc_read_s16(key, &value); ++ if (ret) ++ return ret; ++ *val = 250 * (value >> 6); ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dAc", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_min: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMn", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_max: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMx", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_target: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dTg", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ ++ ret = applesmc_read_key(FANS_MANUAL, buffer, 2); ++ if (ret) ++ return ret; ++ *val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_fan: ++ if (attr == hwmon_fan_min || attr == hwmon_fan_target) { ++ char key[5]; ++ u8 buffer[2]; ++ const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; ++ ++ if (val < 0 || val >= 0x4000) ++ return -EINVAL; ++ scnprintf(key, sizeof(key), fmt, channel); ++ buffer[0] = (val >> 6) & 0xff; ++ buffer[1] = (val << 2) & 0xff; ++ return applesmc_write_key(key, buffer, 2); ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ u16 manual_val; ++ const struct applesmc_entry *entry; ++ ++ if (val != 1 && val != 2) ++ return -EINVAL; ++ ++ entry = applesmc_get_entry_by_key(FANS_MANUAL); ++ if (IS_ERR(entry)) ++ return PTR_ERR(entry); ++ ++ mutex_lock(&smcreg.mutex); ++ ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); ++ if (ret) ++ goto out_unlock; ++ manual_val = (buffer[0] << 8 | buffer[1]); ++ if (val == 1) ++ manual_val |= (0x01 << channel); ++ else ++ manual_val &= ~(0x01 << channel); ++ buffer[0] = (manual_val >> 8) & 0xff; ++ buffer[1] = manual_val & 0xff; ++ ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); ++out_unlock: ++ mutex_unlock(&smcreg.mutex); ++ return ret; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = smcreg.index[channel]; ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ if (attr == hwmon_fan_label) { ++ *str = smcreg.fan_positions[channel] + 4; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops applesmc_hwmon_ops = { ++ .is_visible = applesmc_hwmon_is_visible, ++ .read = applesmc_hwmon_read, ++ .write = applesmc_hwmon_write, ++ .read_string = applesmc_hwmon_read_string, ++}; ++ + static int __init applesmc_init(void) + { ++ int i; + int ret; + + if (!dmi_check_system(applesmc_whitelist)) { +@@ -1348,17 +1457,97 @@ static int __init applesmc_init(void) + if (ret) + goto out_smcreg; + +- ret = applesmc_create_nodes(fan_group, smcreg.fan_count); +- if (ret) ++ /* allocate hwmon channel configs */ ++ applesmc_temp_config = kcalloc(smcreg.index_count + 1, ++ sizeof(*applesmc_temp_config), GFP_KERNEL); ++ applesmc_fan_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_fan_config), GFP_KERNEL); ++ applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_pwm_config), GFP_KERNEL); ++ if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { ++ ret = -ENOMEM; + goto out_info; ++ } + +- ret = applesmc_create_nodes(temp_group, smcreg.index_count); +- if (ret) +- goto out_fans; ++ for (i = 0; i < smcreg.index_count; i++) ++ applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; ++ applesmc_temp_config[smcreg.index_count] = 0; ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | ++ HWMON_F_MAX | HWMON_F_TARGET; ++ applesmc_pwm_config[i] = HWMON_PWM_ENABLE; ++ } ++ applesmc_fan_config[smcreg.fan_count] = 0; ++ applesmc_pwm_config[smcreg.fan_count] = 0; ++ ++ applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); ++ applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); ++ applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); ++ if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_temp->type = hwmon_temp; ++ applesmc_info_temp->config = applesmc_temp_config; ++ ++ applesmc_info_fan->type = hwmon_fan; ++ applesmc_info_fan->config = applesmc_fan_config; ++ ++ applesmc_info_pwm->type = hwmon_pwm; ++ applesmc_info_pwm->config = applesmc_pwm_config; ++ ++ applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ if (!applesmc_info_arr) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_arr[0] = applesmc_info_temp; ++ applesmc_info_arr[1] = applesmc_info_fan; ++ applesmc_info_arr[2] = applesmc_info_pwm; ++ applesmc_info_arr[3] = NULL; ++ ++ applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ if (!applesmc_chip) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_chip->ops = &applesmc_hwmon_ops; ++ applesmc_chip->info = applesmc_info_arr; ++ ++ /* Create non-standard fanX_safe attributes group */ ++ fan_safe_attrs = kcalloc(smcreg.fan_count, ++ sizeof(*fan_safe_attrs), GFP_KERNEL); ++ fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, ++ sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ if (!fan_safe_attrs || !fan_safe_attr_list) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ struct applesmc_dev_attr *node = &fan_safe_attrs[i]; ++ ++ scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); ++ node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ ++ node->sda.dev_attr.show = applesmc_show_fan_speed; ++ node->sda.dev_attr.store = NULL; ++ sysfs_attr_init(&node->sda.dev_attr.attr); ++ node->sda.dev_attr.attr.name = node->name; ++ node->sda.dev_attr.attr.mode = 0444; ++ fan_safe_attr_list[i] = &node->sda.dev_attr.attr; ++ } ++ fan_safe_attr_list[smcreg.fan_count] = NULL; ++ fan_safe_group.attrs = fan_safe_attr_list; ++ applesmc_extra_groups[0] = &fan_safe_group; ++ applesmc_extra_groups[1] = NULL; + + ret = applesmc_create_accelerometer(); + if (ret) +- goto out_temperature; ++ goto out_info; + + ret = applesmc_create_light_sensor(); + if (ret) +@@ -1368,7 +1557,8 @@ static int __init applesmc_init(void) + if (ret) + goto out_light_sysfs; + +- hwmon_dev = hwmon_device_register(&pdev->dev); ++ hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, ++ applesmc_chip, applesmc_extra_groups); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + goto out_light_ledclass; +@@ -1382,11 +1572,8 @@ static int __init applesmc_init(void) + applesmc_release_light_sensor(); + out_accelerometer: + applesmc_release_accelerometer(); +-out_temperature: +- applesmc_destroy_nodes(temp_group); +-out_fans: +- applesmc_destroy_nodes(fan_group); + out_info: ++ applesmc_free_hwmon(); + applesmc_destroy_nodes(info_group); + out_smcreg: + applesmc_destroy_smcreg(); +@@ -1407,13 +1594,12 @@ static void __exit applesmc_exit(void) + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); + applesmc_release_accelerometer(); +- applesmc_destroy_nodes(temp_group); +- applesmc_destroy_nodes(fan_group); + applesmc_destroy_nodes(info_group); + applesmc_destroy_smcreg(); + platform_device_unregister(pdev); + platform_driver_unregister(&applesmc_driver); + release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); ++ applesmc_free_hwmon(); + } + + module_init(applesmc_init); +diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c +--- a/drivers/hwmon/asus-ec-sensors.c ++++ b/drivers/hwmon/asus-ec-sensors.c +@@ -185,6 +185,20 @@ enum ec_sensors { + #define SENSOR_TEMP_SENSOR_EXTRA_2 BIT(ec_sensor_temp_sensor_extra_2) + #define SENSOR_TEMP_SENSOR_EXTRA_3 BIT(ec_sensor_temp_sensor_extra_3) + ++/* ++ * The values for temperature sensor readings without physical sensors connected. ++ * The value varies across generations and is seemingly defined by the EC chip ++ * used in the given board. ++ */ ++static const s32 temperature_blank_values[] = {-62, -60, -40}; ++ ++static const s32 environment_temp_sensors = ++ SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_ALT1 | ++ SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | ++ SENSOR_TEMP_WATER_BLOCK_IN | SENSOR_TEMP_WATER_BLOCK_OUT | ++ SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_SENSOR_EXTRA_1 | ++ SENSOR_TEMP_SENSOR_EXTRA_2 | SENSOR_TEMP_SENSOR_EXTRA_3; ++ + enum board_family { + family_unknown, + family_amd_400_series, +@@ -386,6 +400,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), ++ [ec_sensor_fan_cpu_opt] = ++ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe), + [ec_sensor_temp_water_in] = +@@ -487,6 +503,14 @@ static const struct ec_board_info board_info_crosshair_x670e_hero = { + .family = family_amd_600_series, + }; + ++static const struct ec_board_info board_info_crosshair_x870e_hero = { ++ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, ++ .family = family_amd_800_series, ++}; ++ + static const struct ec_board_info board_info_maximus_vi_hero = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | +@@ -527,6 +551,14 @@ static const struct ec_board_info board_info_maximus_z790_extreme = { + .family = family_intel_700_series, + }; + ++static const struct ec_board_info board_info_maximus_z790_hero = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, ++ .family = family_intel_700_series, ++}; ++ + static const struct ec_board_info board_info_prime_x470_pro = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | +@@ -590,6 +622,13 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { + .family = family_amd_800_series, + }; + ++static const struct ec_board_info board_info_pro_art_z690_creator_wifi = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, ++ .family = family_intel_600_series, ++}; ++ + static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = { + /* Board also has a nct6798 */ + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME | +@@ -743,7 +782,7 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { + + static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | +- SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +@@ -859,6 +898,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_pro_art_x670E_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI", + &board_info_pro_art_x870E_creator_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt Z690-CREATOR WIFI", ++ &board_info_pro_art_z690_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI", + &board_info_pro_ws_trx50_sage_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI A", +@@ -883,6 +924,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_crosshair_x670e_gene), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", + &board_info_crosshair_x670e_hero), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X870E HERO", ++ &board_info_crosshair_x870e_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO", + &board_info_maximus_xi_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", +@@ -893,6 +936,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_maximus_z690_formula), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME", + &board_info_maximus_z790_extreme), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 HERO", ++ &board_info_maximus_z790_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", + &board_info_strix_b550_e_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", +@@ -927,8 +972,12 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_strix_x870_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI", + &board_info_strix_x870e_e_gaming_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI7 R2", ++ &board_info_strix_x870e_e_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7", + &board_info_strix_x870e_h_gaming_wifi7), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING", ++ &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", + &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", +@@ -955,6 +1004,7 @@ static const struct dmi_system_id dmi_table[] = { + }; + + struct ec_sensor { ++ /* this is ec_sensors enum value */ + unsigned int info_index; + s32 cached_value; + }; +@@ -1047,6 +1097,12 @@ get_sensor_info(const struct ec_sensors_data *state, int index) + return state->sensors_info + state->sensors[index].info_index; + } + ++static enum ec_sensors ++get_ec_sensor_type(const struct ec_sensors_data *state, int index) ++{ ++ return state->sensors[index].info_index; ++} ++ + static int find_ec_sensor_index(const struct ec_sensors_data *ec, + enum hwmon_sensor_types type, int channel) + { +@@ -1290,6 +1346,17 @@ static int get_cached_value_or_update(const struct device *dev, + return 0; + } + ++static bool is_blank_temperature_value(s32 value) ++{ ++ size_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(temperature_blank_values); ++i) { ++ if (value == temperature_blank_values[i]) ++ return true; ++ } ++ return false; ++} ++ + /* + * Now follow the functions that implement the hwmon interface + */ +@@ -1297,6 +1364,8 @@ static int get_cached_value_or_update(const struct device *dev, + static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { ++ const struct ec_sensor_info *sensor_info; ++ enum ec_sensors ec_sensor; + int ret; + s32 value = 0; + +@@ -1308,12 +1377,19 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + } + + ret = get_cached_value_or_update(dev, sidx, state, &value); +- if (!ret) { +- *val = scale_sensor_value(value, +- get_sensor_info(state, sidx)->type); +- } ++ if (ret) ++ return ret; + +- return ret; ++ sensor_info = get_sensor_info(state, sidx); ++ if (sensor_info->type == hwmon_temp) { ++ ec_sensor = get_ec_sensor_type(state, sidx); ++ if ((environment_temp_sensors & BIT(ec_sensor)) && ++ is_blank_temperature_value(value)) ++ return -ENODATA; ++ } ++ *val = scale_sensor_value(value, sensor_info->type); ++ ++ return 0; + } + + static int asus_ec_hwmon_read_string(struct device *dev, +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. ++ * hwmon driver for Asus ROG Ryujin AIO coolers. + * + * Copyright 2024 Aleksa Savic + */ +@@ -17,6 +17,9 @@ + + #define USB_VENDOR_ID_ASUS_ROG 0x0b05 + #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ ++#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb ++#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade ++#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada + + struct rog_ryujin_device_info { + u8 temp_offset; +@@ -34,6 +37,14 @@ static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = { + .has_controller = true, + }; + ++static const struct rog_ryujin_device_info rog_ryujin_iii_info = { ++ .temp_offset = 5, ++ .pump_speed_offset = 7, ++ .fan_speed_offset = 10, ++ .duty_channel = 1, ++ .has_controller = false, ++}; ++ + #define STATUS_VALIDITY 1500 /* ms */ + #define MAX_REPORT_LENGTH 65 + +@@ -173,7 +184,6 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { +- unsigned long flags; + int ret; + + /* +@@ -181,9 +191,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ spin_lock_bh(&priv->status_report_request_lock); + reinit_completion(status_completion); +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); ++ spin_unlock_bh(&priv->status_report_request_lock); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -411,18 +421,15 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); +- unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + +- spin_lock_irqsave(&priv->status_report_request_lock, flags); +- + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- goto unlock; ++ return 0; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -436,7 +443,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- goto unlock; ++ return 0; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -448,7 +455,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- goto unlock; ++ return 0; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -467,7 +474,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- goto unlock; ++ return 0; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -477,7 +484,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- goto unlock; ++ return 0; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -496,7 +503,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- goto unlock; ++ return 0; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -505,8 +512,6 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + +-unlock: +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +@@ -596,6 +601,12 @@ static void rog_ryujin_remove(struct hid_device *hdev) + static const struct hid_device_id rog_ryujin_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO), + .driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EXTREME), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { } + }; + +@@ -625,4 +636,4 @@ module_exit(rog_ryujin_exit); + + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Aleksa Savic "); +-MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); ++MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin AIO coolers"); +diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c +--- a/drivers/hwmon/axi-fan-control.c ++++ b/drivers/hwmon/axi-fan-control.c +@@ -508,8 +508,7 @@ static int axi_fan_control_probe(struct platform_device *pdev) + IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + NULL, ctl); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "failed to request an irq\n"); ++ return ret; + + return 0; + } +diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c +--- a/drivers/hwmon/chipcap2.c ++++ b/drivers/hwmon/chipcap2.c +@@ -22,6 +22,8 @@ + #include + #include + #include ++#include ++#include + + #define CC2_START_CM 0xA0 + #define CC2_START_NOM 0x80 +@@ -83,6 +85,7 @@ struct cc2_data { + struct i2c_client *client; + struct regulator *regulator; + const char *name; ++ const char *label; + int irq_ready; + int irq_low; + int irq_high; +@@ -444,6 +447,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_humidity_input: + return 0444; ++ case hwmon_humidity_label: ++ return cc2->label ? 0444 : 0; + case hwmon_humidity_min_alarm: + return cc2->rh_alarm.low_alarm_visible ? 0444 : 0; + case hwmon_humidity_max_alarm: +@@ -461,6 +466,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_temp_input: + return 0444; ++ case hwmon_temp_label: ++ return cc2->label ? 0444 : 0; + default: + return 0; + } +@@ -547,6 +554,16 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) + return 0; + } + ++static int cc2_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ struct cc2_data *data = dev_get_drvdata(dev); ++ ++ *str = data->label; ++ ++ return 0; ++} ++ + static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) + { +@@ -665,8 +682,9 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) + } + + static const struct hwmon_channel_info *cc2_info[] = { +- HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), +- HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX | ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_LABEL | ++ HWMON_H_MIN | HWMON_H_MAX | + HWMON_H_MIN_HYST | HWMON_H_MAX_HYST | + HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM), + NULL +@@ -675,6 +693,7 @@ static const struct hwmon_channel_info *cc2_info[] = { + static const struct hwmon_ops cc2_hwmon_ops = { + .is_visible = cc2_is_visible, + .read = cc2_read, ++ .read_string = cc2_read_string, + .write = cc2_write, + }; + +@@ -705,6 +724,8 @@ static int cc2_probe(struct i2c_client *client) + return dev_err_probe(dev, PTR_ERR(data->regulator), + "Failed to get regulator\n"); + ++ device_property_read_string(dev, "label", &data->label); ++ + ret = cc2_request_ready_irq(data, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request ready irq\n"); +diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c +--- a/drivers/hwmon/coretemp.c ++++ b/drivers/hwmon/coretemp.c +@@ -68,7 +68,7 @@ enum coretemp_attr_index { + * This value is passed as "id" field to rdmsr/wrmsr functions. + * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, + * from where the temperature values should be read. +- * @attr_size: Total number of pre-core attrs displayed in the sysfs. ++ * @attr_size: Number of sysfs attributes in this temperature data group. + */ + struct temp_data { + int temp; +diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c +--- a/drivers/hwmon/corsair-psu.c ++++ b/drivers/hwmon/corsair-psu.c +@@ -884,7 +884,7 @@ static const struct hid_device_id corsairpsu_idtable[] = { + { HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */ + { HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */ +- { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */ ++ { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy, Series 2023 and 2025 */ + { HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */ + { HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */ + { }, +diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c +--- a/drivers/hwmon/cros_ec_hwmon.c ++++ b/drivers/hwmon/cros_ec_hwmon.c +@@ -148,9 +148,17 @@ static bool cros_ec_hwmon_is_error_temp(u8 temp) + temp == EC_TEMP_SENSOR_NOT_CALIBRATED; + } + ++/* This differs slightly from the variant in units.h to avoid rounding inconsistencies. */ ++#define CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS (-273000) ++ ++static long cros_ec_hwmon_kelvin_to_millicelsius(long t) ++{ ++ return t * MILLIDEGREE_PER_DEGREE + CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS; ++} ++ + static long cros_ec_hwmon_temp_to_millicelsius(u8 temp) + { +- return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); ++ return cros_ec_hwmon_kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); + } + + static bool cros_ec_hwmon_attr_is_temp_threshold(u32 attr) +@@ -228,8 +236,13 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + ret = cros_ec_hwmon_read_temp_threshold(priv->cros_ec, channel, + cros_ec_hwmon_attr_to_thres(attr), + &threshold); +- if (ret == 0) +- *val = kelvin_to_millicelsius(threshold); ++ if (ret == 0) { ++ /* Limit to sensible, non-overflowing values. */ ++ if (threshold > 255 + 273) ++ *val = 255000; ++ else ++ *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); ++ } + } + } + +diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c +--- a/drivers/hwmon/da9055-hwmon.c ++++ b/drivers/hwmon/da9055-hwmon.c +@@ -256,11 +256,8 @@ static int da9055_hwmon_probe(struct platform_device *pdev) + NULL, da9055_auxadc_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "adc-irq", hwmon); +- if (ret != 0) { +- dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", +- ret); ++ if (ret != 0) + return ret; +- } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", + hwmon, +diff --git a/drivers/hwmon/eic7700-pvt.c b/drivers/hwmon/eic7700-pvt.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.c +@@ -0,0 +1,509 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ * ++ * Authors: ++ * Yulin Lu ++ * Huan He ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "eic7700-pvt.h" ++ ++static const struct pvt_sensor_info pvt_info[] = { ++ PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP), ++ PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT), ++}; ++ ++static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"}; ++ ++/* ++ * The original translation formulae of the temperature (in degrees of Celsius) ++ * to PVT data and vice-versa are following: ++ * N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) + ++ * 1.6198e2, ++ * T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) + ++ * 3.2843e-1*(N^1) - 4.8690e1, ++ * where T = [-40, 125]C and N = [27, 771]. ++ * They must be accordingly altered to be suitable for the integer arithmetics. ++ * The technique is called 'factor redistribution', which just makes sure the ++ * multiplications and divisions are made so to have a result of the operations ++ * within the integer numbers limit. In addition we need to translate the ++ * formulae to accept millidegrees of Celsius. Here what they look like after ++ * the alterations: ++ * N = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T + ++ * 16198e2) / 1e4, ++ * T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N - ++ * 48690, ++ * where T = [-40000, 125000] mC and N = [27, 771]. ++ */ ++static const struct polynomial poly_N_to_temp = { ++ .total_divider = 1, ++ .terms = { ++ {4, -18439, 1000, 1}, ++ {3, 80705, 1000, 1}, ++ {2, -185010, 1000, 1}, ++ {1, 328430, 1000, 1}, ++ {0, -48690, 1, 1} ++ } ++}; ++ ++/* ++ * Similar alterations are performed for the voltage conversion equations. ++ * The original formulae are: ++ * N = 1.3905e3*V - 5.7685e2, ++ * V = (N + 5.7685e2) / 1.3905e3, ++ * where V = [0.72, 0.88] V and N = [424, 646]. ++ * After the optimization they looks as follows: ++ * N = (13905e-3*V - 5768.5) / 10, ++ * V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10. ++ * where V = [720, 880] mV and N = [424, 646]. ++ */ ++static const struct polynomial poly_N_to_volt = { ++ .total_divider = 10, ++ .terms = { ++ {1, 100000, 13905, 1}, ++ {0, 57685000, 1, 13905} ++ } ++}; ++ ++static inline u32 eic7700_pvt_update(void __iomem *reg, u32 mask, u32 data) ++{ ++ u32 old; ++ ++ old = readl_relaxed(reg); ++ writel((old & ~mask) | (data & mask), reg); ++ ++ return old & mask; ++} ++ ++static inline void eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) ++{ ++ u32 old; ++ ++ mode = FIELD_PREP(PVT_MODE_MASK, mode); ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) ++{ ++ u32 old; ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ writel(val, pvt->regs + PVT_TRIM); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ u32 stat, val; ++ int active; ++ ++ if (IS_ENABLED(CONFIG_PM)) { ++ active = pm_runtime_get_if_active(pvt->dev); ++ if (active <= 0) ++ return IRQ_NONE; ++ } ++ ++ stat = readl(pvt->regs + PVT_INT); ++ if (!(stat & PVT_INT_STAT)) { ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ return IRQ_NONE; ++ } ++ ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ /* ++ * Read the data, update the cache and notify a waiter of this event. ++ */ ++ val = readl(pvt->regs + PVT_DATA); ++ WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val)); ++ complete(&pvt->conversion); ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ ++ return IRQ_HANDLED; ++} ++ ++static int eic7700_pvt_read_data(struct pvt_hwmon *pvt, ++ enum pvt_sensor_type type, long *val) ++{ ++ unsigned long timeout; ++ u32 data; ++ int ret; ++ ++ /* ++ * Wait for PVT conversion to complete and update the data cache. The ++ * data read procedure is following: set the requested PVT sensor mode, ++ * enable conversion, wait until conversion is finished, then disable ++ * conversion and IRQ, and read the cached data. ++ */ ++ reinit_completion(&pvt->conversion); ++ ++ eic7700_pvt_set_mode(pvt, pvt_info[type].mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN); ++ ++ /* ++ * Wait with timeout since in case if the sensor is suddenly powered ++ * down the request won't be completed and the caller will hang up on ++ * this procedure until the power is back up again. Multiply the ++ * timeout by the factor of two to prevent a false timeout. ++ */ ++ timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); ++ ret = wait_for_completion_timeout(&pvt->conversion, timeout); ++ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ ++ if (!ret) ++ synchronize_irq(pvt->irq); ++ ++ data = READ_ONCE(pvt->data_cache); ++ ++ if (!ret) ++ return -ETIMEDOUT; ++ ++ if (type == PVT_TEMP) ++ *val = polynomial_calc(&poly_N_to_temp, data); ++ else ++ *val = polynomial_calc(&poly_N_to_volt, data); ++ ++ return 0; ++} ++ ++static const struct hwmon_channel_info *pvt_channel_info[] = { ++ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), ++ NULL ++}; ++ ++static umode_t eic7700_pvt_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int ch) ++{ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ } ++ break; ++ case hwmon_in: ++ switch (attr) { ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, long *val) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = pm_runtime_get_sync(pvt->dev); ++ if (ret < 0) { ++ dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret); ++ pm_runtime_put_noidle(pvt->dev); ++ return ret; ++ } ++ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ ret = eic7700_pvt_read_data(pvt, ch, val); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_input) ++ ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val); ++ else ++ ret = -EOPNOTSUPP; ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ ++ pm_runtime_mark_last_busy(pvt->dev); ++ pm_runtime_put_autosuspend(pvt->dev); ++ return ret; ++} ++ ++static int eic7700_pvt_hwmon_read_string(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = pvt_info[ch].label; ++ return 0; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_label) { ++ *str = pvt_info[PVT_VOLT + ch].label; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops pvt_hwmon_ops = { ++ .is_visible = eic7700_pvt_hwmon_is_visible, ++ .read = eic7700_pvt_hwmon_read, ++ .read_string = eic7700_pvt_hwmon_read_string ++}; ++ ++static const struct hwmon_chip_info pvt_hwmon_info = { ++ .ops = &pvt_hwmon_ops, ++ .info = pvt_channel_info ++}; ++ ++static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct pvt_hwmon *pvt; ++ ++ pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); ++ if (!pvt) ++ return ERR_PTR(-ENOMEM); ++ ++ pvt->dev = dev; ++ init_completion(&pvt->conversion); ++ ++ return pvt; ++} ++ ++static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt) ++{ ++ /* ++ * Make sure controller are disabled so not to accidentally have ISR ++ * executed before the driver data is fully initialized. Clear the IRQ ++ * status as well. ++ */ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ readl(pvt->regs + PVT_INT); ++ readl(pvt->regs + PVT_DATA); ++ ++ /* Setup default sensor mode and temperature trim. */ ++ eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode); ++ ++ /* ++ * Max conversion latency (~333 µs) derived from PVT spec: ++ * maximum sampling rate = 3000 samples/sec. ++ */ ++ pvt->timeout = ns_to_ktime(PVT_TOUT_MIN); ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int eic7700_pvt_request_irq(struct pvt_hwmon *pvt) ++{ ++ struct platform_device *pdev = to_platform_device(pvt->dev); ++ int ret; ++ ++ pvt->irq = platform_get_irq(pdev, 0); ++ if (pvt->irq < 0) ++ return pvt->irq; ++ ++ ret = devm_request_threaded_irq(pvt->dev, pvt->irq, ++ eic7700_pvt_hard_isr, NULL, ++ IRQF_TRIGGER_HIGH, "pvt", pvt); ++ if (ret) { ++ dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_create_hwmon(struct pvt_hwmon *pvt) ++{ ++ pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", ++ pvt, &pvt_hwmon_info, ++ NULL); ++ if (IS_ERR(pvt->hwmon)) { ++ dev_err(pvt->dev, "Couldn't create hwmon device\n"); ++ return PTR_ERR(pvt->hwmon); ++ } ++ ++ return 0; ++} ++ ++static void eic7700_pvt_disable_pm_runtime(void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ ++ pm_runtime_dont_use_autosuspend(pvt->dev); ++ pm_runtime_disable(pvt->dev); ++ ++ if (!pm_runtime_status_suspended(pvt->dev)) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ pm_runtime_set_suspended(pvt->dev); ++ } ++} ++ ++static int eic7700_pvt_probe(struct platform_device *pdev) ++{ ++ struct reset_control *rst; ++ struct pvt_hwmon *pvt; ++ int i, ret; ++ ++ pvt = eic7700_pvt_create_data(pdev); ++ if (IS_ERR(pvt)) ++ return PTR_ERR(pvt); ++ ++ platform_set_drvdata(pdev, pvt); ++ ++ pvt->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(pvt->regs)) ++ return PTR_ERR(pvt->regs); ++ ++ for (i = 0; i < PVT_CLK_NUM; i++) ++ pvt->clks[i].id = pvt_clk_names[i]; ++ ++ ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Couldn't get clock descriptors\n"); ++ ++ rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); ++ if (IS_ERR(rst)) ++ return dev_err_probe(pvt->dev, PTR_ERR(rst), ++ "Couldn't get reset control\n"); ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(pvt->dev, ret, ++ "Failed to enable clocks\n"); ++ ++ ret = eic7700_pvt_init_iface(pvt); ++ if (ret) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ return ret; ++ } ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ pm_runtime_enable(&pdev->dev); ++ pm_runtime_set_autosuspend_delay(&pdev->dev, 3000); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ pm_runtime_get_noresume(&pdev->dev); ++ ++ ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime, ++ pvt); ++ if (ret) { ++ pm_runtime_put_noidle(&pdev->dev); ++ return dev_err_probe(&pdev->dev, ret, ++ "Can't register PM cleanup\n"); ++ } ++ ++ ret = eic7700_pvt_request_irq(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ ret = eic7700_pvt_create_hwmon(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ pm_runtime_put_autosuspend(&pdev->dev); ++ ++ return 0; ++ ++err_put_pm_runtime: ++ pm_runtime_put_noidle(&pdev->dev); ++ return ret; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) { ++ dev_err(dev, "Failed to enable clocks: %d\n", ret); ++ return ret; ++ } ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops eic7700_pvt_pm_ops = { ++ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) ++ RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume, ++ NULL) ++}; ++ ++static const struct of_device_id pvt_of_match[] = { ++ { .compatible = "eswin,eic7700-pvt"}, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, pvt_of_match); ++ ++static struct platform_driver pvt_driver = { ++ .probe = eic7700_pvt_probe, ++ .driver = { ++ .name = "eic7700-pvt", ++ .of_match_table = pvt_of_match, ++ .pm = pm_ptr(&eic7700_pvt_pm_ops), ++ }, ++}; ++module_platform_driver(pvt_driver); ++ ++MODULE_AUTHOR("Yulin Lu "); ++MODULE_AUTHOR("Huan He "); ++MODULE_DESCRIPTION("Eswin eic7700 PVT driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/eic7700-pvt.h b/drivers/hwmon/eic7700-pvt.h +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.h +@@ -0,0 +1,99 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ */ ++#ifndef __HWMON_EIC7700_PVT_H__ ++#define __HWMON_EIC7700_PVT_H__ ++ ++#include ++#include ++#include ++#include ++ ++/* ESWIN EIC7700 PVT registers and their bitfields */ ++#define PVT_TRIM 0x04 ++#define PVT_MODE 0x08 ++#define PVT_MODE_MASK GENMASK(2, 0) ++#define PVT_CTRL_MODE_TEMP 0x0 ++#define PVT_CTRL_MODE_VOLT 0x4 ++#define PVT_ENA 0x0c ++#define PVT_ENA_EN BIT(0) ++#define PVT_INT 0x10 ++#define PVT_INT_STAT BIT(0) ++#define PVT_INT_CLR BIT(1) ++#define PVT_DATA 0x14 ++#define PVT_DATA_OUT GENMASK(9, 0) ++ ++/* ++ * PVT sensors-related limits and default values ++ * @PVT_TEMP_CHS: Number of temperature hwmon channels. ++ * @PVT_VOLT_CHS: Number of voltage hwmon channels. ++ * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value ++ * when one is determined for ESWIN EIC7700 SoC). ++ * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. ++ */ ++#define PVT_TEMP_CHS 1 ++#define PVT_VOLT_CHS 1 ++#define PVT_TRIM_DEF 0 ++#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) ++ ++/* ++ * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT ++ * sampling mode) ++ * @PVT_TEMP: PVT Temperature sensor. ++ * @PVT_VOLT: PVT Voltage sensor. ++ */ ++enum pvt_sensor_type { ++ PVT_TEMP = 0, ++ PVT_VOLT ++}; ++ ++#define PVT_CLK_NUM 2 ++ ++/* ++ * struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure ++ * @channel: Sensor channel ID. ++ * @label: hwmon sensor label. ++ * @mode: PVT mode corresponding to the channel. ++ * @type: Sensor type. ++ */ ++struct pvt_sensor_info { ++ int channel; ++ const char *label; ++ u32 mode; ++ enum hwmon_sensor_types type; ++}; ++ ++#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \ ++ { \ ++ .channel = _ch, \ ++ .label = _label, \ ++ .mode = PVT_CTRL_MODE_ ##_mode, \ ++ .type = _type, \ ++ } ++ ++/* ++ * struct pvt_hwmon - Eswin EIC7700 PVT private data ++ * @dev: device structure of the PVT platform device. ++ * @hwmon: hwmon device structure. ++ * @regs: pointer to the Eswin EIC7700 PVT registers region. ++ * @irq: PVT events IRQ number. ++ * @clks: PVT clock descriptors. ++ * @data_cache: data cache in raw format. ++ * @conversion: data conversion completion. ++ * @timeout: conversion timeout. ++ */ ++struct pvt_hwmon { ++ struct device *dev; ++ struct device *hwmon; ++ void __iomem *regs; ++ int irq; ++ struct clk_bulk_data clks[PVT_CLK_NUM]; ++ u32 data_cache; ++ struct completion conversion; ++ ktime_t timeout; ++}; ++ ++#endif /* __HWMON_EIC7700_PVT_H__ */ +diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c +--- a/drivers/hwmon/emc1403.c ++++ b/drivers/hwmon/emc1403.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + + #define THERMAL_PID_REG 0xfd +@@ -654,14 +655,20 @@ static int emc1403_probe(struct i2c_client *client) + { + struct thermal_data *data; + struct device *hwmon_dev; +- const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client); ++ int ret; ++ ++ ret = devm_regulator_get_enable(&client->dev, "vdd"); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to enable regulator\n"); + + data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + +- data->chip = id->driver_data; ++ data->chip = (uintptr_t)i2c_get_match_data(client); ++ + data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); +@@ -677,10 +684,20 @@ static const unsigned short emc1403_address_list[] = { + 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END + }; + ++static const struct of_device_id emc1403_of_match[] = { ++ { .compatible = "smsc,emc1402", .data = (void *)emc1402 }, ++ { .compatible = "smsc,emc1403", .data = (void *)emc1403 }, ++ { .compatible = "smsc,emc1404", .data = (void *)emc1404 }, ++ { .compatible = "smsc,emc1428", .data = (void *)emc1428 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, emc1403_of_match); ++ + static struct i2c_driver sensor_emc1403 = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc1403", ++ .of_match_table = emc1403_of_match, + }, + .detect = emc1403_detect, + .probe = emc1403_probe, +diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c +--- a/drivers/hwmon/gxp-fan-ctrl.c ++++ b/drivers/hwmon/gxp-fan-ctrl.c +@@ -1,59 +1,16 @@ + // SPDX-License-Identifier: GPL-2.0-only +-/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ ++/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */ + +-#include + #include + #include + #include + #include + #include + +-#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ +-#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */ +-#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */ +-#define POWER_BIT 24 +- + struct gxp_fan_ctrl_drvdata { + void __iomem *base; +- void __iomem *plreg; +- void __iomem *fn2; + }; + +-static bool fan_installed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_INST); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_failed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_FAIL); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_enabled(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 val; +- +- /* +- * Check the power status as if the platform is off the value +- * reported for the PWM will be incorrect. Report fan as +- * disabled. +- */ +- val = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); +-} +- + static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +@@ -80,37 +37,11 @@ static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, + } + } + +-static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) +-{ +- switch (attr) { +- case hwmon_fan_enable: +- *val = fan_enabled(dev, channel); +- return 0; +- case hwmon_fan_fault: +- *val = fan_failed(dev, channel); +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- + static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 reg; + +- /* +- * Check the power status of the platform. If the platform is off +- * the value reported for the PWM will be incorrect. In this case +- * report a PWM of zero. +- */ +- +- reg = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- if (reg & BIT(POWER_BIT)) +- *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; +- else +- *val = 0; ++ *val = readb(drvdata->base + channel); + + return 0; + } +@@ -119,8 +50,6 @@ static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { + switch (type) { +- case hwmon_fan: +- return gxp_fan_read(dev, attr, channel, val); + case hwmon_pwm: + return gxp_pwm_read(dev, attr, channel, val); + default: +@@ -135,16 +64,6 @@ static umode_t gxp_fan_ctrl_is_visible(const void *_data, + umode_t mode = 0; + + switch (type) { +- case hwmon_fan: +- switch (attr) { +- case hwmon_fan_enable: +- case hwmon_fan_fault: +- mode = 0444; +- break; +- default: +- break; +- } +- break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: +@@ -168,15 +87,6 @@ static const struct hwmon_ops gxp_fan_ctrl_ops = { + }; + + static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { +- HWMON_CHANNEL_INFO(fan, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, +@@ -211,18 +121,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(drvdata->base), + "failed to map base\n"); + +- drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, +- "pl"); +- if (IS_ERR(drvdata->plreg)) +- return dev_err_probe(dev, PTR_ERR(drvdata->plreg), +- "failed to map plreg\n"); +- +- drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, +- "fn2"); +- if (IS_ERR(drvdata->fn2)) +- return dev_err_probe(dev, PTR_ERR(drvdata->fn2), +- "failed to map fn2\n"); +- + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "hpe_gxp_fan_ctrl", + drvdata, +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -57,8 +57,8 @@ struct hwmon_device_attribute { + }; + + #define to_hwmon_attr(d) \ +- container_of(d, struct hwmon_device_attribute, dev_attr) +-#define to_dev_attr(a) container_of(a, struct device_attribute, attr) ++ container_of_const(d, struct hwmon_device_attribute, dev_attr) ++#define to_dev_attr(a) container_of_const(a, struct device_attribute, attr) + + /* + * Thermal zone information +@@ -71,27 +71,27 @@ struct hwmon_thermal_data { + }; + + static ssize_t +-name_show(struct device *dev, struct device_attribute *attr, char *buf) ++name_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->name); + } +-static DEVICE_ATTR_RO(name); ++static const DEVICE_ATTR_RO(name); + + static ssize_t +-label_show(struct device *dev, struct device_attribute *attr, char *buf) ++label_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); + } +-static DEVICE_ATTR_RO(label); ++static const DEVICE_ATTR_RO(label); + +-static struct attribute *hwmon_dev_attrs[] = { ++static const struct attribute *const hwmon_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_label.attr, + NULL + }; + + static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, +- struct attribute *attr, int n) ++ const struct attribute *attr, int n) + { + struct device *dev = kobj_to_dev(kobj); + struct hwmon_device *hdev = to_hwmon_device(dev); +@@ -106,8 +106,8 @@ static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, + } + + static const struct attribute_group hwmon_dev_attr_group = { +- .attrs = hwmon_dev_attrs, +- .is_visible = hwmon_dev_attr_is_visible, ++ .attrs_const = hwmon_dev_attrs, ++ .is_visible_const = hwmon_dev_attr_is_visible, + }; + + static const struct attribute_group *hwmon_dev_attr_groups[] = { +@@ -115,13 +115,13 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = { + NULL + }; + +-static void hwmon_free_attrs(struct attribute **attrs) ++static void hwmon_free_attrs(const struct attribute *const *attrs) + { + int i; + + for (i = 0; attrs[i]; i++) { +- struct device_attribute *dattr = to_dev_attr(attrs[i]); +- struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); ++ const struct device_attribute *dattr = to_dev_attr(attrs[i]); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); + + kfree(hattr); + } +@@ -132,8 +132,8 @@ static void hwmon_dev_release(struct device *dev) + { + struct hwmon_device *hwdev = to_hwmon_device(dev); + +- if (hwdev->group.attrs) +- hwmon_free_attrs(hwdev->group.attrs); ++ if (hwdev->group.attrs_const) ++ hwmon_free_attrs(hwdev->group.attrs_const); + kfree(hwdev->groups); + kfree(hwdev->label); + kfree(hwdev); +@@ -341,7 +341,7 @@ static int hwmon_match_device(struct device *dev, const void *data) + return dev->class == &hwmon_class; + } + +-static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, ++static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, + char *buf) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -349,7 +349,7 @@ static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, + return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); + } + +-static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, ++static ssize_t pec_store(struct device *dev, const struct device_attribute *devattr, + const char *buf, size_t count) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -389,7 +389,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, + return err; + } + +-static DEVICE_ATTR_RW(pec); ++static const DEVICE_ATTR_RW(pec); + + static void hwmon_remove_pec(void *dev) + { +@@ -424,9 +424,9 @@ static int hwmon_pec_register(struct device *hdev) + /* sysfs attribute management */ + + static ssize_t hwmon_attr_show(struct device *dev, +- struct device_attribute *devattr, char *buf) ++ const struct device_attribute *devattr, char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + s64 val64; + long val; +@@ -449,10 +449,10 @@ static ssize_t hwmon_attr_show(struct device *dev, + } + + static ssize_t hwmon_attr_show_string(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + enum hwmon_sensor_types type = hattr->type; + const char *s; +@@ -472,10 +472,10 @@ static ssize_t hwmon_attr_show_string(struct device *dev, + } + + static ssize_t hwmon_attr_store(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + const char *buf, size_t count) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + long val; + int ret; +@@ -509,12 +509,12 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) + (type == hwmon_fan && attr == hwmon_fan_label); + } + +-static struct attribute *hwmon_genattr(const void *drvdata, +- enum hwmon_sensor_types type, +- u32 attr, +- int index, +- const char *template, +- const struct hwmon_ops *ops) ++static const struct attribute *hwmon_genattr(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, ++ int index, ++ const char *template, ++ const struct hwmon_ops *ops) + { + struct hwmon_device_attribute *hattr; + struct device_attribute *dattr; +@@ -551,8 +551,8 @@ static struct attribute *hwmon_genattr(const void *drvdata, + hattr->ops = ops; + + dattr = &hattr->dev_attr; +- dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; +- dattr->store = hwmon_attr_store; ++ dattr->show_const = is_string ? hwmon_attr_show_string : hwmon_attr_show; ++ dattr->store_const = hwmon_attr_store; + + a = &dattr->attr; + sysfs_attr_init(a); +@@ -830,7 +830,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) + } + + static int hwmon_genattrs(const void *drvdata, +- struct attribute **attrs, ++ const struct attribute **attrs, + const struct hwmon_ops *ops, + const struct hwmon_channel_info *info) + { +@@ -849,7 +849,7 @@ static int hwmon_genattrs(const void *drvdata, + u32 attr; + + while (attr_mask) { +- struct attribute *a; ++ const struct attribute *a; + + attr = __ffs(attr_mask); + attr_mask &= ~BIT(attr); +@@ -868,11 +868,11 @@ static int hwmon_genattrs(const void *drvdata, + return aindex; + } + +-static struct attribute ** ++static const struct attribute ** + __hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip) + { + int ret, i, aindex = 0, nattrs = 0; +- struct attribute **attrs; ++ const struct attribute **attrs; + + for (i = 0; chip->info[i]; i++) + nattrs += hwmon_num_channel_attrs(chip->info[i]); +@@ -927,7 +927,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + hdev = &hwdev->dev; + + if (chip) { +- struct attribute **attrs; ++ const struct attribute **attrs; + int ngroups = 2; /* terminating NULL plus &hwdev->groups */ + + if (groups) +@@ -946,7 +946,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + goto free_hwmon; + } + +- hwdev->group.attrs = attrs; ++ hwdev->group.attrs_const = attrs; + ngroups = 0; + hwdev->groups[ngroups++] = &hwdev->group; + +diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c +--- a/drivers/hwmon/ina2xx.c ++++ b/drivers/hwmon/ina2xx.c +@@ -124,6 +124,7 @@ static const struct regmap_config ina2xx_regmap_config = { + enum ina2xx_ids { + ina219, + ina226, ++ ina232, + ina234, + ina260, + sy24655 +@@ -210,6 +211,20 @@ static const struct ina2xx_config ina2xx_config[] = { + .current_shift = 4, + .has_update_interval = true, + }, ++ [ina232] = { ++ .config_default = INA226_CONFIG_DEFAULT, ++ .calibration_value = 2048, ++ .shunt_div = 400, ++ .shunt_voltage_shift = 0, ++ .bus_voltage_shift = 0, ++ .bus_voltage_lsb = 1600, ++ .power_lsb_factor = 32, ++ .has_alerts = true, ++ .has_ishunt = false, ++ .has_power_average = false, ++ .current_shift = 0, ++ .has_update_interval = true, ++ }, + [ina260] = { + .config_default = INA260_CONFIG_DEFAULT, + .shunt_div = 400, +@@ -1123,6 +1138,7 @@ static const struct i2c_device_id ina2xx_id[] = { + { .name = "ina226", .driver_data = ina226 }, + { .name = "ina230", .driver_data = ina226 }, + { .name = "ina231", .driver_data = ina226 }, ++ { .name = "ina232", .driver_data = ina232 }, + { .name = "ina234", .driver_data = ina234 }, + { .name = "ina260", .driver_data = ina260 }, + { .name = "sy24655", .driver_data = sy24655 }, +@@ -1155,6 +1171,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { + .compatible = "ti,ina231", + .data = (void *)ina226 + }, ++ { ++ .compatible = "ti,ina232", ++ .data = (void *)ina232 ++ }, + { + .compatible = "ti,ina234", + .data = (void *)ina234 +diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c +--- a/drivers/hwmon/k10temp.c ++++ b/drivers/hwmon/k10temp.c +@@ -207,6 +207,10 @@ static const char *k10temp_temp_label[] = { + "Tccd10", + "Tccd11", + "Tccd12", ++ "Tccd13", ++ "Tccd14", ++ "Tccd15", ++ "Tccd16", + }; + + static int k10temp_read_labels(struct device *dev, +@@ -243,7 +247,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, + if (*val < 0 && !data->disp_negative) + *val = 0; + break; +- case 2 ... 13: /* Tccd{1-12} */ ++ case 2 ... 17: /* Tccd{1-16} */ + ret = read_ccd_temp_reg(data, channel - 2, ®val); + + if (ret) +@@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const k10temp_info[] = { + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL + }; +@@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) + } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { ++ case 0x00 ... 0x2f: /* Zen5 Turin */ ++ data->ccd_offset = 0x1F0; ++ k10temp_get_ccd_support(data, 16); ++ break; + case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ + data->ccd_offset = 0x308; + k10temp_get_ccd_support(data, 8); +diff --git a/drivers/hwmon/kb9002.c b/drivers/hwmon/kb9002.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/kb9002.c +@@ -0,0 +1,460 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Kandou KB9002 PCIe 5.0 retimer hwmon driver. ++ * ++ * The retimer exposes a system management bus (SMBus 3.0 with PEC) ++ * target for firmware-managed status registers. This driver assumes ++ * the chip is strapped to SMBus mode and exports the aggregated ++ * maximum die temperature as hwmon temp1_input (millidegrees Celsius) ++ * plus the firmware version and boot status under debugfs. ++ * ++ * The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch ++ * the host interface) carries the 32-bit register address and data ++ * big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read) ++ * carries the register address and returned data little-endian. ++ * ++ * Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD). ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define KB9002_DEV_NAME "kb9002" ++ ++/* ++ * SMBus read command codes. Each read is a two-phase PEC-protected ++ * transaction: prime writes the target address, data reads it back with ++ * the register contents. FW reads use a 16-bit address, HW reads 32-bit. ++ */ ++#define KB9002_CC_FW_READ_PRIME 0x82 ++#define KB9002_CC_FW_READ_DATA 0x81 ++#define KB9002_CC_HW_READ_PRIME 0x8a ++#define KB9002_CC_HW_READ_DATA 0x89 ++ ++/* Firmware register offsets (16-bit). */ ++#define KB9002_FW_REG_VID 0x0004 ++#define KB9002_FW_REG_FW_VERSION 0x0500 ++#define KB9002_FW_REG_TEMP_MAXIMUM 0x0550 ++ ++#define KB9002_VID_MASK GENMASK(31, 16) ++#define KB9002_VID_KANDOU 0x1e6f ++ ++/* Firmware boot status: 0xe8 in the top byte means init completed OK. */ ++#define KB9002_HW_REG_FW_BOOT_STATUS 0xe0090008 ++#define KB9002_FW_BOOT_STATUS_OK_MSB 0xe8 ++ ++/* ++ * Hardware registers reached over raw I2C (32-bit addressing). The ++ * host-interface bit selects SMBus (set) vs raw-I2C target; parts ++ * strapped to raw I2C need it set before SMBus access works. ++ */ ++#define KB9002_HW_REG_REVID 0x00480004 ++#define KB9002_HW_REG_HOST_IF 0x00480008 ++#define KB9002_HOST_IF_SMBUS BIT(1) ++ ++#define KB9002_REVID_MASK GENMASK(7, 0) ++#define KB9002_REVID_B0 0x10 ++#define KB9002_REVID_B1 0x11 ++ ++/* Retries to drain a stray leading 0xff from the raw-I2C FIFO. */ ++#define KB9002_REVID_READ_RETRIES 16 ++ ++/* Temperature: 32-bit Q16.16 absolute Kelvin. */ ++#define KB9002_TEMP_FRAC_BITS 16 ++#define KB9002_ABS_ZERO_MILLI_C (-273150) ++ ++/* Firmware takes up to ~2s to respond after a host-interface change. */ ++#define KB9002_FW_READY_POLL_US (25 * USEC_PER_MSEC) ++#define KB9002_FW_READY_TIMEOUT_US (2 * USEC_PER_SEC) ++ ++struct kb9002_data { ++ struct i2c_client *client; ++ struct device *hwmon_dev; ++}; ++ ++/* Raw-I2C read: write the 32-bit BE address, then read 4 BE data bytes. */ ++static int kb9002_i2c_read(struct i2c_client *client, u32 reg, u32 *val) ++{ ++ u8 addr[4]; ++ u8 rbuf[4]; ++ struct i2c_msg msgs[2] = { ++ { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(addr), ++ .buf = addr, ++ }, ++ { ++ .addr = client->addr, ++ .flags = I2C_M_RD, ++ .len = sizeof(rbuf), ++ .buf = rbuf, ++ }, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, addr); ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) ++ return ret; ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *val = get_unaligned_be32(rbuf); ++ return 0; ++} ++ ++/* Raw-I2C write: 4 BE address bytes followed by 4 BE data bytes. */ ++static int kb9002_i2c_write(struct i2c_client *client, u32 reg, u32 val) ++{ ++ u8 buf[8]; ++ struct i2c_msg msg = { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(buf), ++ .buf = buf, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, &buf[0]); ++ put_unaligned_be32(val, &buf[4]); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) ++ return ret; ++ if (ret != 1) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* ++ * Read the silicon revision ID. A fresh FIFO may start with a stray ++ * 0xff that shifts the result, so drain one byte between retries until ++ * the top byte is no longer 0xff. ++ */ ++static int kb9002_read_revid(struct i2c_client *client, u32 *revid) ++{ ++ u8 dummy; ++ int ret; ++ int i; ++ ++ for (i = 0; i < KB9002_REVID_READ_RETRIES; i++) { ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_REVID, revid); ++ if (ret) ++ return ret; ++ if ((*revid >> 24) != 0xff) ++ return 0; ++ /* Drain one byte from the chip to re-align the I2C FIFO. */ ++ i2c_master_recv(client, &dummy, 1); ++ } ++ ++ return -EIO; ++} ++ ++/* ++ * Read a 32-bit firmware register over SMBus: block-write the 16-bit LE ++ * address, then block-read the echoed address plus 4 LE data bytes. ++ */ ++static int kb9002_fw_read(struct kb9002_data *data, u16 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[2]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le16(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_FW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_FW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* Like kb9002_fw_read but for a hardware register (32-bit LE address). */ ++static int kb9002_smbus_hw_read(struct kb9002_data *data, u32 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[4]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le32(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_HW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_HW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* ++ * Switch the host interface from raw-I2C to SMBus and wait for firmware ++ * to come back up. Called only when SMBus access failed in probe, i.e. ++ * the chip is strapped to raw-I2C mode. Confirms the revision, sets the ++ * SMBus-mode bit, then polls until firmware responds again. ++ */ ++static int kb9002_enable_smbus_target(struct kb9002_data *data) ++{ ++ struct i2c_client *client = data->client; ++ u32 revid; ++ u32 val; ++ int op_ret; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "raw I2C required to switch to SMBus mode\n"); ++ ++ ret = kb9002_read_revid(client, &revid); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "revision ID read failed\n"); ++ ++ switch (FIELD_GET(KB9002_REVID_MASK, revid)) { ++ case KB9002_REVID_B0: ++ case KB9002_REVID_B1: ++ break; ++ default: ++ return dev_err_probe(&client->dev, -ENODEV, ++ "unsupported revision ID 0x%08x\n", revid); ++ } ++ ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_HOST_IF, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface read failed\n"); ++ ++ val |= KB9002_HOST_IF_SMBUS; ++ ++ ret = kb9002_i2c_write(client, KB9002_HW_REG_HOST_IF, val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface write failed\n"); ++ ++ /* Wait until firmware re-initialisation completes. */ ++ ret = read_poll_timeout(kb9002_fw_read, op_ret, op_ret == 0, ++ KB9002_FW_READY_POLL_US, ++ KB9002_FW_READY_TIMEOUT_US, true, ++ data, KB9002_FW_REG_VID, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "firmware not responding over SMBus\n"); ++ ++ return 0; ++} ++ ++/* Convert Q16.16 absolute Kelvin to millidegrees Celsius. */ ++static long kb9002_temp_to_milli_c(u32 raw) ++{ ++ s64 milli_k = ((s64)raw * 1000) >> KB9002_TEMP_FRAC_BITS; ++ ++ return (long)milli_k + KB9002_ABS_ZERO_MILLI_C; ++} ++ ++static int kb9002_read_temp(struct kb9002_data *data, long *val) ++{ ++ u32 raw; ++ int ret; ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_TEMP_MAXIMUM, &raw); ++ if (ret) ++ return ret; ++ ++ *val = kb9002_temp_to_milli_c(raw); ++ return 0; ++} ++ ++static umode_t kb9002_is_visible(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ return 0444; ++} ++ ++static int kb9002_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct kb9002_data *data = dev_get_drvdata(dev); ++ ++ if (type == hwmon_temp && attr == hwmon_temp_input) ++ return kb9002_read_temp(data, val); ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops kb9002_hwmon_ops = { ++ .is_visible = kb9002_is_visible, ++ .read = kb9002_read, ++}; ++ ++static const struct hwmon_channel_info * const kb9002_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), ++ NULL, ++}; ++ ++static const struct hwmon_chip_info kb9002_chip_info = { ++ .ops = &kb9002_hwmon_ops, ++ .info = kb9002_hwmon_info, ++}; ++ ++static int kb9002_fw_version_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 ver; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_FW_VERSION, &ver); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%u.%02u.%02u.%u\n", ++ (ver >> 24) & 0xff, (ver >> 16) & 0xff, ++ (ver >> 8) & 0xff, (ver >> 0) & 0xff); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_version); ++ ++static int kb9002_fw_load_status_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 status; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_smbus_hw_read(data, KB9002_HW_REG_FW_BOOT_STATUS, &status); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%s\n", ++ (status >> 24) == KB9002_FW_BOOT_STATUS_OK_MSB ? ++ "normal" : "abnormal"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_load_status); ++ ++static void kb9002_debugfs_init(struct kb9002_data *data) ++{ ++ struct dentry *dir = data->client->debugfs; ++ ++ debugfs_create_file("fw_ver", 0444, dir, data, ++ &kb9002_fw_version_fops); ++ debugfs_create_file("fw_load_status", 0444, dir, data, ++ &kb9002_fw_load_status_fops); ++} ++ ++static int kb9002_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct kb9002_data *data; ++ struct device *hwmon_dev; ++ u32 vid; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_BLOCK_DATA | ++ I2C_FUNC_SMBUS_PEC)) ++ return -ENODEV; ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->client = client; ++ ++ /* All firmware register accesses are PEC-protected. */ ++ client->flags |= I2C_CLIENT_PEC; ++ ++ i2c_set_clientdata(client, data); ++ ++ /* ++ * Try SMBus first. If the chip is strapped to raw-I2C mode it ++ * will not respond to SMBus framing, so fall back to switching ++ * the host interface over raw I2C and retry. ++ */ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) { ++ dev_dbg(dev, "SMBus probe failed (%d), trying raw-I2C host-interface switch\n", ++ ret); ++ ret = kb9002_enable_smbus_target(data); ++ if (ret) ++ return ret; ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "VID read failed after host-interface switch\n"); ++ } ++ if (FIELD_GET(KB9002_VID_MASK, vid) != KB9002_VID_KANDOU) ++ return dev_err_probe(dev, -ENODEV, ++ "unexpected VID 0x%08x\n", vid); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(dev, KB9002_DEV_NAME, ++ data, ++ &kb9002_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++ data->hwmon_dev = hwmon_dev; ++ kb9002_debugfs_init(data); ++ return 0; ++} ++ ++static const struct i2c_device_id kb9002_id[] = { ++ { .name = KB9002_DEV_NAME }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, kb9002_id); ++ ++static const struct of_device_id kb9002_of_match[] = { ++ { .compatible = "kandou,kb9002" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, kb9002_of_match); ++ ++static struct i2c_driver kb9002_driver = { ++ .driver = { ++ .name = KB9002_DEV_NAME, ++ .of_match_table = kb9002_of_match, ++ }, ++ .probe = kb9002_probe, ++ .id_table = kb9002_id, ++}; ++module_i2c_driver(kb9002_driver); ++ ++MODULE_AUTHOR("Andy Chung "); ++MODULE_DESCRIPTION("Kandou KB9002 PCIe retimer hwmon driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c +--- a/drivers/hwmon/lm90.c ++++ b/drivers/hwmon/lm90.c +@@ -101,6 +101,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -108,7 +109,7 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include +@@ -295,7 +296,7 @@ static const struct i2c_device_id lm90_id[] = { + }; + MODULE_DEVICE_TABLE(i2c, lm90_id); + +-static const struct of_device_id __maybe_unused lm90_of_match[] = { ++static const struct of_device_id lm90_of_match[] = { + { + .compatible = "adi,adm1032", + .data = (void *)adm1032 +@@ -2602,7 +2603,6 @@ static void lm90_stop_work(void *_data) + + static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + { +- struct device_node *np = client->dev.of_node; + int config, convrate; + + if (data->flags & LM90_HAVE_CONVRATE) { +@@ -2626,7 +2626,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + + /* Check Temperature Range Select */ + if (data->flags & LM90_HAVE_EXTENDED_TEMP) { +- if (of_property_read_bool(np, "ti,extended-range-enable")) ++ if (device_property_read_bool(&client->dev, "ti,extended-range-enable")) + config |= 0x04; + if (!(config & 0x04)) + data->flags &= ~LM90_HAVE_EXTENDED_TEMP; +@@ -2692,36 +2692,41 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id) + return IRQ_NONE; + } + +-static int lm90_probe_channel_from_dt(struct i2c_client *client, +- struct device_node *child, +- struct lm90_data *data) ++static int lm90_probe_channel(struct i2c_client *client, ++ struct fwnode_handle *child, ++ struct lm90_data *data) + { + u32 id; + s32 val; + int err; + struct device *dev = &client->dev; + +- err = of_property_read_u32(child, "reg", &id); ++ err = fwnode_property_read_u32(child, "reg", &id); + if (err) { +- dev_err(dev, "missing reg property of %pOFn\n", child); ++ dev_err(dev, "missing reg property of %pfw\n", child); + return err; + } + + if (id >= MAX_CHANNELS) { +- dev_err(dev, "invalid reg property value %d in %pOFn\n", id, child); ++ dev_err(dev, "invalid reg property value %d in %pfw\n", id, child); + return -EINVAL; + } + +- err = of_property_read_string(child, "label", &data->channel_label[id]); ++ err = fwnode_property_read_string(child, "label", &data->channel_label[id]); + if (err == -ENODATA || err == -EILSEQ) { +- dev_err(dev, "invalid label property in %pOFn\n", child); ++ dev_err(dev, "invalid label property in %pfw\n", child); + return err; + } + + if (data->channel_label[id]) + data->channel_config[id] |= HWMON_T_LABEL; + +- err = of_property_read_s32(child, "temperature-offset-millicelsius", &val); ++ /* ++ * fwnode_property_read_u32() has no signed equivalent. ++ * temperature-offset-millicelsius is signed, so read and reinterpret it as s32 to ++ * preserve negative offsets values (same behavior as the old of_property_read_s32()). ++ */ ++ err = fwnode_property_read_u32(child, "temperature-offset-millicelsius", (u32 *)&val); + if (!err) { + if (id == 0) { + dev_err(dev, "temperature-offset-millicelsius can't be set for internal channel\n"); +@@ -2739,18 +2744,17 @@ static int lm90_probe_channel_from_dt(struct i2c_client *client, + return 0; + } + +-static int lm90_parse_dt_channel_info(struct i2c_client *client, +- struct lm90_data *data) ++static int lm90_parse_channel_info(struct i2c_client *client, ++ struct lm90_data *data) + { + int err; + struct device *dev = &client->dev; +- const struct device_node *np = dev->of_node; + +- for_each_child_of_node_scoped(np, child) { +- if (strcmp(child->name, "channel")) ++ device_for_each_child_node_scoped(dev, child) { ++ if (!fwnode_name_eq(child, "channel")) + continue; + +- err = lm90_probe_channel_from_dt(client, child, data); ++ err = lm90_probe_channel(client, child, data); + if (err) + return err; + } +@@ -2887,12 +2891,10 @@ static int lm90_probe(struct i2c_client *client) + /* Set maximum conversion rate */ + data->max_convrate = lm90_params[data->kind].max_convrate; + +- /* Parse device-tree channel information */ +- if (client->dev.of_node) { +- err = lm90_parse_dt_channel_info(client, data); +- if (err) +- return err; +- } ++ /* Parse channel information */ ++ err = lm90_parse_channel_info(client, data); ++ if (err) ++ return err; + + /* Initialize the LM90 chip */ + err = lm90_init_client(client, data); +@@ -2918,10 +2920,8 @@ static int lm90_probe(struct i2c_client *client) + err = devm_request_threaded_irq(dev, client->irq, + NULL, lm90_irq_thread, + IRQF_ONESHOT, "lm90", client); +- if (err < 0) { +- dev_err(dev, "cannot request IRQ %d\n", client->irq); ++ if (err < 0) + return err; +- } + } + + return 0; +@@ -2985,7 +2985,7 @@ static struct i2c_driver lm90_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm90", +- .of_match_table = of_match_ptr(lm90_of_match), ++ .of_match_table = lm90_of_match, + .pm = pm_sleep_ptr(&lm90_pm_ops), + }, + .probe = lm90_probe, +diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c +--- a/drivers/hwmon/nct6775-core.c ++++ b/drivers/hwmon/nct6775-core.c +@@ -33,6 +33,7 @@ + * (0xd451) + * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * (0xd429) ++ * nct5585d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3 + * nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3 + * +diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c +--- a/drivers/hwmon/nct6775-platform.c ++++ b/drivers/hwmon/nct6775-platform.c +@@ -34,7 +34,7 @@ static const char * const nct6775_sio_names[] __initconst = { + [nct6795] = "NCT6795D", + [nct6796] = "NCT6796D", + [nct6797] = "NCT6797D", +- [nct6798] = "NCT6798D", ++ [nct6798] = "NCT6798D/NCT5585D", + [nct6799] = "NCT6796D-S/NCT6799D-R", + }; + +diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c +--- a/drivers/hwmon/npcm750-pwm-fan.c ++++ b/drivers/hwmon/npcm750-pwm-fan.c +@@ -996,10 +996,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) + sprintf(name, "NPCM7XX-FAN-MD%d", i); + ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr, + 0, name, (void *)data); +- if (ret) { +- dev_err(dev, "register IRQ fan%d failed\n", i); ++ if (ret) + return ret; +- } + } + + for_each_child_of_node_scoped(np, child) { +diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c +--- a/drivers/hwmon/nzxt-kraken3.c ++++ b/drivers/hwmon/nzxt-kraken3.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. ++ * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73, 2023/2023 Elite, and ++ * 2024 Elite all in one coolers. + * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). + * 2023 models use the Z53 code paths. + * +@@ -25,6 +26,7 @@ + #define USB_PRODUCT_ID_Z53 0x3008 + #define USB_PRODUCT_ID_KRAKEN2023 0x300E + #define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C ++#define USB_PRODUCT_ID_KRAKEN2024_ELITE 0x3012 + + enum kinds { X53, Z53, KRAKEN2023 } __packed; + enum pwm_enable { off, manual, curve } __packed; +@@ -927,6 +929,10 @@ static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id + priv->kind = KRAKEN2023; + device_name = "kraken2023elite"; + break; ++ case USB_PRODUCT_ID_KRAKEN2024_ELITE: ++ priv->kind = KRAKEN2023; ++ device_name = "kraken2024elite"; ++ break; + default: + ret = -ENODEV; + goto fail_and_close; +@@ -994,6 +1000,7 @@ static const struct hid_device_id kraken3_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2024_ELITE) }, + { } + }; + +diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h +--- a/drivers/hwmon/peci/common.h ++++ b/drivers/hwmon/peci/common.h +@@ -31,7 +31,7 @@ struct peci_sensor_data { + + /** + * peci_sensor_need_update() - check whether sensor update is needed or not +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + * + * Return: true if update is needed, false if not. + */ +@@ -44,7 +44,7 @@ static inline bool peci_sensor_need_update(struct peci_sensor_state *state) + + /** + * peci_sensor_mark_updated() - mark the sensor is updated +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + */ + static inline void peci_sensor_mark_updated(struct peci_sensor_state *state) + { +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -52,8 +52,8 @@ config SENSORS_ADM1275 + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, +- ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and +- Digital Power Monitors. ++ ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C ++ Hot-Swap Controller and Digital Power Monitors. + + This driver can also be built as a module. If so, the module will + be called adm1275. +@@ -394,10 +394,10 @@ config SENSORS_MAX20751 + be called max20751. + + config SENSORS_MAX20830 +- tristate "Analog Devices MAX20830" ++ tristate "Analog Devices MAX20830 and compatibles" + help + If you say yes here you get hardware monitoring support for Analog +- Devices MAX20830. ++ Devices MAX20830, MAX20830C, and MAX20840C. + + This driver can also be built as a module. If so, the module will + be called max20830. +@@ -434,8 +434,9 @@ config SENSORS_MAX34440 + tristate "Maxim MAX34440 and compatibles" + help + If you say yes here you get hardware monitoring support for Maxim +- MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. +- Other compatible are ADPM12160, and ADPM12200. ++ MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and ++ MAX34461. Other compatible devices are ADPM12160, ADPM12200, and ++ ADPM12250. + + This driver can also be built as a module. If so, the module will + be called max34440. +@@ -607,6 +608,15 @@ config SENSORS_MPQ7932 + This driver can also be built as a module. If so, the module will + be called mpq7932. + ++config SENSORS_MPQ82D00 ++ tristate "MPS MPQ82D00" ++ help ++ If you say yes here you get hardware monitoring functionality support ++ for power management IC MPS MPQ82D00. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mpq82d00. ++ + config SENSORS_MPQ8785 + tristate "MPS MPQ8785" + help +@@ -616,6 +626,37 @@ config SENSORS_MPQ8785 + This driver can also be built as a module. If so, the module will + be called mpq8785. + ++config SENSORS_MPQ8646 ++ tristate "MPS MPQ8646" ++ depends on REGULATOR || !REGULATOR ++ depends on NVMEM || !NVMEM ++ help ++ If you say yes here you get hardware monitoring support for the ++ Monolithic Power Systems MPQ8646. ++ ++ This driver can also be built as a module. If so, the module ++ will be called mpq8646. ++ ++config SENSORS_MPQ8646_DEBUG_UNSAFE ++ bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)" ++ depends on SENSORS_MPQ8646 && DEBUG_FS ++ default n ++ help ++ Expose additional WRITE-able debugfs files in the client's ++ pmbus debugfs directory, ++ /sys/kernel/debug/i2c/i2c-/-/ ++ ++ These are provisioning and bring-up aids. On many designs the ++ MPQ8646 powers the SoC core rail, so a wrong write can brown out ++ or permanently mis-provision the board. ++ ++ WARNING: Wrong register writes can and likely will physically ++ damage or destroy the chip and/or the board. ++ ++ It shall never be set in a production, shipping, or default configuration. ++ ++ If unsure, say N. ++ + config SENSORS_PIM4328 + tristate "Flex PIM4328 and compatibles" + help +@@ -677,6 +718,25 @@ config SENSORS_STEF48H28 + This driver can also be built as a module. If so, the module will + be called stef48h28. + ++config SENSORS_SQ24860 ++ tristate "Silergy SQ24860" ++ help ++ If you say yes here you get hardware monitoring support for Silergy ++ SQ24860 eFuse. ++ ++ This driver can also be built as a module. If so, the module will ++ be called sq24860. ++ ++config SENSORS_SQ24860_REGULATOR ++ bool "Regulator support for SQ24860" ++ depends on SENSORS_SQ24860 && REGULATOR ++ default SENSORS_SQ24860 ++ help ++ If you say yes here you get regulator support for Silergy SQ24860. ++ The regulator is registered through the PMBus regulator framework and ++ can be used to control the output exposed by the device. ++ This option is only useful if regulator framework support is needed. ++ + config SENSORS_STPDDC60 + tristate "ST STPDDC60" + help +@@ -767,6 +827,15 @@ config SENSORS_UCD9200 + This driver can also be built as a module. If so, the module will + be called ucd9200. + ++config SENSORS_VT7505 ++ tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505" ++ help ++ If you say yes here you get hardware monitoring support for Analog ++ Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers. ++ ++ This driver can also be built as a module. If so, the module will ++ be called vt7505. ++ + config SENSORS_XDP710 + tristate "Infineon XDP710 family" + help +@@ -803,6 +872,16 @@ config SENSORS_XDPE1A2G7B + This driver can also be built as a module. If so, the module will + be called xdpe1a2g7b. + ++config SENSORS_XDPE1A2G7B_REGULATOR ++ bool "Regulator support for XDPE1A2G7B and compatibles" ++ depends on SENSORS_XDPE1A2G7B && REGULATOR ++ help ++ If you say yes here you get regulator support for Infineon ++ XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers. ++ ++ This enables the controllers to be used as regulator devices, ++ providing voltage control through the regulator framework. ++ + config SENSORS_XDPE122 + tristate "Infineon XDPE122 family" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -59,12 +59,15 @@ obj-$(CONFIG_SENSORS_MP5990) += mp5990.o + obj-$(CONFIG_SENSORS_MP9941) += mp9941.o + obj-$(CONFIG_SENSORS_MP9945) += mp9945.o + obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o ++obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o + obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o ++obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.o + obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o + obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o + obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o + obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o + obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o ++obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o + obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o + obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o + obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o +@@ -73,6 +76,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o + obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o + obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o + obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o ++obj-$(CONFIG_SENSORS_VT7505) += vt7505.o + obj-$(CONFIG_SENSORS_XDP710) += xdp710.o + obj-$(CONFIG_SENSORS_XDP720) += xdp720.o + obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o +diff --git a/drivers/hwmon/pmbus/acbel-fsg032.c b/drivers/hwmon/pmbus/acbel-fsg032.c +--- a/drivers/hwmon/pmbus/acbel-fsg032.c ++++ b/drivers/hwmon/pmbus/acbel-fsg032.c +@@ -52,6 +52,7 @@ static const struct i2c_device_id acbel_fsg032_id[] = { + { .name = "acbel_fsg032" }, + { } + }; ++MODULE_DEVICE_TABLE(i2c, acbel_fsg032_id); + + static struct pmbus_driver_info acbel_fsg032_info = { + .pages = 1, +diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c +--- a/drivers/hwmon/pmbus/adm1275.c ++++ b/drivers/hwmon/pmbus/adm1275.c +@@ -19,7 +19,7 @@ + #include "pmbus.h" + + enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, +- adm1293, adm1294, sq24905c }; ++ adm1293, adm1294, bd12780, bd12790, sq24905c }; + + #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) + #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) +@@ -47,6 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, + #define ADM1278_VOUT_EN BIT(1) + + #define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) ++/* The BD127[89]0 data sheets mark TSFILT bit as reserved. */ ++#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN) + + #define ADM1293_IRANGE_25 0 + #define ADM1293_IRANGE_50 BIT(6) +@@ -134,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = { + + }; + ++/* ++ * BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18) ++ * and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m. ++ * ++ * Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V) ++ * -> m = 6770, R=-2 / m = 4062, R=-2 ++ * Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV) ++ * -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R ++ * Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72 ++ * -> m = round(k * 10^(-3-R)), R=-2 for 60V/15mV, R=-3 for the other three ++ * Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1 ++ */ ++static const struct coefficients bd12790_coefficients[] = { ++ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ ++ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ ++ [2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */ ++ [3] = { 664, 20480, -1 }, /* current, vsense range 30mV */ ++ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ ++ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ ++ [6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */ ++ [7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */ ++ [8] = { 42, 31880, -1 }, /* temperature */ ++}; ++ + static const struct coefficients adm1275_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ +@@ -487,6 +513,8 @@ static const struct i2c_device_id adm1275_id[] = { + { .name = "adm1281", .driver_data = adm1281 }, + { .name = "adm1293", .driver_data = adm1293 }, + { .name = "adm1294", .driver_data = adm1294 }, ++ { .name = "bd12780", .driver_data = bd12780 }, ++ { .name = "bd12790", .driver_data = bd12790 }, + { .name = "mc09c", .driver_data = sq24905c }, + { } + }; +@@ -494,12 +522,13 @@ MODULE_DEVICE_TABLE(i2c, adm1275_id); + + /* Enable VOUT & TEMP1 if not enabled (disabled by default) */ + static int adm1275_enable_vout_temp(struct adm1275_data *data, +- struct i2c_client *client, int config) ++ struct i2c_client *client, int config, ++ u16 defconfig) + { + int ret; + +- if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) { +- config |= ADM1278_PMON_DEFCONFIG; ++ if ((config & defconfig) != defconfig) { ++ config |= defconfig; + ret = adm1275_write_pmon_config(data, client, config); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n"); +@@ -524,23 +553,19 @@ static int adm1275_probe(struct i2c_client *client) + u32 shunt; + u32 avg; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) && +- (ret != 2 || strncmp(block_buffer, "SY", 2))) { ++ (ret != 2 || strncmp(block_buffer, "SY", 2)) && ++ (ret != 4 || strncmp(block_buffer, "ROHM", 4))) { + dev_err(&client->dev, "Unsupported Manufacturer ID\n"); + return -ENODEV; + } + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; +@@ -562,6 +587,7 @@ static int adm1275_probe(struct i2c_client *client) + if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || + mid->driver_data == adm1278 || mid->driver_data == adm1281 || + mid->driver_data == adm1293 || mid->driver_data == adm1294 || ++ mid->driver_data == bd12780 || mid->driver_data == bd12790 || + mid->driver_data == sq24905c) + config_read_fn = i2c_smbus_read_word_data; + else +@@ -642,6 +668,7 @@ static int adm1275_probe(struct i2c_client *client) + data->have_power_sampling = true; + + coefficients = adm1272_coefficients; ++ + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ +@@ -666,7 +693,53 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ break; ++ ++ /* ++ * The BD12790 is almost identical to the adm1272. Only the defconfig ++ * and coefficients have minor differences. ++ */ ++ case bd12790: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = bd12790_coefficients; ++ ++ vindex = (config & ADM1275_VRANGE) ? 1 : 0; ++ cindex = (config & ADM1272_IRANGE) ? 3 : 2; ++ /* pindex depends on the combination of the above */ ++ switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { ++ case 0: ++ default: ++ pindex = 4; ++ break; ++ case ADM1275_VRANGE: ++ pindex = 5; ++ break; ++ case ADM1272_IRANGE: ++ pindex = 6; ++ break; ++ case ADM1275_VRANGE | ADM1272_IRANGE: ++ pindex = 7; ++ break; ++ } ++ tindex = 8; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); + if (ret) + return ret; + +@@ -728,13 +801,45 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; ++ ++ /* ++ * The BD12780 is almost functionally identical with the adm1278 above. ++ * Only differences visible to the driver are lack of TSFILT bits and ++ * different identification register contents. ++ */ ++ case bd12780: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = adm1278_coefficients; ++ vindex = 0; ++ cindex = 1; ++ pindex = 2; ++ tindex = 3; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ ++ break; + case adm1293: + case adm1294: + data->have_iout_min = true; +@@ -870,9 +975,27 @@ static int adm1275_probe(struct i2c_client *client) + return pmbus_do_probe(client, info); + } + ++static const struct of_device_id adm1275_of_match[] = { ++ { .compatible = "adi,adm1075", }, ++ { .compatible = "adi,adm1272", }, ++ { .compatible = "adi,adm1273", }, ++ { .compatible = "adi,adm1275", }, ++ { .compatible = "adi,adm1276", }, ++ { .compatible = "adi,adm1278", }, ++ { .compatible = "adi,adm1281", }, ++ { .compatible = "adi,adm1293", }, ++ { .compatible = "adi,adm1294", }, ++ { .compatible = "rohm,bd12780", }, ++ { .compatible = "rohm,bd12790", }, ++ { .compatible = "silergy,mc09c", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, adm1275_of_match); ++ + static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", ++ .of_match_table = adm1275_of_match, + }, + .probe = adm1275_probe, + .id_table = adm1275_id, +diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c +--- a/drivers/hwmon/pmbus/ir36021.c ++++ b/drivers/hwmon/pmbus/ir36021.c +@@ -31,21 +31,13 @@ static int ir36021_probe(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_READ_WORD_DATA +- | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf); +- if (ret < 0) { +- dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); +- return ret; +- } +- if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) { +- dev_err(&client->dev, "MFR_MODEL unrecognised\n"); +- return -ENODEV; +- } ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read PMBUS_MFR_MODEL\n"); ++ if (ret != 1 || buf[0] != 0x2d) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "MFR_MODEL unrecognised\n"); + + return pmbus_do_probe(client, &ir36021_info); + } +diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c +--- a/drivers/hwmon/pmbus/lm25066.c ++++ b/drivers/hwmon/pmbus/lm25066.c +@@ -35,6 +35,7 @@ enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; + #define LM25066_READ_AVG_PIN 0xdf + + #define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ ++#define LM25066_DEV_SETUP_CL_CFG BIT(2) /* Current limit configuration */ + + #define LM25066_SAMPLES_FOR_AVG_MAX 4096 + +@@ -485,6 +486,42 @@ static int lm25066_probe(struct i2c_client *client) + + data->id = (enum chips)(unsigned long)i2c_get_match_data(client); + ++ if (data->id != lm25056) { ++ int config_new = config; ++ const char *cl_setting; ++ int ret; ++ ++ if (!of_property_read_string(client->dev.of_node, ++ "ti,current-range", &cl_setting)) { ++ config_new |= LM25066_DEV_SETUP_CL_CFG; ++ if (strcmp(cl_setting, "high") == 0) { ++ if (data->id == lm25066) ++ config_new |= LM25066_DEV_SETUP_CL; ++ else ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ } else if (strcmp(cl_setting, "low") == 0) { ++ if (data->id == lm25066) ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ else ++ config_new |= LM25066_DEV_SETUP_CL; ++ } else { ++ dev_err(&client->dev, ++ "invalid current-range setting: %s\n", ++ cl_setting); ++ return -EINVAL; ++ } ++ } ++ ++ if (config_new != config) { ++ ret = i2c_smbus_write_byte_data(client, ++ LM25066_DEVICE_SETUP, ++ config_new); ++ if (ret < 0) ++ return ret; ++ config = config_new; ++ } ++ } ++ + info = &data->info; + + info->pages = 1; +diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c +--- a/drivers/hwmon/pmbus/ltc2978.c ++++ b/drivers/hwmon/pmbus/ltc2978.c +@@ -611,17 +611,13 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) + return ret; + if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3))) + return -ENODEV; + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return ret; + for (id = <c2978_id[0]; strlen(id->name); id++) { +@@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- sizeof(buf), buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, ++ buf); + if (ret < 0) + return ret; + +- if (!strncmp(buf + 1, "LT7170", 6) || +- !strncmp(buf + 1, "LT7170-1", 8)) ++ if (ret < 6) ++ return -ENODEV; ++ ++ if (!strncmp(buf, "LT7170", 6)) + return lt7170; +- if (!strncmp(buf + 1, "LT7171", 6) || +- !strncmp(buf + 1, "LT7171-1", 8)) ++ if (!strncmp(buf, "LT7171", 6)) + return lt7171; + + return -ENODEV; +diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c +--- a/drivers/hwmon/pmbus/max20830.c ++++ b/drivers/hwmon/pmbus/max20830.c +@@ -7,12 +7,69 @@ + + #include + #include ++#include + #include + #include + #include "pmbus.h" + + #define MAX20830_IC_DEVICE_ID_LENGTH 9 + ++struct max20830_data { ++ struct pmbus_driver_info info; ++ u32 vout_rfb1; ++ u32 vout_rfb2; ++}; ++ ++static const char * const supported_chip_ids[] = { ++ "MAX20830", ++ "MAX20830C", ++ "MAX20840C", ++}; ++ ++/* ++ * MAX20830 only supports READ_VOUT for VOUT monitoring. ++ * ++ * Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not ++ * supported by this driver and return -ENODATA. This means sysfs attributes ++ * like in1_max, in1_crit, etc. will not be available. Only in1_input (the ++ * scaled output voltage) is supported. ++ * ++ * MAX20830 uses an external resistor divider for voltage sensing: ++ * - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin ++ * - READ_VOUT reports the feedback voltage, which needs to be scaled for actual ++ * output voltage ++ * ++ * Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2) ++ * ++ * If regulator support is added in the future, some adjustments are needed to ++ * ensure correct feedback voltages are set. ++ */ ++static int max20830_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ const struct max20830_data *data = container_of(info, struct max20830_data, info); ++ int ret; ++ u64 temp; ++ ++ switch (reg) { ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ /* Apply voltage divider scaling if resistors are non-zero */ ++ if (data->vout_rfb1 && data->vout_rfb2) { ++ temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2; ++ temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2); ++ ret = clamp_val(temp, 0, 0xFFFF); ++ } ++ return ret; ++ default: ++ return -ENODATA; ++ } ++} ++ + static struct pmbus_driver_info max20830_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, +@@ -23,61 +80,53 @@ static struct pmbus_driver_info max20830_info = { + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, ++ .read_word_data = max20830_read_word_data, ++ .have_pmbus_revision = true, ++ .pmbus_revision = PMBUS_REV_13, + }; + + static int max20830_probe(struct i2c_client *client) + { + u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {}; +- int ret; ++ struct max20830_data *data; ++ int i, ret; + +- if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) && +- !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) +- return -ENODEV; ++ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; + +- /* +- * Use i2c_smbus_read_block_data() if supported, otherwise fall back +- * to i2c_smbus_read_i2c_block_data() to support I2C controllers +- * which do not support SMBus block reads. +- */ +- if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { +- /* Reads 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- } else { +- /* Reads 1 length byte + 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- MAX20830_IC_DEVICE_ID_LENGTH + 1, +- buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- /* +- * Moves data forward, removing the length byte, this is to +- * match the format of i2c_smbus_read_block_data(). +- * Also adjust return value to reflect length byte removal. +- */ +- memmove(buf, buf + 1, MAX20830_IC_DEVICE_ID_LENGTH); +- ret = ret - 1; +- } ++ data->info = max20830_info; + +- /* +- * MAX20830 IC_DEVICE_ID sends string data "MAX20830\0". +- * Return value should at least be 9 bytes of data. +- */ ++ /* Read optional voltage divider resistor values */ ++ device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1); ++ device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2); ++ ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read IC_DEVICE_ID\n"); ++ ++ /* Verify we read the expected number of bytes */ + if (ret < MAX20830_IC_DEVICE_ID_LENGTH) + return dev_err_probe(&client->dev, -ENODEV, +- "IC_DEVICE_ID too short: expected at least 9 bytes, got %d\n", +- ret); ++ "IC_DEVICE_ID too short: expected %d bytes, got %d\n", ++ MAX20830_IC_DEVICE_ID_LENGTH, ret); + +- /* 9 bytes of data, buf[0]-buf[7] = "MAX20830", buf[8] = '\0' */ +- buf[MAX20830_IC_DEVICE_ID_LENGTH - 1] = '\0'; +- if (strncmp(buf, "MAX20830", MAX20830_IC_DEVICE_ID_LENGTH - 1)) ++ /* Null-terminate the string */ ++ buf[ret] = '\0'; ++ ++ /* Verify the device ID matches what we expect */ ++ for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) { ++ if (!strcmp(buf, supported_chip_ids[i])) ++ break; ++ } ++ ++ /* No match found - unsupported device */ ++ if (i == ARRAY_SIZE(supported_chip_ids)) + return dev_err_probe(&client->dev, -ENODEV, +- "Unsupported device: '%s'\n", buf); ++ "Unsupported device: '%*pE'\n", ret, buf); + +- return pmbus_do_probe(client, &max20830_info); ++ return pmbus_do_probe(client, &data->info); + } + + static const struct i2c_device_id max20830_id[] = { +diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c +--- a/drivers/hwmon/pmbus/max34440.c ++++ b/drivers/hwmon/pmbus/max34440.c +@@ -23,6 +23,7 @@ enum chips { + max34441, + max34446, + max34451, ++ max34452, + max34460, + max34461, + }; +@@ -59,6 +60,7 @@ enum chips { + #define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A + + #define MAX34451ETNA6_MFR_REV 0x0012 ++#define MAX34451ETNA8_MFR_REV 0x0014 + + #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 + #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f +@@ -105,16 +107,21 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_MFR_MAX_TEMP_1: + /* +- * MAX34451/ADPM family do not support VIN/IIN limit registers, +- * manufacturer-specific min/max registers, or undercurrent/ +- * undertemperature fault limits. Accessing these triggers CML +- * error and asserts ALERT. ++ * MAX34451/MAX34452/ADPM family do not support VIN/IIN limit ++ * registers, manufacturer-specific min/max registers, or ++ * undercurrent/undertemperature fault limits. Accessing these ++ * triggers CML error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + ret = -ENODATA; + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_VOUT_MIN); +@@ -125,8 +132,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446 && data->id != max34451 && +- data->id != adpm12160 && data->id != adpm12200 && +- data->id != adpm12250) ++ data->id != max34452 && data->id != adpm12160 && ++ data->id != adpm12200 && data->id != adpm12250) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_IOUT_AVG); +@@ -191,6 +198,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit, + word); + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); +@@ -211,8 +222,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && (data->id == max34446 || data->id == max34451 || +- data->id == adpm12160 || data->id == adpm12200 || +- data->id == adpm12250)) ++ data->id == max34452 || data->id == adpm12160 || ++ data->id == adpm12200 || data->id == adpm12250)) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + +@@ -280,12 +291,13 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg) + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Accessing them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -303,12 +315,13 @@ static int max34451_write_byte_data(struct i2c_client *client, int page, + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Writing to them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -339,16 +352,28 @@ static int max34451_set_supported_funcs(struct i2c_client *client, + if (rv < 0) + return rv; + +- if (rv >= MAX34451ETNA6_MFR_REV) { ++ if (data->id == max34451 && rv >= MAX34451ETNA6_MFR_REV) { + max34451_na6 = true; + data->info.format[PSC_VOLTAGE_IN] = direct; + data->info.format[PSC_CURRENT_IN] = direct; +- data->info.m[PSC_VOLTAGE_IN] = 1; ++ + data->info.b[PSC_VOLTAGE_IN] = 0; +- data->info.R[PSC_VOLTAGE_IN] = 3; +- data->info.m[PSC_CURRENT_IN] = 1; + data->info.b[PSC_CURRENT_IN] = 0; +- data->info.R[PSC_CURRENT_IN] = 2; ++ if (rv >= MAX34451ETNA8_MFR_REV) { ++ data->info.m[PSC_VOLTAGE_IN] = 125; ++ data->info.R[PSC_VOLTAGE_IN] = 0; ++ data->info.m[PSC_VOLTAGE_OUT] = 125; ++ data->info.R[PSC_VOLTAGE_OUT] = 0; ++ data->info.m[PSC_CURRENT_IN] = 250; ++ data->info.R[PSC_CURRENT_IN] = -1; ++ data->info.m[PSC_CURRENT_OUT] = 250; ++ data->info.R[PSC_CURRENT_OUT] = -1; ++ } else { ++ data->info.m[PSC_VOLTAGE_IN] = 1; ++ data->info.R[PSC_VOLTAGE_IN] = 3; ++ data->info.m[PSC_CURRENT_IN] = 1; ++ data->info.R[PSC_CURRENT_IN] = 2; ++ } + data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; + } +@@ -665,6 +690,32 @@ static struct pmbus_driver_info max34440_info[] = { + .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, + }, ++ [max34452] = { ++ .pages = 21, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .m[PSC_CURRENT_OUT] = 1, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 2, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 2, ++ /* func 0-15 is set dynamically before probing */ ++ .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .read_byte_data = max34451_read_byte_data, ++ .read_word_data = max34440_read_word_data, ++ .write_byte_data = max34451_write_byte_data, ++ .write_word_data = max34440_write_word_data, ++ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, ++ }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, +@@ -748,7 +799,7 @@ static int max34440_probe(struct i2c_client *client) + data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; + +- if (data->id == max34451) { ++ if (data->id == max34451 || data->id == max34452) { + rv = max34451_set_supported_funcs(client, data); + if (rv) + return rv; +@@ -769,6 +820,7 @@ static const struct i2c_device_id max34440_id[] = { + { .name = "max34441", .driver_data = max34441 }, + { .name = "max34446", .driver_data = max34446 }, + { .name = "max34451", .driver_data = max34451 }, ++ { .name = "max34452", .driver_data = max34452 }, + { .name = "max34460", .driver_data = max34460 }, + { .name = "max34461", .driver_data = max34461 }, + { } +diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c +--- a/drivers/hwmon/pmbus/mp2975.c ++++ b/drivers/hwmon/pmbus/mp2975.c +@@ -176,7 +176,6 @@ mp2975_vid2direct(int vrf, int val) + static u16 mp2975_data2reg_linear11(s64 val) + { + s16 exponent = 0, mantissa; +- bool negative = false; + + /* simple case */ + if (val == 0) +@@ -196,10 +195,6 @@ static u16 mp2975_data2reg_linear11(s64 val) + /* Convert mantissa from milli-units to units */ + mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); + +- /* restore sign */ +- if (negative) +- mantissa = -mantissa; +- + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); + } +diff --git a/drivers/hwmon/pmbus/mpq82d00.c b/drivers/hwmon/pmbus/mpq82d00.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mpq82d00.c +@@ -0,0 +1,249 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00) ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define MPQ82D00_VOUT_DIV 64 ++ ++#define MPQ82D00_PAGE_NUM 1 ++ ++#define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \ ++ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \ ++ PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_INPUT) ++ ++struct mpq82d00_data { ++ struct pmbus_driver_info info; ++ int vout_scale; ++}; ++ ++#define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info) ++ ++static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent) ++{ ++ s16 exponent, mantissa, target_exponent; ++ ++ exponent = ((s16)word) >> 11; ++ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; ++ target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11; ++ ++ /* ++ * The MPQ82D00 does not support negtive limit value, if a negtive ++ * limit value is written, the limit value will become to 0. And ++ * the maximum positive limit value is limitted to 0x3FF. ++ */ ++ if (mantissa < 0) { ++ mantissa = 0; ++ } else { ++ if (exponent > target_exponent) { ++ mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ? ++ mantissa << (exponent - target_exponent) : ++ 0x3FF; ++ } else { ++ mantissa = clamp_val(mantissa >> (target_exponent - exponent), ++ 0, 0x3FF); ++ } ++ } ++ ++ return mantissa | ((expect_exponent << 11) & 0xf800); ++} ++ ++static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ /* ++ * The MPQ82D00 does not follow standard PMBus protocol completely, ++ * and the calculation of vout in this driver is based on direct ++ * format. As a result, the format of vout is enforced to direct. ++ */ ++ ret = PB_VOUT_MODE_DIRECT; ++ break; ++ default: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_STATUS_WORD: ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_POUT: ++ case PMBUS_READ_PIN: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale, ++ MPQ82D00_VOUT_DIV); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other telemetry and limit ++ * value reading, so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg, ++ u16 word) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b00000), so the ++ * exponent of word parameter should be converted to 5'b00000. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x00)); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ /* ++ * The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b11100), so the ++ * exponent of word parameter should be converted to 5'b11100. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x1C)); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other limit value configuration, ++ * so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info) ++{ ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE ++ * bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5 ++ * is 0, it is linear mode, the vout scale is 1.953125mv/LSB. ++ */ ++ if (FIELD_GET(GENMASK(5, 5), ret)) ++ data->vout_scale = 320; ++ else ++ data->vout_scale = 125; ++ ++ return 0; ++} ++ ++static struct pmbus_driver_info mpq82d00_info = { ++ .pages = MPQ82D00_PAGE_NUM, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_CURRENT_OUT] = linear, ++ .format[PSC_TEMPERATURE] = linear, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_POWER] = linear, ++ ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ ++ .func[0] = MPQ82D00_RAIL1_FUNC, ++ .read_word_data = mpq82d00_read_word_data, ++ .read_byte_data = mpq82d00_read_byte_data, ++ .write_word_data = mpq82d00_write_word_data, ++ .identify = mpq82d00_identify, ++}; ++ ++static int mpq82d00_probe(struct i2c_client *client) ++{ ++ struct mpq82d00_data *data; ++ ++ data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info)); ++ ++ return pmbus_do_probe(client, &data->info); ++} ++ ++static const struct i2c_device_id mpq82d00_id[] = { ++ {.name = "mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mpq82d00_id); ++ ++static const struct of_device_id __maybe_unused mpq82d00_of_match[] = { ++ {.compatible = "mps,mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq82d00_of_match); ++ ++static struct i2c_driver mpq82d00_driver = { ++ .driver = { ++ .name = "mpq82d00", ++ .of_match_table = mpq82d00_of_match, ++ }, ++ .probe = mpq82d00_probe, ++ .id_table = mpq82d00_id, ++}; ++ ++module_i2c_driver(mpq82d00_driver); ++ ++MODULE_AUTHOR("Wensheng Wang ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++/* Default cadence for the in-driver alarm-poll fallback (ms) */ ++#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 ++ ++/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ ++#define MPS_CLEAR_LAST_FAULT 0x08 ++#define MPS_MFR_CFG_EXT 0xF5 ++#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6) ++#define MPS_PROTECTION_LAST 0xFB ++ ++/* ++ * PMBus 1.3 NVM commit / revert commands. MPS equivalent of ++ * STORE_ALL (15h) and RESTORE_ALL (16h). ++ */ ++#define PMBUS_STORE_USER_ALL 0x15 ++#define PMBUS_RESTORE_USER_ALL 0x16 ++ ++/* PMBus 1.3 timing / UVLO command codes */ ++#define PMBUS_VIN_ON 0x35 ++#define PMBUS_VIN_OFF 0x36 ++#define PMBUS_TON_DELAY 0x60 ++#define PMBUS_TON_RISE 0x61 ++#define PMBUS_TOFF_DELAY 0x64 ++#define PMBUS_TOFF_FALL 0x65 ++ ++/* MPS vendor-extended observability / identity registers */ ++#define MPS_MFR_CONFIG_ID 0xC0 ++#define MPS_MFR_CONFIG_CODE_REV 0xC1 ++#define MPS_MFR_PRODUCT_REV_USER 0xC2 ++#define MPS_MFR_SILICON_REV 0xC3 ++#define MPS_MFR_RETRY_TIMES 0xF4 ++#define MPS_MFR_VBOOT_CFG 0xFC ++ ++/* ++ * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate ++ * subsequent PMBus writes ++ * bits[1:0] = 00 -- unlocked (POR default) ++ * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21) ++ * so the operator can still DVFS the rail ++ * 11 -- lock all writes ++ * A negative-going PG edge resets these bits to 00, the lock ++ * is operationally reversible without a full chip POR. ++ */ ++#define MPS_MFR_PMBUS_LOCK 0xEE ++ ++/* ++ * Retry parameters for the MFR_CFG_EXT gate-close write after ++ * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this ++ * silicon is about 1 ms; the datasheet does not have information. ++ */ ++#define MPQ8646_NVM_RETRY_MAX 5 ++#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000 ++#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000 ++ ++#define MPQ8646_DEBUG(client, fmt, ...) \ ++ dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) ++ ++/* ++ * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon ++ * The chip uses an 11-bit VOUT feedback-divider scale register. ++ */ ++#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0) ++ ++/* Forward declaration */ ++struct mpq8646_dbg_reg_ctx; ++ ++/* Per-instance state */ ++struct mpq8646_priv { ++ struct pmbus_driver_info info; /* must be first, container_of target */ ++ struct i2c_client *client; ++ ++ /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ ++ struct mutex mps_lock; ++ ++ /* Set alarm_poll_interval_ms = 0 to disable. */ ++ struct delayed_work alarm_poll_work; ++ u32 alarm_poll_interval_ms; ++ ++#ifdef CONFIG_DEBUG_FS ++ /* the only debugfs file that can re-arm the poll worker */ ++ struct dentry *dbg_poll; ++ struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; ++#endif ++}; ++ ++static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ ++ return container_of(info, struct mpq8646_priv, info); ++} ++ ++/* ++ * VID-mode m/b/R coefficients, same values as the mpq8785 driver for ++ * the MPS VID encoding. Per the PMBus Direct formula used by ++ * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields ++ * 1.5625 mV per LSB. ++ */ ++#define MPQ8646_VID_M 64 ++#define MPQ8646_VID_B 0 ++#define MPQ8646_VID_R 1 ++ ++static int mpq8646_identify(struct i2c_client *client, ++ struct pmbus_driver_info *info) ++{ ++ int vout_mode; ++ ++ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); ++ if (vout_mode < 0) ++ return vout_mode; ++ ++ switch (vout_mode & PB_VOUT_MODE_MODE_MASK) { ++ case PB_VOUT_MODE_LINEAR: ++ info->format[PSC_VOLTAGE_OUT] = linear; ++ break; ++ case PB_VOUT_MODE_VID: ++ case PB_VOUT_MODE_DIRECT: ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M; ++ info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B; ++ info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R; ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ return 0; ++}; ++ ++static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg); ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ ret = pmbus_read_byte_data(client, page, reg); ++ MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret); ++ if (ret < 0) ++ return ret; ++ ++ if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID) ++ return PB_VOUT_MODE_DIRECT; ++ ++ return ret; ++ case PMBUS_STATUS_BYTE: ++ case PMBUS_STATUS_CML: ++ case PMBUS_STATUS_OTHER: ++ case PMBUS_STATUS_MFR_SPECIFIC: ++ case PMBUS_STATUS_FAN_12: ++ case PMBUS_STATUS_FAN_34: ++ return -ENXIO; ++ case PMBUS_MFR_LOCATION: ++ case PMBUS_MFR_DATE: ++ case PMBUS_MFR_SERIAL: ++ case PMBUS_IC_DEVICE_ID: ++ case PMBUS_IC_DEVICE_REV: ++ MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); ++ return -ENXIO; ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * Reference: ltc2978.c::ltc2978_write_word_data which uses the ++ * same virtual-register channel for its real chip-side peak reset. ++ */ ++static int mpq8646_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ int rc; ++ ++ switch (reg) { ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n", ++ reg); ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); ++ if (rc < 0) ++ MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc); ++ return rc < 0 ? rc : 0; ++ default: ++ return -ENODATA; /* let pmbus_core do the direct write */ ++ } ++} ++ ++static int mpq8646_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ int rc; ++ ++ MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n", ++ page, phase, reg); ++ ++ switch (reg) { ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_VOUT: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_STATUS_WORD: ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_MFR_VIN_MAX: ++ case PMBUS_MFR_VOUT_MAX: ++ case PMBUS_MFR_IOUT_MAX: ++ case PMBUS_MFR_MAX_TEMP_1: ++ break; ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++ ++ rc = i2c_smbus_read_word_data(client, reg); ++ ++ MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc); ++ return rc; ++} ++ ++static struct pmbus_driver_info mpq8646_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_VOLTAGE_IN] = 4, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 1, ++ .m[PSC_CURRENT_OUT] = 16, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 0, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, ++}; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++static const struct regulator_desc mpq8646_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++}; ++#endif /* CONFIG_REGULATOR */ ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++/* ++ * Expose using ++ * /sys/bus/nvmem/devices//nvmem ++ * ++ * Layout (16 bytes): ++ * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM ++ * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM ++ * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM ++ * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM ++ * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM ++ * offset 9..15 reserved (zero-fill, leaves room for additions) ++ */ ++#define MPQ8646_NVMEM_SIZE 16 ++ ++struct mpq8646_nvmem_entry { ++ unsigned int off; ++ u8 reg; ++ bool is_word; ++}; ++ ++static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = { ++ { 0, MPS_PROTECTION_LAST, true }, ++ { 2, MPS_MFR_RETRY_TIMES, true }, ++ { 4, MPS_MFR_CONFIG_ID, true }, ++ { 6, MPS_MFR_VBOOT_CFG, true }, ++ { 8, MPS_MFR_SILICON_REV, false }, ++}; ++ ++static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val, ++ size_t bytes) ++{ ++ struct mpq8646_priv *priv = data; ++ u8 *out = val; ++ size_t i; ++ ++ if (offset >= MPQ8646_NVMEM_SIZE) ++ return -EINVAL; ++ if (offset + bytes > MPQ8646_NVMEM_SIZE) ++ bytes = MPQ8646_NVMEM_SIZE - offset; ++ ++ memset(out, 0, bytes); ++ ++ guard(mutex)(&priv->mps_lock); ++ for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { ++ const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i]; ++ unsigned int e_start = e->off; ++ unsigned int e_end = e_start + (e->is_word ? 2 : 1); ++ u8 raw[2]; ++ int rc; ++ unsigned int j; ++ ++ if (e_end <= offset || e_start >= offset + bytes) ++ continue; /* outside requested slice */ ++ ++ if (e->is_word) ++ rc = i2c_smbus_read_word_data(priv->client, e->reg); ++ else ++ rc = i2c_smbus_read_byte_data(priv->client, e->reg); ++ if (rc < 0) ++ continue; /* leave the zero-fill in place */ ++ ++ raw[0] = rc & 0xff; ++ raw[1] = (rc >> 8) & 0xff; ++ ++ for (j = 0; j < e_end - e_start; j++) { ++ unsigned int abs = e_start + j; ++ ++ if (abs >= offset && abs < offset + bytes) ++ out[abs - offset] = raw[j]; ++ } ++ } ++ return 0; ++} ++#endif /* CONFIG_NVMEM */ ++ ++static const struct i2c_device_id mpq8646_id[] = { ++ { .name = "mpq8646" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(i2c, mpq8646_id); ++ ++static const struct of_device_id __maybe_unused mpq8646_of_match[] = { ++ { .compatible = "mps,mpq8646" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq8646_of_match); ++ ++static struct pmbus_platform_data mpq8646_no_pec_pdata = { ++ .flags = PMBUS_NO_CAPABILITY, ++}; ++ ++#ifdef CONFIG_DEBUG_FS ++/* ++ * Read-only decode cases in the client's pmbus debugfs directory: ++ * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the ++ * identity/timing registers. ++ */ ++ ++struct mpq_status_bit { ++ u16 mask; ++ const char *name; ++}; ++ ++static const struct mpq_status_bit mpq8646_status_word_bits[] = { ++ { PB_STATUS_VOUT, "VOUT" }, ++ { PB_STATUS_IOUT_POUT, "IOUT_POUT" }, ++ { PB_STATUS_INPUT, "INPUT" }, ++ { PB_STATUS_WORD_MFR, "NVM_SUMMARY" }, ++ { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" }, ++ { PB_STATUS_FANS, "FANS" }, ++ { PB_STATUS_OTHER, "OTHER" }, ++ { PB_STATUS_UNKNOWN, "WATCH_DOG" }, ++ { PB_STATUS_BUSY, "BUSY" }, ++ { PB_STATUS_OFF, "OFF" }, ++ { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" }, ++ { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" }, ++ { PB_STATUS_VIN_UV, "VIN_UV_FAULT" }, ++ { PB_STATUS_TEMPERATURE, "TEMP" }, ++ { PB_STATUS_CML, "CML" }, ++ { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" }, ++ { /* sentinel */ } ++}; ++ ++/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */ ++static const struct mpq_status_bit mpq8646_protection_last_bits[] = { ++ { BIT(15), "INIT_FAULT" }, ++ { BIT(14), "NVM_CRC_ERROR" }, ++ { BIT(13), "NVM_FAULT" }, ++ { BIT(12), "OC_PHASE_FAULT" }, ++ { BIT(11), "OTP_SELF_FAULT" }, ++ { BIT(9), "SWITCH_PRD_FAULT" }, ++ { BIT(8), "VIN_OV_FAULT" }, ++ { BIT(7), "VOUT_OV_FAULT" }, ++ { BIT(6), "VOUT_UV_FAULT" }, ++ { BIT(5), "OC_TOT_FAULT" }, ++ { BIT(4), "VIN_UVLO_FAULT" }, ++ { BIT(3), "DRMOS_OTP" }, ++ { /* sentinel */ } ++}; ++ ++static void mpq8646_print_bits(struct seq_file *s, u16 v, ++ const struct mpq_status_bit *tab) ++{ ++ const struct mpq_status_bit *t; ++ bool first = true; ++ ++ seq_printf(s, "0x%04x", v); ++ if (!v) { ++ seq_puts(s, " [clean]\n"); ++ return; ++ } ++ seq_puts(s, " ["); ++ for (t = tab; t->mask; t++) { ++ if (v & t->mask) { ++ if (!first) ++ seq_putc(s, ' '); ++ seq_puts(s, t->name); ++ first = false; ++ } ++ } ++ seq_puts(s, "]\n"); ++} ++ ++static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ scoped_guard(pmbus_lock, priv->client) ++ rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); ++ if (rc < 0) { ++ seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "STATUS_WORD: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits); ++ seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); ++ ++static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "PROTECTION_LAST: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits); ++ seq_puts(s, "(NVM-backed, survives chip POR)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); ++ ++struct mpq8646_dbg_reg { ++ u8 reg; ++ bool is_word; ++ const char *name; ++}; ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = { ++ /* MPS vendor extensions (read-only identity / observability) */ ++ { MPS_MFR_CONFIG_ID, true, "mfr_config_id" }, ++ { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" }, ++ { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" }, ++ { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" }, ++ { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" }, ++ /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ ++ { PMBUS_VIN_ON, true, "vin_on" }, ++ { PMBUS_VIN_OFF, true, "vin_off" }, ++ { PMBUS_TON_DELAY, true, "ton_delay" }, ++ { PMBUS_TON_RISE, true, "ton_rise" }, ++ { PMBUS_TOFF_DELAY, true, "toff_delay" }, ++ { PMBUS_TOFF_FALL, true, "toff_fall" }, ++}; ++ ++struct mpq8646_dbg_reg_ctx { ++ struct mpq8646_priv *priv; ++ const struct mpq8646_dbg_reg *desc; ++}; ++ ++static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n", ++ ctx->desc->reg, ctx->desc->name, rc); ++ return 0; ++ } ++ seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); ++ ++#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE ++/* ++ * Write/provisioning data, disabled by default: NVM commit and ++ * revert, the CLEAR_LAST_FAULT sequences and a small set of named ++ * writable registers. ++ */ ++ ++static void mpq8646_unsafe_banner(struct mpq8646_priv *priv) ++{ ++ dev_warn(&priv->client->dev, ++ "**********************************************************\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "** **\n" ++ "** The MPQ8646 provisioning debugfs writes are enabled. **\n" ++ "** Wrong register writes can and likely will physically **\n" ++ "** damage or destroy the chip and/or the board. **\n" ++ "** **\n" ++ "** If you see this message and you are not debugging **\n" ++ "** the kernel, report this immediately to your system **\n" ++ "** administrator! **\n" ++ "** **\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "**********************************************************\n"); ++} ++ ++static int mpq8646_dbg_clear_protection_last(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops, ++ NULL, mpq8646_dbg_clear_protection_last, "%llu\n"); ++ ++static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc, ret; ++ int wp_orig, cfg_orig; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ guard(mutex)(&priv->mps_lock); ++ ++ wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); ++ if (wp_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n", ++ wp_orig); ++ return wp_orig; ++ } ++ cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); ++ if (cfg_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n", ++ cfg_orig); ++ return cfg_orig; ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, 0); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP clear failed (%d), aborting\n", ++ rc); ++ return rc; ++ } ++ } ++ ++ ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, ++ (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); ++ if (ret < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: gate open failed (%d)\n", ++ ret); ++ } else { ++ ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (ret < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n", ++ ret); ++ ++ for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { ++ rc = i2c_smbus_write_word_data(priv->client, ++ MPS_MFR_CFG_EXT, ++ (u16)cfg_orig); ++ if (rc >= 0) ++ break; ++ usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN, ++ MPQ8646_NVM_RETRY_DELAY_US_MAX); ++ } ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, ++ (u8)wp_orig); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP restore failed (%d)\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ return ret; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops, ++ NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n"); ++ ++static int mpq8646_dbg_store_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "store_all: STORE_USER_ALL (0x15) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops, ++ NULL, mpq8646_dbg_store_all, "%llu\n"); ++ ++static int mpq8646_dbg_restore_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, ++ NULL, mpq8646_dbg_restore_all, "%llu\n"); ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = { ++ /* PMBus 1.3 control / margin */ ++ { PMBUS_ON_OFF_CONFIG, false, "on_off_config" }, ++ { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" }, ++ { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" }, ++ /* MPS PMBus-level write-protect */ ++ { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" }, ++ /* MPS user-writable product revision */ ++ { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" }, ++}; ++ ++static int mpq8646_dbg_reg_get(void *data, u64 *val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ if (rc < 0) ++ return rc; ++ *val = rc; ++ return 0; ++} ++ ++static int mpq8646_dbg_reg_set(void *data, u64 val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_write_word_data(ctx->priv->client, ++ ctx->desc->reg, (u16)val); ++ else ++ rc = i2c_smbus_write_byte_data(ctx->priv->client, ++ ctx->desc->reg, (u8)val); ++ return rc < 0 ? rc : 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops, ++ mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n"); ++ ++static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx; ++ size_t i; ++ ++ mpq8646_unsafe_banner(priv); ++ ++ debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv, ++ &mpq8646_dbg_clear_protection_last_fops); ++ debugfs_create_file_unsafe("clear_protection_last_force", 0200, root, ++ priv, ++ &mpq8646_dbg_clear_protection_last_force_fops); ++ debugfs_create_file_unsafe("store_all", 0200, root, priv, ++ &mpq8646_dbg_store_all_fops); ++ debugfs_create_file_unsafe("restore_all", 0200, root, priv, ++ &mpq8646_dbg_restore_all_fops); ++ ++ ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs_unsafe), ++ sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) { ++ ctx[i].priv = priv; ++ ctx[i].desc = &mpq8646_dbg_regs_unsafe[i]; ++ debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name, ++ 0600, root, &ctx[i], ++ &mpq8646_dbg_reg_rw_fops); ++ } ++} ++#else ++static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) {} ++#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */ ++ ++static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) ++{ ++ struct mpq8646_priv *priv = data; ++ ++ *val = priv->alarm_poll_interval_ms; ++ return 0; ++} ++ ++static int mpq8646_dbg_poll_interval_set(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ bool was_off = !priv->alarm_poll_interval_ms; ++ ++ priv->alarm_poll_interval_ms = (u32)val; ++ ++ if (val && was_off && !priv->client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies((u32)val)); ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops, ++ mpq8646_dbg_poll_interval_get, ++ mpq8646_dbg_poll_interval_set, "%llu\n"); ++ ++static void mpq8646_debugfs_register(struct mpq8646_priv *priv) ++{ ++ struct dentry *root; ++ size_t i; ++ ++ root = pmbus_get_debugfs_dir(priv->client); ++ if (!root) ++ return; ++ ++ /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */ ++ debugfs_create_file("status_decoded", 0400, root, priv, ++ &mpq8646_dbg_status_decoded_fops); ++ debugfs_create_file("protection_last", 0400, root, priv, ++ &mpq8646_dbg_protection_last_fops); ++ priv->dbg_poll = ++ debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600, ++ root, priv, ++ &mpq8646_dbg_poll_interval_fops); ++ ++ priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs), ++ sizeof(*priv->dbg_reg_ctx), ++ GFP_KERNEL); ++ if (!priv->dbg_reg_ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { ++ priv->dbg_reg_ctx[i].priv = priv; ++ priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i]; ++ debugfs_create_file(mpq8646_dbg_regs[i].name, 0400, ++ root, &priv->dbg_reg_ctx[i], ++ &mpq8646_dbg_reg_fops); ++ } ++ ++ mpq8646_debugfs_register_unsafe(priv, root); ++} ++ ++static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) ++{ ++ debugfs_remove(priv->dbg_poll); ++} ++#else ++static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {} ++static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {} ++#endif /* CONFIG_DEBUG_FS */ ++ ++static void mpq8646_alarm_poll_work(struct work_struct *work) ++{ ++ struct mpq8646_priv *priv = container_of(to_delayed_work(work), ++ struct mpq8646_priv, ++ alarm_poll_work); ++ ++ if (priv->client->irq) ++ return; /* SMBALERT# wired; polling not needed */ ++ ++ if (!priv->alarm_poll_interval_ms) ++ return; /* polling disabled; don't re-arm */ ++ ++ pmbus_check_and_notify_faults(priv->client); ++ ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++} ++ ++static int mpq8646_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ struct mpq8646_priv *priv; ++ u32 voltage_scale; ++ int ret; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ priv->client = client; ++ mutex_init(&priv->mps_lock); ++ memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); ++ info = &priv->info; ++ ++ info->identify = mpq8646_identify; ++ info->read_byte_data = mpq8646_read_byte_data; ++ info->read_word_data = mpq8646_read_word_data; ++ info->write_word_data = mpq8646_write_word_data; ++ dev->platform_data = &mpq8646_no_pec_pdata; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++ info->reg_desc = mpq8646_reg_desc; ++ info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc); ++#endif ++ ++ INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); ++ priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT; ++ ++ if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", ++ &voltage_scale)) { ++ if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX) ++ return -EINVAL; ++ ++ ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, ++ voltage_scale); ++ if (ret) ++ return ret; ++ } ++ ++ ret = pmbus_do_probe(client, info); ++ if (ret) ++ return ret; ++ ++ mpq8646_debugfs_register(priv); ++ ++ if (!client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++ { ++ struct nvmem_config cfg = { ++ .dev = &client->dev, ++ .name = dev_name(&client->dev), ++ .owner = THIS_MODULE, ++ .read_only = true, ++ .root_only = true, ++ .word_size = 1, ++ .stride = 1, ++ .size = MPQ8646_NVMEM_SIZE, ++ .reg_read = mpq8646_nvmem_read, ++ .priv = priv, ++ }; ++ struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg); ++ ++ if (IS_ERR(nv)) ++ dev_warn(&client->dev, ++ "nvmem snapshot register failed (%pe)\n", ++ nv); ++ } ++#endif ++ return 0; ++}; ++ ++static void mpq8646_remove(struct i2c_client *client) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ ++ mpq8646_debugfs_unregister(priv); ++ cancel_delayed_work_sync(&priv->alarm_poll_work); ++} ++ ++static struct i2c_driver mpq8646_driver = { ++ .driver = { ++ .name = "mpq8646", ++ .of_match_table = of_match_ptr(mpq8646_of_match), ++ }, ++ .probe = mpq8646_probe, ++ .remove = mpq8646_remove, ++ .id_table = mpq8646_id, ++}; ++ ++module_i2c_driver(mpq8646_driver); ++ ++MODULE_AUTHOR("Vincent Jardin "); ++MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h +--- a/drivers/hwmon/pmbus/pmbus.h ++++ b/drivers/hwmon/pmbus/pmbus.h +@@ -421,10 +421,12 @@ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; + enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; + + /* PMBus revision identifiers */ +-#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ +-#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ +-#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ +-#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ ++#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ ++#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ ++#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_131 0x44 /* PMBus revision 1.3.1 */ ++#define PMBUS_REV_14 0x55 /* PMBus revision 1.4 */ + + /* Operation type flags for pmbus_update_ts */ + #define PMBUS_OP_WRITE BIT(0) +@@ -489,6 +491,16 @@ struct pmbus_driver_info { + int access_delay; /* in microseconds */ + int write_delay; /* in microseconds */ + int page_change_delay; /* in microseconds */ ++ ++ /* ++ * Some chips do not support the PMBUS_REVISION command. ++ * Drivers for such chips can report the supported PMBus revision here. ++ * ++ * Drivers must set have_pmbus_revision to true and provide the ++ * supported PMBus version in pmbus_revision. ++ */ ++ bool have_pmbus_revision; /* true if pmbus_revision is valid */ ++ u8 pmbus_revision; /* PMBus revision */ + }; + + /* Regulator ops */ +@@ -550,6 +562,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); + void pmbus_wait(struct i2c_client *client); + void pmbus_update_ts(struct i2c_client *client, int op); + int pmbus_set_page(struct i2c_client *client, int page, int phase); ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf); + int pmbus_read_word_data(struct i2c_client *client, int page, int phase, + u8 reg); + int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, +@@ -561,6 +574,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, + int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + u8 mask, u8 value); + void pmbus_clear_faults(struct i2c_client *client); ++void pmbus_check_and_notify_faults(struct i2c_client *client); + bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); + bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); + int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -96,7 +96,8 @@ struct pmbus_data { + + u32 flags; /* from platform data */ + +- u8 revision; /* The PMBus revision the device is compliant with */ ++ bool have_pmbus_revision; ++ u8 revision; /* The PMBus revision the device is compliant with */ + + int exponent[PMBUS_PAGES]; + /* linear mode: exponent for output voltages */ +@@ -181,7 +182,12 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); + void pmbus_wait(struct i2c_client *client) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); ++ s64 delay; ++ ++ if (!data) ++ return; ++ ++ delay = ktime_us_delta(data->next_access_backoff, ktime_get()); + + if (delay > 0) + fsleep(delay); +@@ -192,8 +198,14 @@ EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); + void pmbus_update_ts(struct i2c_client *client, int op) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- const struct pmbus_driver_info *info = data->info; +- int delay = info->access_delay; ++ const struct pmbus_driver_info *info; ++ int delay; ++ ++ if (!data) ++ return; ++ ++ info = data->info; ++ delay = info->access_delay; + + if (op & PMBUS_OP_WRITE) + delay = max(delay, info->write_delay); +@@ -517,6 +529,81 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + } + EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS"); + ++/** ++ * pmbus_read_smbus_i2c_block_data() - Read SMBus/I2C block data ++ * @client: Handle to slave device ++ * @reg: Byte interpreted by slave ++ * @data_buf: Byte array into which data will be read ++ * Return: Negative errno or number of bytes read ++ * ++ * PMBus internal function to read a SMBus block from a PMBus chip. ++ * ++ * PMBus chips report various properties using SMBus block read operations. ++ * However, not all I2C controllers support this operation. ++ * ++ * Execute SMBus block read if supported. If not supported, but SMBus I2C block ++ * read is supported, use it instead. Note that at most 31 data bytes can be ++ * read from the device if i2c_smbus_read_i2c_block_data() is used to read the ++ * data. This is a SMBUs protocol limit which can not be avoided. ++ * ++ * Return -EOPNOTSUPP if neither I2C_FUNC_SMBUS_READ_BLOCK_DATA nor ++ * I2C_FUNC_SMBUS_READ_I2C_BLOCK is supported. ++ * ++ * Callers must hold pmbus_lock or execute calls from the probe function. ++ */ ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf) ++{ ++ u8 buf[I2C_SMBUS_BLOCK_MAX]; ++ int blen, len, ret; ++ ++ if (i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { ++ pmbus_wait(client); ++ ret = i2c_smbus_read_block_data(client, reg, data_buf); ++ pmbus_update_ts(client, 0); ++ return ret; ++ } ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { ++ dev_err_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_I2C_BLOCK\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ /* ++ * If the returned data is valid SMBus block data, the first byte ++ * must be the data length. ++ * ++ * i2c_smbus_read_i2c_block_data() may return an error if the chip ++ * sends NACK before the number of requested bytes is received. ++ * Handle this by reading the data length first, then reading the ++ * entire message up to I2C_SMBUS_BLOCK_MAX bytes. This ensures ++ * that requested number of bytes never exceeds the number of ++ * bytes sent by the chip. ++ */ ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ ++ len = buf[0]; ++ if (len == 0) ++ return 0; ++ blen = len; ++ if (len >= I2C_SMBUS_BLOCK_MAX) ++ len = I2C_SMBUS_BLOCK_MAX - 1; ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, len + 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ if (buf[0] != blen) ++ return -EIO; ++ memcpy(data_buf, buf + 1, len); ++ return len; ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_read_smbus_i2c_block_data, "PMBUS"); ++ + static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + char *data_buf) + { +@@ -526,11 +613,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + if (rv < 0) + return rv; + +- pmbus_wait(client); +- rv = i2c_smbus_read_block_data(client, reg, data_buf); +- pmbus_update_ts(client, 0); +- +- return rv; ++ return pmbus_read_smbus_i2c_block_data(client, reg, data_buf); + } + + static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, +@@ -2849,9 +2932,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, + if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) + pmbus_init_wp(client, data); + +- ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); +- if (ret >= 0) +- data->revision = ret; ++ if (info->have_pmbus_revision) { ++ data->have_pmbus_revision = true; ++ data->revision = info->pmbus_revision; ++ } else { ++ ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); ++ if (ret >= 0) { ++ data->have_pmbus_revision = true; ++ data->revision = ret; ++ } ++ } + + if (data->info->pages) + pmbus_clear_faults(client); +@@ -3455,10 +3545,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, + return ret; + } + +-static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++void pmbus_check_and_notify_faults(struct i2c_client *client) + { +- struct pmbus_data *data = pdata; +- struct i2c_client *client = to_i2c_client(data->dev); ++ struct pmbus_data *data = i2c_get_clientdata(client); + int i, status, event; + + guard(pmbus_lock)(client); +@@ -3471,6 +3560,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) + } + + pmbus_clear_faults(client); ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS"); ++ ++static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++{ ++ struct pmbus_data *data = pdata; ++ struct i2c_client *client = to_i2c_client(data->dev); ++ ++ pmbus_check_and_notify_faults(client); + + return IRQ_HANDLED; + } +@@ -3514,10 +3612,8 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data) + /* Register notifiers */ + err = devm_request_threaded_irq(dev, client->irq, NULL, pmbus_fault_handler, + IRQF_ONESHOT, "pmbus-irq", data); +- if (err) { +- dev_err(dev, "failed to request an irq %d\n", err); ++ if (err) + return err; +- } + + return 0; + } +@@ -3543,6 +3639,17 @@ static int pmbus_debugfs_get(void *data, u64 *val) + DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, + "0x%02llx\n"); + ++static int pmbus_debugfs_get_revision(void *data, u64 *val) ++{ ++ struct pmbus_data *pdata = data; ++ ++ *val = pdata->revision; ++ ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_revision_ops, pmbus_debugfs_get_revision, NULL, ++ "0x%02llx\n"); ++ + static int pmbus_debugfs_get_status(void *data, u64 *val) + { + struct pmbus_debugfs_entry *entry = data; +@@ -3698,14 +3805,9 @@ static void pmbus_init_debugfs(struct i2c_client *client, + &entries[idx++], + &pmbus_debugfs_ops); + } +- if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) { +- entries[idx].client = client; +- entries[idx].page = 0; +- entries[idx].reg = PMBUS_REVISION; +- debugfs_create_file("pmbus_revision", 0444, debugfs, +- &entries[idx++], +- &pmbus_debugfs_ops); +- } ++ if (data->have_pmbus_revision) ++ debugfs_create_file("pmbus_revision", 0444, debugfs, data, ++ &pmbus_debugfs_revision_ops); + + for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) { + const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i]; +diff --git a/drivers/hwmon/pmbus/sq24860.c b/drivers/hwmon/pmbus/sq24860.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/sq24860.c +@@ -0,0 +1,430 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Author: Ziming Zhu ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define SQ24860_IIN_CAL_GAIN 0x38 ++#define SQ24860_READ_VAUX 0xd0 ++#define SQ24860_READ_VIN_MIN 0xd1 ++#define SQ24860_READ_VIN_PEAK 0xd2 ++#define SQ24860_READ_IIN_PEAK 0xd4 ++#define SQ24860_READ_PIN_PEAK 0xd5 ++#define SQ24860_READ_TEMP_AVG 0xd6 ++#define SQ24860_READ_TEMP_PEAK 0xd7 ++#define SQ24860_READ_VOUT_MIN 0xda ++#define SQ24860_READ_VIN_AVG 0xdc ++#define SQ24860_READ_VOUT_AVG 0xdd ++#define SQ24860_READ_IIN_AVG 0xde ++#define SQ24860_READ_PIN_AVG 0xdf ++#define SQ24860_VIREF 0xe0 ++#define SQ24860_PK_MIN_AVG 0xea ++#define PK_MIN_AVG_RST_PEAK BIT(7) ++#define PK_MIN_AVG_RST_AVG BIT(6) ++#define PK_MIN_AVG_RST_MIN BIT(5) ++#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0) ++#define SQ24860_MFR_WRITE_PROTECT 0xf8 ++#define SQ24860_UNLOCKED BIT(7) ++ ++#define SQ24860_8B_SHIFT 2 ++#define SQ24860_IIN_OCF_NUM 1000000 ++#define SQ24860_IIN_OCF_DIV 129278 ++#define SQ24860_IIN_OCF_OFF 165 ++ ++#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ ++ PK_MIN_AVG_RST_AVG | \ ++ PK_MIN_AVG_RST_MIN) ++#define SQ24860_MAX_SAMPLES BIT(FIELD_MAX(PK_MIN_AVG_AVG_CNT)) ++/* ++ * Arbitrary default Rimon value: 1.6kOhm ++ */ ++#define SQ24860_DEFAULT_RIMON 1600000000 ++#define SQ24860_GIMON 18180 ++ ++#define SQ24860_VAUX_DIV 20 ++ ++static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon) ++{ ++ u64 temp = 6400ULL * 1000000000ULL * 1000ULL; ++ u64 denom; ++ u64 word; ++ ++ if (!rimon) ++ return -EINVAL; ++ ++ denom = (u64)rimon * SQ24860_GIMON; ++ word = div64_u64(temp, denom); ++ if (!word || word > U16_MAX) ++ return -EINVAL; ++ ++ return i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN, ++ (u16)word); ++} ++ ++static int sq24860_mfr_write_protect_set(struct i2c_client *client, ++ u8 protect) ++{ ++ u8 val; ++ ++ switch (protect) { ++ case 0: ++ val = 0xa2; ++ break; ++ case PB_WP_ALL: ++ val = 0x0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT, ++ val); ++} ++ ++static int sq24860_mfr_write_protect_get(struct i2c_client *client) ++{ ++ int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT); ++ ++ if (ret < 0) ++ return ret; ++ ++ return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL; ++} ++ ++static int sq24860_read_word_data(struct i2c_client *client, ++ int page, int phase, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VMON: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VAUX); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV); ++ break; ++ ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ /* ++ * These registers provide an 8 bits value instead of a ++ * 10bits one. Just shifting twice the register value is ++ * enough to make the sensor type conversion work, even ++ * if the datasheet provides different m, b and R for ++ * those. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ break; ++ ret <<= SQ24860_8B_SHIFT; ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ /* ++ * VIREF directly sets the over-current limit at which the eFuse ++ * will turn the FET off and trigger a fault. Expose it through ++ * this generic property instead of a manufacturer specific one. ++ */ ++ ret = pmbus_read_byte_data(client, page, SQ24860_VIREF); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM, ++ SQ24860_IIN_OCF_DIV); ++ ret += SQ24860_IIN_OCF_OFF; ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG); ++ if (ret < 0) ++ break; ++ ret = BIT(FIELD_GET(PK_MIN_AVG_AVG_CNT, ret)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ ret = 0; ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_word_data(struct i2c_client *client, ++ int page, int reg, u16 value) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ value = max_t(s16, (s16)value, 0); ++ value >>= SQ24860_8B_SHIFT; ++ value = clamp_val(value, 0, 0xff); ++ ret = pmbus_write_word_data(client, page, reg, value); ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ value = max_t(s16, (s16)value, SQ24860_IIN_OCF_OFF); ++ value -= SQ24860_IIN_OCF_OFF; ++ value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV, ++ SQ24860_IIN_OCF_NUM); ++ value = clamp_val(value, 0, 0x3f); ++ ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value); ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ value = clamp_val(value, 1, SQ24860_MAX_SAMPLES); ++ value = ilog2(value); ++ ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_AVG_CNT, ++ FIELD_PREP(PK_MIN_AVG_AVG_CNT, value)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ /* ++ * SQ24860 has history resets based on MIN/AVG/PEAK instead of per ++ * sensor type. Exposing this quirk in hwmon is not desirable so ++ * reset MIN, AVG and PEAK together. Even is there effectively only ++ * one reset, which resets everything, expose the 5 entries so ++ * userspace is not required map a sensor type to another to trigger ++ * a reset ++ */ ++ ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_RST_MASK, ++ PK_MIN_AVG_RST_MASK); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_read_byte_data(struct i2c_client *client, ++ int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_get(client); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_byte_data(struct i2c_client *client, ++ int page, int reg, u8 byte) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_set(client, byte); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++static const struct regulator_desc sq24860_reg_desc[] = { ++ PMBUS_REGULATOR_ONE_NODE("vout"), ++}; ++#endif ++ ++static const struct pmbus_driver_info sq24860_base_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .m[PSC_VOLTAGE_IN] = 64, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 0, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 64, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 0, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ /* ++ * Current and power measurements depend on the calibration gain ++ * programmed from the board-specific IMON resistor value. ++ */ ++ .format[PSC_CURRENT_IN] = direct, ++ .m[PSC_CURRENT_IN] = 16, ++ .b[PSC_CURRENT_IN] = 0, ++ .R[PSC_CURRENT_IN] = 0, ++ .format[PSC_POWER] = direct, ++ .m[PSC_POWER] = 2, ++ .b[PSC_POWER] = 0, ++ .R[PSC_POWER] = 0, ++ .func[0] = PMBUS_HAVE_VIN | ++ PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_VMON | ++ PMBUS_HAVE_IIN | ++ PMBUS_HAVE_PIN | ++ PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_SAMPLES, ++ .read_word_data = sq24860_read_word_data, ++ .write_word_data = sq24860_write_word_data, ++ .read_byte_data = sq24860_read_byte_data, ++ .write_byte_data = sq24860_write_byte_data, ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++ .reg_desc = sq24860_reg_desc, ++ .num_regulators = ARRAY_SIZE(sq24860_reg_desc), ++#endif ++}; ++ ++static const struct i2c_device_id sq24860_i2c_id[] = { ++ { "sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id); ++ ++static const struct of_device_id sq24860_of_match[] = { ++ { .compatible = "silergy,sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, sq24860_of_match); ++ ++static int sq24860_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ u32 rimon; ++ int ret; ++ ++ if (device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon)) ++ rimon = SQ24860_DEFAULT_RIMON; ++ ret = sq24860_write_iin_cal_gain(client, rimon); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to set gain\n"); ++ info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static struct i2c_driver sq24860_driver = { ++ .driver = { ++ .name = "sq24860", ++ .of_match_table = sq24860_of_match, ++ }, ++ .probe = sq24860_probe, ++ .id_table = sq24860_i2c_id, ++}; ++module_i2c_driver(sq24860_driver); ++ ++MODULE_AUTHOR("Ziming Zhu "); ++MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/vt7505.c b/drivers/hwmon/pmbus/vt7505.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/vt7505.c +@@ -0,0 +1,287 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and ++ * Volterra VT7505 PMBus controllers. ++ * ++ * Copyright 2026 Hewlett Packard Enterprise Development LP ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define VT7505_MFR_CONFIG 0xd0 ++#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14) ++ ++#define VT7505_MFR_PEAK_VIN 0xd1 ++#define VT7505_MFR_PEAK_IOUT 0xd2 ++#define VT7505_MFR_PEAK_PIN 0xd3 ++#define VT7505_MFR_PEAK_TEMP 0xd4 ++#define VT7505_MFR_CLEAR_PEAKS 0xd5 ++#define VT7505_MFR_PEAK_VOUT 0xfd ++ ++#define VT7505_RLOAD_DEFAULT 4750 ++/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */ ++#define VT7505_RLOAD_MAX 283383959U ++ ++struct vt7505_chip_data { ++ int temp_m; ++ int temp_b; ++ bool has_ocp_filter; ++}; ++ ++static const struct vt7505_chip_data max16545_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++}; ++ ++static const struct vt7505_chip_data max16550_data = { ++ .temp_m = 199, ++ .temp_b = 7046, ++ .has_ocp_filter = true, ++}; ++ ++static const struct vt7505_chip_data vt7505_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++ .has_ocp_filter = true, ++}; ++ ++static int vt7505_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VIN); ++ case PMBUS_VIRT_READ_IOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_IOUT); ++ case PMBUS_VIRT_READ_PIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_PIN); ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_TEMP); ++ case PMBUS_VIRT_READ_VOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VOUT); ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++} ++ ++static int vt7505_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ switch (reg) { ++ /* ++ * A single reset command clears all peak values. CLEAR_PEAKS is a ++ * send-byte command; the device NAKs a word or byte-data write to it. ++ */ ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return pmbus_write_byte(client, page, ++ VT7505_MFR_CLEAR_PEAKS); ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * None of these controllers implement the standard PMBus WRITE_PROTECT ++ * (0x10) register, so tell the core not to access it. ++ */ ++static struct pmbus_platform_data vt7505_pdata = { ++ .flags = PMBUS_NO_WRITE_PROTECT, ++}; ++ ++static int vt7505_set_ocp_filter(struct i2c_client *client) ++{ ++ u32 ocp_us; ++ u8 field; ++ int ret; ++ u16 word; ++ ++ if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us", ++ &ocp_us)) ++ return 0; ++ ++ switch (ocp_us) { ++ case 0: ++ field = 0; ++ break; ++ case 1: ++ field = 1; ++ break; ++ case 2: ++ field = 2; ++ break; ++ case 10: ++ field = 3; ++ break; ++ default: ++ return dev_err_probe(&client->dev, -EINVAL, ++ "invalid adi,ocp-severe-filter-us value %u\n", ++ ocp_us); ++ } ++ ++ ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to read MFR_CONFIG\n"); ++ ++ word = ret & ~VT7505_CFG_OCP_S_FILT_MASK; ++ word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field); ++ ++ ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to write MFR_CONFIG\n"); ++ ++ return 0; ++} ++ ++static void vt7505_set_m(int *m, u32 rload) ++{ ++ u64 val = (u64)*m * rload; ++ ++ /* rload is range-checked in probe, so the result fits in int. */ ++ *m = DIV_ROUND_CLOSEST_ULL(val, 1000); ++} ++ ++static int vt7505_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ const struct vt7505_chip_data *chip; ++ struct pmbus_driver_info *info; ++ u32 rload; ++ int ret; ++ ++ chip = i2c_get_match_data(client); ++ if (!chip) ++ return -ENODEV; ++ ++ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ dev->platform_data = &vt7505_pdata; ++ ++ /* ++ * The m coefficient used in the direct-format current and power ++ * calculations depends on RLOAD, the external current-report resistor ++ * connected between the ILOAD pin and ground. Use the default value if ++ * none is specified. ++ */ ++ if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload)) ++ rload = VT7505_RLOAD_DEFAULT; ++ ++ if (!rload || rload > VT7505_RLOAD_MAX) ++ return dev_err_probe(dev, -EINVAL, ++ "adi,rload-ohms must be 1-%u\n", ++ VT7505_RLOAD_MAX); ++ ++ info->pages = 1; ++ info->read_word_data = vt7505_read_word_data; ++ info->write_word_data = vt7505_write_word_data; ++ ++ info->format[PSC_VOLTAGE_IN] = direct; ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->format[PSC_CURRENT_IN] = direct; ++ info->format[PSC_CURRENT_OUT] = direct; ++ info->format[PSC_POWER] = direct; ++ info->format[PSC_TEMPERATURE] = direct; ++ ++ /* ++ * Direct data format coefficients from the device datasheet ("PMBus ++ * Equation Parameters"). The current and power m coefficients scale ++ * with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving ++ * m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The ++ * temperature coefficients are chip specific (see the chip data). ++ */ ++ info->m[PSC_VOLTAGE_IN] = 7578; ++ info->R[PSC_VOLTAGE_IN] = -2; ++ info->m[PSC_VOLTAGE_OUT] = 7578; ++ info->R[PSC_VOLTAGE_OUT] = -2; ++ info->m[PSC_CURRENT_IN] = 3824; ++ info->b[PSC_CURRENT_IN] = -4300; ++ info->R[PSC_CURRENT_IN] = -3; ++ info->m[PSC_CURRENT_OUT] = 3824; ++ info->b[PSC_CURRENT_OUT] = -4300; ++ info->R[PSC_CURRENT_OUT] = -3; ++ info->m[PSC_POWER] = 895; ++ info->b[PSC_POWER] = -9100; ++ info->R[PSC_POWER] = -2; ++ ++ vt7505_set_m(&info->m[PSC_CURRENT_IN], rload); ++ vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload); ++ vt7505_set_m(&info->m[PSC_POWER], rload); ++ ++ info->m[PSC_TEMPERATURE] = chip->temp_m; ++ info->b[PSC_TEMPERATURE] = chip->temp_b; ++ info->R[PSC_TEMPERATURE] = -2; ++ ++ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_IIN | PMBUS_HAVE_PIN; ++ ++ /* ++ * The severe OCP deglitch filter is programmable on the MAX16550 and ++ * the VT7505, but fixed on the MAX16545. ++ */ ++ if (chip->has_ocp_filter) { ++ ret = vt7505_set_ocp_filter(client); ++ if (ret) ++ return ret; ++ } ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id vt7505_id[] = { ++ { .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data }, ++ { .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data }, ++ { .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, vt7505_id); ++ ++static const struct of_device_id vt7505_of_match[] = { ++ { .compatible = "adi,max16545", .data = &max16545_data }, ++ { .compatible = "adi,max16550", .data = &max16550_data }, ++ { .compatible = "adi,vt7505", .data = &vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, vt7505_of_match); ++ ++static struct i2c_driver vt7505_driver = { ++ .driver = { ++ .name = "vt7505", ++ .of_match_table = vt7505_of_match, ++ }, ++ .probe = vt7505_probe, ++ .id_table = vt7505_id, ++}; ++module_i2c_driver(vt7505_driver); ++ ++MODULE_AUTHOR("Georgi Vlaev "); ++MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c +--- a/drivers/hwmon/pmbus/xdpe1a2g7b.c ++++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c +@@ -57,6 +57,13 @@ static int xdpe1a2g7b_identify(struct i2c_client *client, + return 0; + } + ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++static const struct regulator_desc xdpe1a2g7b_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++ PMBUS_REGULATOR("vout", 1), ++}; ++#endif ++ + static struct pmbus_driver_info xdpe1a2g7b_info = { + .pages = XDPE1A2G7B_PAGE_NUM, + .identify = xdpe1a2g7b_identify, +@@ -72,6 +79,10 @@ static struct pmbus_driver_info xdpe1a2g7b_info = { + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++ .num_regulators = XDPE1A2G7B_PAGE_NUM, ++ .reg_desc = xdpe1a2g7b_reg_desc, ++#endif + }; + + static int xdpe1a2g7b_probe(struct i2c_client *client) +diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c +--- a/drivers/hwmon/pwm-fan.c ++++ b/drivers/hwmon/pwm-fan.c +@@ -634,12 +634,8 @@ static int pwm_fan_probe(struct platform_device *pdev) + if (tach->irq > 0) { + ret = devm_request_irq(dev, tach->irq, pulse_handler, + IRQF_NO_THREAD, pdev->name, tach); +- if (ret) { +- dev_err(dev, +- "Failed to request interrupt: %d\n", +- ret); ++ if (ret) + return ret; +- } + } + + if (!ctx->pulses_per_revolution[i]) { +diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c +--- a/drivers/hwmon/sht15.c ++++ b/drivers/hwmon/sht15.c +@@ -981,10 +981,8 @@ static int sht15_probe(struct platform_device *pdev) + IRQF_TRIGGER_FALLING, + "sht15 data", + data); +- if (ret) { +- dev_err(&pdev->dev, "failed to get irq for data line\n"); ++ if (ret) + goto err_release_reg; +- } + disable_irq_nosync(gpiod_to_irq(data->data)); + ret = sht15_connection_reset(data); + if (ret) +diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c +--- a/drivers/hwmon/sht3x.c ++++ b/drivers/hwmon/sht3x.c +@@ -62,7 +62,7 @@ static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 }; + #define SHT3X_MAX_HUMIDITY 100000 + + enum sht3x_chips { +- sht3x, ++ sht3x = 1, + sts3x, + }; + +@@ -940,8 +940,19 @@ static const struct i2c_device_id sht3x_ids[] = { + + MODULE_DEVICE_TABLE(i2c, sht3x_ids); + ++static const struct of_device_id sht3x_of_match[] = { ++ { .compatible = "sensirion,sht30", .data = (void *)(uintptr_t)sht3x }, ++ { .compatible = "sensirion,sts30", .data = (void *)(uintptr_t)sts3x }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(of, sht3x_of_match); ++ + static struct i2c_driver sht3x_i2c_driver = { +- .driver.name = "sht3x", ++ .driver = { ++ .name = "sht3x", ++ .of_match_table = sht3x_of_match, ++ }, + .probe = sht3x_probe, + .id_table = sht3x_ids, + }; +diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c +--- a/drivers/hwmon/tmp102.c ++++ b/drivers/hwmon/tmp102.c +@@ -396,6 +396,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume + + static const struct i2c_device_id tmp102_id[] = { + { .name = "tmp102" }, ++ { .name = "tmp110" }, ++ { .name = "tmp113" }, + { } + }; + MODULE_DEVICE_TABLE(i2c, tmp102_id); +diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c +--- a/drivers/hwmon/tmp401.c ++++ b/drivers/hwmon/tmp401.c +@@ -695,7 +695,7 @@ static int tmp401_probe(struct i2c_client *client) + info->type = hwmon_chip; + info->config = data->chip_channel_config; + +- data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; ++ data->chip_channel_config[0] = HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL; + + info = &data->temp_info; + info->type = hwmon_temp; +diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/tvs-mpfs.c +@@ -0,0 +1,390 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Author: Lars Randers ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MPFS_TVS_CTRL 0x08 ++#define MPFS_TVS_OUTPUT0 0x24 ++#define MPFS_TVS_OUTPUT1 0x28 ++ ++#define MPFS_TVS_CTRL_TEMP_VALID BIT(19) ++#define MPFS_TVS_CTRL_V2P5_VALID BIT(18) ++#define MPFS_TVS_CTRL_V1P8_VALID BIT(17) ++#define MPFS_TVS_CTRL_V1P05_VALID BIT(16) ++ ++#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3) ++#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2) ++#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1) ++#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0) ++#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0) ++ ++/* ++ * For all of these the value in millivolts is stored in 16 bits, with an upper ++ * sign bit and a lower 3 bits of decimal. These masks discard the sign bit and ++ * decimal places, because if Linux is running these voltages cannot be negative ++ * and so avoid having to convert to two's complement. ++ */ ++#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19) ++#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3) ++#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3) ++ ++/* ++ * The register map claims that the temperature is stored in bits 31:16, but ++ * application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor" ++ * says that 31 is reserved. Temperature is in kelvin, so what's probably a ++ * sign bit has no value anyway. ++ */ ++#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16) ++ ++#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8) ++#define MPFS_TVS_INTERVAL_OFFSET 8 ++/* The interval register is in increments of 32 us */ ++#define MPFS_TVS_INTERVAL_SCALE 32 ++/* with 254 usable increments of 32 us available, 8 ms is the integer limit */ ++#define MPFS_TVS_INTERVAL_MAX_MS 8 ++ ++/* 273.1875 in 11.4 fixed-point notation */ ++#define MPFS_TVS_K_TO_C 0x1113 ++ ++enum mpfs_tvs_sensors { ++ SENSOR_V1P05 = 0, ++ SENSOR_V1P8, ++ SENSOR_V2P5, ++}; ++ ++static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" }; ++ ++struct mpfs_tvs { ++ struct regmap *regmap; ++}; ++ ++static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr, ++ int channel, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_in_input && attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V2P5_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp); ++ break; ++ case SENSOR_V1P8: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P8_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp); ++ break; ++ case SENSOR_V1P05: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P05_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr, ++ int channel, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V2P5_ENABLE, tmp); ++ break; ++ case SENSOR_V1P8: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P8_ENABLE, tmp); ++ break; ++ case SENSOR_V1P05: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P05_ENABLE, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_temp_input && attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ if (attr == hwmon_temp_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control); ++ return 0; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_TEMP_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp); ++ *val -= MPFS_TVS_K_TO_C; ++ *val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */ ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_TEMP_ENABLE, tmp); ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp); ++ *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp); ++ *val *= MPFS_TVS_INTERVAL_SCALE; ++ *val = roundup(*val, 1000); ++ *val /= 1000; ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ long temp = val; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS); ++ ++ temp *= 1000; ++ temp /= MPFS_TVS_INTERVAL_SCALE; ++ ++ temp <<= MPFS_TVS_INTERVAL_OFFSET; ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_INTERVAL_MASK, temp); ++ ++ return 0; ++} ++ ++static umode_t mpfs_tvs_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ if (type == hwmon_chip && attr == hwmon_chip_update_interval) ++ return 0644; ++ ++ if (type == hwmon_temp) { ++ switch (attr) { ++ case hwmon_temp_enable: ++ return 0644; ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ if (type == hwmon_in) { ++ switch (attr) { ++ case hwmon_in_enable: ++ return 0644; ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_read(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_read(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_read(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_write(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_write(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_write(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_read_labels(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, ++ const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ *str = "Die Temp"; ++ return 0; ++ case hwmon_in: ++ *str = mpfs_tvs_voltage_labels[channel]; ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static const struct hwmon_ops mpfs_tvs_ops = { ++ .is_visible = mpfs_tvs_is_visible, ++ .read_string = mpfs_tvs_read_labels, ++ .read = mpfs_tvs_read, ++ .write = mpfs_tvs_write, ++}; ++ ++static const struct hwmon_channel_info *mpfs_tvs_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_ENABLE), ++ HWMON_CHANNEL_INFO(in, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE), ++ NULL ++}; ++ ++static const struct hwmon_chip_info mpfs_tvs_chip_info = { ++ .ops = &mpfs_tvs_ops, ++ .info = mpfs_tvs_info, ++}; ++ ++static int mpfs_tvs_probe(struct platform_device *pdev) ++{ ++ struct device *hwmon_dev; ++ struct mpfs_tvs *data; ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->regmap = device_node_to_regmap(pdev->dev.parent->of_node); ++ if (IS_ERR(data->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap), ++ "Failed to find syscon regmap\n"); ++ ++ /* ++ * It's an MMIO regmap with no resources, there's nothing that can fail ++ * and return an error ++ */ ++ regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs", ++ data, ++ &mpfs_tvs_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev), ++ "hwmon device registration failed.\n"); ++ ++ return 0; ++} ++ ++static struct platform_driver mpfs_tvs_driver = { ++ .probe = mpfs_tvs_probe, ++ .driver = { ++ .name = "mpfs-tvs", ++ }, ++}; ++module_platform_driver(mpfs_tvs_driver); ++ ++MODULE_AUTHOR("Lars Randers "); ++MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c +--- a/drivers/hwmon/xgene-hwmon.c ++++ b/drivers/hwmon/xgene-hwmon.c +@@ -133,10 +133,6 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) + init_completion(&ctx->rd_complete); + ctx->resp_pending = true; + +- /* Write signature for subspace */ +- WRITE_ONCE(generic_comm_base->signature, +- cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx)); +- + /* Write to the shared command region */ + WRITE_ONCE(generic_comm_base->command, + cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR)); +diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c +--- a/drivers/hwmon/yogafan.c ++++ b/drivers/hwmon/yogafan.c +@@ -53,6 +53,7 @@ struct yoga_fan_data { + }; + + /* Specific configurations mapped via DMI */ ++ + static const struct yogafan_config yoga_8bit_fans_cfg = { + .multiplier = 100, + .fan_count = 1, +@@ -71,6 +72,24 @@ static const struct yogafan_config legion_16bit_dual_cfg = { + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } + }; + ++static const struct yogafan_config loq_15iax9_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config xiaoxin_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config yoga_pro_7_14iah10_cfg = { ++ .multiplier = 100, ++ .fan_count = 1, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL } ++}; ++ + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) + { + ktime_t now = ktime_get_boottime(); +@@ -170,6 +189,54 @@ static const struct hwmon_chip_info yoga_fan_chip_info = { + }; + + static const struct dmi_system_id yogafan_quirks[] = { ++ { ++ .ident = "Lenovo LOQ 15IAX9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), ++ }, ++ .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo XiaoXin Pro 13ARE 2020", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "XiaoXinPro-13ARE 2020"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo IdeaPad 3 15ALC6", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6"), ++ }, ++ .driver_data = (void *)&ideapad_8bit_fan0_cfg, ++ }, ++ { ++ .ident = "Lenovo Legion Pro 7 16AFR10H", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion Pro 7 16AFR10H"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga Pro 7 14IAH10", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga Pro 7 14IAH10"), ++ }, ++ .driver_data = (void *)&yoga_pro_7_14iah10_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga 7 16ARP8", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 7 16ARP8"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, + { + .ident = "Lenovo Yoga", + .matches = { +diff --git a/include/linux/hwmon-sysfs.h b/include/linux/hwmon-sysfs.h +--- a/include/linux/hwmon-sysfs.h ++++ b/include/linux/hwmon-sysfs.h +@@ -15,10 +15,10 @@ struct sensor_device_attribute{ + int index; + }; + #define to_sensor_dev_attr(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute, dev_attr) + +-#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ ++#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ + .index = _index } + + #define SENSOR_ATTR_RO(_name, _func, _index) \ +@@ -49,11 +49,11 @@ struct sensor_device_attribute_2 { + u8 nr; + }; + #define to_sensor_dev_attr_2(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute_2, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute_2, dev_attr) + +-#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ +- .index = _index, \ ++#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ ++ .index = _index, \ + .nr = _nr } + + #define SENSOR_ATTR_2_RO(_name, _func, _nr, _index) \ diff --git a/pkgbuilds/linux-omarchy-eevdf/0740-hwmon-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0740-hwmon-updates.patch.sig new file mode 100644 index 0000000..9a153ff Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0740-hwmon-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0741-hwmon-fixes-1.patch b/pkgbuilds/linux-omarchy-eevdf/0741-hwmon-fixes-1.patch new file mode 100644 index 0000000..15533e0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0741-hwmon-fixes-1.patch @@ -0,0 +1,179 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -1481,9 +1481,9 @@ static int __init applesmc_init(void) + applesmc_fan_config[smcreg.fan_count] = 0; + applesmc_pwm_config[smcreg.fan_count] = 0; + +- applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); +- applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); +- applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); ++ applesmc_info_temp = kzalloc_obj(*applesmc_info_temp); ++ applesmc_info_fan = kzalloc_obj(*applesmc_info_fan); ++ applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm); + if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { + ret = -ENOMEM; + goto out_info; +@@ -1498,7 +1498,7 @@ static int __init applesmc_init(void) + applesmc_info_pwm->type = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + +- applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ applesmc_info_arr = kzalloc_objs(*applesmc_info_arr, 4); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; +@@ -1509,7 +1509,7 @@ static int __init applesmc_init(void) + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + +- applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ applesmc_chip = kzalloc_obj(*applesmc_chip); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; +@@ -1519,10 +1519,9 @@ static int __init applesmc_init(void) + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ +- fan_safe_attrs = kcalloc(smcreg.fan_count, +- sizeof(*fan_safe_attrs), GFP_KERNEL); +- fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, +- sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ fan_safe_attrs = kzalloc_objs(*fan_safe_attrs, smcreg.fan_count); ++ fan_safe_attr_list = kzalloc_objs(*fan_safe_attr_list, ++ smcreg.fan_count + 1); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -184,6 +184,7 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { ++ unsigned long flags; + int ret; + + /* +@@ -191,9 +192,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_bh(&priv->status_report_request_lock); ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); + reinit_completion(status_completion); +- spin_unlock_bh(&priv->status_report_request_lock); ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -421,15 +422,18 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); ++ unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- return 0; ++ goto unlock; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -443,7 +447,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- return 0; ++ goto unlock; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -455,7 +459,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- return 0; ++ goto unlock; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -474,7 +478,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- return 0; ++ goto unlock; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -484,7 +488,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- return 0; ++ goto unlock; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -503,7 +507,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- return 0; ++ goto unlock; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -512,6 +516,8 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + ++unlock: ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -318,6 +318,11 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + return 1; + } + ++static bool is_hwmon_device(struct device *dev) ++{ ++ return dev->class == &hwmon_class; ++} ++ + #if IS_REACHABLE(CONFIG_I2C) + + /* +@@ -338,7 +343,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + + static int hwmon_match_device(struct device *dev, const void *data) + { +- return dev->class == &hwmon_class; ++ return is_hwmon_device(dev); + } + + static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, +@@ -781,6 +786,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, + const char *template; + int base; + ++ if (WARN(!is_hwmon_device(dev), "%s is not a hardware monitoring device\n", ++ dev_name(dev))) ++ return -EINVAL; + if (type >= ARRAY_SIZE(__templates)) + return -EINVAL; + if (attr >= __templates_size[type]) diff --git a/pkgbuilds/linux-omarchy-eevdf/0741-hwmon-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0741-hwmon-fixes-1.patch.sig new file mode 100644 index 0000000..11778c9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0741-hwmon-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0742-hwmon-fixes-2.patch b/pkgbuilds/linux-omarchy-eevdf/0742-hwmon-fixes-2.patch new file mode 100644 index 0000000..9552822 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0742-hwmon-fixes-2.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c +--- a/drivers/hwmon/k10temp.c ++++ b/drivers/hwmon/k10temp.c +@@ -523,7 +523,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) + } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { +- case 0x00 ... 0x2f: /* Zen5 Turin */ ++ case 0x00 ... 0x1f: /* Zen5 Turin */ + data->ccd_offset = 0x1F0; + k10temp_get_ccd_support(data, 16); + break; diff --git a/pkgbuilds/linux-omarchy-eevdf/0742-hwmon-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0742-hwmon-fixes-2.patch.sig new file mode 100644 index 0000000..db34c4b Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0742-hwmon-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch b/pkgbuilds/linux-omarchy-eevdf/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch rename to pkgbuilds/linux-omarchy-eevdf/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch diff --git a/pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig diff --git a/pkgbuilds/linux-omarchy/0800-platform-updates.patch b/pkgbuilds/linux-omarchy-eevdf/0800-platform-updates.patch similarity index 92% rename from pkgbuilds/linux-omarchy/0800-platform-updates.patch rename to pkgbuilds/linux-omarchy-eevdf/0800-platform-updates.patch index bb6a9de..c6018e3 100644 --- a/pkgbuilds/linux-omarchy/0800-platform-updates.patch +++ b/pkgbuilds/linux-omarchy-eevdf/0800-platform-updates.patch @@ -364,78 +364,6 @@ diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/am 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 @@ -520,20 +448,6 @@ diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig 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 @@ -1795,19 +1709,6 @@ diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsm + */ +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 @@ -3763,41 +3664,6 @@ diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-w { 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 @@ -3832,30 +3698,6 @@ diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c /* 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 @@ -4164,43 +4006,6 @@ diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell 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 @@ -4251,35 +4056,6 @@ diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/del } 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 @@ -4415,21 +4191,6 @@ diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujits 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 @@ -4463,22 +4224,17 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c #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 = { +@@ -133,24 +142,41 @@ 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; + struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; + const struct hp_wmi_fan_profile_params *fan_profile; +}; + @@ -4487,41 +4243,30 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c +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[] = { + static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params *active_board_params; +@@ -181,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", "88F7", "88FD", "88FE", "88FF", "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", @@ -4530,33 +4275,21 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c "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 = { - { +@@ -217,6 +243,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, -- .driver_data = (void *)&omen_v1_legacy_thermal_params, -+ .driver_data = (void *)&omen_v1_legacy_board_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, - }, - { + .driver_data = (void *)&omen_v1_legacy_board_params, +@@ -225,6 +255,14 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, -- .driver_data = (void *)&omen_v1_legacy_thermal_params, -+ .driver_data = (void *)&omen_v1_legacy_board_params, -+ }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, + .driver_data = (void *)&omen_v1_board_params, @@ -4564,107 +4297,33 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c + { + .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, - }, - { + .driver_data = (void *)&omen_v1_board_params, +@@ -233,6 +271,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, -- .driver_data = (void *)&victus_s_thermal_params, -+ .driver_data = (void *)&victus_s_board_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, - }, - { + .driver_data = (void *)&victus_s_board_params, +@@ -289,6 +331,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, -- .driver_data = (void *)&omen_v1_no_ec_thermal_params, -+ .driver_data = (void *)&omen_v1_no_ec_board_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 { + .driver_data = (void *)&omen_v1_no_ec_board_params, +@@ -360,6 +406,7 @@ enum hp_wmi_commandtype { HPWMI_POSTCODEERROR_QUERY = 0x2a, HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, @@ -4672,7 +4331,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c }; struct victus_power_limits { -@@ -504,9 +575,9 @@ struct hp_wmi_hwmon_priv { +@@ -528,9 +575,9 @@ struct hp_wmi_hwmon_priv { struct mutex lock; /* protects mode, pwm */ u8 min_rpm; u8 max_rpm; @@ -4684,7 +4343,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c struct delayed_work keep_alive_dwork; }; -@@ -833,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) +@@ -857,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) return enabled; } @@ -4719,7 +4378,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -891,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) { @@ -4730,7 +4389,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c } 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) +@@ -1152,12 +1197,134 @@ static int camera_shutter_input_setup(void) return err; } @@ -4865,7 +4524,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c static struct attribute *hp_wmi_attrs[] = { &dev_attr_display.attr, -@@ -1142,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { +@@ -1166,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { &dev_attr_dock.attr, &dev_attr_tablet.attr, &dev_attr_postcode.attr, @@ -4873,7 +4532,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c NULL, }; ATTRIBUTE_GROUPS(hp_wmi); -@@ -1793,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) +@@ -1817,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) return is_victus_s_board; } @@ -4912,77 +4571,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -2436,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) switch (priv->mode) { case PWM_MODE_MAX: @@ -4991,7 +4580,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -2448,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); return 0; case PWM_MODE_MANUAL: @@ -5011,7 +4600,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2481,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, { switch (type) { case hwmon_pwm: @@ -5027,7 +4616,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2510,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, priv = dev_get_drvdata(dev); switch (type) { case hwmon_fan: @@ -5038,7 +4627,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2520,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, return 0; case hwmon_pwm: if (attr == hwmon_pwm_input) { @@ -5051,7 +4640,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2550,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct hp_wmi_hwmon_priv *priv; @@ -5069,7 +4658,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2566,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, /* ensure PWM input is within valid fan speeds */ rpm = pwm_to_rpm(val, priv); rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); @@ -5081,7 +4670,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2574,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, priv->mode = PWM_MODE_MAX; return hp_wmi_apply_fan_settings(priv); case PWM_MODE_MANUAL: @@ -5107,7 +4696,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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, +@@ -2599,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, static const struct hwmon_channel_info * const info[] = { HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), @@ -5116,7 +4705,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c NULL }; -@@ -2608,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) +@@ -2640,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) struct victus_s_fan_table *fan_table; u8 min_rpm, max_rpm; u8 cpu_rpm, gpu_rpm, noise_db; @@ -5134,7 +4723,7 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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) +@@ -2686,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) if (min_rpm == U8_MAX || max_rpm == 0) return -EINVAL; @@ -5145,54 +4734,6 @@ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c 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 @@ -6042,7 +5583,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c #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"); +@@ -78,26 +88,29 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); #define WMBB_USB_CHARGE 0x10B #define WMBB_BATT_LIMIT 0x10C @@ -6084,8 +5625,8 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c -#define INIT_SPARSE_KEYMAP 0x80 static int battery_limit_use_wmbb; -+static bool kbd_backlight_available; - static struct led_classdev kbd_backlight; + static bool kbd_backlight_available; +@@ -105,16 +118,65 @@ static struct led_classdev kbd_backlight; static enum led_brightness get_kbd_backlight_level(struct device *dev); static const struct key_entry wmi_keymap[] = { @@ -6158,7 +5699,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c static int ggov(u32 arg0) { union acpi_object args[1]; -@@ -154,30 +217,97 @@ static int ggov(u32 arg0) +@@ -155,30 +217,97 @@ static int ggov(u32 arg0) return res; } @@ -6273,13 +5814,14 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } 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 +@@ -212,76 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u return (union acpi_object *)buffer.pointer; } -static void wmi_notify(union acpi_object *obj, void *context) -{ - long data = (long)context; +- unsigned int brightness; - - pr_debug("event guid %li\n", data); - if (!obj) @@ -6290,8 +5832,11 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c - struct key_entry *key; - - if (eventcode == 0x10000000) { -- led_classdev_notify_brightness_hw_changed( -- &kbd_backlight, get_kbd_backlight_level(kbd_backlight.dev->parent)); +- if (kbd_backlight_available) { +- brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); +- led_classdev_notify_brightness_hw_changed(&kbd_backlight, +- brightness); +- } - } else { - key = sparse_keymap_entry_from_scancode( - wmi_input_dev, eventcode); @@ -6346,7 +5891,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c int ret; ret = kstrtoul(buffer, 10, &value); -@@ -285,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, +@@ -290,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, if (value >= 3) return -EINVAL; @@ -6361,7 +5906,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c return count; } -@@ -296,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, +@@ -301,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr, char *buffer) { @@ -6390,7 +5935,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } static ssize_t usb_charge_store(struct device *dev, -@@ -361,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, +@@ -366,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, const char *buffer, size_t count) { bool value; @@ -6441,7 +5986,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } static ssize_t fn_lock_store(struct device *dev, -@@ -403,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, +@@ -408,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, const char *buffer, size_t count) { bool value; @@ -6491,7 +6036,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } 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, +@@ -458,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, if (value == 100 || value == 80) { union acpi_object *r; @@ -6516,7 +6061,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c return count; } -@@ -471,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, +@@ -476,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, struct device_attribute *attr, char *buf) { @@ -6527,7 +6072,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c 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, +@@ -490,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, } status = r->buffer.pointer[0x10]; @@ -6552,7 +6097,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c if (status != 80 && status != 100) status = 0; -@@ -563,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { +@@ -568,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { static void tpad_led_set(struct led_classdev *cdev, enum led_brightness brightness) { @@ -6564,7 +6109,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } static enum led_brightness tpad_led_get(struct led_classdev *cdev) -@@ -579,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); +@@ -584,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); static void kbd_backlight_set(struct led_classdev *cdev, enum led_brightness brightness) { @@ -6644,7 +6189,7 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c } 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) +@@ -634,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); @@ -6822,42 +6367,36 @@ diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c 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) +@@ -865,10 +1036,6 @@ 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 = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); +- if (ret) +- goto out_platform_device; +- + /* LEDs are optional */ + ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); + if (ret < 0) +@@ -878,7 +1045,14 @@ static int acpi_probe(struct platform_device *pdev) + + devm_led_classdev_register(&pdev->dev, &tpad_led); + +- wmi_input_setup(); + ret = lg_laptop_input_init(&pdev->dev, device->handle); + if (ret < 0) + goto out_platform_device; + - ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); - if (ret) - goto out_platform_device; - -- /* LEDs are optional */ -- led_classdev_register(&pf_device->dev, &kbd_backlight); -- led_classdev_register(&pf_device->dev, &tpad_led); -- -- wmi_input_setup(); ++ ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); ++ if (ret) ++ goto out_platform_device; ++ battery_hook_register(&battery_hook); return 0; -@@ -884,11 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) - { +@@ -895,7 +1069,6 @@ 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); diff --git a/pkgbuilds/linux-omarchy-eevdf/0800-platform-updates.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0800-platform-updates.patch.sig new file mode 100644 index 0000000..37ef2d1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/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-eevdf/0801-amd-pmf-util-unbind-use-after-free.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch rename to pkgbuilds/linux-omarchy-eevdf/0801-amd-pmf-util-unbind-use-after-free.patch diff --git a/pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0801-amd-pmf-util-unbind-use-after-free.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0801-amd-pmf-util-unbind-use-after-free.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0801-amd-pmf-util-unbind-use-after-free.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0802-platform-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0802-platform-fixes.patch new file mode 100644 index 0000000..b7d82cc --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0802-platform-fixes.patch @@ -0,0 +1,240 @@ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -17,7 +17,6 @@ + #include + #include "pmf.h" + +-#ifdef CONFIG_AMD_PMF_DEBUG + u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { +@@ -31,6 +30,7 @@ u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + } + EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + ++#ifdef CONFIG_AMD_PMF_DEBUG + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) + { + int i; +diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c +--- a/drivers/platform/x86/x86-android-tablets/core.c ++++ b/drivers/platform/x86/x86-android-tablets/core.c +@@ -156,7 +156,6 @@ static struct platform_device **pdevs; + static struct serdev_device **serdevs; + static const struct software_node **gpio_button_swnodes; + static const struct software_node **swnode_group; +-static const struct software_node **gpiochip_node_group; + static void (*exit_handler)(void); + + static __init struct i2c_adapter * +@@ -362,6 +361,15 @@ static const struct software_node *cherryview_gpiochip_node_group[] = { + NULL + }; + ++const struct software_node crystalcove_gpiochip_node = { ++ .name = "INT33FD:00", ++}; ++ ++static const struct software_node *crystalcove_gpiochip_node_group[] = { ++ &crystalcove_gpiochip_node, ++ NULL ++}; ++ + static void gpio_secondary_unset(void *data) + { + struct device *dev = data; +@@ -377,27 +385,28 @@ static void gpio_secondary_unregister_node_group(void *data) + software_node_unregister_node_group(nodes); + } + +-static int gpio_secondary_fwnode_init(struct device *parent) ++static int gpio_secondary_fwnode_init(struct device *parent, ++ const struct software_node * const *node_group) + { + const struct software_node *const *swnode; + struct fwnode_handle *fwnode; + struct device *phys_dev; + int ret; + +- if (!gpiochip_node_group) ++ if (!node_group) + return 0; + +- ret = software_node_register_node_group(gpiochip_node_group); ++ ret = software_node_register_node_group(node_group); + if (ret) + return ret; + + ret = devm_add_action_or_reset(parent, + gpio_secondary_unregister_node_group, +- gpiochip_node_group); ++ (void *)node_group); + if (ret) + return ret; + +- for (swnode = gpiochip_node_group; *swnode; swnode++) { ++ for (swnode = node_group; *swnode; swnode++) { + struct device *dev __free(put_device) = + acpi_bus_find_device_by_name((*swnode)->name); + if (!dev) +@@ -460,6 +469,7 @@ static void x86_android_tablet_remove(struct platform_device *pdev) + + static __init int x86_android_tablet_probe(struct platform_device *pdev) + { ++ const struct software_node * const *gpiochip_node_group; + const struct x86_dev_info *dev_info; + const struct dmi_system_id *id; + int i, ret = 0; +@@ -491,12 +501,20 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) + break; + } + +- ret = gpio_secondary_fwnode_init(&pdev->dev); ++ ret = gpio_secondary_fwnode_init(&pdev->dev, gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + ++ if (dev_info->has_crystalcove) { ++ ret = gpio_secondary_fwnode_init(&pdev->dev, crystalcove_gpiochip_node_group); ++ if (ret) { ++ x86_android_tablet_remove(pdev); ++ return ret; ++ } ++ } ++ + ret = software_node_register_node_group(dev_info->swnode_group); + if (ret) { + x86_android_tablet_remove(pdev); +diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c +--- a/drivers/platform/x86/x86-android-tablets/lenovo.c ++++ b/drivers/platform/x86/x86-android-tablets/lenovo.c +@@ -61,13 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { + .initial_brightness = 128, + }; + +-static const struct software_node arizona_gpiochip_node = { +- .name = "arizona", +-}; +- +-static const struct software_node crystalcove_gpiochip_node = { +- .name = "gpio_crystalcove", +-}; + + /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ + +@@ -416,15 +409,17 @@ static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __ini + + #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" + ++static const struct software_node lenovo_yoga_tab2_830_1050_wm5102; ++ + static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", + &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", +- &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ &lenovo_yoga_tab2_830_1050_wm5102, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", +- &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), ++ &lenovo_yoga_tab2_830_1050_wm5102, 4, GPIO_ACTIVE_LOW), + { } + }; + +@@ -432,14 +427,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { + .properties = lenovo_yoga_tab2_830_1050_wm1502_props, + }; + +-static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { +- &crystalcove_gpiochip_node, +- &arizona_gpiochip_node, +- &lenovo_yoga_tab2_830_1050_wm5102, +- &generic_lipo_hv_4v35_battery_node, +- NULL +-}; +- + static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); + static void lenovo_yoga_tab2_830_1050_exit(void); + +@@ -455,8 +442,9 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { + .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_830_1050_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -800,8 +788,9 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { + .pdev_info = lenovo_yoga_tab2_1380_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_1380_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -985,13 +974,15 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { + }, + }; + ++static const struct software_node lenovo_yt3_wm5102; ++ + static const struct property_entry lenovo_yt3_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", + &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), +- PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &lenovo_yt3_wm5102, 2, GPIO_ACTIVE_HIGH), + { } + }; + +@@ -1000,11 +991,6 @@ static const struct software_node lenovo_yt3_wm5102 = { + .name = "wm5102", + }; + +-static const struct software_node *lenovo_yt3_swnodes[] = { +- &arizona_gpiochip_node, +- &lenovo_yt3_wm5102, +- NULL +-}; + + static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { + { +@@ -1072,7 +1058,6 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { + .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), + .spi_dev_info = lenovo_yt3_spi_devs, + .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), +- .swnode_group = lenovo_yt3_swnodes, + .modules = lenovo_yt3_modules, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, + .init = lenovo_yt3_init, +diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +--- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h ++++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +@@ -96,6 +96,7 @@ struct x86_dev_info { + int (*init)(struct device *dev); + void (*exit)(void); + bool use_pci; ++ bool has_crystalcove; + enum x86_gpiochip_type gpiochip_type; + }; + +@@ -107,6 +108,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); + /* Software nodes representing GPIO chips used by various tablets */ + extern const struct software_node baytrail_gpiochip_nodes[]; + extern const struct software_node cherryview_gpiochip_nodes[]; ++extern const struct software_node crystalcove_gpiochip_node; + + /* + * Extern declarations of x86_dev_info structs so there can be a single diff --git a/pkgbuilds/linux-omarchy-eevdf/0802-platform-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0802-platform-fixes.patch.sig new file mode 100644 index 0000000..f2ddd7a Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0802-platform-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0803-acpi-battery-hook-api-acpi-prefix.patch b/pkgbuilds/linux-omarchy-eevdf/0803-acpi-battery-hook-api-acpi-prefix.patch new file mode 100644 index 0000000..c3246f0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0803-acpi-battery-hook-api-acpi-prefix.patch @@ -0,0 +1,412 @@ +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -736,7 +736,7 @@ static LIST_HEAD(acpi_battery_list); + static LIST_HEAD(battery_hook_list); + static DEFINE_MUTEX(hook_mutex); + +-static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -753,7 +753,7 @@ static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + pr_info("hook unregistered: %s\n", hook->name); + } + +-void battery_hook_unregister(struct acpi_battery_hook *hook) ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) + { + mutex_lock(&hook_mutex); + /* +@@ -762,13 +762,13 @@ void battery_hook_unregister(struct acpi_battery_hook *hook) + * adding a new battery. + */ + if (!list_empty(&hook->list)) +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_unregister); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); + +-void battery_hook_register(struct acpi_battery_hook *hook) ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -789,7 +789,7 @@ void battery_hook_register(struct acpi_battery_hook *hook) + * hooks. + */ + pr_err("hook failed to load: %s", hook->name); +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + goto end; + } + +@@ -799,22 +799,22 @@ void battery_hook_register(struct acpi_battery_hook *hook) + end: + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_register); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); + +-static void devm_battery_hook_unregister(void *data) ++static void devm_acpi_battery_hook_unregister(void *data) + { + struct acpi_battery_hook *hook = data; + +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) ++int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) + { +- battery_hook_register(hook); ++ acpi_battery_hook_register(hook); + +- return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook); ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); + } +-EXPORT_SYMBOL_GPL(devm_battery_hook_register); ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); + + /* + * This function gets called right after the battery sysfs +@@ -843,7 +843,7 @@ static void battery_hook_add_battery(struct acpi_battery *battery) + */ + pr_err("error in hook, unloading: %s", + hook_node->name); +- battery_hook_unregister_unlocked(hook_node); ++ acpi_battery_hook_unregister_unlocked(hook_node); + } + } + mutex_unlock(&hook_mutex); +@@ -876,7 +876,7 @@ static void __exit battery_hook_exit(void) + * need to remove the hooks. + */ + list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + mutex_destroy(&hook_mutex); + } +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -1681,14 +1681,14 @@ static void asus_wmi_battery_init(struct asus_wmi *asus) + asus->battery_rsoc_available = false; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) { + asus->battery_rsoc_available = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + } + } + + static void asus_wmi_battery_exit(struct asus_wmi *asus) + { + if (asus->battery_rsoc_available) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* LEDs ***********************************************************************/ +diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c +--- a/drivers/platform/x86/ayaneo-ec.c ++++ b/drivers/platform/x86/ayaneo-ec.c +@@ -493,7 +493,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev) + data->battery_hook.add_battery = ayaneo_add_battery; + data->battery_hook.remove_battery = ayaneo_remove_battery; + data->battery_hook.name = "Ayaneo Battery"; +- ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook); ++ ret = devm_acpi_battery_hook_register(&pdev->dev, &data->battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c +--- a/drivers/platform/x86/dell/dell-laptop.c ++++ b/drivers/platform/x86/dell/dell-laptop.c +@@ -2446,13 +2446,13 @@ static void __init dell_battery_init(struct device *dev) + battery_supported_modes = battery_get_supported_modes(); + + if (battery_supported_modes != 0) +- battery_hook_register(&dell_battery_hook); ++ acpi_battery_hook_register(&dell_battery_hook); + } + + static void dell_battery_exit(void) + { + if (battery_supported_modes != 0) +- battery_hook_unregister(&dell_battery_hook); ++ acpi_battery_hook_unregister(&dell_battery_hook); + } + + static int __init dell_init(void) +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -972,7 +972,7 @@ static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) + data->eppid_attr.attr.mode = 0444; + data->eppid_attr.show = eppid_show; + +- return devm_battery_hook_register(&data->wdev->dev, &data->hook); ++ return devm_acpi_battery_hook_register(&data->wdev->dev, &data->hook); + } + + static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method) +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -267,7 +267,7 @@ static int fujitsu_battery_charge_control_add(struct device *dev) + return -ENODEV; + + priv->charge_control_supported = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + } +@@ -277,7 +277,7 @@ static void fujitsu_battery_charge_control_remove(struct device *dev) + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + + if (priv->charge_control_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* Hardware access for LCD brightness control */ +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -512,7 +512,7 @@ static void huawei_wmi_battery_setup(struct device *dev) + return; + } + +- battery_hook_register(&huawei_wmi_battery_hook); ++ acpi_battery_hook_register(&huawei_wmi_battery_hook); + device_create_file(dev, &dev_attr_charge_control_thresholds); + } + +@@ -521,7 +521,7 @@ static void huawei_wmi_battery_exit(struct device *dev) + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + if (huawei->battery_available) { +- battery_hook_unregister(&huawei_wmi_battery_hook); ++ acpi_battery_hook_unregister(&huawei_wmi_battery_hook); + device_remove_file(dev, &dev_attr_charge_control_thresholds); + } + } +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2197,8 +2197,8 @@ static int ideapad_check_features(struct ideapad_private *priv) + priv->battery_hook.remove_battery = ideapad_battery_remove; + priv->battery_hook.name = "Ideapad Battery Extension"; + +- err = devm_battery_hook_register(&priv->platform_device->dev, +- &priv->battery_hook); ++ err = devm_acpi_battery_hook_register(&priv->platform_device->dev, ++ &priv->battery_hook); + if (err) + return err; + } +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -10021,13 +10021,13 @@ static int __init tpacpi_battery_init(struct ibm_init_struct *ibm) + battery_quirk_table, + ARRAY_SIZE(battery_quirk_table)); + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void tpacpi_battery_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + static struct ibm_struct battery_driver_data = { +diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c +--- a/drivers/platform/x86/lenovo/wmi-other.c ++++ b/drivers/platform/x86/lenovo/wmi-other.c +@@ -946,7 +946,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv) + priv->battery_hook.name = "Lenovo WMI Other Battery Extension"; + priv->bh_registered = true; + +- battery_hook_register(&priv->battery_hook); ++ acpi_battery_hook_register(&priv->battery_hook); + } + + /** +@@ -960,7 +960,7 @@ static void lwmi_om_psy_remove(struct lwmi_om_priv *priv) + if (!priv->bh_registered) + return; + +- battery_hook_unregister(&priv->battery_hook); ++ acpi_battery_hook_unregister(&priv->battery_hook); + priv->bh_registered = false; + } + +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -1053,7 +1053,7 @@ static int acpi_probe(struct platform_device *pdev) + if (ret) + goto out_platform_device; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +@@ -1068,7 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1428,13 +1428,13 @@ static int __init msi_ec_init(void) + if (result < 0) + return result; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void __exit msi_ec_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -956,7 +956,7 @@ static int oxp_platform_probe(struct platform_device *pdev) + return PTR_ERR(hwdev); + + if (oxp_psy_ext_supported()) { +- ret = devm_battery_hook_register(dev, &battery_hook); ++ ret = devm_acpi_battery_hook_register(dev, &battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -549,7 +549,8 @@ static int galaxybook_battery_threshold_init(struct samsung_galaxybook *galaxybo + galaxybook->battery_hook.remove_battery = galaxybook_battery_remove; + galaxybook->battery_hook.name = "Samsung Galaxy Book Battery Extension"; + +- return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook); ++ return devm_acpi_battery_hook_register(&galaxybook->platform->dev, ++ &galaxybook->battery_hook); + } + + /* +diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c +--- a/drivers/platform/x86/samsung-laptop.c ++++ b/drivers/platform/x86/samsung-laptop.c +@@ -1142,8 +1142,8 @@ static int __init samsung_battery_hook_init(struct samsung_laptop *samsung) + samsung->battery_hook.add_battery = samsung_battery_add; + samsung->battery_hook.remove_battery = samsung_battery_remove; + samsung->battery_hook.name = "Samsung Battery Extension"; +- retval = devm_battery_hook_register(&samsung->platform_device->dev, +- &samsung->battery_hook); ++ retval = devm_acpi_battery_hook_register(&samsung->platform_device->dev, ++ &samsung->battery_hook); + } + + return retval; +diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c +--- a/drivers/platform/x86/system76_acpi.c ++++ b/drivers/platform/x86/system76_acpi.c +@@ -288,12 +288,12 @@ static struct acpi_battery_hook system76_battery_hook = { + + static void system76_battery_init(void) + { +- battery_hook_register(&system76_battery_hook); ++ acpi_battery_hook_register(&system76_battery_hook); + } + + static void system76_battery_exit(void) + { +- battery_hook_unregister(&system76_battery_hook); ++ acpi_battery_hook_unregister(&system76_battery_hook); + } + + // Get the airplane mode LED brightness +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -3301,7 +3301,7 @@ static void toshiba_acpi_remove(struct platform_device *pdev) + } + + if (dev->battery_charge_mode_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + + if (toshiba_acpi) + toshiba_acpi = NULL; +@@ -3566,7 +3566,7 @@ static int toshiba_acpi_probe(struct platform_device *pdev) + * set, this must be done after toshiba_acpi is assigned. + */ + if (dev->battery_charge_mode_supported) +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -2165,7 +2165,7 @@ static int uniwill_battery_init(struct uniwill_data *data) + data->hook.add_battery = uniwill_add_battery; + data->hook.remove_battery = uniwill_remove_battery; + +- return devm_battery_hook_register(data->dev, &data->hook); ++ return devm_acpi_battery_hook_register(data->dev, &data->hook); + } + + static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy) +diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c +--- a/drivers/power/supply/cros_charge-control.c ++++ b/drivers/power/supply/cros_charge-control.c +@@ -315,7 +315,7 @@ static int cros_chctl_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + +- return devm_battery_hook_register(dev, &priv->battery_hook); ++ return devm_acpi_battery_hook_register(dev, &priv->battery_hook); + } + + static const struct platform_device_id cros_chctl_id[] = { +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,8 +18,9 @@ struct acpi_battery_hook { + struct list_head list; + }; + +-void battery_hook_register(struct acpi_battery_hook *hook); +-void battery_hook_unregister(struct acpi_battery_hook *hook); +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook); ++void acpi_battery_hook_register(struct acpi_battery_hook *hook); ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook); + + #endif diff --git a/pkgbuilds/linux-omarchy-eevdf/0803-acpi-battery-hook-api-acpi-prefix.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0803-acpi-battery-hook-api-acpi-prefix.patch.sig new file mode 100644 index 0000000..84b7206 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0803-acpi-battery-hook-api-acpi-prefix.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0804-acpi-battery-unified-hooks-acpi-sbs.patch b/pkgbuilds/linux-omarchy-eevdf/0804-acpi-battery-unified-hooks-acpi-sbs.patch new file mode 100644 index 0000000..a50095e --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0804-acpi-battery-unified-hooks-acpi-sbs.patch @@ -0,0 +1,472 @@ +diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig +--- a/drivers/acpi/Kconfig ++++ b/drivers/acpi/Kconfig +@@ -171,8 +171,12 @@ config ACPI_AC + To compile this driver as a module, choose M here: + the module will be called ac. + ++config ACPI_BATTERY_HOOKS ++ tristate ++ + config ACPI_BATTERY + tristate "Battery" ++ select ACPI_BATTERY_HOOKS + select POWER_SUPPLY + default y + help +@@ -445,6 +449,7 @@ config ACPI_HOTPLUG_IOAPIC + config ACPI_SBS + tristate "Smart Battery System" + depends on X86 && ACPI_EC ++ select ACPI_BATTERY_HOOKS + select POWER_SUPPLY + help + This driver supports the Smart Battery System, another +diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile +--- a/drivers/acpi/Makefile ++++ b/drivers/acpi/Makefile +@@ -97,6 +97,7 @@ obj-$(CONFIG_ACPI_NHLT) += nhlt.o + obj-$(CONFIG_ACPI_NUMA) += numa/ + obj-$(CONFIG_ACPI) += acpi_memhotplug.o + obj-$(CONFIG_ACPI_HOTPLUG_IOAPIC) += ioapic.o ++obj-$(CONFIG_ACPI_BATTERY_HOOKS) += battery_hooks.o + obj-$(CONFIG_ACPI_BATTERY) += battery.o + obj-$(CONFIG_ACPI_SBS) += sbshc.o + obj-$(CONFIG_ACPI_SBS) += sbs.o +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -48,6 +48,7 @@ MODULE_AUTHOR("Paul Diefenbaugh"); + MODULE_AUTHOR("Alexey Starikovskiy "); + MODULE_DESCRIPTION("ACPI Battery Driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static int battery_bix_broken_package; + static int battery_notification_delay_ms; +@@ -97,7 +98,7 @@ struct acpi_battery { + struct acpi_device *device; + struct device *phys_dev; + struct notifier_block pm_nb; +- struct list_head list; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long flags; + + struct mutex property_lock; /* Protects properties below. */ +@@ -723,164 +724,6 @@ static struct attribute *acpi_battery_attrs[] = { + }; + ATTRIBUTE_GROUPS(acpi_battery); + +-/* +- * The Battery Hooking API +- * +- * This API is used inside other drivers that need to expose +- * platform-specific behaviour within the generic driver in a +- * generic way. +- * +- */ +- +-static LIST_HEAD(acpi_battery_list); +-static LIST_HEAD(battery_hook_list); +-static DEFINE_MUTEX(hook_mutex); +- +-static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- /* +- * In order to remove a hook, we first need to +- * de-register all the batteries that are registered. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (!hook->remove_battery(battery->bat, hook)) +- power_supply_changed(battery->bat); +- } +- list_del_init(&hook->list); +- +- pr_info("hook unregistered: %s\n", hook->name); +-} +- +-void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) +-{ +- mutex_lock(&hook_mutex); +- /* +- * Ignore already unregistered battery hooks. This might happen +- * if a battery hook was previously unloaded due to an error when +- * adding a new battery. +- */ +- if (!list_empty(&hook->list)) +- acpi_battery_hook_unregister_unlocked(hook); +- +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); +- +-void acpi_battery_hook_register(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- mutex_lock(&hook_mutex); +- list_add(&hook->list, &battery_hook_list); +- /* +- * Now that the driver is registered, we need +- * to notify the hook that a battery is available +- * for each battery, so that the driver may add +- * its attributes. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (hook->add_battery(battery->bat, hook)) { +- /* +- * If a add-battery returns non-zero, +- * the registration of the hook has failed, +- * and we will not add it to the list of loaded +- * hooks. +- */ +- pr_err("hook failed to load: %s", hook->name); +- acpi_battery_hook_unregister_unlocked(hook); +- goto end; +- } +- +- power_supply_changed(battery->bat); +- } +- pr_info("new hook: %s\n", hook->name); +-end: +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_register); +- +-static void devm_acpi_battery_hook_unregister(void *data) +-{ +- struct acpi_battery_hook *hook = data; +- +- acpi_battery_hook_unregister(hook); +-} +- +-int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) +-{ +- acpi_battery_hook_register(hook); +- +- return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); +-} +-EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); +- +-/* +- * This function gets called right after the battery sysfs +- * attributes have been added, so that the drivers that +- * define custom sysfs attributes can add their own. +- */ +-static void battery_hook_add_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook_node, *tmp; +- +- mutex_lock(&hook_mutex); +- INIT_LIST_HEAD(&battery->list); +- list_add(&battery->list, &acpi_battery_list); +- /* +- * Since we added a new battery to the list, we need to +- * iterate over the hooks and call add_battery for each +- * hook that was registered. This usually happens +- * when a battery gets hotplugged or initialized +- * during the battery module initialization. +- */ +- list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { +- if (hook_node->add_battery(battery->bat, hook_node)) { +- /* +- * The notification of the hook has failed, to +- * prevent further errors we will unload the hook. +- */ +- pr_err("error in hook, unloading: %s", +- hook_node->name); +- acpi_battery_hook_unregister_unlocked(hook_node); +- } +- } +- mutex_unlock(&hook_mutex); +-} +- +-static void battery_hook_remove_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook; +- +- mutex_lock(&hook_mutex); +- /* +- * Before removing the hook, we need to remove all +- * custom attributes from the battery. +- */ +- list_for_each_entry(hook, &battery_hook_list, list) { +- hook->remove_battery(battery->bat, hook); +- } +- /* Then, just remove the battery from the list */ +- list_del(&battery->list); +- mutex_unlock(&hook_mutex); +-} +- +-static void __exit battery_hook_exit(void) +-{ +- struct acpi_battery_hook *hook; +- struct acpi_battery_hook *ptr; +- /* +- * At this point, the acpi_bus_unregister_driver() +- * has called remove for all batteries. We just +- * need to remove the hooks. +- */ +- list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- acpi_battery_hook_unregister(hook); +- } +- mutex_destroy(&hook_mutex); +-} +- + static int sysfs_add_battery(struct acpi_battery *battery) + { + struct power_supply_config psy_cfg = { +@@ -936,7 +779,7 @@ static int sysfs_add_battery(struct acpi_battery *battery) + battery->bat = NULL; + return result; + } +- battery_hook_add_battery(battery); ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); + return 0; + } + +@@ -945,7 +788,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery) + if (!battery->bat) + return; + +- battery_hook_remove_battery(battery); ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); + battery->bat = NULL; + } +@@ -1391,7 +1234,6 @@ static int __init acpi_battery_init(void) + static void __exit acpi_battery_exit(void) + { + platform_driver_unregister(&acpi_battery_driver); +- battery_hook_exit(); + } + + module_init(acpi_battery_init); +diff --git a/drivers/acpi/battery_hooks.c b/drivers/acpi/battery_hooks.c +new file mode 100644 +--- /dev/null ++++ b/drivers/acpi/battery_hooks.c +@@ -0,0 +1,159 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ACPI Battery Hooks ++ * ++ * Provides helpers for registering and unregistering battery hooks for ++ * drivers that use platform-specific extensions to ACPI-enumerated ++ * batteries (both ACPI Control Method batteries and Smart Battery System ++ * batteries). ++ */ ++ ++#define pr_fmt(fmt) "ACPI: battery: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static LIST_HEAD(acpi_battery_list); ++static LIST_HEAD(battery_hook_list); ++static DEFINE_MUTEX(hook_mutex); ++ ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ /* ++ * In order to remove a hook, we first need to ++ * de-register all the batteries that are registered. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (!hook->remove_battery(entry->battery, hook)) ++ power_supply_changed(entry->battery); ++ } ++ list_del_init(&hook->list); ++ ++ pr_info("hook unregistered: %s\n", hook->name); ++} ++ ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) ++{ ++ guard(mutex)(&hook_mutex); ++ ++ /* ++ * Ignore already unregistered battery hooks. This might happen ++ * if a battery hook was previously unloaded due to an error when ++ * adding a new battery. ++ */ ++ if (!list_empty(&hook->list)) ++ acpi_battery_hook_unregister_unlocked(hook); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); ++ ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&hook->list, &battery_hook_list); ++ /* ++ * Now that the driver is registered, we need ++ * to notify the hook that a battery is available ++ * for each battery, so that the driver may add ++ * its attributes. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (hook->add_battery(entry->battery, hook)) { ++ /* ++ * If a add-battery returns non-zero, ++ * the registration of the hook has failed, ++ * and we will not add it to the list of loaded ++ * hooks. ++ */ ++ pr_err("hook failed to load: %s\n", hook->name); ++ acpi_battery_hook_unregister_unlocked(hook); ++ return; ++ } ++ ++ power_supply_changed(entry->battery); ++ } ++ pr_info("new hook: %s\n", hook->name); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); ++ ++static void devm_acpi_battery_hook_unregister(void *data) ++{ ++ struct acpi_battery_hook *hook = data; ++ ++ acpi_battery_hook_unregister(hook); ++} ++ ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook) ++{ ++ acpi_battery_hook_register(hook); ++ ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); ++} ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); ++ ++/* ++ * This function gets called right after the battery sysfs ++ * attributes have been added, so that the drivers that ++ * define custom sysfs attributes can add their own. ++ */ ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery) ++{ ++ struct acpi_battery_hook *hook_node, *tmp; ++ ++ entry->battery = battery; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&entry->list_entry, &acpi_battery_list); ++ /* ++ * Since we added a new battery to the list, we need to ++ * iterate over the hooks and call add_battery for each ++ * hook that was registered. This usually happens ++ * when a battery gets hotplugged or initialized ++ * during the battery module initialization. ++ */ ++ list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { ++ if (hook_node->add_battery(entry->battery, hook_node)) { ++ /* ++ * The notification of the hook has failed, to ++ * prevent further errors we will unload the hook. ++ */ ++ pr_err("error in hook, unloading: %s\n", hook_node->name); ++ acpi_battery_hook_unregister_unlocked(hook_node); ++ } ++ } ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_add_hooks, "ACPI_BATTERY_HOOKS"); ++ ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry) ++{ ++ struct acpi_battery_hook *hook; ++ ++ guard(mutex)(&hook_mutex); ++ /* ++ * Before removing the hook, we need to remove all ++ * custom attributes from the battery. ++ */ ++ list_for_each_entry(hook, &battery_hook_list, list) ++ hook->remove_battery(entry->battery, hook); ++ ++ /* Then, just remove the battery from the list */ ++ list_del(&entry->list_entry); ++ entry->battery = NULL; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_remove_hooks, "ACPI_BATTERY_HOOKS"); ++ ++MODULE_DESCRIPTION("ACPI battery hooks"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c +--- a/drivers/acpi/sbs.c ++++ b/drivers/acpi/sbs.c +@@ -36,6 +36,7 @@ + MODULE_AUTHOR("Alexey Starikovskiy "); + MODULE_DESCRIPTION("Smart Battery System ACPI interface driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static unsigned int cache_time = 1000; + module_param(cache_time, uint, 0644); +@@ -54,6 +55,7 @@ struct acpi_battery { + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct acpi_sbs *sbs; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long update_time; + char name[8]; + char manufacturer_name[ACPI_SBS_BLOCK_MAX]; +@@ -555,6 +557,8 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id) + goto end; + } + ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); ++ + end: + pr_info("%s [%s]: Battery Slot [%s] (battery %s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), +@@ -566,8 +570,10 @@ static void acpi_battery_remove(struct acpi_sbs *sbs, int id) + { + struct acpi_battery *battery = &sbs->battery[id]; + +- if (battery->bat) ++ if (battery->bat) { ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); ++ } + } + + static int acpi_charger_add(struct acpi_sbs *sbs) +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,9 +18,18 @@ struct acpi_battery_hook { + struct list_head list; + }; + ++struct acpi_battery_hooks_list_entry { ++ struct list_head list_entry; ++ struct power_supply *battery; ++}; ++ + void acpi_battery_hook_register(struct acpi_battery_hook *hook); + void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); + int devm_acpi_battery_hook_register(struct device *dev, + struct acpi_battery_hook *hook); + ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery); ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-eevdf/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig new file mode 100644 index 0000000..3d8754f Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0805-hwmon-applesmc-charge-control-end-threshold.patch b/pkgbuilds/linux-omarchy-eevdf/0805-hwmon-applesmc-charge-control-end-threshold.patch new file mode 100644 index 0000000..9b45c66 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0805-hwmon-applesmc-charge-control-end-threshold.patch @@ -0,0 +1,209 @@ +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -380,6 +380,7 @@ config SENSORS_FAM15H_POWER + config SENSORS_APPLESMC + tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" + depends on INPUT && X86 ++ depends on POWER_SUPPLY || POWER_SUPPLY=n + select NEW_LEDS + select LEDS_CLASS + help +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,8 @@ + #include + #include + #include ++#include ++#include + #include + + /* data port used by Apple SMC */ +@@ -76,6 +78,20 @@ + + #define TEMP_SENSOR_TYPE "sp78" + ++/* ++ * BCLM caps charging at a percentage. BFCL only sets when the charging LED ++ * switches from orange to green. ++ */ ++#define BATTERY_CHARGE_LIMIT_KEY "BCLM" /* r/w ui8 */ ++#define BATTERY_CHARGE_LIMIT_LED_KEY "BFCL" /* r/w ui8 */ ++ ++/* ++ * Points kept between BCLM and BFCL so the LED turns green before charging ++ * stops. Measured on a MacBookAir6,2, where a margin of 1 only just ties; ++ * 3 leaves headroom for other models. ++ */ ++#define APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN 3 ++ + /* List of keys used to read/write fan speeds */ + static const char *const fan_speed_fmt[] = { + "F%dAc", /* actual speed */ +@@ -131,6 +147,8 @@ static struct applesmc_registers { + int num_light_sensors; /* number of light sensors */ + bool has_accelerometer; /* has motion sensor */ + bool has_key_backlight; /* has keyboard backlight */ ++ bool has_battery_charge_limit; /* has BCLM battery charge limit */ ++ bool has_battery_charge_limit_led; /* has BFCL MagSafe LED charge limit */ + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ +@@ -633,6 +651,12 @@ static int applesmc_init_smcreg_try(void) + if (ret) + return ret; + ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_KEY, &s->has_battery_charge_limit); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_LED_KEY, &s->has_battery_charge_limit_led); + if (ret) + return ret; + +@@ -724,6 +748,120 @@ static struct platform_driver applesmc_driver = { + }, + }; + ++static DEFINE_MUTEX(applesmc_bat_lock); ++ ++static int applesmc_bat_get_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ u8 limit; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ scoped_guard(mutex, &applesmc_bat_lock) ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ val->intval = limit; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_set_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ const union power_supply_propval *val) ++{ ++ u8 limit, led_limit, readback; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ /* 20 is known to work and 10 is not accepted; the floor is unknown. */ ++ if (val->intval < 20 || val->intval > 100) ++ return -EINVAL; ++ ++ limit = val->intval; ++ /* At 100% charging is never cut short, so no margin is needed. */ ++ led_limit = limit < 100 ? ++ limit - APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN : limit; ++ ++ scoped_guard(mutex, &applesmc_bat_lock) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ ++ /* ++ * The SMC silently ignores values it does not accept and ++ * still reports success, so read the limit back. ++ */ ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &readback, 1); ++ if (ret) ++ return ret; ++ if (readback != limit) ++ return -EINVAL; ++ ++ if (smcreg.has_battery_charge_limit_led) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_LED_KEY, ++ &led_limit, 1); ++ /* BCLM is already applied, so this is not an error. */ ++ if (ret) ++ pr_warn("failed to update MagSafe LED threshold (BFCL): %d\n", ++ ret); ++ } ++ } ++ ++ power_supply_changed(psy); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_property_is_writeable(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp) ++{ ++ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; ++} ++ ++static const enum power_supply_property applesmc_bat_props[] = { ++ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, ++}; ++ ++static const struct power_supply_ext applesmc_bat_ext = { ++ .name = "applesmc-charge-control", ++ .properties = applesmc_bat_props, ++ .num_properties = ARRAY_SIZE(applesmc_bat_props), ++ .get_property = applesmc_bat_get_property, ++ .set_property = applesmc_bat_set_property, ++ .property_is_writeable = applesmc_bat_property_is_writeable, ++}; ++ ++static int applesmc_bat_add(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ return power_supply_register_extension(battery, &applesmc_bat_ext, &pdev->dev, NULL); ++} ++ ++static int applesmc_bat_remove(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ power_supply_unregister_extension(battery, &applesmc_bat_ext); ++ return 0; ++} ++ ++static struct acpi_battery_hook applesmc_bat_hook = { ++ .name = "Apple SMC Battery Charge Control", ++ .add_battery = applesmc_bat_add, ++ .remove_battery = applesmc_bat_remove, ++}; ++ + /* + * applesmc_calibrate - Set our "resting" values. Callers must + * hold applesmc_lock. +@@ -1563,6 +1701,10 @@ static int __init applesmc_init(void) + goto out_light_ledclass; + } + ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_register(&applesmc_bat_hook); ++ + return 0; + + out_light_ledclass: +@@ -1589,6 +1731,10 @@ static int __init applesmc_init(void) + + static void __exit applesmc_exit(void) + { ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_unregister(&applesmc_bat_hook); ++ + hwmon_device_unregister(hwmon_dev); + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); diff --git a/pkgbuilds/linux-omarchy-eevdf/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig new file mode 100644 index 0000000..992c843 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch b/pkgbuilds/linux-omarchy-eevdf/0850-futex-wait-multiple.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch rename to pkgbuilds/linux-omarchy-eevdf/0850-futex-wait-multiple.patch diff --git a/pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0850-futex-wait-multiple.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0850-futex-wait-multiple.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0850-futex-wait-multiple.patch.sig diff --git a/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0851-futex-wait-multiple-fixes.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch rename to pkgbuilds/linux-omarchy-eevdf/0851-futex-wait-multiple-fixes.patch diff --git a/pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0851-futex-wait-multiple-fixes.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0851-futex-wait-multiple-fixes.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0851-futex-wait-multiple-fixes.patch.sig diff --git a/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch b/pkgbuilds/linux-omarchy-eevdf/0852-futex-wait-multiple-abi-fixes.patch similarity index 100% rename from pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch rename to pkgbuilds/linux-omarchy-eevdf/0852-futex-wait-multiple-abi-fixes.patch diff --git a/pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0852-futex-wait-multiple-abi-fixes.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/0852-futex-wait-multiple-abi-fixes.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/0852-futex-wait-multiple-abi-fixes.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/0860-module-add-module-kallsyms-on-each-addr.patch b/pkgbuilds/linux-omarchy-eevdf/0860-module-add-module-kallsyms-on-each-addr.patch new file mode 100644 index 0000000..ebd6e0d --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0860-module-add-module-kallsyms-on-each-addr.patch @@ -0,0 +1,95 @@ +diff --git a/include/linux/module.h b/include/linux/module.h +--- a/include/linux/module.h ++++ b/include/linux/module.h +@@ -975,6 +975,10 @@ unsigned long module_kallsyms_lookup_name(const char *name); + + unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name); + ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data); ++ + #else /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + static inline int module_kallsyms_on_each_symbol(const char *modname, +@@ -1018,6 +1022,12 @@ static inline unsigned long find_kallsyms_symbol_value(struct module *mod, + return 0; + } + ++static inline void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++} ++ + #endif /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + /* Define __free(module_put) macro for struct module *. */ +diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c +--- a/kernel/module/kallsyms.c ++++ b/kernel/module/kallsyms.c +@@ -246,6 +246,18 @@ static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms, unsigned + return kallsyms->strtab + kallsyms->symtab[symnum].st_name; + } + ++/* ++ * Whether find_kallsyms_symbol() may resolve an address to symbol @symnum. ++ * Unnamed symbols are ignored: they're uninformative and inserted at a whim. ++ */ ++static bool is_lookup_symbol(struct mod_kallsyms *kallsyms, unsigned int symnum) ++{ ++ const char *name = kallsyms_symbol_name(kallsyms, symnum); ++ ++ return kallsyms->symtab[symnum].st_shndx != SHN_UNDEF && ++ *name != '\0' && !is_mapping_symbol(name); ++} ++ + /* + * Given a module and address, find the corresponding symbol and return its name + * while providing its size and offset if needed. +@@ -286,15 +298,7 @@ static const char *find_kallsyms_symbol(struct module *mod, + const Elf_Sym *sym = &kallsyms->symtab[i]; + unsigned long thisval = kallsyms_symbol_value(sym); + +- if (sym->st_shndx == SHN_UNDEF) +- continue; +- +- /* +- * We ignore unnamed symbols: they're uninformative +- * and inserted at a whim. +- */ +- if (*kallsyms_symbol_name(kallsyms, i) == '\0' || +- is_mapping_symbol(kallsyms_symbol_name(kallsyms, i))) ++ if (!is_lookup_symbol(kallsyms, i)) + continue; + + if (thisval <= addr && thisval > bestval) { +@@ -458,6 +462,28 @@ unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name) + return __find_kallsyms_symbol_value(mod, name); + } + ++/* ++ * Call @fn with the address of each symbol of @mod that find_kallsyms_symbol() ++ * may resolve an address to. Unlike module_kallsyms_on_each_symbol(), this ++ * also works while @mod is still being loaded. ++ */ ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++ struct mod_kallsyms *kallsyms; ++ unsigned int i; ++ ++ guard(rcu)(); ++ kallsyms = rcu_dereference(mod->kallsyms); ++ ++ /* ELF starts real symbols at 1. */ ++ for (i = 1; i < kallsyms->num_symtab; i++) { ++ if (is_lookup_symbol(kallsyms, i)) ++ fn(data, kallsyms_symbol_value(&kallsyms->symtab[i])); ++ } ++} ++ + int module_kallsyms_on_each_symbol(const char *modname, + int (*fn)(void *, const char *, unsigned long), + void *data) diff --git a/pkgbuilds/linux-omarchy-eevdf/0860-module-add-module-kallsyms-on-each-addr.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0860-module-add-module-kallsyms-on-each-addr.patch.sig new file mode 100644 index 0000000..bf22caf Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0860-module-add-module-kallsyms-on-each-addr.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/0861-ftrace-drop-weak-function-locations-of-modules.patch b/pkgbuilds/linux-omarchy-eevdf/0861-ftrace-drop-weak-function-locations-of-modules.patch new file mode 100644 index 0000000..82b00bb --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/0861-ftrace-drop-weak-function-locations-of-modules.patch @@ -0,0 +1,113 @@ +diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c +--- a/kernel/trace/ftrace.c ++++ b/kernel/trace/ftrace.c +@@ -4445,11 +4445,6 @@ static int print_rec(struct seq_file *m, unsigned long ip) + return ret == NULL ? -1 : 0; + } + #else +-static inline int test_for_valid_rec(struct dyn_ftrace *rec) +-{ +- return 1; +-} +- + static inline int print_rec(struct seq_file *m, unsigned long ip) + { + seq_printf(m, "%ps", (void *)ip); +@@ -7611,6 +7606,73 @@ static void test_is_sorted(unsigned long *start, unsigned long count) + } + #endif + ++#ifdef FTRACE_MCOUNT_MAX_OFFSET ++struct ftrace_mod_locs { ++ unsigned long *start; ++ unsigned long count; ++ unsigned long *valid; ++}; ++ ++/* Mark the locations that lie at most FTRACE_MCOUNT_MAX_OFFSET after @addr. */ ++static void ftrace_mark_valid_locs(void *data, unsigned long addr) ++{ ++ struct ftrace_mod_locs *locs = data; ++ unsigned long lo = 0, hi = locs->count, mid, ip; ++ ++ /* ++ * ftrace_call_adjust() moves a location forward by at most ++ * FTRACE_MCOUNT_MAX_OFFSET, so start looking that far before @addr. ++ */ ++ while (lo < hi) { ++ mid = lo + (hi - lo) / 2; ++ if (locs->start[mid] + FTRACE_MCOUNT_MAX_OFFSET < addr) ++ lo = mid + 1; ++ else ++ hi = mid; ++ } ++ ++ for (; lo < locs->count; lo++) { ++ if (locs->start[lo] > addr + FTRACE_MCOUNT_MAX_OFFSET) ++ break; ++ ip = ftrace_call_adjust(locs->start[lo]); ++ if (ip >= addr && ip - addr <= FTRACE_MCOUNT_MAX_OFFSET) ++ __set_bit(lo, locs->valid); ++ } ++} ++ ++/* ++ * A weak function overridden within its module keeps its mcount location but ++ * has no symbol. Zero such locations before they become records, as sorttable ++ * does for vmlinux: keep only those with a symbol at most ++ * FTRACE_MCOUNT_MAX_OFFSET before them. ++ */ ++static int ftrace_zero_weak_locs(struct module *mod, unsigned long *start, ++ unsigned long count) ++{ ++ struct ftrace_mod_locs locs = { .start = start, .count = count }; ++ unsigned long i; ++ ++ locs.valid = bitmap_zalloc(count, GFP_KERNEL); ++ if (!locs.valid) ++ return -ENOMEM; ++ ++ module_kallsyms_on_each_addr(mod, ftrace_mark_valid_locs, &locs); ++ ++ for_each_clear_bit(i, locs.valid, count) ++ start[i] = 0; ++ ++ bitmap_free(locs.valid); ++ return 0; ++} ++#else ++static inline int ftrace_zero_weak_locs(struct module *mod, ++ unsigned long *start, ++ unsigned long count) ++{ ++ return 0; ++} ++#endif ++ + static int ftrace_process_locs(struct module *mod, + unsigned long *start, + unsigned long *end) +@@ -7644,6 +7706,9 @@ static int ftrace_process_locs(struct module *mod, + test_is_sorted(start, count); + } + ++ if (mod && ftrace_zero_weak_locs(mod, start, count)) ++ return -ENOMEM; ++ + start_pg = ftrace_allocate_pages(count, &pages); + if (!start_pg) + return -ENOMEM; +@@ -8049,13 +8114,6 @@ void ftrace_module_enable(struct module *mod) + + cond_resched(); + +- /* Weak functions should still be ignored */ +- if (!test_for_valid_rec(rec)) { +- /* Clear all other flags. Should not be enabled anyway */ +- rec->flags = FTRACE_FL_DISABLED; +- continue; +- } +- + cnt = 0; + + /* diff --git a/pkgbuilds/linux-omarchy-eevdf/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig b/pkgbuilds/linux-omarchy-eevdf/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig new file mode 100644 index 0000000..ba105f0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy-eevdf/8204-mm-opportunistic-compaction.patch similarity index 97% rename from pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch rename to pkgbuilds/linux-omarchy-eevdf/8204-mm-opportunistic-compaction.patch index 0a31aa0..353ca7c 100644 --- a/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch +++ b/pkgbuilds/linux-omarchy-eevdf/8204-mm-opportunistic-compaction.patch @@ -100,7 +100,7 @@ diff --git c/mm/internal.h i/mm/internal.h 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, +@@ -476,7 +476,7 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl, #ifdef CONFIG_MEMCG static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, @@ -109,7 +109,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c { 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, +@@ -538,6 +538,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, .gfp_mask = gfp_mask, .nid = nid, .memcg = memcg, @@ -117,7 +117,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c }; 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, +@@ -597,7 +598,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, } #else /* !CONFIG_MEMCG */ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, @@ -127,7 +127,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c { return 0; } -@@ -607,6 +609,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +@@ -609,6 +611,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, * @nid: node whose slab caches to target * @memcg: memory cgroup whose slab caches to target * @priority: the reclaim priority @@ -135,7 +135,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c * * Call the shrink functions to age shrinkable caches. * -@@ -622,7 +625,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +@@ -624,7 +627,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, * Returns the number of reclaimed slab objects. */ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, @@ -144,7 +144,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c { 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, +@@ -637,7 +640,8 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, * oom. */ if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) @@ -154,7 +154,7 @@ diff --git c/mm/shrinker.c i/mm/shrinker.c /* * lockless algorithm of global shrink. -@@ -664,6 +668,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +@@ -666,6 +670,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, .gfp_mask = gfp_mask, .nid = nid, .memcg = memcg, diff --git a/pkgbuilds/linux-omarchy-eevdf/8204-mm-opportunistic-compaction.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8204-mm-opportunistic-compaction.patch.sig new file mode 100644 index 0000000..69d7459 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/8204-mm-opportunistic-compaction.patch.sig differ diff --git a/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch b/pkgbuilds/linux-omarchy-eevdf/8205-mm-hint-uses-allocation-order.patch similarity index 100% rename from pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch rename to pkgbuilds/linux-omarchy-eevdf/8205-mm-hint-uses-allocation-order.patch diff --git a/pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8205-mm-hint-uses-allocation-order.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/8205-mm-hint-uses-allocation-order.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/8205-mm-hint-uses-allocation-order.patch.sig diff --git a/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch b/pkgbuilds/linux-omarchy-eevdf/8206-mm-carry-order-and-hint-in-one-word.patch similarity index 95% rename from pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch rename to pkgbuilds/linux-omarchy-eevdf/8206-mm-carry-order-and-hint-in-one-word.patch index 92b39ef..65dd70b 100644 --- a/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch +++ b/pkgbuilds/linux-omarchy-eevdf/8206-mm-carry-order-and-hint-in-one-word.patch @@ -1,6 +1,6 @@ -diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h ---- c/include/linux/mmzone.h -+++ i/include/linux/mmzone.h +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h @@ -1498,6 +1498,32 @@ enum kswapd_opportunistic_compaction_type { KSWAPD_OPPORTUNISTIC_COMPACTION, }; @@ -56,10 +56,10 @@ diff --git c/include/linux/mmzone.h i/include/linux/mmzone.h 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) +diff --git a/mm/mm_init.c b/mm/mm_init.c +--- a/mm/mm_init.c ++++ b/mm/mm_init.c +@@ -1558,7 +1558,8 @@ int __ref free_area_init_core_hotplug(struct pglist_data *pgdat) * when it starts in the near future. */ pgdat->nr_zones = 0; @@ -69,9 +69,9 @@ diff --git c/mm/mm_init.c i/mm/mm_init.c 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 +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c @@ -7413,6 +7413,49 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; } diff --git a/pkgbuilds/linux-omarchy-eevdf/8206-mm-carry-order-and-hint-in-one-word.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8206-mm-carry-order-and-hint-in-one-word.patch.sig new file mode 100644 index 0000000..2e878d2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/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-eevdf/8207-mm-classify-huge-page-allocations-as-failable.patch similarity index 100% rename from pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch rename to pkgbuilds/linux-omarchy-eevdf/8207-mm-classify-huge-page-allocations-as-failable.patch diff --git a/pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8207-mm-classify-huge-page-allocations-as-failable.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/8207-mm-classify-huge-page-allocations-as-failable.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/8207-mm-classify-huge-page-allocations-as-failable.patch.sig diff --git a/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch b/pkgbuilds/linux-omarchy-eevdf/8209-xe-shrinker-use-opportunistic-hint.patch similarity index 100% rename from pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch rename to pkgbuilds/linux-omarchy-eevdf/8209-xe-shrinker-use-opportunistic-hint.patch diff --git a/pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8209-xe-shrinker-use-opportunistic-hint.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/8209-xe-shrinker-use-opportunistic-hint.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/8209-xe-shrinker-use-opportunistic-hint.patch.sig diff --git a/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch b/pkgbuilds/linux-omarchy-eevdf/8210-xe-shrinker-single-backup-decision.patch similarity index 100% rename from pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch rename to pkgbuilds/linux-omarchy-eevdf/8210-xe-shrinker-single-backup-decision.patch diff --git a/pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8210-xe-shrinker-single-backup-decision.patch.sig similarity index 100% rename from pkgbuilds/linux-omarchy/8210-xe-shrinker-single-backup-decision.patch.sig rename to pkgbuilds/linux-omarchy-eevdf/8210-xe-shrinker-single-backup-decision.patch.sig diff --git a/pkgbuilds/linux-omarchy-eevdf/8211-xe-shrinker-skip-active-vm.patch b/pkgbuilds/linux-omarchy-eevdf/8211-xe-shrinker-skip-active-vm.patch new file mode 100644 index 0000000..76470c1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/8211-xe-shrinker-skip-active-vm.patch @@ -0,0 +1,116 @@ +diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c +--- a/drivers/gpu/drm/xe/xe_bo.c ++++ b/drivers/gpu/drm/xe/xe_bo.c +@@ -28,6 +28,7 @@ + #include "xe_ggtt.h" + #include "xe_map.h" + #include "xe_migrate.h" ++#include "xe_module.h" + #include "xe_pat.h" + #include "xe_pm.h" + #include "xe_preempt_fence.h" +@@ -1244,6 +1245,26 @@ xe_bo_eviction_valuable(struct ttm_buffer_object *bo, const struct ttm_place *pl + return true; + } + ++static bool xe_bo_bound_to_active_vm(struct xe_bo *bo) ++{ ++ u64 window = (u64)READ_ONCE(xe_modparam.shrink_active_ms) * NSEC_PER_MSEC; ++ u64 now = ktime_get_ns(); ++ struct drm_gpuvm_bo *vm_bo; ++ ++ if (!window) ++ return false; ++ ++ drm_gem_for_each_gpuvm_bo(vm_bo, &bo->ttm.base) { ++ struct xe_vm *vm = gpuvm_to_vm(vm_bo->vm); ++ u64 last = READ_ONCE(vm->last_exec_ns); ++ ++ if (!xe_vm_in_fault_mode(vm) && last && now - last < window) ++ return true; ++ } ++ ++ return false; ++} ++ + /** + * xe_bo_shrink() - Try to shrink an xe bo. + * @ctx: The struct ttm_operation_ctx used for shrinking. +@@ -1295,6 +1316,10 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo, + goto out_unref; + } + ++ if (xe_bo_bound_to_active_vm(xe_bo) && ++ try_wait_for_completion(&xe->pm_block)) ++ goto out_unref; ++ + /* System CCS needs gpu copy when moving PL_TT -> PL_SYSTEM */ + needs_rpm = (!IS_DGFX(xe) && bo->resource->mem_type != XE_PL_SYSTEM && + xe_bo_needs_ccs_pages(xe_bo)); +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -22,5 +22,6 @@ + #define XE_DEFAULT_WEDGED_MODE XE_WEDGED_MODE_UPON_CRITICAL_ERROR + #define XE_DEFAULT_WEDGED_MODE_STR "upon-critical-error" + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 ++#define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 + + #endif +diff --git a/drivers/gpu/drm/xe/xe_exec.c b/drivers/gpu/drm/xe/xe_exec.c +--- a/drivers/gpu/drm/xe/xe_exec.c ++++ b/drivers/gpu/drm/xe/xe_exec.c +@@ -344,6 +344,7 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file) + if (!xe_vm_in_lr_mode(vm)) + xe_exec_queue_last_fence_set(q, vm, &job->drm.s_fence->finished); + xe_sched_job_push(job); ++ WRITE_ONCE(vm->last_exec_ns, ktime_get_ns()); + xe_vm_reactivate_rebind(vm); + + if (!err && !xe_vm_in_lr_mode(vm)) { +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -30,6 +30,7 @@ struct xe_modparam xe_modparam = { + #endif + .wedged_mode = XE_DEFAULT_WEDGED_MODE, + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, ++ .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, + /* the rest are 0 by default */ + }; + +@@ -37,6 +38,11 @@ module_param_named(svm_notifier_size, xe_modparam.svm_notifier_size, uint, 0600) + MODULE_PARM_DESC(svm_notifier_size, "Set the svm notifier size in MiB, must be power of 2 " + "[default=" __stringify(XE_DEFAULT_SVM_NOTIFIER_SIZE) "]"); + ++module_param_named(shrink_active_ms, xe_modparam.shrink_active_ms, uint, 0600); ++MODULE_PARM_DESC(shrink_active_ms, "Do not back up a buffer object bound to a VM that submitted work " ++ "within this many ms, 0=disabled " ++ "[default=" __stringify(XE_DEFAULT_SHRINK_ACTIVE_MS) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -25,6 +25,7 @@ struct xe_modparam { + #endif + unsigned int wedged_mode; + u32 svm_notifier_size; ++ unsigned int shrink_active_ms; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_vm_types.h b/drivers/gpu/drm/xe/xe_vm_types.h +--- a/drivers/gpu/drm/xe/xe_vm_types.h ++++ b/drivers/gpu/drm/xe/xe_vm_types.h +@@ -245,6 +245,8 @@ struct xe_vm { + /** @lru_bulk_move: Bulk LRU move list for this VM's BOs */ + struct ttm_lru_bulk_move lru_bulk_move; + ++ u64 last_exec_ns; ++ + u64 size; + + struct xe_pt *pt_root[XE_MAX_TILES_PER_DEVICE]; diff --git a/pkgbuilds/linux-omarchy-eevdf/8211-xe-shrinker-skip-active-vm.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8211-xe-shrinker-skip-active-vm.patch.sig new file mode 100644 index 0000000..7467cb6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/8211-xe-shrinker-skip-active-vm.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/8212-xe-shrinker-backup-in-kswapd-only.patch b/pkgbuilds/linux-omarchy-eevdf/8212-xe-shrinker-backup-in-kswapd-only.patch new file mode 100644 index 0000000..f77d2f2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/8212-xe-shrinker-backup-in-kswapd-only.patch @@ -0,0 +1,95 @@ +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -23,5 +23,6 @@ + #define XE_DEFAULT_WEDGED_MODE_STR "upon-critical-error" + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 + #define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 ++#define XE_DEFAULT_SHRINK_DIRECT_BACKUP false + + #endif +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -31,6 +31,7 @@ struct xe_modparam xe_modparam = { + .wedged_mode = XE_DEFAULT_WEDGED_MODE, + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, + .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, ++ .shrink_direct_backup = XE_DEFAULT_SHRINK_DIRECT_BACKUP, + /* the rest are 0 by default */ + }; + +@@ -43,6 +44,10 @@ MODULE_PARM_DESC(shrink_active_ms, "Do not back up a buffer object bound to a VM + "within this many ms, 0=disabled " + "[default=" __stringify(XE_DEFAULT_SHRINK_ACTIVE_MS) "]"); + ++module_param_named(shrink_direct_backup, xe_modparam.shrink_direct_backup, bool, 0600); ++MODULE_PARM_DESC(shrink_direct_backup, "Back up buffer objects in direct reclaim too, not only in kswapd " ++ "[default=" __stringify(XE_DEFAULT_SHRINK_DIRECT_BACKUP) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -26,6 +26,7 @@ struct xe_modparam { + unsigned int wedged_mode; + u32 svm_notifier_size; + unsigned int shrink_active_ms; ++ bool shrink_direct_backup; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c +--- a/drivers/gpu/drm/xe/xe_shrinker.c ++++ b/drivers/gpu/drm/xe/xe_shrinker.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + + #include + #include +@@ -11,6 +12,7 @@ + #include + + #include "xe_bo.h" ++#include "xe_module.h" + #include "xe_pm.h" + #include "xe_shrinker.h" + +@@ -170,9 +172,12 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + +-static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, ++ const struct shrink_control *sc) + { +- return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction; ++ return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction && ++ (current_is_kswapd() || READ_ONCE(xe_modparam.shrink_direct_backup) || ++ !try_wait_for_completion(&shrinker->xe->pm_block)); + } + + static unsigned long +@@ -188,7 +193,7 @@ xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + * wants, and it needs __GFP_FS. + */ + read_lock(&shrinker->lock); +- if (xe_shrinker_can_backup(sc)) ++ if (xe_shrinker_can_backup(shrinker, sc)) + num_pages = min_t(unsigned long, + ttm_backup_bytes_avail() >> PAGE_SHIFT, + shrinker->shrinkable_pages); +@@ -241,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + }; + bool runtime_pm; + bool purgeable; +- bool can_backup = xe_shrinker_can_backup(sc); ++ bool can_backup = xe_shrinker_can_backup(shrinker, sc); + + nr_to_scan = sc->nr_to_scan; + diff --git a/pkgbuilds/linux-omarchy-eevdf/8212-xe-shrinker-backup-in-kswapd-only.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8212-xe-shrinker-backup-in-kswapd-only.patch.sig new file mode 100644 index 0000000..ae4ebed Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/8212-xe-shrinker-backup-in-kswapd-only.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/8213-xe-hibernation-writeback-gated.patch b/pkgbuilds/linux-omarchy-eevdf/8213-xe-hibernation-writeback-gated.patch new file mode 100644 index 0000000..b639c28 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/8213-xe-hibernation-writeback-gated.patch @@ -0,0 +1,112 @@ +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -24,5 +24,6 @@ + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 + #define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 + #define XE_DEFAULT_SHRINK_DIRECT_BACKUP false ++#define XE_DEFAULT_HIBERNATE_WRITEBACK false + + #endif +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -32,6 +32,7 @@ struct xe_modparam xe_modparam = { + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, + .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, + .shrink_direct_backup = XE_DEFAULT_SHRINK_DIRECT_BACKUP, ++ .hibernate_writeback = XE_DEFAULT_HIBERNATE_WRITEBACK, + /* the rest are 0 by default */ + }; + +@@ -48,6 +49,10 @@ module_param_named(shrink_direct_backup, xe_modparam.shrink_direct_backup, bool, + MODULE_PARM_DESC(shrink_direct_backup, "Back up buffer objects in direct reclaim too, not only in kswapd " + "[default=" __stringify(XE_DEFAULT_SHRINK_DIRECT_BACKUP) "]"); + ++module_param_named(hibernate_writeback, xe_modparam.hibernate_writeback, bool, 0600); ++MODULE_PARM_DESC(hibernate_writeback, "Write buffer objects to swap before hibernation, " ++ "integrated GPUs only [default=" __stringify(XE_DEFAULT_HIBERNATE_WRITEBACK) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -27,6 +27,7 @@ struct xe_modparam { + u32 svm_notifier_size; + unsigned int shrink_active_ms; + bool shrink_direct_backup; ++ bool hibernate_writeback; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_pm.c b/drivers/gpu/drm/xe/xe_pm.c +--- a/drivers/gpu/drm/xe/xe_pm.c ++++ b/drivers/gpu/drm/xe/xe_pm.c +@@ -26,6 +26,7 @@ + #include "xe_pcode.h" + #include "xe_printk.h" + #include "xe_pxp.h" ++#include "xe_shrinker.h" + #include "xe_sriov_vf_ccs.h" + #include "xe_sysctrl.h" + #include "xe_trace.h" +@@ -450,6 +451,9 @@ static int xe_pm_notifier_callback(struct notifier_block *nb, + if (err) + drm_dbg(&xe->drm, "Notifier evict user failed (%d)\n", err); + ++ if (action == PM_HIBERNATION_PREPARE) ++ xe_shrinker_backup_for_hibernation(xe->mem.shrinker); ++ + err = xe_bo_notifier_prepare_all_pinned(xe); + if (err) + drm_dbg(&xe->drm, "Notifier prepare pin failed (%d)\n", err); +diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c +--- a/drivers/gpu/drm/xe/xe_shrinker.c ++++ b/drivers/gpu/drm/xe/xe_shrinker.c +@@ -172,6 +172,34 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker) ++{ ++ struct ttm_operation_ctx ctx = { ++ .interruptible = false, ++ .no_wait_gpu = false, ++ }; ++ struct xe_bo_shrink_flags flags = { ++ .purge = false, ++ .writeback = true, ++ }; ++ unsigned long to_scan, scanned = 0, freed = 0; ++ ktime_t start = ktime_get(); ++ int ret; ++ ++ if (!READ_ONCE(xe_modparam.hibernate_writeback) || IS_DGFX(shrinker->xe) || ++ !ttm_backup_bytes_avail()) ++ return; ++ ++ read_lock(&shrinker->lock); ++ to_scan = shrinker->shrinkable_pages; ++ read_unlock(&shrinker->lock); ++ ++ ret = __xe_shrinker_walk(shrinker, &ctx, flags, to_scan, &scanned, &freed); ++ drm_info(&shrinker->xe->drm, ++ "Hibernation writeback: %lu of %lu pages scanned, %lu freed in %lld ms (%d)\n", ++ scanned, to_scan, freed, ktime_ms_delta(ktime_get(), start), ret); ++} ++ + static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, + const struct shrink_control *sc) + { +diff --git a/drivers/gpu/drm/xe/xe_shrinker.h b/drivers/gpu/drm/xe/xe_shrinker.h +--- a/drivers/gpu/drm/xe/xe_shrinker.h ++++ b/drivers/gpu/drm/xe/xe_shrinker.h +@@ -13,4 +13,6 @@ void xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long p + + int xe_shrinker_create(struct xe_device *xe); + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-eevdf/8213-xe-hibernation-writeback-gated.patch.sig b/pkgbuilds/linux-omarchy-eevdf/8213-xe-hibernation-writeback-gated.patch.sig new file mode 100644 index 0000000..eb7552a Binary files /dev/null and b/pkgbuilds/linux-omarchy-eevdf/8213-xe-hibernation-writeback-gated.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-eevdf/PKGBUILD b/pkgbuilds/linux-omarchy-eevdf/PKGBUILD new file mode 100644 index 0000000..c7066b4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-eevdf/PKGBUILD @@ -0,0 +1,1010 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy-eevdf +pkgver=7.2.8 +pkgrel=5 +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%%.*} +_srcname=linux-7.2.8 +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} + 0140-sched-always-inline.patch{,.sig} + 0141-sched-urgent-fixes.patch{,.sig} + 0143-sched-hybrid-cluster-balancing.patch{,.sig} + 0144-sched-nohz-idle-core.patch{,.sig} + 0145-sched-eevdf-tunables.patch{,.sig} + 0210-pm-updates.patch{,.sig} + 0211-pm-fixes-1.patch{,.sig} + 0212-pm-fixes-2.patch{,.sig} + 0213-pm-fixes-3.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} + 0281-mm-no-deferred-kernel-page-table-free-at-boot.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} + 0300-btrfs.patch{,.sig} + 0301-btrfs-fixes-1.patch{,.sig} + 0302-btrfs-zstd-decompress-direct-to-page.patch{,.sig} + 0303-btrfs-fixes-2.patch{,.sig} + 0304-btrfs-fixes-3.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} + 0320-vfs-open-consume-path-reference.patch{,.sig} + 0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch{,.sig} + 0360-gpu-mem-cgroup.patch{,.sig} + 0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch{,.sig} + 0401-psr2-early-transport-panels.patch{,.sig} + 0402-drm-i915-quirk-panel-replay-exit-on-flush.patch{,.sig} + 0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch{,.sig} + 0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch{,.sig} + 0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch{,.sig} + 0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.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} + 0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch{,.sig} + 0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch{,.sig} + 0444-drm-i915-vrr-dc-balance-off-by-default.patch{,.sig} + 0450-amd-hdmi-vrr-allm.patch{,.sig} + 0451-amd-vtem-tmds-links.patch{,.sig} + 0452-amd-hdmi-frl-default.patch{,.sig} + 0453-amd-vtem-null-sink.patch{,.sig} + 0454-amd-hdmi-frl-status-polling.patch{,.sig} + 0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch{,.sig} + 0456-amd-dml-frl-compressed-cap-check.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} + 0474-amd-display-oled-vesa-backlight.patch{,.sig} + 0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch{,.sig} + 0476-amd-display-ycbcr420-only-modes.patch{,.sig} + 0477-amd-display-irq-atomic-rmw.patch{,.sig} + 0478-amd-freesync-vcp-code-check-hdmi-pcon.patch{,.sig} + 0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch{,.sig} + 0510-sound-updates.patch{,.sig} + 0511-asoc-updates.patch{,.sig} + 0512-sound-fixes-1.patch{,.sig} + 0513-asoc-fixes-1.patch{,.sig} + 0514-sound-fixes-2.patch{,.sig} + 0515-xps13-sof-quirk.patch{,.sig} + 0516-rt766-stream-config-type.patch{,.sig} + 0517-hda-realtek-rog-strix-g733zw-speakers.patch{,.sig} + 0518-asoc-amd-yc-acer-aspire-a314-23p.patch{,.sig} + 0522-sound-fixes-3.patch{,.sig} + 0523-asoc-fixes-2.patch{,.sig} + 0524-sound-fixes-4.patch{,.sig} + 0525-asoc-fixes-3.patch{,.sig} + 0526-sof-amd-acp7x-suspend-unknown-revision.patch{,.sig} + 0527-sound-fixes-5.patch{,.sig} + 0528-asoc-fixes-4.patch{,.sig} + 0541-cvs-nova-lake-acpi-id.patch{,.sig} + 0542-media-cvs-wake-irq-without-claiming-gpio.patch{,.sig} + 0543-media-laptop-cameras.patch{,.sig} + 0544-ipu-bridge-lenovo-x9-upside-down.patch{,.sig} + 0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch{,.sig} + 0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch{,.sig} + 0547-int3472-imx471-vana-power-enable.patch{,.sig} + 0548-media-cvs-sensor-node-in-bound-callback.patch{,.sig} + 0549-media-imx471-analog-gain-max-960.patch{,.sig} + 0560-input-updates.patch{,.sig} + 0561-input-fixes.patch{,.sig} + 0562-hid-fixes.patch{,.sig} + 0563-input-fixes-2.patch{,.sig} + 0565-i2c-asue140d-touchpad-100khz.patch{,.sig} + 0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch{,.sig} + 0601-thunderbolt-updates.patch{,.sig} + 0602-thunderbolt-fixes.patch{,.sig} + 0603-usb-updates.patch{,.sig} + 0604-xhci-fixes.patch{,.sig} + 0605-thunderbolt-pcie-rescan-after-tunnel.patch{,.sig} + 0606-thunderbolt-fixes-2.patch{,.sig} + 0607-usb-fixes.patch{,.sig} + 0620-usb-string-sanitize.patch{,.sig} + 0650-wireguard-tstamp-type.patch{,.sig} + 0660-btusb-mediatek-mt7922-13d3-3625.patch{,.sig} + 0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch{,.sig} + 0663-btintel-pcie-serialize-reset-type.patch{,.sig} + 0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch{,.sig} + 0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch{,.sig} + 0666-btintel-pcie-verify-serialize-dstate.patch{,.sig} + 0667-btintel-pcie-gp0-cause-w1c-write.patch{,.sig} + 0668-btintel-pcie-dstate-races.patch{,.sig} + 0669-btintel-version-tlv-parse-errors.patch{,.sig} + 0670-bluetooth-fixes.patch{,.sig} + 0700-pci-target-speed-quirk.patch{,.sig} + 0701-pci-amd-enhanced-atomics.patch{,.sig} + 0702-ahci-jmb585-32bit-dma-revert.patch{,.sig} + 0703-pci-vmd-scan-bios-enumerated-devices.patch{,.sig} + 0704-pci-vmd-meteor-lake-mtl016-flush.patch{,.sig} + 0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch{,.sig} + 0711-iommu-amd-perfopt-support.patch{,.sig} + 0712-drm-amdgpu-perfopt-in-identity-domain.patch{,.sig} + 0740-hwmon-updates.patch{,.sig} + 0741-hwmon-fixes-1.patch{,.sig} + 0742-hwmon-fixes-2.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} + 0802-platform-fixes.patch{,.sig} + 0803-acpi-battery-hook-api-acpi-prefix.patch{,.sig} + 0804-acpi-battery-unified-hooks-acpi-sbs.patch{,.sig} + 0805-hwmon-applesmc-charge-control-end-threshold.patch{,.sig} + 0850-futex-wait-multiple.patch{,.sig} + 0851-futex-wait-multiple-fixes.patch{,.sig} + 0852-futex-wait-multiple-abi-fixes.patch{,.sig} + 0860-module-add-module-kallsyms-on-each-addr.patch{,.sig} + 0861-ftrace-drop-weak-function-locations-of-modules.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} + 8209-xe-shrinker-use-opportunistic-hint.patch{,.sig} + 8210-xe-shrinker-single-backup-decision.patch{,.sig} + 8211-xe-shrinker-skip-active-vm.patch{,.sig} + 8212-xe-shrinker-backup-in-kswapd-only.patch{,.sig} + 8213-xe-hibernation-writeback-gated.patch{,.sig} +) +source_x86_64=(config.x86_64) +validpgpkeys=( + ABAF11C65A2970B130ABE3C479BE3E4300411886 # Linus Torvalds + 647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman + 12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7 # Krzysztof Wilczynski +) +b2sums=('5326dde778eb945f282740ef0d8765b46fbd198f1209cdfd7b91e5dac1312fc01f8bf0b2bc67b16a76de736edd8ea21b38b6c91e696ec0c4b39746cd21a15214' + 'SKIP' + '60eac409f1ba50fe319f6b3aa5b0af81bc7ce5628ca41c1af346a6d5ef0fc9c577527742d1f70ece7e07ea410fcd112b32d64f0a05af5b532f27b62e5e7d837a' + 'SKIP' + 'cc2a66a097b5567e80c59b7d6c492fef5e667fc71a0390908712c69512c4136dbaf3d150b62d04eff960c796f7a94e6d0915d3b92cb336318b49f5f516a3bba6' + 'SKIP' + 'c5a06c5632ba1eeb592f19b7f1581acfb7adf603b098d1d966359a1bb266a330a5707b64aa1f0e0346003e0b3008f46d8af669b621ccacd9f219838fb5e122bc' + 'SKIP' + '6aa6ea3690f3e4e145818650537cd110d33720d662af0b3660d95e15a3f91e48c993e31117c0a2fde852f55a77a83e3a50a1a95c0df78a33ae75d43d46a9fe15' + 'SKIP' + '91e65754432c2d33fd44d81f9c8664b8c233dece280ed27800d9a319f035db18764ec6b8c90617238c6a54ab98fef9f524ca65facbe7434c00891a2dcde1eea9' + 'SKIP' + '39dfe3393acf96effce036db341dc7310f33f2f6431edc5e9cfe95edae0e9782dbce46a29fcec9134cfcff361cdffcb24f195bc0c648e79e94eca19ec93962e5' + 'SKIP' + '9f0d647d9cbe6f758f2d260920c3fda78ed91ed52f8c3108ff9a1a263505b18430ea21700311e5e162dfcdadc8feef120b999d622db803363785c8beca1f50ec' + 'SKIP' + 'c94778de1f106d30c7f2628be8b99f5bed8864e09dc3298e5c85181804150df49f3b151eaa94f801f14b1d6c3b132184d133840c4903192b565799cae226a4e9' + 'SKIP' + '3d5a9ed6d78d5f82b43da9fb43cb57b5621faa7601ff04bba71b5c3ead05bd4fd916323ce68e50cf178e2a9c14bc826ea85ab203c878de26446618f180a5fb4c' + 'SKIP' + '64a2e489285c40df691f822dbca67e2b2f21446f300465f01bb589380173f6da182eda71d09a840e7ea3e14acae940d3cde66fc3b476ef6417c6e8479841cc56' + 'SKIP' + '921a4b6ca25fb1a10588d7a3df65277cf6acf1115e618741486d230bd8eee16daef3f712518f98c8a49222aed468e706015eb238ba289769a464f5f33a21b21e' + 'SKIP' + '486987a57ff24ae8c58953c01db6c8e4f4eca4093a8484b28b9a647790a6757a381631e13d64605bb009613985b686a0f98ef58b2e2f9c9ddc5b4d0de1c62de1' + 'SKIP' + 'e2d21e10be9239260aaf41ec5584885555d3d5150a817fdc2b04001444ae07cebefd140b20e841c0988843749e850ad52f93b98d005d5b158e158454c2020691' + 'SKIP' + 'cd6d3232202e8e6a44e80783becc1b527336027420e308f354b32b6de1f83de210aac78ee0c639ba23030b4a0811cf7728bcaeed08e9dbe11caf3edff380ed6c' + 'SKIP' + 'e59c4da548b2ea4fd6144886e3e2e765b3e73972121e9e73dbc05c066834b2b35e6ef4c17617e77efc90260566bf7c0910d2138d8d5c1c3b6094eeac9de64cfe' + 'SKIP' + '9809e258eb684b80d508fa2f2fa270683b62bd1412ed05a970114aaa7d84c37c0beaea51f9ff8c6ee1bc7318c4aee3725e9a1131fefb51ba700801de6ac414c5' + 'SKIP' + 'f115e8c80d723f7c24e6b1638b801cd664bef20acb1d2f6e6f5612744b916278693011782228cd3f4d0518654b8d53309bc5b06bbe8ed722fab9995f369bf548' + 'SKIP' + '31abdd15ae3f9709d6912c90319a0b62d8380e1ae1169575c0e8e419f8ce264a7cf7c72558a85719cd5a1cae7341007adb3e3936268999ebd5215c2ed288f22c' + 'SKIP' + 'f95af54bcfaf17e80d9c0f60243d946aff2bbb796ae47c92a97a353f598483f03ca87fccefbaa47639b1018b82e7879dae4b8759e8fd9206b33d219560d682a7' + 'SKIP' + 'ff9a3e5df8457880b25caef9de90a7eb513ad8de99296ed503268749a32d8fa79da11f598a5fd80d28231abdd65a247e2aa92ce7c8ccc9f497fa483eee93aa82' + 'SKIP' + '55951c59286df43f069302b0b1036424812f80b45c5ea13219fce1bfc3e831731432c216288d722e44493c736cbf89a3e44801b5a4f1f242c17b5a89b236a5ef' + 'SKIP' + '737db39c2f7f46550de4d6d80659bd6c1fc2bf1ca42bd7bd6fc345fc90af995acd056051e12175ac6c957333629a017051d76d5359c871158c93f0a294fb8de5' + 'SKIP' + '4d0646c8ad139add6abb62d75308d2d7b5b07afbc74a0f0297ffc2148f0db38abaea16b048704187da49fe3ddee8370b10a7a41d6e62b665650cd79e8c392b4e' + 'SKIP' + 'bd7bce1bf0ee24ce201fe0e5f0b8a6c829a84d224800547cf97a893326381e2e6eaed9c70529a6864b0c338276b3a307b14e26fba221e62263b90c02eb5e5f34' + 'SKIP' + 'ad2cfdc3d8de2193110325395e11015577f26ef1081307bb236ed73fbd109822dd44ea026a57c2e17233e1fb7982e78aa986b71c043f42887e6c5d7f51b84ea9' + 'SKIP' + '92cadbb0f7c288987838534f523f85a67d07db19ce27fd6c8717f73c02f7ec7b7b47276fea8b2a86593178dbc4f44e774727050f2b46baafdb7296028fb24a0f' + 'SKIP' + '0b281a5780efd064a6c98a08a41eadb1286a2aa5a7f1bab33d3adaa82713f4676bd3acf07556f3617ab0649f95fa5ce3a0418da1b6ae48cb580f0d7d5d9752b3' + 'SKIP' + 'a51e5f2a5e37c350fb4d715ece644c565a508970c3aeb1d28cb6cd03a0c7316a1935e028b64f20a03818926c611b50ae0ffd77d04c676d604d303beed0f96e1f' + 'SKIP' + '71e8a5d91d285519367ba73241d01adc3b61a2b7b21f8061ecc3749fbf9d87f5ca414809343fd37530bfd5226f919cc3d87dc3b4034cc5b8be9c720f8b4676b2' + 'SKIP' + '64ca5b3c45b579c51c04f1177c8e6eb25db4c76f17f7b253c5ed959c9688bafaa97da701257435b34338432e88071f04a8d4049ba66895dd3ba804173cb08b28' + 'SKIP' + '4dfbcdb23d7117b75202944547f7ce956dd8fa7405bc5ca4856135e2becc21d950eb6e1aaec082da4290ac6cffa402686c2c9137d4dc23952796ce3334b95ff7' + 'SKIP' + '51a7a4f87d53e4b3d25041a0536b238d4550fcfdef0e7de5aa75c26a7a9217f6f3e260670154b9fa6674470f72e5e09c76571e6224d8be4eb5dd4383fef64a55' + 'SKIP' + 'fd487cb02212ffdae5d8beb7d895819377686085b95658dcf597a534b017adcb65dfdf06e0276e7322a46dc28118803b05c246c22e7d3c361f1778ed9e12a3c6' + 'SKIP' + '2c58614d716943cc1883dc513c2206e82f49574eca79f3e7dde21f8dd0f02f18611b2b2a444745df820c30c887d8263bdf9786a4a90b7979efee8d86633cbb88' + 'SKIP' + '218d01002d5487e2cb29f479f9ef9a2beb8226d2adccd1a9f932dc162a8502003db19b1bf74395b0239a2a15888131bcdc969b737cf56358dac6ab14d1b4501d' + 'SKIP' + '1b340a502dcf5a1680caf5320b3aa72aa4317e4cf1d877c1f6425e281a5668d5f092417e1f282371c0a211344f5c187fb3bdaf3f97c7e122951edf7e4c08ca58' + 'SKIP' + '1124c5c3fd5104c886c841ead1ae9afb42683388b640b1a86523dad9919c6bd59a1f78b07ee5a213e0dca77c636a29676af0592d4ed1735ea35f282f6f23e46f' + 'SKIP' + '505bf01a9d6b0926b557b673bb686d1a4ff73ee70539991e52272fbf58aad3ea25cf5ffc5c2d094a2d5e78b18a1a134f2ab530f595073a2d3eba9ff2c1418e1e' + 'SKIP' + 'b4bf4c4c0672bad91d7f8d309e741104918dd8ee8688a00d600379eba5828d1b8157953377c73d0b58050b4ff888c43013ede4e9c959be8d9c028a6143bf58ed' + 'SKIP' + 'a5ae2f6c29ba850292fcd1cd39d7af6041169ad9d73151d8fcc07ec2e1da473f3ac801eda892082b615d7f19c2a3ac17bceeb1d100dc1e2ff4b30287bbd8fc8b' + 'SKIP' + '3b8e01822f9f97842c5f7d7211df876d46d8477321546bd5b8cb74484550a2c520ffe351ce0ebb7df5659d2745187081883c091574c855ae7361315d153bc557' + 'SKIP' + '90e189032c6a04ca809607a5c9a8e798ff2f3dfa7d82bb510eb608da961196bb4b53f35907bfa9ce4006fe182102900bfa20f4566b146aaddaace4fc19adc305' + 'SKIP' + 'b9f60ea91694fb859543cf895ac9d654edd0aad92a3bce6ba9cfcfc450f413e49bc8cdb08adc6fba1a46ef319c10cdc501e3ffe6b37de222b11cd1c6cf138db9' + 'SKIP' + 'f80573494f330b31af70f1a079100082e00e3c7e185bbc51928d09d2ad3ccabb3134c72a0edcef994d6cc517015190d88c41301cf73c73419c594977f86a2906' + 'SKIP' + 'e403bdc300284c4f0f4302e501b8ccfe5e9af04e73a6070eb6615322f5f40ed6edad53af2ed278741f61a4e3811b92faf1af02f113dbcc2a19bde335a55930d7' + 'SKIP' + '5de29d0eb5264e24ab9228bea2ea4b549b3fe37ea67c045df9f83064f11c2f557b9641e772d28db59f8d258418c77435b66ff471c3695043f0f8275d5e7b1685' + 'SKIP' + '25a4ec87d1c789cab55e8ee6357276eb2e87b5e70eba2cd978b2c898e7b79de3fbc8cf5997fcd660998223b6173c98e440c853ad7c3e116f52a48d38622dfb46' + 'SKIP' + '2b33b11e48d8dd8d6743f26170fb99eae38541e3665e2448d91cedf729cca7c0278bfba1078ecbe88201eac4407ec5bd0292a97dbab9cfc67e42646712befc1f' + 'SKIP' + '5ef0d9561a570941752fd1976a2929eb25fb4dc16ff7fad09ce71e0d6881924f1ba8839574f7f877bd8089d116d689e33a54c14e2f177fe797dc86b305a74c0f' + 'SKIP' + '0415549ab1bf3ef0f2af4e62130b66e80d31dd3a1d1acda1cfcc2e4c926f25cf9449153736216a564cbd0710c0f0f1294607bf27c26790bad16e0f8956127e32' + 'SKIP' + 'a0a17aa48cdbf60db7a6d3677fae8a3388757b0f1de71043d8eb93028710f5a26aa57eb2011199b84e9689f7e4457843b74a5731c7dc14e4d1fca44b957bf33e' + 'SKIP' + 'ff1f07b3ad829d444b92c93fa63ab2c08277fa512483806d119a4cc75efbbcfc197dc982d9a146b7e9b94a9ca5bac95eed54735f607d7715cd52444ff0039e30' + 'SKIP' + 'a5670a422ae3d084f20800a5ca2c5615efaef4cdc58e466ceb25a8c56597e592cbf3eea7f4b8b28f1e7c79e9ecc7ff29c284d7be41de26fc6291df020fdd3d33' + 'SKIP' + '353b7e89a7044b7426db730d5b900eb347907aa0c20e307d2317925d6021a969a343649ede7a32e698dad60dd4472eb197c9578fb0e36fc354a60266fd680cd7' + 'SKIP' + 'd70e2f90fd0630f53b9680a1a935294d0a9e86e52ef9a712905a7799827ced2ff5acfa27a51980cf6f8157d60d9831cc0d62c623f98cf577810a4cab02b334fe' + 'SKIP' + '88a071e8e20be14db31253152064a26961b74c329793b7fd0c81b629973bd39040e9a66189e3dfda31ecc189f82e65781b310603f498e13456b8090662f80b0a' + 'SKIP' + '0cdfeb8a469295f0d7afc27800f5109f5c88c4afda93056d4c4fc9601988334c32d287e1490966ae21bbe80c5e4f8b8da2cdb6a019ec72682521972840247b8c' + 'SKIP' + 'acc6611ef0605b18b3326e2170f32c3daad02b6472b4fd8fd2a8ba4f99a35b319f2d67b6f1f017e9beb6e8cdb8edc081e97382f3cbe7999d8ad57bd5e929a9bb' + 'SKIP' + '29f44ba6256ca05c8f40f0f1b0e813a5b3928f23e37a23cef9e7e148832f41d8ab4ec43b398ef21a06cc8588e4feee24fbf1f3ec4428ef1b22b1d43a776022c8' + 'SKIP' + 'f3437588231ac856c45eee210b6d1e4f31019480665ae31f62e0fcc4b9931cea4bdcd2bb95345cb9dea26dba43931ccc73e9c9ed209ab3281a1618378658ec0d' + 'SKIP' + '824089f9f45e2ff9e788d874bb9294b9208b4486fd22c56cb59d4d88cb996ff5a81ac22f8ff136ddffa1e40330f02e53ab759ffea7e4535c11d4f8195069966b' + 'SKIP' + '22e8b6835a86ad485e5b23ba73a4e1515242b06f42063c99d0dda1f9248ce222e003acd43a517dced198a28bbd1812ce5faf66fa589717734558783fa385bb13' + 'SKIP' + '7228e249d25a99f5106040b8ddb5705979b3a5af21ca60d0d43a9ecaf6eaccef6bde8ea69c896cffbf2b5963ec4f68987e69b184a8885c7f4c98d2b05fd59fbb' + 'SKIP' + '4d5c5e2fbb5d8fe50a899bcfc629645d1bc9ef995c76611ecb38d25070552487d53ea59cb48bb3922d9d9965cc1f7d37ceea4f2ec789ed1bf37d69b831dea6bf' + 'SKIP' + '47015b9baebb1d6dd2a68321980c1c97da097825966f480c8569bcc9df5bef0257c51d5f7398960f28016258eaf1fff25bf142a64a01b829e882b4c1dda10612' + 'SKIP' + '4f3df2be1b52573ef4299934896dd5b4a5b9ebfa1f69c92f125edee1aa2db2f99bd2e6cf67734e1c953e9413bc6936067e8ac619cad66a5fe8dbcfcfc7906b83' + 'SKIP' + 'ee01ba98e614695154ba62789874843e48ce2a4715b3e6ca0304da0526f705672bb29253e0c52dc8f8fd3148c072067232ae6f4e86315dfc27b1563073cb92eb' + 'SKIP' + '0963ee814b6f64903d67757c5c55a93ec1f5b16374af16aeff2e7e494254d19031d4e1422428f4f7a8167592b7750a1db87476871eeb5cba851b377e59911149' + 'SKIP' + 'dcb350ef2987b801e481b5cf32cdfbe47018939f6b43e45b6dcff87eb75cc1a48f25e679caa823f3fe6391048f216ed9d95ae79b9cac237041857458f56e7f7a' + 'SKIP' + '0e4156949673d16caa6ee1405bf33d432ce2632e433d0abea2f4e9bc5b413500573ca06c10b8dfb477d2a6c7b4201f66ebe81ffb05302e3771dd2f0c515fc5da' + 'SKIP' + '9576c977d2c95cc474e84ac8dd7683a71df38d1d6e1ad80065cfa78db8185c7a52bb408af9881a71767a1c7e3f011424a180d0cba574b14ea53f65a567784181' + 'SKIP' + '4834e1cc74f3902e8e88eb28f9fb1f2bc3cdcda6745083092e7fc39c131bb0b1a8b06d74e6112e5d4c97d14e8b56302096691005474e6e5d2720da02463246a9' + 'SKIP' + '5d82f32f7c04b084530722b9848254aac289c2a754a10e16821083a871ec14065dd9ee83e72b8e9e8ba2915f3fbaf17b0874df724002eb08a5f4f84d1b6aecd8' + 'SKIP' + '4928cc008dc91fddb89b154b89b1ce2bbff14d8186c0ec1f81971fb9d3b622475e93e42015916699c3442e2268680779f545d183112b57dedae96fc48a75844c' + 'SKIP' + 'f93494f5f3196cc35d017ff9719d146bf3e5e7bfe32a7f5e75d96468ce30ef367428ba83b1c99b665064d3edaec84cf3bf18211d8f7b4f118b3bdee495eb8ef9' + 'SKIP' + 'eb79554ef0d014b94bc72d4d559c2cc5419df96486acb3b6a09d5e8e9dc48db626ce0f90338c67fe873b2cc3c45c3f308c8080532b60f74a07c9e39e01d34197' + 'SKIP' + '89482f70ea83fde5a88554f54c866d11c98baab1cbcb7fa6fe0079187821736b7ad9896246cb3976ec9d471fc0ebcec6dab32b95b4928704e23e3f4ff935f221' + 'SKIP' + '31202d94636ee8a62214952d0d54eb12b8e1e264edd20c374678dd442d5fc9f8b3ffa2e94cdb6d27c58004a660823d62ffe11aa81decb5af166cc6912486b9cc' + 'SKIP' + 'c0551a31a3c63b70966c9f87eb852cbbf43a13f649bb14b6fd2e8ca23f7fb311ba55d94ddb589a78a432eb225cd2336e553d399787071cee9933f2078fe8b5ad' + 'SKIP' + 'e6f6460dd43c5a7e4d943e6545479438286712237c9feec357317b1431aa778684132b734c5c113f1e57180de366411542c29f62507095e7db62e2d9aa6ad9a6' + 'SKIP' + '58899f7c2bb14ee442c6cfedb9a354f729c03cefd325cfe71da23f33d5a548f6083d5ad922231108fb58ee00f7ef2a2d45ce58c5767079e3ae3efaff306924d8' + 'SKIP' + 'acfcf80889e146bde867b12a7c1b4a7b88a71c09603d58974b647d8066cc281b300b7a43c0e1820f22fafb5f7056522091b9c6c0072ae6a4835e603578c5f95f' + 'SKIP' + 'c446acd1c5e498f0972febf76bf6198b4ee72c6e3e4ef285862564f2467d7ad7ad74eee88191fe1fd887a90a19ad61a3945ed9ee22a2ff6902fc5a806788b151' + 'SKIP' + '8bf8f181ffbd249b0af3a8fcc8463d84daa3c4aeb567595065c311f29b2dec2344fdd240d30314eae2c3d4a4daac53dad34e8452df11e386ce0a028267b18c0d' + 'SKIP' + 'bb06066e90132f0172333d46be54ca816eb7d3230a193a6cae765d8edc50df4a6ba9fb7c0647cd6befc8c3bbbe1d96c285ee202812477d7295c1130ea8cdd6f4' + 'SKIP' + '788488e828ac47a55ab78ad43251f7a2e5ad15bd5b5468bb31d4ee31e5de01a79b5680d5b048cb123eb3b7bde75e6b3d1e366e745009ed4129c8a6585d92b6b1' + 'SKIP' + '3bdf369bc73fad9bcf5b866043ff007365302318f42de7107fee47a7e8404c02c9c8fb5762d07ef10aebd2eb815c5483fc4960b7936e30ea5aa969a55f392474' + 'SKIP' + 'bdc66e50a9ef697e3baeee768f0f2dc56485afeb8a4e9f9ffd45f183d9614c33e0d0e72a8babca87963d1d9d701148eb41990730f2215a1c40e0965cc7f05e78' + 'SKIP' + 'a22e13b9bf0eac5d4190fd42bfde72ad0830cdbde8c0d309b2dfe9b0151e4c85f9f14f595ba3f36af479173ba1274f7700293c3d61dd913f711196a2dae8d6dd' + 'SKIP' + 'ebf13eb83afc051e3b9c84bfff7b326d73de6bac03f5b4d5996485d06a9cc87aca795b5c029646f804d62314a97e4a2b5da0b5f72f71556f33b62fd26529797e' + 'SKIP' + '8c6b5b96a3992c7cfbe50384fc0a4504e6d522c6918739f822b5bab29066eaeee7d1cc525b41a9b5b354ce020b1837e88d9bb940f3d2ba55a034aa9a845d489e' + 'SKIP' + 'aa24ab789db9badbd38db52f1e01fbc9d5abc09d812e2426ff281c869bbc0923cd903842d0c10ed8cf0e1a4695d3508b8ee4650f6a3eac3655247830aebb2c0b' + 'SKIP' + '1cf95a50e985ac37abd0faf86f4138e510cfb33af0ec172a2ca63f9252e6e4cba2f6c43a0babb11a96973bef3b7bf309a0f22ede26dc437673fcc77afef7fc31' + 'SKIP' + 'd5ab07d8a1db9191fc3b0d1f44c0237e93d662fb8bf04dd55385d8cded9589f66c8212a4d37c651c505fcfcb4a26da072c0be71d97fa0774b4724ba925a7af8e' + 'SKIP' + '7da8c7cd405efee8b37aff9916215b2d7052850f734b693656d2b66bae485661484efadc7207fa00dca5aa02d5e1bbaec2df9abc3e583f280097a939fc32096b' + 'SKIP' + '36fa6403c6519e064972afa0905cbff55f88fb89cad7582a89079f657537458da3d93deae474e33e018928d03090e1a373c6246b180d79f43366e16ccee533f7' + 'SKIP' + '44dcaad2ab326ebee62d3e63207a161dcb1b63bdb21412b5624ca7875bb6e99997528683a633d7dd526dc5ae9408e73bd066e2a1e5b34ba5c829162df2265346' + 'SKIP' + '69c1d61ffec58e2d3058d9e9e5fda57a089397d5ad451224f75f1cc2816c7f3fbd650b31bfb5911157fd364f77cb6f720e0825ae3deed8fe9f03576b8f7f1b31' + 'SKIP' + 'e3d60abe9fbfa046409598588dd23a9b16439474595136526e3169ff47982e011ede263d403211f5f5692ac43cf88af5bbf0d11b8e7e959478305c67687c525d' + 'SKIP' + '1fb895cb1deb1b227515087e12fb0f7fab70b0db25a1a535fa3c294a5455af4d987d02c7c8ff24aed41359055d1383ea90bf9709999382610ed156dbc4069086' + 'SKIP' + 'a2fb510d537b9afe8719334e5578a79a8ce7b021ff76c59830c749dbd963c38b7dfb60a61422b88f76886ccffb5ad5cb456b307a14ed68a5c86981eefd4fe3d0' + 'SKIP' + '5ecc37fe83f8dad00a82aa40a0c75d7518d6458b2f22d77820a28a34d772f1255cdb2befa5e525344d5deabac8f5dd092434a9ee2a9c27ccca5c5c231790c017' + 'SKIP' + 'bf138fd2ba2618ae9ff64838706c2212b478513e04b54ca3bdd8bf97292a8a310d73e0cd96533be62f139b645d7ac5de22a4c3bc1fd9f67e807bb862e965ea08' + 'SKIP' + '825397852f1c25b18c37d25bc53a7706af1eedd97cb9cf85ac590d1044e2254d06c4639bd54cac06ad42534399081b4ca17b0b10605c152ed41a22674c0ae42d' + 'SKIP' + '42a4c3debfa847f46c26db0d3e7cb1a540d3d15deae359b582eb2c71514c2cdb36c28c8c57b22dc32d0cee4d00ad7519da01c66127446692c81ea7a9178d722e' + 'SKIP' + 'ab28a1f4f1756b8451840566d8100cb3d0b498b13d006593c72829b9783e94900bae78ab47c576d156e1d08c56602274b3f7d5bd885de17336623b23c3e4f255' + 'SKIP' + '8481f23c33a84d8efb798f4b83f78011f74cd5a3dec428b177d7e600922836c1eec61c4238724038221d8ca2e2cdf20462c5911085da3461aa315f7dd19d96d8' + 'SKIP' + 'eca2778279d88085b6bf7af742773ad533906f98b146ba2526a9e00136e154913f233be728105765b0d2fc2774068f670a4ab1b49d8a7486d20e04d116f71c0b' + 'SKIP' + 'fea498b5c5ada6d7f57caf652b68f687081aae8552d2d04cd9e96764efd1b896208671f4f501210bb557710cca3ceb0c02ac2b5da6d4c04c210e87ec2d715384' + 'SKIP' + '39650b6ac7f62174d4b24e5267f9c6042c22452f16c649e032570eacf428e83ef8e10959fe54adf8573ac1c84d6082eb713ae5f173165af7d1f687e5d26fe6bc' + 'SKIP' + '3acc8aeadbac6ea2dd5a4fba4c10165dc1e6a4a55b838bff184fa46e827df67ea4e9a8535e17ed8cc80cc5338252306a76049af5956dc767bd591729b2aceb4c' + 'SKIP' + 'd442bf561a4b5dd1058c45a8181157feb268a84da94ec7f260dc6d10f7e0ddb71f1097bef945509f3d212431975ad3197f778bcc1c93ce1829ed2070bcf6bf9c' + 'SKIP' + 'c966ed5918d71acb91e988f3776d84db0b072f42e3e3f05cc178fa92a0d902fa3e34beb790c963042b52bc500d6ab9a2802b84a8411ca02cc1e28d361603bb2d' + 'SKIP' + '60fde203ea68744ea9bb8b81a18aedc8a4de8a985dc65bfd6b2f6b43210f87c2875e53eb61f4498c9b236892abde92da32755b69ee5bdb23f079079f22b529ac' + 'SKIP' + '56420f41da60382143b4df2d1fd58384180ca3a179b9f1b5082b2e4b74976ed13a29725c622e61c698631d923384082b43c9f703ce417d09e99556e4600f981f' + 'SKIP' + '5d880f3cdb32ed6fb7ce26ffbd1774fda56641d9304b5372806b6e559dab31dcac233cd6765a0b64bb6afe99fd9602402fb5e3b8c791cd1a9eaf9af28ca01bee' + 'SKIP' + 'a6098df3b653702e2689ec25934839c8a986b0e2f113b216ff4f86c8c5d530584f61d33bf4a027d1b304deb989406a3b342f13e11a66cc91ec69457c5d2e6f14' + 'SKIP' + '4a4af88fc8a8c37ae6d20b35d19e60a17672569d5489be120edbcc62ab3ba4bbc43b78cd0799b9fd73c84b089364320bcbad6fcb7128c2cb06d15ed984d6fc8c' + 'SKIP' + '411fd61157830b68da00e19be0d0db7ed61de97fd99b46a31c3958c5f66885265521e9e099c4f3404975db9de9b178cb698dbbe1399d81bf13ecff79b9ebd8b8' + 'SKIP' + '49adc1fc4c68da5c39d96746007d19107ae2d293518cfb36f3742aeba7723db31660fb1f7b3579e8fa88bb53206dbd1e718934f9b37e87925c6c0b06759e77fd' + 'SKIP' + 'f8a67f01fca5614f0eb3bfef59658913fb9b88b043f57c8bfedf04ad79912ff04d5d356dce8ff453ca001c151ab1bc017dcd7daffd5024e2bc20b087d08ab8ce' + 'SKIP' + '561a635f88fb3729937e1632d736a8981002cffd57cf16edad538381de8bb6a583f48b05e4d2bf1c8b6c03aece2495ac2850c1d66c4b540af2b88e1a9a0d9c2b' + 'SKIP' + 'da96dbbe3b6b4e189cf7bdce14647bba07bbc5599ce28ed385a1c34d237ee7ae3ac595d2552444615f2b15c2b7eda42f40743d5bdebd443e3ec31e5743f59056' + 'SKIP' + 'cb2494c930b2c2c4cd6755494339c5f95ace87940b2b2e51928d98ea63262e4bf7a4245f629b8bb06e7592f45a0c379e5583c8695563011009fdb07f965d7c1a' + 'SKIP' + '6083db5b92aa46bf54144b4d2fab368916974ef01334fbe934bded3818c5dfb4fc8d5d0e896bf1baddc29aac1c559f80dabc727082a757301117843def8089c8' + 'SKIP' + 'c25bff6e5be62aca887ae0781899e37a39e1aec90f0eb934ec5394abf560f4d02ad1fb3973a45d00058b46555bbc8d9febfcc8026e210f53a8864a91b3b80a9d' + 'SKIP' + 'e70e0077249bf008e069041a0239c6bb63075044cd0df3218649ff92a6a2d43467026886f2a6a70c70552268f6936764459d7cc0817a103b6a0f74c37c52c72d' + 'SKIP' + 'f1f853a0ef095a53080629a00598b7b1e0023141fb1890622ffd8efc4688afdeb87be4f1a096b76868840d45c10b3c88bb33f44677ff70378d63ec81eb3110e3' + 'SKIP' + '1b63569b589e994d1869e2f8eaf5d4ee225fe68862ac5c848e46544b827ca5fddb4bf060f5f39001608fd9bb284eec005f5f9b095307d3a008c9ea8957c2fad1' + 'SKIP' + 'f6430036af77a31a9ddd16200bd3c2c7b1b7e46475c7917dee91c835766be046a31736123ba8429ca2f0f8d3b93ef60522873bbec45d50b954cbb8319e6f25ba' + 'SKIP' + 'd7dffb68fd22db7ef41d1ac193dd297fb6150f034f257a44e6cb0a4848dc4a94e328c2e26d5296a72993e2db75d09e3ac46456d337a5bae767fb266082d3cc66' + 'SKIP' + '4e4792acaf0455f41cb1541d20d37e7a39104aa2dfb505e9be29dc3e7fecf328557780982b32cd8f67f77594542f5b8104766a4e8b5ce2270c9c601728363b3f' + 'SKIP' + '00417f024d2cc0b0fad28dec7f11c3054f5da7bf8c353d06fd1086be1926eacbe07ba924380157f270aac910d771f909f34beed0f60106f6ae60c96f58423b4a' + 'SKIP' + '2c254a2df00e79f03b51d13846f232073e9276e022c7bf46bfe810cc98456d481535152afa9d915e0726e7701594453f9363b6190b8e616db203d176a08fd31f' + 'SKIP' + 'cbfa20431371f3332f28013bfa21beef21a5a6f616e09fa91e9adb42899cb2e1880e27cf4b90bba980bce26710af3ac3c1e49054df4e48051bcc4612732d3e9e' + 'SKIP' + 'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835' + 'SKIP' + '803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67' + 'SKIP' + '4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d' + 'SKIP' + '335c0c1164e9a2b9a9a252ddf7418ca0e6493f2f0fddcf3f01ae20666b6cb6713b8bf7a2a165d37485d2107b6c00f42b201974154c41c15c4456615b3bb0d78f' + 'SKIP' + '01a28159b9c42e6b52147d1b061e1a2cb40ee381f8b6f7e2db3f94adc160365bc0dee602713cf42745fd2275b822b273df25d739d7b77f03273922edc746e50f' + 'SKIP' + '42703ec988c2b1865fbe4eef67840f4370958e3e874d9f8a03a46c43619bed7656c56cae0ef1d8cca5340648b47ba7ddf813d25d9db3307f10146c45b8e34ade' + 'SKIP' + '029d9e899dd9e7bdbcacee34ee9b468e2e2a8f726e38d014891c8353f6afcc52a58c7d9289fb0487cce4d90c8d51f578c27b1dbcdab42de7308f50b7baf27877' + 'SKIP' + '343b48e982195e3f07aa95dac09d5e09d5c02f823b91cc4f0775fa9529e9a985923a5af48067a1f071b99613acfa25b18e3b083e094f3cd97f6758a495c91c3e' + 'SKIP' + '9ac15b3542629c5333cc7d7f53b4f2995d807a31331b9e04d3da051be0ddde7e8c3cbc79837ffb49a4d364db1680cdc6d763465fa56bd4d26b16b765985babe1' + 'SKIP' + '70e35154f9385156a11280294cc254cb314fd89653daedb46546a6bd64c550e3fc7e7a8df3e653c516687f0317bcc710fce82b4d649472333c501ed85fcd6cf6' + 'SKIP' + '576fd826f28d945782840865f3e5b7c87d7f6f91f08e473ff0d06a37caa2f89cdd9cb4a7aafef5551363aab9c60e887ee794d5b6292874c12baf88c23ea5668e' + 'SKIP' + '01125c624a4971686d02a23918e3da697645a322e5a7638159636d9a16c82f815efdad1d8e22c507518dc0b92b6842277637cb2d0940f4eaab40c0faa77d8da6' + 'SKIP' + '322e4e255a2b900ad224ff376b00199c40e26fc5407e15dd36c1ca4e51c7ca0431397a69696959d1c1910b02a4bdac8efbaa20f6f6f6dd5424b711c149ea367b' + 'SKIP' + 'ae93d961d0eef41ed39c473fe9478b62fab13ba395333b3c11b34b7787904a3d2684520ec76dbe2603dd96a146a68bcbe98da1b7e4cc1e659246127384bce22c' + 'SKIP') +b2sums_x86_64=('94b27e0d777e81615d3d98ab7f1d4caec6f607ad72c1a9d495e1d5f3fc8bd3789a183d4fca35900e45cd2d27608478ba1a12d49ecef0281346f7ef9429784d11') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('12e8d5a973d1ad7c5a5c69882e4022b131ed715db7003fdcd760ddf8c3e51941' + 'SKIP' + '777e4bbfe44531c6504fa646ee74e95e8ff11f264a3fa281b528e25960e741ad' + 'SKIP' + '6cf74fc3f28a751354187b6cf610c3e43cf9088b5f730b68dbb575828c3a7478' + 'SKIP' + '58f21f8c48dbbb1295d488815aae50003155ec9ea5633ffbca4f0e17b18e8803' + 'SKIP' + '6a27e1b363f1bb133b9905f84b00e4d01885cac84858db46bdbf764849c9984b' + 'SKIP' + '83d51eee049d9898a217d3846edeef317365f4b74b277988582668fa71cb4a28' + 'SKIP' + '86dcf3d18fba4756eccba9997a3302445de347e878369c7b119851354d6a1c1e' + 'SKIP' + 'c89cb6da5e007551dfc6437a485adb664e9ac68e6bb80763dea757ef66c989da' + 'SKIP' + '01319b540a2cb4772eb4cccdc8c85ba4c96b150be0dcc88e3ac1290b4fdf6cdc' + 'SKIP' + '4fc90cc8e9d9979d7c839fd1761e42221577667ceb7e4502c354081b8d40d3f5' + 'SKIP' + '40177376862e4d7f45684c5e2a743f3da40d59817b0afa3050a3002ac6838085' + 'SKIP' + 'df870910371645a95140a3f92424db43c4e1a92bd493393e598d61c6266f1c7b' + 'SKIP' + '3d5550935087a7d82b527227e8f08ed98c2ffdb830b526c09e30b93137b3e607' + 'SKIP' + '2351136c78ad2dfe68e34bf9a70d377724257f6239e68bf8f501637b607af6b2' + 'SKIP' + '6c93475fc28d211ad7c15c691ea882deedeea8c0defe153cdde0a2a74488c7a0' + 'SKIP' + '718b40e15350049e03c0ed281b9257fca5432b8dec2e0e145e8b26507ee8892d' + 'SKIP' + 'b7ba949eea77e169aceb2c401d4eb83a7413aab6f15ee2a7ab1a96352f0aa12d' + 'SKIP' + '341424b925d506869793686e22ea8f095a34048595ebebec409c0a7bbd346ca2' + 'SKIP' + '7c25d0a53a115bc1f072ff0cfdd9aa88858e754ae8b15026a2582656e8bd9c43' + 'SKIP' + '336d72ab830bb33ef2a23bda1869e1fc071af64b24779281e197073ab70c851a' + 'SKIP' + '829300e576f418e64101a20d87f30f7c90798cd6e179bfb72768a83d99fa711e' + 'SKIP' + '5e3b455024fab2855b590091c9d14b0d12d5363f3d4bf025b31b9aa88b65eba5' + 'SKIP' + 'abd8c9326cecc9bb85db4b3dc98ddcb66306b34a6d84fb05d0805aed537e2dc4' + 'SKIP' + '60a9df54821cd98581e7476f8a7bb6cb0819762bdbed4d356332e41d164a8182' + 'SKIP' + '929108ae0a8eb4782cafdd89e3d4426d02d86f2c2102ed99e760e31f0bc3794a' + 'SKIP' + 'e1226c836a9c2fb6daff109e56ba21c13fd198e109cbb181de920af04fe153e6' + 'SKIP' + '2cdd81f5520b935e1d99629fbd6e1cc9d1351b301396316f37946b95084f1a26' + 'SKIP' + 'e65c2645b0d01301a91874183faed44a390b7a402d2dffd45171d210034dcc93' + 'SKIP' + 'd8e64b9da3beab7c33832c019da50d0977b39dc32ec1f649a7c3bc93a2fdb9a5' + 'SKIP' + '5e2515879d58eba10c861f73318f4c4b478b8dd592680eb58961aa4065d4c3df' + 'SKIP' + '874efac3eb32970f3e06275adc072a384b89ff69e2e5b7452d6146a6e89049b0' + 'SKIP' + '43aa4a11d9d48f5094b4c0e807d1d31598e861a50695bdfa52f19819b5ff5285' + 'SKIP' + '2b49e4c98c5d704bee0a0e2b38051d59177161bca7b0188e6c0feb555a95fbf1' + 'SKIP' + 'e6b770d37e80509ed7ec5d8b59b4d2e5d6dba54ba659d7392de3b567f1eb6e54' + 'SKIP' + 'a9b3ef8aa79488d1cd813507e040e0762d4c8c5845b7f4ecc29c6cea1ade8056' + 'SKIP' + '264d49d74eb4006c8eded08e39409045032920d76df035aebe8a1d0939d4b7b7' + 'SKIP' + '5cc215215fed4247c2d6b9732deff586331a07d54c3b00538a81921093a3a1a1' + 'SKIP' + 'e303da14a3c8a15fd1cb45394138f03dd16e66d2ac0d1ebcd933977df937d771' + 'SKIP' + '1f0c958b64ba48b8dc8b5548e1ad1934b8d4903fc16fec15014759e3681ea275' + 'SKIP' + 'd9eb3717ba98e270b0a998f8dd90cbda072f63b6a6ea8f66763bde3ea64e591d' + 'SKIP' + 'ce634014dc0131a44c3c95a8e9cb0a9de433f59d73fd99d3e3297adde65fef71' + 'SKIP' + 'd069e594d170adb016c5000d9cb7eac12c3c267a6b60c27afde7f046dba0509b' + 'SKIP' + '78feb2e2fabccaf954fdae5b776a22cec4a3f4135fbd24f79596a5274fc0129b' + 'SKIP' + 'f864c7daeaac0bad482bbf3853583d8a6cbbf2bf264562e397fc26355ffd5504' + 'SKIP' + 'c3a709af1589bd0c467565dc66ec4c01bed9c2b94314dde30c839084e594557b' + 'SKIP' + '1e2fddb2e0c5d184a2e8c774886cf9be2f2f292e4f05832b50e5b49c48a7bacc' + 'SKIP' + 'b9b8771b6a8bd7128f4344ad189e32c155abf72abd3ff646dcf5c78b08eb94c4' + 'SKIP' + '34991cf6cec6c22bc1a55a76bc5f72b405165a2f14ef977a0446df7c18bdfbb4' + 'SKIP' + 'cae59282e3d1afd69f4f5dc00a180c712ef464c98b8fd7ccc9b03de944dc79b9' + 'SKIP' + '3b06b5285923c251b0ebaf69911875f796cf7f1d7580ec33bb218509fea313a7' + 'SKIP' + '15609bef72571868fec6b8971f53b74441e5e93fbdd3c677ede963d0719cbc40' + 'SKIP' + '1231a6e23557936b811ce5dc184c23a7fbd268a20d582b6820c67125df8f07c0' + 'SKIP' + 'ec12a77671afb85f8297c86fc97f8314b4f00a1630acb199af12e3550a9d4ec8' + 'SKIP' + '1b1660064a3dadf2bb3006b140d1c7016a1ad051f27e7a4d6d9f3f40d491fe9c' + 'SKIP' + '40ac31789399a5964c4fd820d4cdc7214add91109bbcf4a614619e5d8b367441' + 'SKIP' + 'ab3b968d8bc2073e8c53478aa5d5afbadc5329a22b1486a014e5d0d84bfcf752' + 'SKIP' + '2a2a81e647aefda80192d230cd1dbae2aeb611f66b7d6c83694b9f9f8e2befdb' + 'SKIP' + '2772f0733267d12f36ae2a339c9aeaf0a133a20381a6b73e0c26f2ef92996ff3' + 'SKIP' + '3998c774884fdfc94be3da3c4c3641bd4b5aeb1878d8ef13bf0e27cd9f2e9994' + 'SKIP' + '0d5afb79681a948a65943f557d143c15126e2497e71c5ff50ac13ed8619a67e5' + 'SKIP' + 'e95f8fb6d116b30c2b86d9fb51747a9f9b4e5f980c1a3b627a38940db9d0e521' + 'SKIP' + 'dab92dbc6eac02771c72801d94e6a53aabcf629a9cd79e85e0ce2ba44a65b06b' + 'SKIP' + 'f55bc6366f8b9ffef744b289788a19e8604239e84c1af8b929a6922cf07d223d' + 'SKIP' + '0057dff07a3ce9c7b7085edd1f912958e01ce8f7400151d55dfb74c0c6a084e2' + 'SKIP' + '9c4c16b9593449ce556a4d6325e5e2205cb1698f9000a967ba7df118395b1ef0' + 'SKIP' + '00afdc277c53dff0898ac86fd44ead020e007373134d7c7c333285a9f1dbf786' + 'SKIP' + 'bed39a5e6bbb7cdac3f56a883a4684b2a63f3e8fe2f8cdcdd0b89b374b8d2a21' + 'SKIP' + '75d7fc371ba9b00a78af2e4b0fdb7cf79bf25a6620c983662d6b2e90cf5ce8c0' + 'SKIP' + '0fe32924142868879ac9417c67b0233b6ef8ddb0163183352479f5163bf7b5df' + 'SKIP' + '7444186ad4f6d8e2b52a3729064f649b035af6eae4f4ad46b9768c006419fdbc' + 'SKIP' + '94750a925a726ad6631d0486e7f3cb3796b3941715b188e790fa135d0aa5734d' + 'SKIP' + '143193a175084d65035bcfdcc81e9dfef2e794dd56d637556db2c73b117f708f' + 'SKIP' + 'f18821fa9527c7ee07a49b9c1c983a3584d5058f9734b520b5d51cb485c93f17' + 'SKIP' + 'f7ff13ea46359217d696c6ccd49bc3d74c441ac5893d5151d95138017b0d10aa' + 'SKIP' + 'd17493b3579c730d27dc723447175b70d991f50253c9fd419df5e923bdf7a244' + 'SKIP' + '92971bb4dbde209cf170838cfcd75d8ac9978239821d3493c28cc12623c2bb60' + 'SKIP' + '7245c6e2cb5c0b0ddf800894f087770cbf8ccc6f8b3f53b9a21c704e9dfa42c2' + 'SKIP' + 'd49629fd003900f13418c05d8c2fc37c8dea2fd8a15debb5b863008c9c46021f' + 'SKIP' + '411fe33ab7021dcf73b02cdc80a64094a511333c52dd754e54d382fe1145de08' + 'SKIP' + '5104ee45fc11994b1cb44130811435c84fe4db532a2fa9a722c7b03dbbdc8bf1' + 'SKIP' + 'f7617e3c4248e015f6f174e7c8659299d831035d0fb3c89acd1a858a202f91b9' + 'SKIP' + 'e7dd17ccd4b417a1813c21285c738a3fdede1326d8a1445086ea1af294c3710b' + 'SKIP' + '058aa32a18723035b18a770e1bd7f4b4f101bf5618bf0114e1874a398de42e06' + 'SKIP' + '833c5ede23f4a014f428c64b7da550cdcd7ecc939dfeb45dce172b18417888a5' + 'SKIP' + '21a9196887fe8453218e4805e171b19958005f38e5d21886c58a55f8d9b86795' + 'SKIP' + '29c61ec984bd342113b3c4310a3d5fddfd6d7db83847cfb26b84cc730385320a' + 'SKIP' + '4b5abcc24f557e83e4d385c8304c781d6ab78fc0a3935db97e01e7d27fc617ea' + 'SKIP' + '0dc5f0f91640715ee1744fb8cfe680de6ac1370f4df8e17d31016744f31d3638' + 'SKIP' + 'cd068c7e199f458356886eb321f759a91894bca374c71ccac242407d8b22a1f5' + 'SKIP' + '618369a173be9a5a2f91fb6044931d897bbf4911586bd94a10b43a25838872a2' + 'SKIP' + 'bd3535f78183d56b133986b814ec31a8c1aa933d71b3a611937e6377ee04af10' + 'SKIP' + 'bb1e2ab62b109e434bd2c2635b5912602b8a8cc0ba24743b7bb5f691dcde2353' + 'SKIP' + '55ddb7c072f3933eef42ac48f3b32f90a5298f5cf90b4ba5b2128a197250494a' + 'SKIP' + '62e446b250e96fb65a3068bac424e1e5fe4482ad6d7ac473fa94cb8ac047c0ab' + 'SKIP' + '8b55dbedf730b0dc675793418c21b909aed5db4311248bd277d3184839d261f0' + 'SKIP' + '5a09e7b8e10fdc61839ada49e9a33a64b240fa54847c1c0e0044518297196f0b' + 'SKIP' + 'b857f4c98ff5998adab1765a06ad307f9334a8d7d9644a3de556f2b0e9cb2b4b' + 'SKIP' + 'c28fca9ba015e96599627acf42e52737a6a846bdcbfd3d4af4147eda3109e12d' + 'SKIP' + 'd15cdfc9b86c40a119a4f21f4fbaec72efebc67677a7386df46a61816d4c6e02' + 'SKIP' + '1b070eef78ccffb3763b464f5a1b0636936f4c33b0be9b44ab116c5dcbadbebe' + 'SKIP' + 'eb0923de01175707151930d28b1a1a912d24bbc171248fa9f45f57a2125d077a' + 'SKIP' + 'c076ed3752bcc2033e435043668fe007eb1ea1760597416fbb5d90bed4947bec' + 'SKIP' + '0fa9fdc6d6c85435d7b3cb38b51823f458ab273c527fcbe21b945dbd89686304' + 'SKIP' + 'ef66f6c32ac98a311b7437265b21a8c70746f1ea4dfbe28aff655e778d09c28b' + 'SKIP' + 'b45303b559ab8990ecb4e0281e5d529625311e795d6c3a9a52c2c27498141e18' + 'SKIP' + '1728e516153acca3c343f5184c9894fe6f1a1fbc95abe1594f544ef601db39ed' + 'SKIP' + '534e68a989d36823f14a10299803f294eef39391619b4c329d78c20539d9fcd5' + 'SKIP' + '516e6ec51ca0330989128a4eb2a8f6785e5d45d930899cbbbf37b8147ddfd854' + 'SKIP' + '757ccc291d53030b89088802615ecf727df6f18ab2515cc0075008ebbd564e36' + 'SKIP' + '3a271bc6b0219152047fb2b78d9e695eb791b796ea6d8233096912c77d62a5c0' + 'SKIP' + 'fd6bf3ed3d16c788330630fdd15604836ec2a0b6a7db2b04216f756da9117bd2' + 'SKIP' + '4d598b9e00edf7c17d915eec7c8d369fc0ea40badb7f4576cb3dedd92a685d4b' + 'SKIP' + '98ff0d6196470c582b3e3841ef35bf82a63b57ebaef952885bd1905bcb8e627b' + 'SKIP' + 'de2682af1db7f5ae8cd74da4f9020bfb7d2167f030eb15f0afb7bd46ffe49e84' + 'SKIP' + '73dc22b7f97a6cc5a5f35d6b30b9b7b12be2566160a024769550290a0e7e4029' + 'SKIP' + 'f06834dfaa9e976a5778fa4fd07117a85bbff3b788c91e61d8cbc628e1b8bfb3' + 'SKIP' + '11884198740c5ac572cbe87086b1744ce19f0e1f856775e6edccce843ab097a3' + 'SKIP' + 'e6d848172228e98dde3a7ec679e7ea6bb0a42cca0b0105c58c8f9d6d44e33943' + 'SKIP' + '4e80f2d05591175d4ed0a1a144509e97260e5593472aae586c07956215d99162' + 'SKIP' + '1b442ad6f28c684068844ce93616690b84651e35870802b02937351bea078b4a' + 'SKIP' + '9be4f91e6a4be87791d700f44c228fe599468f06a4f627b79e75718ac020db4d' + 'SKIP' + 'ed635ed3b63cdb61614b8805aa5a9515d5b538522165be625dca89bdb7d6e801' + 'SKIP' + 'e28c29b9f359db85a60bfd49d2f28ba51f4d2272eb79b7d3e19ba8b3d5acdac9' + 'SKIP' + '3175c56fd2935848ce99fe10be1bc348231909f8479c3d3af911c5c7ab7f381b' + 'SKIP' + '30a12a616afa8fe95b969e90b42aeb240962b9fab8b8bd357ac00706abdd68c7' + 'SKIP' + 'eb0ebb8c55332dab8931c667ec84cd32c91c657d21af4504327ae05643108e1d' + 'SKIP' + '945c90d99f9a9fa47d61e471f76d17c0229ffa0fa8cda22de990a2ccc4194bb2' + 'SKIP' + '70ff41b2341836c27c5545a7948258f137d991c2879cbeb29f7af740c11d20e6' + 'SKIP' + 'b4efd997d3f09cbf258fd0f78aca44e4c96694dc4efc5c8c9a677ea67ebf5a54' + 'SKIP' + '2e1e2a0eb039ce61c1612bf6c7ab5227b99df053bcd5592da7758715dd333fda' + 'SKIP' + '6800dd82115ebe71d80dde0bb368fbd8563caa6c29127ffde0d6b5df81b502b0' + 'SKIP' + 'd90cc6570a47c8754e4c5d45834e0434c0a1fcabda9b8933b1fba84e3dabc083' + 'SKIP' + 'a008c052ca657efcd4e27b8f7b819adbc9606e261aac37d41a40cf67c094cff9' + 'SKIP' + '57169ef893dff957cbf33f7c0dc22f2fdfcfcb3befcd62ad59e2456484423bac' + 'SKIP' + '01ecc1adbe715bb31153a2350ca46e1cb8a6e3fdc6899b2debeff015af2412ed' + 'SKIP' + 'a4331d3968fc5002534063b42bd219bddfe4a7d6b08e4cb30ed60f4366ca3c41' + 'SKIP' + '028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8' + 'SKIP' + 'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311' + 'SKIP' + '44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a' + 'SKIP' + '6f01b5357af7deed3f4bccea6678b4d9e8afc83e598978f42f244241667f975d' + 'SKIP' + '0da7247035a359e2491933f9effff43ea7b29748f21a0e3080bef0018c2488e3' + 'SKIP' + 'f81f6b5769e47716ca318ecf76ab1c0fafa0e165fa858a64251359937bffcf04' + 'SKIP' + 'c67fee35a42866dd3c9056ff099fc504cdad9530c107d959331b5572b21af818' + 'SKIP' + 'fdfe62019e6ef5dfba2fabecc6ee5b58400f41ef000883fed135ecaca881b40c' + 'SKIP' + '07683e522d6dea3d3ce658ee80bf4317f6c3caf248259da960bc4610f4d1e807' + 'SKIP' + 'c5279468d93e562f509a6b599a2b7ca7ee0032d3fe35314c5769fc6c9a9234ab' + 'SKIP' + 'c0111614ec44014cbf621eedf5a4ef302a855423bc1633ee692392950e971d0e' + 'SKIP' + 'c2f916fa04d91edf926abf8fed086302fe538ba62d92450af0ddb6d3a33942f1' + 'SKIP' + 'd17774053ab07de92e18abdb0ff4d00352a02b6a97c48e28d8c875954c8eec36' + 'SKIP' + '774f337663b215c5aa8877c06ffce95be44403fe77516c1be9ef9789e2d049e5' + '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 + */ +@@ -2092,6 +2098,11 @@ __latent_entropy struct task_struct *copy_process( + return ERR_PTR(-EPERM); + } + ++ if ((clone_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return ERR_PTR(-EPERM); ++ } ++ + /* + * Force any signals received before this point to be delivered + * before the fork happens. Collect up signals sent to multiple +@@ -3165,6 +3176,10 @@ static int check_unshare_flags(unsigned long unshare_flags) + if (!current_is_single_threaded()) + return -EINVAL; + } ++ if ((unshare_flags & CLONE_NEWUSER) && !unprivileged_userns_clone) { ++ if (!capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ } + + return 0; + } +@@ -3400,6 +3415,15 @@ static const struct ctl_table fork_sysctl_table[] = { + .mode = 0644, + .proc_handler = sysctl_max_threads, + }, ++#ifdef CONFIG_USER_NS ++ { ++ .procname = "unprivileged_userns_clone", ++ .data = &unprivileged_userns_clone, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec, ++ }, ++#endif + }; + + static int __init init_fork_sysctl(void) diff --git a/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch.sig b/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch.sig new file mode 100644 index 0000000..a828924 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0010-archlinux-base.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0011-kbuild-optimize-for-performance-o3.patch b/pkgbuilds/linux-omarchy-muqss/0011-kbuild-optimize-for-performance-o3.patch new file mode 100644 index 0000000..9506ee1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0011-kbuild-optimize-for-performance-o3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0011-kbuild-optimize-for-performance-o3.patch.sig new file mode 100644 index 0000000..90d5219 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0011-kbuild-optimize-for-performance-o3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch b/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch new file mode 100644 index 0000000..16f35c4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch @@ -0,0 +1,17203 @@ +diff --git a/arch/m68k/coldfire/pci.c b/arch/m68k/coldfire/pci.c +--- a/arch/m68k/coldfire/pci.c ++++ b/arch/m68k/coldfire/pci.c +@@ -226,7 +226,7 @@ static int __init mcf_pci_init(void) + /* Turn of PCI reset, and wait for devices to settle */ + mcf_write32(0, PCIGSCR); + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(200)); ++ schedule_msec_hrtimeout(200); + + + pci_add_resource(&bridge->windows, &ioport_resource); +diff --git a/arch/powerpc/platforms/powernv/vas-window.c b/arch/powerpc/platforms/powernv/vas-window.c +--- a/arch/powerpc/platforms/powernv/vas-window.c ++++ b/arch/powerpc/platforms/powernv/vas-window.c +@@ -1185,7 +1185,7 @@ static void poll_window_credits(struct pnv_vas_window *window) + if (creds < window->vas_win.wcreds_max) { + val = 0; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout(10); + count++; + /* + * Process can not close send window until all credits are +@@ -1218,7 +1218,7 @@ static void poll_window_busy_state(struct pnv_vas_window *window) + if (busy) { + val = 0; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout(10); + count++; + /* + * Takes around few milliseconds to process all pending +diff --git a/block/Makefile b/block/Makefile +--- a/block/Makefile ++++ b/block/Makefile +@@ -14,6 +14,7 @@ obj-y := bdev.o fops.o bio.o elevator.o blk-core.o blk-sysfs.o \ + disk-events.o blk-ia-ranges.o early-lookup.o + + obj-$(CONFIG_BLK_ERROR_INJECTION) += error-injection.o ++obj-$(CONFIG_MUQSS_IOTIME) += blk-iotime.o + obj-$(CONFIG_BLK_DEV_BSG_COMMON) += bsg.o + obj-$(CONFIG_BLK_DEV_BSGLIB) += bsg-lib.o + obj-$(CONFIG_BLK_CGROUP) += blk-cgroup.o +diff --git a/block/bio.c b/block/bio.c +--- a/block/bio.c ++++ b/block/bio.c +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + + #include + #include "blk.h" +@@ -185,6 +186,8 @@ void bio_uninit(struct bio *bio) + bio->bi_blkg = NULL; + } + #endif ++ muqss_iotime_put_owner(bio); ++ + if (bio_integrity(bio)) + bio_integrity_free(bio); + +@@ -234,13 +237,18 @@ void bio_init(struct bio *bio, struct block_device *bdev, struct bio_vec *table, + bio->bi_private = NULL; + #ifdef CONFIG_BLK_CGROUP + bio->bi_blkg = NULL; +- bio->issue_time_ns = 0; + if (bdev) + bio_associate_blkg(bio); + #ifdef CONFIG_BLK_CGROUP_IOCOST + bio->bi_iocost_cost = 0; + #endif + #endif ++#if defined(CONFIG_BLK_CGROUP) || defined(CONFIG_MUQSS_IOTIME) ++ bio->issue_time_ns = 0; ++#endif ++#ifdef CONFIG_MUQSS_IOTIME ++ bio->bi_muqss_owner = NULL; ++#endif + #ifdef CONFIG_BLK_INLINE_ENCRYPTION + bio->bi_crypt_context = NULL; + #endif +@@ -868,6 +876,7 @@ static int __bio_clone(struct bio *bio, struct bio *bio_src, gfp_t gfp) + bio_set_flag(bio, BIO_REMAPPED); + bio_clone_blkg_association(bio, bio_src); + } ++ muqss_iotime_clone_owner(bio, bio_src); + + if (bio_crypt_clone(bio, bio_src, gfp) < 0) + return -ENOMEM; +@@ -1865,6 +1874,7 @@ void bio_endio(struct bio *bio) + bio->bi_blkg = NULL; + } + #endif ++ muqss_iotime_put_owner(bio); + + if (bio->bi_end_io) + bio->bi_end_io(bio); +diff --git a/block/blk-iotime.c b/block/blk-iotime.c +new file mode 100644 +--- /dev/null ++++ b/block/blk-iotime.c +@@ -0,0 +1,753 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * I/O aware scheduling support for MuQSS. ++ * ++ * The CPU scheduler can see how much CPU a task uses but nothing of what it ++ * costs the storage device. This attaches an rq_qos policy that attributes ++ * block device time back to the task that caused the I/O, so that cost is ++ * visible to the scheduler. ++ * ++ * Two different quantities are recorded and deliberately kept apart: ++ * ++ * Latency is what a waiting task experiences, measured per bio from ++ * bio->issue_time_ns, which is stamped in blk_mq_submit_bio() before merging ++ * and request allocation. It therefore includes plug, merge and queue wait as ++ * well as service. ++ * ++ * Occupancy is what the task cost everybody else, measured per request from ++ * rq->io_start_time_ns at dispatch to rq->muqss_done_ns at completion. This is ++ * the quantity the scheduler charges for. ++ * ++ * Attribution has two halves. Reads and synchronous writes are submitted by ++ * the originating task, so the owner is simply current. Writeback is not: the ++ * dirtier is recorded on the folio when it is dirtied and recovered when the ++ * flusher thread submits the I/O. See the owner table below. ++ * ++ * The owner table earns its keep a second time over. A kworker running a work ++ * item declares itself to be acting for whoever queued that work, which both ++ * attributes the I/O the work item issues and lets the CPU time it costs be ++ * charged back as kern_debt_ns. See muqss_kerntime_begin(). ++ * ++ * The occupancy is charged one for one against the deadline, with nothing to ++ * tune it by: the accounting below and the scheduling it feeds are one ++ * feature, and only CONFIG_MUQSS_IOTIME=n removes either. See ++ * consume_iotime_penalty() in kernel/sched/MuQSS.c and ++ * MuQSS-iotime-design.md. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "blk.h" ++#include "blk-mq.h" ++#include "blk-mq-debugfs.h" ++#include "blk-rq-qos.h" ++#include "blk-stat.h" ++ ++struct blk_iotime { ++ struct rq_qos rqos; ++ ++ /* Unattributed device busy time, see comment above. */ ++ atomic64_t occupancy_ns; ++ atomic64_t requests; ++ ++ /* ++ * Bios that completed with no owner recorded. Counted so the size of ++ * the attribution gap stays visible rather than silently skewing the ++ * per task numbers. ++ */ ++ atomic64_t unattributed; ++ ++ /* Requests disowned because a merge brought in another task's bio. */ ++ atomic64_t mixed; ++ ++ /* ++ * Requests that reached completion with no captured timestamp, and so ++ * could not be measured at all. See muqss_iotime_done(). ++ */ ++ atomic64_t unstamped; ++ ++ /* Bios that completed with an owner but no submit timestamp. */ ++ atomic64_t nostamp_bio; ++}; ++ ++static inline struct blk_iotime *BLK_IOTIME(struct rq_qos *rqos) ++{ ++ return container_of(rqos, struct blk_iotime, rqos); ++} ++ ++/* ++ * Owner table. ++ * ++ * Reads are attributable at submit time because the submitting task is the ++ * originating one. Writeback is not: the folio is dirtied by one task and ++ * written out much later by a flusher thread. The dirtier is therefore ++ * recorded on the folio itself, in spare page->flags bits, as an index into ++ * this table. ++ * ++ * Only MUQSS_IOWNER_WIDTH bits are available, so the table is small and the ++ * index carries no generation. Slot 0 is reserved to mean "no owner". A slot ++ * is held from the task's first dirty until it exits, and the table holds a ++ * reference for that whole period, so a resolved pointer is always a live ++ * task_struct. ++ * ++ * Slots are released on exit and never stolen from a live task, so a resolved ++ * index is never charged to the wrong task. The cost of that choice is that ++ * beyond IOWNER_SLOTS concurrent dirtiers, further tasks get no slot and their ++ * writeback goes unattributed; iowner_exhausted counts it. ++ * ++ * Writeback still in flight for a task that has exited resolves to nothing, ++ * which is correct: there is no longer anybody to charge. ++ */ ++#define IOWNER_SLOTS (1U << MUQSS_IOWNER_SHIFT) ++ ++static struct task_struct *iowner_table[IOWNER_SLOTS]; ++/* Work can be queued with a raw runqueue lock held (for example by PSI). */ ++static DEFINE_RAW_SPINLOCK(iowner_lock); ++ ++static atomic64_t iowner_exhausted; ++ ++static atomic64_t iowner_proxied; ++ ++/* Work items charged back to a task, and the CPU time they cost. */ ++static atomic64_t kernwork_charged; ++static atomic64_t kernwork_ns; ++ ++/* ++ * Slot for @tsk, allocating one on first use. ++ * ++ * @tsk is not necessarily current: an io-wq worker allocates on behalf of the ++ * task that queued the request it is running, so that pages it dirties are ++ * tagged with the real owner. The caller holds a reference to @tsk. ++ */ ++static unsigned int iotime_task_slot(struct task_struct *tsk) ++{ ++ unsigned int slot, scan; ++ unsigned long flags; ++ ++ if (!MUQSS_IOWNER_WIDTH) ++ return 0; ++ ++ slot = READ_ONCE(tsk->io_owner_slot); ++ if (slot) ++ return slot; ++ ++ if (tsk->flags & (PF_KTHREAD | PF_EXITING)) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iowner_lock, flags); ++ ++ /* ++ * Recheck PF_EXITING under the lock. It is set well before ++ * muqss_iotime_release_slot() runs, and that also takes this lock, so ++ * a task seen here as not exiting either has not reached exit or will ++ * release the slot we are about to hand it. Without the recheck a slot ++ * claimed for a remote task racing through exit would never be freed. ++ */ ++ if (tsk->flags & PF_EXITING) ++ goto out; ++ ++ /* Raced with another dirty on the same task. */ ++ if (tsk->io_owner_slot) { ++ slot = tsk->io_owner_slot; ++ goto out; ++ } ++ ++ /* Prefer a free slot. */ ++ for (scan = 1; scan < IOWNER_SLOTS; scan++) { ++ if (!iowner_table[scan]) { ++ slot = scan; ++ goto claim; ++ } ++ } ++ ++ /* ++ * Table full of live dirtiers. Leave this task unattributed rather ++ * than evicting somebody still using their slot: a stolen slot would ++ * silently charge one task's writeback to another, which is worse ++ * than not charging it at all. ++ */ ++ atomic64_inc(&iowner_exhausted); ++ slot = 0; ++ goto out; ++ ++claim: ++ iowner_table[slot] = get_task_struct(tsk); ++ WRITE_ONCE(tsk->io_owner_slot, slot); ++out: ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++ return slot; ++} ++ ++unsigned int muqss_iotime_owner_slot(void) ++{ ++ struct task_struct *tsk = current; ++ struct task_struct *owner; ++ ++ /* ++ * A thread running inside a proxy window dirties pages on behalf of ++ * the task that window belongs to, so tag them with that task. Doing ++ * otherwise would both misattribute the writeback and, for io-wq ++ * workers, let a churn of short lived workers consume the whole slot ++ * table. ++ * ++ * An io-wq worker outside a window has nobody to speak for and must ++ * not claim a slot of its own. A kworker outside a window needs no ++ * such test: iotime_task_slot() refuses PF_KTHREAD outright. ++ */ ++ owner = READ_ONCE(tsk->io_owner_override); ++ if (owner) ++ tsk = owner; ++ else if (tsk->flags & PF_IO_WORKER) ++ return 0; ++ ++ return iotime_task_slot(tsk); ++} ++ ++struct task_struct *muqss_iotime_proxy_begin(struct task_struct *owner) ++{ ++ struct task_struct *prev = current->io_owner_override; ++ ++ WRITE_ONCE(current->io_owner_override, owner); ++ return prev; ++} ++ ++void muqss_iotime_proxy_end(struct task_struct *prev) ++{ ++ WRITE_ONCE(current->io_owner_override, prev); ++} ++ ++void muqss_work_set_owner(struct work_struct *work) ++{ ++ /* Interrupt work does not belong to the task it interrupted. */ ++ work->muqss_owner_slot = in_task() ? muqss_iotime_owner_slot() : 0; ++} ++ ++void muqss_kerntime_begin(struct muqss_kern_window *w, unsigned int slot) ++{ ++ w->prev = NULL; ++ w->start = 0; ++ ++ /* ++ * Resolving the slot is what costs here, so it gates everything else. ++ * Work queued by kernel threads on their own account carries slot 0 ++ * and leaves this function having done one branch, which matters: ++ * timers, readahead, RCU and any amount of driver housekeeping run ++ * through work items that belong to nobody. ++ */ ++ w->owner = muqss_iotime_owner_task(slot); ++ if (!w->owner) ++ return; ++ ++ w->prev = muqss_iotime_proxy_begin(w->owner); ++ w->start = muqss_task_runtime_live(); ++} ++ ++void muqss_kerntime_end(struct muqss_kern_window *w) ++{ ++ s64 ns; ++ ++ if (!w->owner) ++ return; ++ ++ ns = muqss_task_runtime_live() - w->start; ++ muqss_iotime_proxy_end(w->prev); ++ ++ /* ++ * Runtime rather than elapsed time, so a worker preempted for a whole ++ * scheduling round does not hand the bill for it to the task it is ++ * working for. Negative or absurd deltas are possible if the two ends ++ * of the window landed either side of a clock warp, and are dropped ++ * rather than clamped: there is no sensible value to substitute. ++ */ ++ if (ns > 0) { ++ atomic64_add(ns, &w->owner->kern_time_ns); ++ atomic64_add(ns, &w->owner->kern_debt_ns); ++ atomic64_inc(&kernwork_charged); ++ atomic64_add(ns, &kernwork_ns); ++ } ++ ++ put_task_struct(w->owner); ++ w->owner = NULL; ++} ++ ++struct task_struct *muqss_iotime_owner_task(unsigned int slot) ++{ ++ struct task_struct *tsk = NULL; ++ unsigned long flags; ++ ++ if (!slot || slot >= IOWNER_SLOTS) ++ return NULL; ++ ++ raw_spin_lock_irqsave(&iowner_lock, flags); ++ if (iowner_table[slot]) ++ tsk = get_task_struct(iowner_table[slot]); ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++ ++ return tsk; ++} ++ ++void muqss_iotime_release_slot(struct task_struct *p) ++{ ++ unsigned int slot = READ_ONCE(p->io_owner_slot); ++ unsigned long flags; ++ ++ if (!slot) ++ return; ++ ++ raw_spin_lock_irqsave(&iowner_lock, flags); ++ if (iowner_table[slot] == p) { ++ iowner_table[slot] = NULL; ++ WRITE_ONCE(p->io_owner_slot, 0); ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++ put_task_struct(p); ++ return; ++ } ++ WRITE_ONCE(p->io_owner_slot, 0); ++ raw_spin_unlock_irqrestore(&iowner_lock, flags); ++} ++ ++void muqss_iotime_dirty_folio(struct folio *folio) ++{ ++ unsigned int slot = muqss_iotime_owner_slot(); ++ ++ folio_set_io_owner(folio, slot); ++} ++ ++void muqss_iotime_task_init(struct task_struct *p) ++{ ++ atomic64_set(&p->io_latency_ns, 0); ++ atomic64_set(&p->io_count, 0); ++ atomic64_set(&p->io_occupancy_ns, 0); ++ atomic64_set(&p->io_debt_ns, 0); ++ atomic64_set(&p->kern_time_ns, 0); ++ atomic64_set(&p->kern_debt_ns, 0); ++ p->io_owner_slot = 0; ++ p->io_owner_override = NULL; ++} ++ ++void muqss_iotime_set_owner(struct bio *bio) ++{ ++ struct task_struct *tsk = current; ++ ++ if (bio->bi_muqss_owner) ++ return; ++ ++ /* ++ * Not in task context: current is whatever this interrupted, which ++ * has nothing to do with the I/O. Bios submitted from softirq or hard ++ * IRQ, such as a filesystem completing one write by starting another, ++ * would otherwise be charged to a bystander. ++ */ ++ if (!in_task()) ++ return; ++ ++ /* ++ * Passthrough requests are not issued on any task's behalf. Neither ++ * is the flush machinery's own empty bio, but REQ_PREFLUSH is also set ++ * on an ordinary write that wants a cache flush ahead of it, and that ++ * write is somebody's. Test for the payload rather than the flag. ++ */ ++ if (blk_op_is_passthrough(bio->bi_opf)) ++ return; ++ if ((bio->bi_opf & REQ_PREFLUSH) && !bio_has_data(bio)) ++ return; ++ ++ if (tsk->flags & (PF_KTHREAD | PF_IO_WORKER)) { ++ struct task_struct *owner; ++ struct folio *folio; ++ ++ /* ++ * io_uring punted this submission to an io-wq worker, which ++ * published the task that queued the request. Charge that ++ * task: the worker is an anonymous thread that exists only to ++ * run somebody else's I/O. ++ * ++ * Writeback that happens to be issued from inside the same ++ * window is charged here too, which is a small misattribution ++ * within one application rather than a loss. ++ */ ++ owner = READ_ONCE(tsk->io_owner_override); ++ if (owner) { ++ atomic64_inc(&iowner_proxied); ++ bio->bi_muqss_owner = get_task_struct(owner); ++ return; ++ } ++ ++ /* ++ * Writeback. current is a flusher thread rather than the task ++ * that dirtied the data, so recover the owner from the folio, ++ * where it was recorded at dirty time. ++ * ++ * The first page decides for the whole bio. A writeback bio ++ * normally covers one file's pages dirtied by one task, and ++ * reading every page to take a vote would put a loop on the ++ * submit path for a heuristic that does not need it. ++ * ++ * Cloned bios are skipped: their bi_io_vec belongs to the ++ * parent, and muqss_iotime_clone_owner() has already carried ++ * the owner across. ++ */ ++ if (op_is_write(bio->bi_opf) && bio->bi_vcnt && ++ !bio_flagged(bio, BIO_CLONED)) { ++ folio = page_folio(bio->bi_io_vec[0].bv_page); ++ bio->bi_muqss_owner = ++ muqss_iotime_owner_task(folio_io_owner(folio)); ++ return; ++ } ++ ++ /* ++ * An io_uring SQPOLL thread submits for whoever shares its ++ * ring, and the request does not record which task that was; ++ * with IORING_SETUP_ATTACH_WQ it need not be a single one. ++ * The thread is a long lived schedulable task in its own ++ * right though, so charge it directly rather than lose the ++ * time: the device cost is real, and the sq thread is the ++ * entity the scheduler can actually act on. ++ * ++ * Kernel threads proper get nothing. ++ */ ++ if (tsk->flags & PF_IO_WORKER) ++ bio->bi_muqss_owner = get_task_struct(tsk); ++ return; ++ } ++ ++ bio->bi_muqss_owner = get_task_struct(tsk); ++} ++ ++void muqss_iotime_put_owner(struct bio *bio) ++{ ++ if (bio->bi_muqss_owner) { ++ put_task_struct(bio->bi_muqss_owner); ++ bio->bi_muqss_owner = NULL; ++ } ++} ++ ++void muqss_iotime_clone_owner(struct bio *bio, struct bio *bio_src) ++{ ++ muqss_iotime_put_owner(bio); ++ ++ if (bio_src->bi_muqss_owner) ++ bio->bi_muqss_owner = get_task_struct(bio_src->bi_muqss_owner); ++} ++ ++/* ++ * Per bio completion. The owner reference is not dropped here: bio_endio() ++ * and bio_uninit() release it alongside the blkg reference, which keeps the ++ * lifetime rules identical to the ones the block layer already follows. ++ */ ++static void muqss_iotime_done_bio(struct rq_qos *rqos, struct bio *bio) ++{ ++ struct blk_iotime *bit = BLK_IOTIME(rqos); ++ struct task_struct *tsk = bio->bi_muqss_owner; ++ u64 now; ++ ++ if (!tsk) { ++ atomic64_inc(&bit->unattributed); ++ return; ++ } ++ if (!bio->issue_time_ns) { ++ atomic64_inc(&bit->nostamp_bio); ++ return; ++ } ++ ++ now = blk_time_get_ns(); ++ if (now <= bio->issue_time_ns) ++ return; ++ ++ atomic64_add(now - bio->issue_time_ns, &tsk->io_latency_ns); ++ atomic64_inc(&tsk->io_count); ++} ++ ++/* ++ * A request is being built from its first bio. Take our own reference for the ++ * request: the bio's reference is released by bio_endio() during ++ * blk_update_request(), which runs before rq_qos_done(), so borrowing it would ++ * leave a dangling pointer at completion. ++ */ ++static void muqss_iotime_track(struct rq_qos *rqos, struct request *rq, ++ struct bio *bio) ++{ ++ if (bio->bi_muqss_owner) ++ rq->muqss_owner = get_task_struct(bio->bi_muqss_owner); ++} ++ ++/* ++ * A further bio is merging into an existing request. Occupancy is measured ++ * per request and cannot be split between owners, so a request that ends up ++ * serving more than one task is disowned rather than charged to whichever ++ * task happened to be first. ++ */ ++static void muqss_iotime_merge(struct rq_qos *rqos, struct request *rq, ++ struct bio *bio) ++{ ++ if (!rq->muqss_owner || rq->muqss_owner == bio->bi_muqss_owner) ++ return; ++ ++ put_task_struct(rq->muqss_owner); ++ rq->muqss_owner = NULL; ++ atomic64_inc(&BLK_IOTIME(rqos)->mixed); ++} ++ ++/* ++ * Two whole requests are being merged: next's bios are about to be appended to ++ * rq and next freed. The rq_qos ->merge hook does not cover this, it only sees ++ * a bio being merged into a request, so without this the survivor keeps its own ++ * owner and is charged for both requests' device time. ++ * ++ * Called from attempt_merge() rather than being an rq_qos op because the ++ * framework has no request-to-request callback to hang it on. ++ */ ++void muqss_iotime_merge_requests(struct request *rq, struct request *next) ++{ ++ struct rq_qos *rqos; ++ ++ if (!rq->muqss_owner || rq->muqss_owner == next->muqss_owner) ++ return; ++ ++ put_task_struct(rq->muqss_owner); ++ rq->muqss_owner = NULL; ++ ++ rqos = rq_qos_id(rq->q, RQ_QOS_MUQSS_IOTIME); ++ if (rqos) ++ atomic64_inc(&BLK_IOTIME(rqos)->mixed); ++} ++ ++/* Per request completion. This is where device occupancy is attributed. */ ++static void muqss_iotime_done(struct rq_qos *rqos, struct request *rq) ++{ ++ struct blk_iotime *bit = BLK_IOTIME(rqos); ++ struct task_struct *tsk = rq->muqss_owner; ++ u64 now, occupancy; ++ ++ if (!(rq->rq_flags & RQF_STATS) || !rq->io_start_time_ns) ++ goto out; ++ ++ /* ++ * Use the timestamp captured when the request completed, not one ++ * taken here: rq_qos_done() runs after blk_update_request() has ++ * completed every bio, so measuring now would fold the completion ++ * path into what is supposed to be device service time. That bias is ++ * roughly constant per request, which would quietly turn part of the ++ * charge into a per-I/O-count charge and penalise small random I/O. ++ * ++ * Consuming the stamp also makes this idempotent. rq_qos_done() runs ++ * twice for a request with an end_io handler that frees it: once from ++ * __blk_mq_end_request() and again from blk_mq_free_request(). Every ++ * policy has to tolerate that. Clearing muqss_owner alone stopped the ++ * task being charged twice but left the device totals below counting ++ * the same request on both passes. ++ * ++ * A request that never went through blk_mq_end_request() has no stamp ++ * and is not measured. Timing it here instead would report the ++ * completion path as device time, which is the bias this exists to ++ * avoid; unstamped counts them so the loss stays visible. ++ */ ++ now = rq->muqss_done_ns; ++ if (!now) { ++ atomic64_inc(&bit->unstamped); ++ goto out; ++ } ++ rq->muqss_done_ns = 0; ++ ++ if (now <= rq->io_start_time_ns) ++ goto out; ++ ++ occupancy = now - rq->io_start_time_ns; ++ atomic64_add(occupancy, &bit->occupancy_ns); ++ atomic64_inc(&bit->requests); ++ ++ if (tsk) { ++ atomic64_add(occupancy, &tsk->io_occupancy_ns); ++ atomic64_add(occupancy, &tsk->io_debt_ns); ++ } ++out: ++ if (tsk) { ++ put_task_struct(tsk); ++ rq->muqss_owner = NULL; ++ } ++} ++ ++static void muqss_iotime_exit(struct rq_qos *rqos) ++{ ++ struct blk_iotime *bit = BLK_IOTIME(rqos); ++ ++ /* ++ * QUEUE_FLAG_BIO_ISSUE_TIME is deliberately left set. blk-iolatency ++ * uses the same flag, and the queue is being torn down here anyway. ++ */ ++ blk_stat_disable_accounting(rqos->disk->queue); ++ kfree(bit); ++} ++ ++#ifdef CONFIG_BLK_DEBUG_FS ++static int muqss_iotime_occupancy_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->occupancy_ns)); ++ return 0; ++} ++ ++static int muqss_iotime_requests_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->requests)); ++ return 0; ++} ++ ++static int muqss_iotime_unattributed_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->unattributed)); ++ return 0; ++} ++ ++static int muqss_iotime_mixed_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&BLK_IOTIME(rqos)->mixed)); ++ return 0; ++} ++ ++static int muqss_iotime_unstamped_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->unstamped)); ++ return 0; ++} ++ ++static int muqss_iotime_nostamp_bio_show(void *data, struct seq_file *m) ++{ ++ struct rq_qos *rqos = data; ++ ++ seq_printf(m, "%llu\n", ++ (u64)atomic64_read(&BLK_IOTIME(rqos)->nostamp_bio)); ++ return 0; ++} ++ ++static int muqss_iotime_exhausted_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&iowner_exhausted)); ++ return 0; ++} ++ ++static int muqss_iotime_proxied_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&iowner_proxied)); ++ return 0; ++} ++ ++static int muqss_iotime_owner_width_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%u\n", MUQSS_IOWNER_WIDTH); ++ return 0; ++} ++ ++static int muqss_iotime_kernwork_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&kernwork_charged)); ++ return 0; ++} ++ ++static int muqss_iotime_kernwork_ns_show(void *data, struct seq_file *m) ++{ ++ seq_printf(m, "%llu\n", (u64)atomic64_read(&kernwork_ns)); ++ return 0; ++} ++ ++static const struct blk_mq_debugfs_attr muqss_iotime_debugfs_attrs[] = { ++ {"occupancy_ns", 0400, muqss_iotime_occupancy_show}, ++ {"requests", 0400, muqss_iotime_requests_show}, ++ {"unattributed", 0400, muqss_iotime_unattributed_show}, ++ {"mixed", 0400, muqss_iotime_mixed_show}, ++ {"unstamped", 0400, muqss_iotime_unstamped_show}, ++ {"nostamp_bio", 0400, muqss_iotime_nostamp_bio_show}, ++ {"owner_slots_exhausted", 0400, muqss_iotime_exhausted_show}, ++ {"proxied", 0400, muqss_iotime_proxied_show}, ++ {"owner_tag_bits", 0400, muqss_iotime_owner_width_show}, ++ {"kernwork", 0400, muqss_iotime_kernwork_show}, ++ {"kernwork_ns", 0400, muqss_iotime_kernwork_ns_show}, ++ {}, ++}; ++#endif ++ ++static const struct rq_qos_ops muqss_iotime_ops = { ++ .track = muqss_iotime_track, ++ .merge = muqss_iotime_merge, ++ .done = muqss_iotime_done, ++ .done_bio = muqss_iotime_done_bio, ++ .exit = muqss_iotime_exit, ++#ifdef CONFIG_BLK_DEBUG_FS ++ .debugfs_attrs = muqss_iotime_debugfs_attrs, ++#endif ++}; ++ ++void muqss_iotime_enable(struct gendisk *disk) ++{ ++ struct request_queue *q = disk->queue; ++ unsigned int memflags; ++ struct blk_iotime *bit; ++ int ret; ++ ++ if (!queue_is_mq(q)) ++ return; ++ ++ /* ++ * Only attach to queues backed by real hardware. ++ * ++ * A stacking driver that uses blk-mq -- loop is the common one -- ++ * serves its requests by doing I/O to another block device, which runs ++ * this policy too. Accounting both charges the task twice for one ++ * trip to the disk, and with the deadline charge live that is a real ++ * distortion rather than a cosmetic one. ++ * ++ * Drivers for physical devices pass their parent through ++ * device_add_disk(): nvme passes ctrl->device, sd its scsi_device, ++ * virtio_blk its virtio device. Virtual ones call add_disk(), which ++ * is device_add_disk(NULL, ...) -- loop, brd, zram, nbd. So the ++ * absence of a parent is a serviceable test for "not a device whose ++ * busy time means anything". ++ * ++ * Bio based stacking drivers (dm, md) never reach here at all, having ++ * failed queue_is_mq() above. ++ */ ++ if (!disk_to_dev(disk)->parent) ++ return; ++ ++ bit = kzalloc_obj(*bit); ++ if (!bit) ++ return; ++ ++ mutex_lock(&q->rq_qos_mutex); ++ ret = rq_qos_add(&bit->rqos, disk, RQ_QOS_MUQSS_IOTIME, ++ &muqss_iotime_ops); ++ mutex_unlock(&q->rq_qos_mutex); ++ if (ret) { ++ kfree(bit); ++ return; ++ } ++ ++ /* Needed for rq->io_start_time_ns to be stamped at dispatch. */ ++ blk_stat_enable_accounting(q); ++ /* Needed for bio->issue_time_ns to be stamped at submit. */ ++ blk_queue_flag_set(QUEUE_FLAG_BIO_ISSUE_TIME, q); ++ ++ memflags = blk_debugfs_lock(q); ++ blk_mq_debugfs_register_rq_qos(q); ++ blk_debugfs_unlock(q, memflags); ++} +diff --git a/block/blk-merge.c b/block/blk-merge.c +--- a/block/blk-merge.c ++++ b/block/blk-merge.c +@@ -12,6 +12,8 @@ + + #include + ++#include ++ + #include "blk.h" + #include "blk-mq-sched.h" + #include "blk-rq-qos.h" +@@ -848,6 +850,7 @@ static struct request *attempt_merge(struct request_queue *q, + * 'next' is going away, so update stats accordingly + */ + blk_account_io_merge_request(next); ++ muqss_iotime_merge_requests(req, next); + + trace_block_rq_merge(next); + +diff --git a/block/blk-mq-debugfs.c b/block/blk-mq-debugfs.c +--- a/block/blk-mq-debugfs.c ++++ b/block/blk-mq-debugfs.c +@@ -757,6 +757,8 @@ static const char *rq_qos_id_to_name(enum rq_qos_id id) + return "latency"; + case RQ_QOS_COST: + return "cost"; ++ case RQ_QOS_MUQSS_IOTIME: ++ return "iotime"; + } + return "unknown"; + } +diff --git a/block/blk-mq.c b/block/blk-mq.c +--- a/block/blk-mq.c ++++ b/block/blk-mq.c +@@ -41,6 +41,7 @@ + #include "blk-stat.h" + #include "blk-mq-sched.h" + #include "blk-rq-qos.h" ++#include + + static DEFINE_PER_CPU(struct llist_head, blk_cpu_done); + static DEFINE_PER_CPU(call_single_data_t, blk_cpu_csd); +@@ -398,12 +399,48 @@ static inline void blk_mq_rq_time_init(struct request *rq, u64 alloc_time_ns) + #endif + } + ++/* ++ * Record the completion timestamp for MuQSS I/O accounting. ++ * ++ * rq_qos_done() is reached only after blk_update_request() has run bio_endio() ++ * on every bio of the request, so a timestamp taken inside the policy is later ++ * than the request actually finished, and lands after the per bio latency ++ * stamp. Occupancy then measures longer than the end to end latency it is ++ * supposed to be a subset of. ++ * ++ * Does not overwrite: the first caller to record a stamp is the earliest and ++ * therefore the most accurate one. ++ */ ++static inline void blk_mq_set_iotime_done(struct request *rq, u64 now) ++{ ++#ifdef CONFIG_MUQSS_IOTIME ++ if (!rq->muqss_done_ns) ++ rq->muqss_done_ns = now; ++#endif ++} ++ ++/* ++ * Take the completion stamp before bio completion. Costs an extra timestamp ++ * on the completion path, which cannot be avoided by reusing the one taken ++ * for blk-stat: that one is deliberately read after blk_update_request() and ++ * moving it would change accounting shared with everybody else. ++ */ ++static inline void blk_mq_capture_iotime_done(struct request *rq) ++{ ++#ifdef CONFIG_MUQSS_IOTIME ++ if (blk_mq_need_time_stamp(rq)) ++ rq->muqss_done_ns = blk_time_get_ns(); ++#endif ++} ++ + static inline void blk_mq_bio_issue_init(struct request_queue *q, + struct bio *bio) + { +-#ifdef CONFIG_BLK_CGROUP +- if (test_bit(QUEUE_FLAG_BIO_ISSUE_TIME, &q->queue_flags)) ++#if defined(CONFIG_BLK_CGROUP) || defined(CONFIG_MUQSS_IOTIME) ++ if (test_bit(QUEUE_FLAG_BIO_ISSUE_TIME, &q->queue_flags)) { + bio->issue_time_ns = blk_time_get_ns(); ++ muqss_iotime_set_owner(bio); ++ } + #endif + } + +@@ -435,6 +472,10 @@ static struct request *blk_mq_rq_ctx_init(struct blk_mq_alloc_data *data, + + rq->part = NULL; + rq->io_start_time_ns = 0; ++#ifdef CONFIG_MUQSS_IOTIME ++ rq->muqss_owner = NULL; ++ rq->muqss_done_ns = 0; ++#endif + rq->stats_sectors = 0; + rq->nr_phys_segments = 0; + rq->nr_integrity_segments = 0; +@@ -1127,8 +1168,12 @@ static inline void __blk_mq_end_request_acct(struct request *rq, u64 now) + + inline void __blk_mq_end_request(struct request *rq, blk_status_t error) + { +- if (blk_mq_need_time_stamp(rq)) +- __blk_mq_end_request_acct(rq, blk_time_get_ns()); ++ if (blk_mq_need_time_stamp(rq)) { ++ u64 now = blk_time_get_ns(); ++ ++ blk_mq_set_iotime_done(rq, now); ++ __blk_mq_end_request_acct(rq, now); ++ } + + blk_mq_finish_request(rq); + +@@ -1144,6 +1189,7 @@ EXPORT_SYMBOL(__blk_mq_end_request); + + void blk_mq_end_request(struct request *rq, blk_status_t error) + { ++ blk_mq_capture_iotime_done(rq); + if (blk_update_request(rq, error, blk_rq_bytes(rq))) + BUG(); + __blk_mq_end_request(rq, error); +@@ -1178,8 +1224,10 @@ void blk_mq_end_request_batch(struct io_comp_batch *iob) + prefetch(rq->rq_next); + + blk_complete_request(rq); +- if (iob->need_ts) ++ if (iob->need_ts) { ++ blk_mq_set_iotime_done(rq, now); + __blk_mq_end_request_acct(rq, now); ++ } + + blk_mq_finish_request(rq); + +diff --git a/block/blk-rq-qos.h b/block/blk-rq-qos.h +--- a/block/blk-rq-qos.h ++++ b/block/blk-rq-qos.h +@@ -17,6 +17,7 @@ enum rq_qos_id { + RQ_QOS_WBT, + RQ_QOS_LATENCY, + RQ_QOS_COST, ++ RQ_QOS_MUQSS_IOTIME, + }; + + struct rq_wait { +diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c +--- a/block/blk-sysfs.c ++++ b/block/blk-sysfs.c +@@ -20,6 +20,7 @@ + #include "blk-cgroup.h" + #include "blk-throttle.h" + #include "error-injection.h" ++#include + + struct queue_sysfs_entry { + struct attribute attr; +@@ -993,6 +994,7 @@ int blk_register_queue(struct gendisk *disk) + + blk_queue_flag_set(QUEUE_FLAG_REGISTERED, q); + wbt_init_enable_default(disk); ++ muqss_iotime_enable(disk); + + /* Now everything is ready and send out KOBJ_ADD uevent */ + kobject_uevent(&disk->queue_kobj, KOBJ_ADD); +diff --git a/drivers/accessibility/speakup/speakup_acntpc.c b/drivers/accessibility/speakup/speakup_acntpc.c +--- a/drivers/accessibility/speakup/speakup_acntpc.c ++++ b/drivers/accessibility/speakup/speakup_acntpc.c +@@ -207,7 +207,7 @@ static void do_catch_up(struct spk_synth *synth) + full_time_val = full_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth_full()) { +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -235,7 +235,7 @@ static void do_catch_up(struct spk_synth *synth) + jiffy_delta_val = jiffy_delta->u.n.value; + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_apollo.c b/drivers/accessibility/speakup/speakup_apollo.c +--- a/drivers/accessibility/speakup/speakup_apollo.c ++++ b/drivers/accessibility/speakup/speakup_apollo.c +@@ -178,7 +178,7 @@ static void do_catch_up(struct spk_synth *synth) + if (!synth->io_ops->synth_out(synth, ch)) { + synth->io_ops->tiocmset(synth, 0, UART_MCR_RTS); + synth->io_ops->tiocmset(synth, UART_MCR_RTS, 0); +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) { +@@ -188,11 +188,9 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth->io_ops->synth_out(synth, synth->procspeech)) +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + else +- schedule_timeout(msecs_to_jiffies +- (full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + set_current_state(TASK_RUNNING); +diff --git a/drivers/accessibility/speakup/speakup_decext.c b/drivers/accessibility/speakup/speakup_decext.c +--- a/drivers/accessibility/speakup/speakup_decext.c ++++ b/drivers/accessibility/speakup/speakup_decext.c +@@ -189,7 +189,7 @@ static void do_catch_up(struct spk_synth *synth) + if (ch == '\n') + ch = 0x0D; + if (synth_full() || !synth->io_ops->synth_out(synth, ch)) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -213,8 +213,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, + flags); +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c +--- a/drivers/accessibility/speakup/speakup_decpc.c ++++ b/drivers/accessibility/speakup/speakup_decpc.c +@@ -409,7 +409,7 @@ static void do_catch_up(struct spk_synth *synth) + if (ch == '\n') + ch = 0x0D; + if (dt_sendchar(ch)) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -432,8 +432,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, + flags); +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_dectlk.c b/drivers/accessibility/speakup/speakup_dectlk.c +--- a/drivers/accessibility/speakup/speakup_dectlk.c ++++ b/drivers/accessibility/speakup/speakup_dectlk.c +@@ -265,7 +265,7 @@ static void do_catch_up(struct spk_synth *synth) + if (ch == '\n') + ch = 0x0D; + if (synth_full_val || !synth->io_ops->synth_out(synth, ch)) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -289,8 +289,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, + flags); +- schedule_timeout(msecs_to_jiffies +- (delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_dtlk.c b/drivers/accessibility/speakup/speakup_dtlk.c +--- a/drivers/accessibility/speakup/speakup_dtlk.c ++++ b/drivers/accessibility/speakup/speakup_dtlk.c +@@ -221,7 +221,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth_full()) { +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -237,7 +237,7 @@ static void do_catch_up(struct spk_synth *synth) + delay_time_val = delay_time->u.n.value; + jiffy_delta_val = jiffy_delta->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/speakup_keypc.c b/drivers/accessibility/speakup/speakup_keypc.c +--- a/drivers/accessibility/speakup/speakup_keypc.c ++++ b/drivers/accessibility/speakup/speakup_keypc.c +@@ -208,7 +208,7 @@ static void do_catch_up(struct spk_synth *synth) + full_time_val = full_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth_full()) { +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + set_current_state(TASK_RUNNING); +@@ -241,7 +241,7 @@ static void do_catch_up(struct spk_synth *synth) + jiffy_delta_val = jiffy_delta->u.n.value; + delay_time_val = delay_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); +- schedule_timeout(msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + } +diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c +--- a/drivers/accessibility/speakup/synth.c ++++ b/drivers/accessibility/speakup/synth.c +@@ -98,7 +98,7 @@ static void _spk_do_catch_up(struct spk_synth *synth, int unicode) + else + ret = synth->io_ops->synth_out(synth, ch); + if (!ret) { +- schedule_timeout(msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + continue; + } + if (time_after_eq(jiffies, jiff_max) && (ch == SPACE)) { +@@ -108,11 +108,9 @@ static void _spk_do_catch_up(struct spk_synth *synth, int unicode) + full_time_val = full_time->u.n.value; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + if (synth->io_ops->synth_out(synth, synth->procspeech)) +- schedule_timeout( +- msecs_to_jiffies(delay_time_val)); ++ schedule_msec_hrtimeout(delay_time_val); + else +- schedule_timeout( +- msecs_to_jiffies(full_time_val)); ++ schedule_msec_hrtimeout(full_time_val); + jiff_max = jiffies + jiffy_delta_val; + } + set_current_state(TASK_RUNNING); +diff --git a/drivers/auxdisplay/hd44780_common.c b/drivers/auxdisplay/hd44780_common.c +--- a/drivers/auxdisplay/hd44780_common.c ++++ b/drivers/auxdisplay/hd44780_common.c +@@ -34,7 +34,7 @@ + /* sleeps that many milliseconds with a reschedule */ + static void long_sleep(int ms) + { +- schedule_timeout_interruptible(msecs_to_jiffies(ms)); ++ schedule_msec_hrtimeout_interruptible(ms); + } + + int hd44780_common_print(struct charlcd *lcd, int c) +diff --git a/drivers/block/swim.c b/drivers/block/swim.c +--- a/drivers/block/swim.c ++++ b/drivers/block/swim.c +@@ -330,7 +330,7 @@ static inline void swim_motor(struct swim __iomem *base, + if (swim_readbit(base, MOTOR_ON)) + break; + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + } else if (action == OFF) { + swim_action(base, MOTOR_OFF); +@@ -349,7 +349,7 @@ static inline void swim_eject(struct swim __iomem *base) + if (!swim_readbit(base, DISK_IN)) + break; + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + swim_select(base, RELAX); + } +@@ -373,7 +373,7 @@ static inline int swim_step(struct swim __iomem *base) + for (wait = 0; wait < HZ; wait++) { + + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + + swim_select(base, RELAX); + if (!swim_readbit(base, STEP)) +diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c +--- a/drivers/char/ipmi/ipmi_msghandler.c ++++ b/drivers/char/ipmi/ipmi_msghandler.c +@@ -3804,7 +3804,7 @@ static void cleanup_smi_msgs(struct ipmi_smi *intf) + /* Current message first, to preserve order */ + while (intf->curr_msg && !list_empty(&intf->waiting_rcv_msgs)) { + /* Wait for the message to clear out. */ +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + + /* No need for locks, the interface is down. */ +diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c +--- a/drivers/char/ipmi/ipmi_ssif.c ++++ b/drivers/char/ipmi/ipmi_ssif.c +@@ -1282,7 +1282,7 @@ static void shutdown_ssif(void *send_info) + + /* make sure the driver is not looking for flags any more. */ + while (ssif_info->ssif_state != SSIF_IDLE) +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + + ssif_info->stopping = true; + timer_delete_sync(&ssif_info->watch_timer); +diff --git a/drivers/comedi/drivers/ni_mio_common.c b/drivers/comedi/drivers/ni_mio_common.c +--- a/drivers/comedi/drivers/ni_mio_common.c ++++ b/drivers/comedi/drivers/ni_mio_common.c +@@ -4762,7 +4762,7 @@ static int cs5529_wait_for_idle(struct comedi_device *dev) + if ((status & NI67XX_CAL_STATUS_BUSY) == 0) + break; + set_current_state(TASK_INTERRUPTIBLE); +- if (schedule_timeout(1)) ++ if (schedule_min_hrtimeout()) + return -EIO; + } + if (i == timeout) { +diff --git a/drivers/crypto/ccp/tee-dev.c b/drivers/crypto/ccp/tee-dev.c +--- a/drivers/crypto/ccp/tee-dev.c ++++ b/drivers/crypto/ccp/tee-dev.c +@@ -270,7 +270,7 @@ static int tee_submit_cmd(struct psp_tee_device *tee, enum tee_cmd_id cmd_id, + + /* Wait if ring buffer is full or TEE is processing data */ + mutex_unlock(&tee->rb_mgr.mutex); +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + mutex_lock(&tee->rb_mgr.mutex); + + } while (--nloop); +diff --git a/drivers/gpib/nec7210/nec7210.c b/drivers/gpib/nec7210/nec7210.c +--- a/drivers/gpib/nec7210/nec7210.c ++++ b/drivers/gpib/nec7210/nec7210.c +@@ -317,7 +317,7 @@ int nec7210_go_to_standby(struct gpib_board *board, struct nec7210_priv *priv) + if (i == timeout) { + for (i = 0; i < HZ; i++) { + set_current_state(TASK_INTERRUPTIBLE); +- if (schedule_timeout(1)) ++ if (schedule_min_hrtimeout()) + return -ERESTARTSYS; + adsr_bits = read_byte(priv, ADSR); + if (adsr_bits & HR_NATN) +diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c +--- a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c ++++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c +@@ -215,7 +215,7 @@ static int vmw_fifo_wait_noirq(struct vmw_private *dev_priv, + DRM_ERROR("SVGA device lockup.\n"); + break; + } +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + if (interruptible && signal_pending(current)) { + ret = -ERESTARTSYS; + break; +diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c b/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c +--- a/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c ++++ b/drivers/gpu/drm/vmwgfx/vmwgfx_irq.c +@@ -200,7 +200,7 @@ int vmw_fallback_wait(struct vmw_private *dev_priv, + break; + } + if (lazy) +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + else if ((++count & 0x0F) == 0) { + /** + * FIXME: Use schedule_hr_timeout here for +diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c +--- a/drivers/hwmon/adt7470.c ++++ b/drivers/hwmon/adt7470.c +@@ -332,7 +332,7 @@ static int adt7470_update_thread(void *p) + if (kthread_should_stop()) + break; + +- schedule_timeout_interruptible(msecs_to_jiffies(data->auto_update_interval)); ++ schedule_msec_hrtimeout_interruptible(data->auto_update_interval); + } + + return 0; +diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c +--- a/drivers/hwmon/fam15h_power.c ++++ b/drivers/hwmon/fam15h_power.c +@@ -221,7 +221,7 @@ static ssize_t power1_average_show(struct device *dev, + prev_ptsc[cu] = data->cpu_sw_pwr_ptsc[cu]; + } + +- leftover = schedule_timeout_interruptible(msecs_to_jiffies(data->power_period)); ++ leftover = schedule_msec_hrtimeout_interruptible(data->power_period); + if (leftover) + return 0; + +diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c +--- a/drivers/hwmon/ibmaem.c ++++ b/drivers/hwmon/ibmaem.c +@@ -827,9 +827,8 @@ static ssize_t aem_show_power(struct device *dev, + time = ktime_get_ns(); + before = data->energy[attr->index]; + +- leftover = schedule_timeout_interruptible( +- msecs_to_jiffies(data->power_period[attr->index]) +- ); ++ leftover = schedule_msec_hrtimeout_interruptible( ++ data->power_period[attr->index]); + if (leftover) { + mutex_unlock(&data->lock); + return 0; +diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c +--- a/drivers/iio/light/tsl2563.c ++++ b/drivers/iio/light/tsl2563.c +@@ -274,11 +274,7 @@ static void tsl2563_wait_adc(struct tsl2563_chip *chip) + default: + delay = 402; + } +- /* +- * TODO: Make sure that we wait at least required delay but why we +- * have to extend it one tick more? +- */ +- schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); ++ schedule_msec_hrtimeout_interruptible(delay + 1); + } + + static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +diff --git a/drivers/md/bcache/btree.c b/drivers/md/bcache/btree.c +--- a/drivers/md/bcache/btree.c ++++ b/drivers/md/bcache/btree.c +@@ -1846,8 +1846,7 @@ static void bch_btree_gc(struct cache_set *c) + cond_resched(); + + if (ret == -EAGAIN) +- schedule_timeout_interruptible( +- msecs_to_jiffies(btree_gc_sleep_ms(c))); ++ schedule_msec_hrtimeout_interruptible(btree_gc_sleep_ms(c)); + else if (ret) + pr_warn("gc failed!\n"); + } while (ret && !test_bit(CACHE_SET_IO_DISABLE, &c->flags)); +diff --git a/drivers/media/i2c/bt866.c b/drivers/media/i2c/bt866.c +--- a/drivers/media/i2c/bt866.c ++++ b/drivers/media/i2c/bt866.c +@@ -65,7 +65,7 @@ static int bt866_write(struct bt866 *encoder, u8 subaddr, u8 data) + err++; + v4l_warn(client, "error #%d writing to 0x%02x\n", + err, subaddr); +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + } + if (err == 3) { + v4l_warn(client, "giving up\n"); +diff --git a/drivers/media/i2c/ir-kbd-i2c.c b/drivers/media/i2c/ir-kbd-i2c.c +--- a/drivers/media/i2c/ir-kbd-i2c.c ++++ b/drivers/media/i2c/ir-kbd-i2c.c +@@ -698,7 +698,7 @@ static int zilog_tx(struct rc_dev *rcdev, unsigned int *txbuf, + */ + for (i = 0; i < 20; ++i) { + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout(50); + ret = i2c_master_send(ir->tx_c, buf, 1); + if (ret == 1) + break; +diff --git a/drivers/media/i2c/msp3400-driver.c b/drivers/media/i2c/msp3400-driver.c +--- a/drivers/media/i2c/msp3400-driver.c ++++ b/drivers/media/i2c/msp3400-driver.c +@@ -170,7 +170,7 @@ static int msp_read(struct i2c_client *client, int dev, int addr) + break; + dev_warn(&client->dev, "I/O error #%d (read 0x%02x/0x%02x)\n", err, + dev, addr); +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + } + if (err == 3) { + dev_warn(&client->dev, "resetting chip, sound will go off.\n"); +@@ -211,7 +211,7 @@ static int msp_write(struct i2c_client *client, int dev, int addr, int val) + break; + dev_warn(&client->dev, "I/O error #%d (write 0x%02x/0x%02x)\n", err, + dev, addr); +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + } + if (err == 3) { + dev_warn(&client->dev, "resetting chip, sound will go off.\n"); +diff --git a/drivers/media/i2c/saa7110.c b/drivers/media/i2c/saa7110.c +--- a/drivers/media/i2c/saa7110.c ++++ b/drivers/media/i2c/saa7110.c +@@ -184,7 +184,7 @@ static v4l2_std_id determine_norm(struct v4l2_subdev *sd) + saa7110_write_block(sd, initseq, sizeof(initseq)); + saa7110_selmux(sd, decoder->input); + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(250)); ++ schedule_msec_hrtimeout(250); + finish_wait(&decoder->wq, &wait); + status = saa7110_read(sd); + if (status & 0x40) { +@@ -219,7 +219,7 @@ static v4l2_std_id determine_norm(struct v4l2_subdev *sd) + /*saa7110_write(sd,0x2E,0x9A);*/ + + prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(250)); ++ schedule_msec_hrtimeout(250); + finish_wait(&decoder->wq, &wait); + + status = saa7110_read(sd); +diff --git a/drivers/media/pci/cx18/cx18-gpio.c b/drivers/media/pci/cx18/cx18-gpio.c +--- a/drivers/media/pci/cx18/cx18-gpio.c ++++ b/drivers/media/pci/cx18/cx18-gpio.c +@@ -81,11 +81,11 @@ static void gpio_reset_seq(struct cx18 *cx, u32 active_lo, u32 active_hi, + + /* Assert */ + gpio_update(cx, mask, ~active_lo); +- schedule_timeout_uninterruptible(msecs_to_jiffies(assert_msecs)); ++ schedule_msec_hrtimeout_uninterruptible(assert_msecs); + + /* Deassert */ + gpio_update(cx, mask, ~active_hi); +- schedule_timeout_uninterruptible(msecs_to_jiffies(recovery_msecs)); ++ schedule_msec_hrtimeout_uninterruptible(recovery_msecs); + } + + /* +diff --git a/drivers/media/pci/hws/hws_pci.c b/drivers/media/pci/hws/hws_pci.c +--- a/drivers/media/pci/hws/hws_pci.c ++++ b/drivers/media/pci/hws/hws_pci.c +@@ -215,7 +215,7 @@ static int main_ks_thread_handle(void *data) + /* If we're suspending, don't touch hardware; just sleep/freeze. */ + if (READ_ONCE(pdx->suspended)) { + try_to_freeze(); +- schedule_timeout_interruptible(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout_interruptible(1000); + continue; + } + +@@ -225,7 +225,7 @@ static int main_ks_thread_handle(void *data) + try_to_freeze(); /* cooperate with freezer each loop */ + + /* Sleep 1s or until signaled to wake/stop */ +- schedule_timeout_interruptible(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout_interruptible(1000); + } + + dev_dbg(&pdx->pdev->dev, "%s: exiting\n", __func__); +diff --git a/drivers/media/pci/ivtv/ivtv-gpio.c b/drivers/media/pci/ivtv/ivtv-gpio.c +--- a/drivers/media/pci/ivtv/ivtv-gpio.c ++++ b/drivers/media/pci/ivtv/ivtv-gpio.c +@@ -105,7 +105,7 @@ void ivtv_reset_ir_gpio(struct ivtv *itv) + curout = (curout & ~0xF) | 1; + write_reg(curout, IVTV_REG_GPIO_OUT); + /* We could use something else for smaller time */ +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + curout |= 2; + write_reg(curout, IVTV_REG_GPIO_OUT); + curdir &= ~0x80; +@@ -125,11 +125,11 @@ int ivtv_reset_tuner_gpio(void *dev, int component, int cmd, int value) + curout = read_reg(IVTV_REG_GPIO_OUT); + curout &= ~(1 << itv->card->xceive_pin); + write_reg(curout, IVTV_REG_GPIO_OUT); +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + + curout |= 1 << itv->card->xceive_pin; + write_reg(curout, IVTV_REG_GPIO_OUT); +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + return 0; + } + +diff --git a/drivers/media/pci/ivtv/ivtv-ioctl.c b/drivers/media/pci/ivtv/ivtv-ioctl.c +--- a/drivers/media/pci/ivtv/ivtv-ioctl.c ++++ b/drivers/media/pci/ivtv/ivtv-ioctl.c +@@ -1141,7 +1141,7 @@ void ivtv_s_std_dec(struct ivtv *itv, v4l2_std_id std) + TASK_UNINTERRUPTIBLE); + if ((read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16) < 100) + break; +- schedule_timeout(msecs_to_jiffies(25)); ++ schedule_msec_hrtimeout(25); + } + finish_wait(&itv->vsync_waitq, &wait); + mutex_lock(&itv->serialize_lock); +diff --git a/drivers/media/pci/ivtv/ivtv-streams.c b/drivers/media/pci/ivtv/ivtv-streams.c +--- a/drivers/media/pci/ivtv/ivtv-streams.c ++++ b/drivers/media/pci/ivtv/ivtv-streams.c +@@ -835,7 +835,7 @@ int ivtv_stop_v4l2_encode_stream(struct ivtv_stream *s, int gop_end) + while (!test_bit(IVTV_F_I_EOS, &itv->i_flags) && + time_before(jiffies, + then + msecs_to_jiffies(2000))) { +- schedule_timeout(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout(10); + } + + /* To convert jiffies to ms, we must multiply by 1000 +diff --git a/drivers/media/pci/ivtv/ivtvfb.c b/drivers/media/pci/ivtv/ivtvfb.c +--- a/drivers/media/pci/ivtv/ivtvfb.c ++++ b/drivers/media/pci/ivtv/ivtvfb.c +@@ -474,7 +474,7 @@ static int ivtvfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long ar + + case FBIO_WAITFORVSYNC: + prepare_to_wait(&itv->vsync_waitq, &wait, TASK_INTERRUPTIBLE); +- if (!schedule_timeout(msecs_to_jiffies(50))) ++ if (!schedule_msec_hrtimeout(50)) + rc = -ETIMEDOUT; + finish_wait(&itv->vsync_waitq, &wait); + return rc; +diff --git a/drivers/media/pci/saa7134/saa7134-tvaudio.c b/drivers/media/pci/saa7134/saa7134-tvaudio.c +--- a/drivers/media/pci/saa7134/saa7134-tvaudio.c ++++ b/drivers/media/pci/saa7134/saa7134-tvaudio.c +@@ -307,8 +307,7 @@ static int tvaudio_sleep(struct saa7134_dev *dev, int timeout) + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } else { +- schedule_timeout_interruptible +- (msecs_to_jiffies(timeout)); ++ schedule_msec_hrtimeout_interruptible(timeout); + } + } + return dev->thread.scan1 != dev->thread.scan2; +diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c +--- a/drivers/media/radio/radio-mr800.c ++++ b/drivers/media/radio/radio-mr800.c +@@ -366,7 +366,7 @@ static int vidioc_s_hw_freq_seek(struct file *file, void *priv, + retval = -ENODATA; + break; + } +- if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { ++ if (schedule_msec_hrtimeout_interruptible(10)) { + retval = -ERESTARTSYS; + break; + } +diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c +--- a/drivers/media/radio/radio-tea5777.c ++++ b/drivers/media/radio/radio-tea5777.c +@@ -235,7 +235,7 @@ static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait) + } + + if (wait) { +- if (schedule_timeout_interruptible(msecs_to_jiffies(wait))) ++ if (schedule_msec_hrtimeout_interruptible(wait)) + return -ERESTARTSYS; + } + +diff --git a/drivers/media/radio/tea575x.c b/drivers/media/radio/tea575x.c +--- a/drivers/media/radio/tea575x.c ++++ b/drivers/media/radio/tea575x.c +@@ -401,7 +401,7 @@ int snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea, + for (;;) { + if (time_after(jiffies, timeout)) + break; +- if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { ++ if (schedule_msec_hrtimeout_interruptible(10)) { + /* some signal arrived, stop search */ + tea->val &= ~TEA575X_BIT_SEARCH; + snd_tea575x_set_freq(tea); +diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c +--- a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c ++++ b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c +@@ -297,7 +297,7 @@ static int rotation_thread_function(void *data) + __s32 vflip, hflip; + + set_current_state(TASK_INTERRUPTIBLE); +- while (!schedule_timeout(msecs_to_jiffies(100))) { ++ while (!schedule_msec_hrtimeout(100)) { + if (mutex_lock_interruptible(&sd->gspca_dev.usb_lock)) + break; + +diff --git a/drivers/mfd/ucb1x00-core.c b/drivers/mfd/ucb1x00-core.c +--- a/drivers/mfd/ucb1x00-core.c ++++ b/drivers/mfd/ucb1x00-core.c +@@ -253,7 +253,7 @@ unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync) + break; + /* yield to other processes */ + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } + + return UCB_ADC_DAT(val); +diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c +--- a/drivers/misc/mei/hw-me.c ++++ b/drivers/misc/mei/hw-me.c +@@ -1464,7 +1464,7 @@ int mei_me_polling_thread(void *_dev) + MEI_POLLING_TIMEOUT_IDLE); + } + +- schedule_timeout_interruptible(msecs_to_jiffies(polling_timeout)); ++ schedule_msec_hrtimeout_interruptible(polling_timeout); + } + + return 0; +diff --git a/drivers/misc/sgi-xp/xpc_channel.c b/drivers/misc/sgi-xp/xpc_channel.c +--- a/drivers/misc/sgi-xp/xpc_channel.c ++++ b/drivers/misc/sgi-xp/xpc_channel.c +@@ -834,7 +834,7 @@ xpc_allocate_msg_wait(struct xpc_channel *ch) + + atomic_inc(&ch->n_on_msg_allocate_wq); + prepare_to_wait(&ch->msg_allocate_wq, &wait, TASK_INTERRUPTIBLE); +- ret = schedule_timeout(1); ++ ret = schedule_min_hrtimeout(); + finish_wait(&ch->msg_allocate_wq, &wait); + atomic_dec(&ch->n_on_msg_allocate_wq); + +diff --git a/drivers/net/can/usb/peak_usb/pcan_usb.c b/drivers/net/can/usb/peak_usb/pcan_usb.c +--- a/drivers/net/can/usb/peak_usb/pcan_usb.c ++++ b/drivers/net/can/usb/peak_usb/pcan_usb.c +@@ -308,7 +308,7 @@ static int pcan_usb_write_mode(struct peak_usb_device *dev, u8 onoff) + } else { + /* the PCAN-USB needs time to init */ + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(PCAN_USB_STARTUP_TIMEOUT)); ++ schedule_msec_hrtimeout(PCAN_USB_STARTUP_TIMEOUT); + } + + return err; +diff --git a/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c b/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c +--- a/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c ++++ b/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c +@@ -1090,7 +1090,7 @@ static int lio_reset_queues(struct net_device *netdev, uint32_t num_qs) + struct napi_struct *napi, *n; + int ret; + +- schedule_timeout_uninterruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_uninterruptible(100); + + if (wait_for_pending_requests(oct)) + dev_err(&oct->pci_dev->dev, "There were pending requests\n"); +diff --git a/drivers/net/ethernet/cavium/liquidio/lio_main.c b/drivers/net/ethernet/cavium/liquidio/lio_main.c +--- a/drivers/net/ethernet/cavium/liquidio/lio_main.c ++++ b/drivers/net/ethernet/cavium/liquidio/lio_main.c +@@ -758,7 +758,7 @@ static int liquidio_watchdog(void *param) + while (!kthread_should_stop()) { + /* sleep for a couple of seconds so that we don't hog the CPU */ + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(2000)); ++ schedule_msec_hrtimeout(2000); + + mask_of_crashed_or_stuck_cores = + (u16)octeon_read_csr64(oct, CN23XX_SLI_SCRATCH2); +diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c +--- a/drivers/net/usb/lan78xx.c ++++ b/drivers/net/usb/lan78xx.c +@@ -3514,7 +3514,7 @@ static void lan78xx_terminate_urbs(struct lan78xx_net *dev) + /* maybe wait for deletions to finish. */ + while (!skb_queue_empty(&dev->rxq) || + !skb_queue_empty(&dev->txq)) { +- schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS)); ++ schedule_msec_hrtimeout(UNLINK_TIMEOUT_MS); + set_current_state(TASK_UNINTERRUPTIBLE); + netif_dbg(dev, ifdown, dev->net, + "waited for %d urb completions", temp); +diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c +--- a/drivers/net/usb/usbnet.c ++++ b/drivers/net/usb/usbnet.c +@@ -791,7 +791,7 @@ static void wait_skb_queue_empty(struct sk_buff_head *q) + spin_lock_irqsave(&q->lock, flags); + while (!skb_queue_empty(q)) { + spin_unlock_irqrestore(&q->lock, flags); +- schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS)); ++ schedule_msec_hrtimeout(UNLINK_TIMEOUT_MS); + set_current_state(TASK_UNINTERRUPTIBLE); + spin_lock_irqsave(&q->lock, flags); + } +diff --git a/drivers/net/wireless/intel/ipw2x00/ipw2100.c b/drivers/net/wireless/intel/ipw2x00/ipw2100.c +--- a/drivers/net/wireless/intel/ipw2x00/ipw2100.c ++++ b/drivers/net/wireless/intel/ipw2x00/ipw2100.c +@@ -799,7 +799,7 @@ static int ipw2100_hw_send_command(struct ipw2100_priv *priv, + * doesn't seem to have as many firmware restart cycles... + * + * As a test, we're sticking in a 1/100s delay here */ +- schedule_timeout_uninterruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_uninterruptible(10); + + return 0; + +@@ -1250,7 +1250,7 @@ static int ipw2100_start_adapter(struct ipw2100_priv *priv) + IPW_DEBUG_FW("Waiting for f/w initialization to complete...\n"); + i = 5000; + do { +- schedule_timeout_uninterruptible(msecs_to_jiffies(40)); ++ schedule_msec_hrtimeout_uninterruptible(40); + /* Todo... wait for sync command ... */ + + read_register(priv->net_dev, IPW_REG_INTA, &inta); +diff --git a/drivers/parport/ieee1284.c b/drivers/parport/ieee1284.c +--- a/drivers/parport/ieee1284.c ++++ b/drivers/parport/ieee1284.c +@@ -202,7 +202,7 @@ int parport_wait_peripheral(struct parport *port, + /* parport_wait_event didn't time out, but the + * peripheral wasn't actually ready either. + * Wait for another 10ms. */ +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + } + } + +diff --git a/drivers/parport/ieee1284_ops.c b/drivers/parport/ieee1284_ops.c +--- a/drivers/parport/ieee1284_ops.c ++++ b/drivers/parport/ieee1284_ops.c +@@ -520,7 +520,7 @@ size_t parport_ieee1284_ecp_read_data (struct parport *port, + /* Yield the port for a while. */ + if (dev->port->irq != PARPORT_IRQ_NONE) { + parport_release (dev); +- schedule_timeout_interruptible(msecs_to_jiffies(40)); ++ schedule_msec_hrtimeout_interruptible(40); + parport_claim_or_block (dev); + } + else +diff --git a/drivers/parport/parport_pc.c b/drivers/parport/parport_pc.c +--- a/drivers/parport/parport_pc.c ++++ b/drivers/parport/parport_pc.c +@@ -177,8 +177,7 @@ static int change_mode(struct parport *p, int m) + if (time_after_eq(jiffies, expire)) + /* The FIFO is stuck. */ + return -EBUSY; +- schedule_timeout_interruptible( +- msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + if (signal_pending(current)) + break; + } +diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c +--- a/drivers/platform/x86/intel_ips.c ++++ b/drivers/platform/x86/intel_ips.c +@@ -806,7 +806,7 @@ static int ips_adjust(void *data) + ips_gpu_lower(ips); + + sleep: +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_ADJUST_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_ADJUST_PERIOD); + } while (!kthread_should_stop()); + + dev_dbg(ips->dev, "ips-adjust thread stopped\n"); +@@ -984,7 +984,7 @@ static int ips_monitor(void *data) + seqno_timestamp = get_jiffies_64(); + + old_cpu_power = thm_readl(THM_CEC); +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_SAMPLE_PERIOD); + + /* Collect an initial average */ + for (i = 0; i < IPS_SAMPLE_COUNT; i++) { +@@ -1011,7 +1011,7 @@ static int ips_monitor(void *data) + mchp_samples[i] = mchp; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_SAMPLE_PERIOD); + if (kthread_should_stop()) + break; + } +@@ -1038,7 +1038,7 @@ static int ips_monitor(void *data) + * us to reduce the sample frequency if the CPU and GPU are idle. + */ + old_cpu_power = thm_readl(THM_CEC); +- schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); ++ schedule_msec_hrtimeout_interruptible(IPS_SAMPLE_PERIOD); + last_sample_period = IPS_SAMPLE_PERIOD; + + timer_setup(&ips->timer, monitor_timeout, TIMER_DEFERRABLE); +diff --git a/drivers/rtc/rtc-wm8350.c b/drivers/rtc/rtc-wm8350.c +--- a/drivers/rtc/rtc-wm8350.c ++++ b/drivers/rtc/rtc-wm8350.c +@@ -114,7 +114,7 @@ static int wm8350_rtc_settime(struct device *dev, struct rtc_time *tm) + /* Wait until confirmation of stopping */ + do { + rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL); +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (--retries && !(rtc_ctrl & WM8350_RTC_STS)); + + if (!retries) { +@@ -197,7 +197,7 @@ static int wm8350_rtc_stop_alarm(struct wm8350 *wm8350) + /* Wait until confirmation of stopping */ + do { + rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL); +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (retries-- && !(rtc_ctrl & WM8350_RTC_ALMSTS)); + + if (!(rtc_ctrl & WM8350_RTC_ALMSTS)) +@@ -220,7 +220,7 @@ static int wm8350_rtc_start_alarm(struct wm8350 *wm8350) + /* Wait until confirmation */ + do { + rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL); +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (retries-- && rtc_ctrl & WM8350_RTC_ALMSTS); + + if (rtc_ctrl & WM8350_RTC_ALMSTS) +diff --git a/drivers/s390/char/sclp_sdias.c b/drivers/s390/char/sclp_sdias.c +--- a/drivers/s390/char/sclp_sdias.c ++++ b/drivers/s390/char/sclp_sdias.c +@@ -68,7 +68,7 @@ static int sdias_sclp_send(struct sclp_req *req) + /* not initiated, wait some time and retry */ + set_current_state(TASK_INTERRUPTIBLE); + TRACE("add request failed: rc = %i\n",rc); +- schedule_timeout(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout(500); + continue; + } + /* initiated, wait for completion of service call */ +diff --git a/drivers/scsi/be2iscsi/be_cmds.c b/drivers/scsi/be2iscsi/be_cmds.c +--- a/drivers/scsi/be2iscsi/be_cmds.c ++++ b/drivers/scsi/be2iscsi/be_cmds.c +@@ -611,7 +611,7 @@ static int be_mbox_db_ready_poll(struct be_ctrl_info *ctrl) + if (time_after(jiffies, timeout)) + break; + /* 1ms sleep is enough in most cases */ +- schedule_timeout_uninterruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_uninterruptible(1); + } while (!ready); + + beiscsi_log(phba, KERN_ERR, +diff --git a/drivers/scsi/esas2r/esas2r_flash.c b/drivers/scsi/esas2r/esas2r_flash.c +--- a/drivers/scsi/esas2r/esas2r_flash.c ++++ b/drivers/scsi/esas2r/esas2r_flash.c +@@ -965,7 +965,7 @@ static bool esas2r_flash_access(struct esas2r_adapter *a, u32 function) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > timeout) { + /* +diff --git a/drivers/scsi/esas2r/esas2r_init.c b/drivers/scsi/esas2r/esas2r_init.c +--- a/drivers/scsi/esas2r/esas2r_init.c ++++ b/drivers/scsi/esas2r/esas2r_init.c +@@ -994,7 +994,7 @@ bool esas2r_check_adapter(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 180000) { + esas2r_hdebug("FW ready TMO"); +@@ -1017,7 +1017,7 @@ bool esas2r_check_adapter(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout_interruptible(50); + + if ((jiffies_to_msecs(jiffies) - starttime) > 3000) { + esas2r_hdebug("timeout waiting for interface down"); +@@ -1106,7 +1106,7 @@ bool esas2r_check_adapter(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 3000) { + esas2r_hdebug( +@@ -1356,7 +1356,7 @@ bool esas2r_init_adapter_hw(struct esas2r_adapter *a, bool init_poll) + atomic_dec(&a->disable_cnt); + + while (test_bit(AF_DISC_PENDING, &a->flags)) { +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + /* + * Determine the need for a timer tick based on the +@@ -1512,7 +1512,7 @@ static void esas2r_power_down_notify_firmware(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 30000) { + esas2r_hdebug("Timeout waiting for power down"); +@@ -1557,7 +1557,7 @@ void esas2r_power_down(struct esas2r_adapter *a) + break; + } + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > 3000) { + esas2r_hdebug( +diff --git a/drivers/scsi/esas2r/esas2r_main.c b/drivers/scsi/esas2r/esas2r_main.c +--- a/drivers/scsi/esas2r/esas2r_main.c ++++ b/drivers/scsi/esas2r/esas2r_main.c +@@ -1247,7 +1247,7 @@ void esas2r_wait_request(struct esas2r_adapter *a, struct esas2r_request *rq) + if (rq->req_stat != RS_STARTED) + break; + +- schedule_timeout_interruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_interruptible(100); + + if ((jiffies_to_msecs(jiffies) - starttime) > timeout) { + esas2r_hdebug("request TMO"); +diff --git a/drivers/scsi/fnic/fnic_scsi.c b/drivers/scsi/fnic/fnic_scsi.c +--- a/drivers/scsi/fnic/fnic_scsi.c ++++ b/drivers/scsi/fnic/fnic_scsi.c +@@ -258,7 +258,7 @@ int fnic_fw_reset_handler(struct fnic *fnic) + + /* wait for io cmpl */ + while (atomic_read(&fnic->in_flight)) +- schedule_timeout(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout(1); + + spin_lock_irqsave(&fnic->wq_copy_lock[0], flags); + +@@ -1634,7 +1634,7 @@ static void fnic_cleanup_io(struct fnic *fnic, int exclude_id) + io_count, + atomic64_read(&fnic->fnic_stats.io_stats.active_ios)); + +- schedule_timeout(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout(100); + } + } + +@@ -1912,7 +1912,7 @@ void fnic_rport_exch_reset(struct fnic *fnic, u32 port_id) + atomic_dec(&fnic->in_flight); + + while ((io_count = fnic_count_ioreqs(fnic, port_id))) +- schedule_timeout(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout(1000); + + FNIC_SCSI_DBG(KERN_DEBUG, fnic, + "rport: 0x%x remaining portid-io-count: %d ", +@@ -2510,7 +2510,7 @@ static int fnic_clean_pending_aborts(struct fnic *fnic, + ret = iter_data.ret; + goto clean_pending_aborts_end; + } +- schedule_timeout(msecs_to_jiffies(2 * fnic->config.ed_tov)); ++ schedule_msec_hrtimeout(2 * fnic->config.ed_tov); + + /* walk again to check, if IOs are still pending in fw */ + if (fnic_is_abts_pending(fnic, lr_sc)) +@@ -2811,7 +2811,7 @@ int fnic_device_reset(struct scsi_cmnd *sc) + } + FNIC_SCSI_DBG(KERN_ERR, fnic, + "Cannot clean up all IOs for the LUN\n"); +- schedule_timeout(msecs_to_jiffies(1000)); ++ schedule_msec_hrtimeout(1000); + count++; + } + +diff --git a/drivers/scsi/lpfc/lpfc_scsi.c b/drivers/scsi/lpfc/lpfc_scsi.c +--- a/drivers/scsi/lpfc/lpfc_scsi.c ++++ b/drivers/scsi/lpfc/lpfc_scsi.c +@@ -5920,7 +5920,7 @@ lpfc_chk_tgt_mapped(struct lpfc_vport *vport, struct fc_rport *rport) + return FAILED; + if (pnode->nlp_state == NLP_STE_MAPPED_NODE) + return SUCCESS; +- schedule_timeout_uninterruptible(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout_uninterruptible(500); + rdata = rport->dd_data; + if (!rdata) + return FAILED; +@@ -5962,7 +5962,7 @@ lpfc_reset_flush_io_context(struct lpfc_vport *vport, uint16_t tgt_id, + tgt_id, lun_id, context); + later = secs_to_jiffies(2 * vport->cfg_devloss_tmo) + jiffies; + while (time_after(later, jiffies) && cnt) { +- schedule_timeout_uninterruptible(msecs_to_jiffies(20)); ++ schedule_msec_hrtimeout_uninterruptible(20); + cnt = lpfc_sli_sum_iocb(vport, tgt_id, lun_id, context); + } + if (cnt) { +diff --git a/drivers/scsi/qla4xxx/ql4_nx.c b/drivers/scsi/qla4xxx/ql4_nx.c +--- a/drivers/scsi/qla4xxx/ql4_nx.c ++++ b/drivers/scsi/qla4xxx/ql4_nx.c +@@ -1552,7 +1552,7 @@ static int qla4_82xx_cmdpeg_ready(struct scsi_qla_host *ha, int pegtune_val) + (val == PHAN_INITIALIZE_ACK)) + return 0; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout(500); + + } while (--retries); + +diff --git a/drivers/scsi/snic/snic_scsi.c b/drivers/scsi/snic/snic_scsi.c +--- a/drivers/scsi/snic/snic_scsi.c ++++ b/drivers/scsi/snic/snic_scsi.c +@@ -1863,7 +1863,7 @@ snic_dr_clean_pending_req(struct snic *snic, struct scsi_cmnd *lr_sc) + } + } + +- schedule_timeout(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout(100); + + /* Walk through all the cmds and check abts status. */ + if (snic_is_abts_pending(snic, lr_sc)) +@@ -2321,7 +2321,7 @@ snic_reset(struct Scsi_Host *shost, struct scsi_cmnd *sc) + + /* Wait for all the IOs that are entered in Qcmd */ + while (atomic_read(&snic->ios_inflight)) +- schedule_timeout(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout(1); + + ret = snic_issue_hba_reset(snic, sc); + if (ret) { +diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c +--- a/drivers/usb/class/usblp.c ++++ b/drivers/usb/class/usblp.c +@@ -923,7 +923,7 @@ static int usblp_wwait(struct usblp *usblp, int nonblock) + if (rc <= 0) + break; + +- if (schedule_timeout(msecs_to_jiffies(1500)) == 0) { ++ if (schedule_msec_hrtimeout(1500) == 0) { + if (usblp->flags & LP_ABORT) { + err = usblp_check_status(usblp, err); + if (err == 1) { /* Paper out */ +diff --git a/drivers/video/fbdev/omap/hwa742.c b/drivers/video/fbdev/omap/hwa742.c +--- a/drivers/video/fbdev/omap/hwa742.c ++++ b/drivers/video/fbdev/omap/hwa742.c +@@ -926,7 +926,7 @@ static void hwa742_resume(void) + if (hwa742_read_reg(HWA742_PLL_DIV_REG) & (1 << 7)) + break; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(5)); ++ schedule_msec_hrtimeout(5); + } + hwa742_set_update_mode(hwa742.update_mode_before_suspend); + } +diff --git a/drivers/video/fbdev/pxafb.c b/drivers/video/fbdev/pxafb.c +--- a/drivers/video/fbdev/pxafb.c ++++ b/drivers/video/fbdev/pxafb.c +@@ -1272,7 +1272,7 @@ static int pxafb_smart_thread(void *arg) + mutex_unlock(&fbi->ctrlr_lock); + + set_current_state(TASK_INTERRUPTIBLE); +- schedule_timeout(msecs_to_jiffies(30)); ++ schedule_msec_hrtimeout(30); + } + + pr_debug("%s(): task ending\n", __func__); +diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c +--- a/fs/f2fs/checkpoint.c ++++ b/fs/f2fs/checkpoint.c +@@ -28,7 +28,7 @@ static inline void get_lock_elapsed_time(struct f2fs_time_stat *ts) + { + ts->total_time = ktime_get(); + #ifdef CONFIG_64BIT +- ts->running_time = current->se.sum_exec_runtime; ++ ts->running_time = tsk_seruntime(current); + #endif + #if defined(CONFIG_SCHED_INFO) && defined(CONFIG_SCHEDSTATS) + ts->runnable_time = current->sched_info.run_delay; +diff --git a/fs/jffs2/background.c b/fs/jffs2/background.c +--- a/fs/jffs2/background.c ++++ b/fs/jffs2/background.c +@@ -110,7 +110,7 @@ static int jffs2_garbage_collect_thread(void *_c) + * This forces the GCD to slow the hell down. Pulling an + * inode in with read_inode() is much preferable to having + * the GC thread get there first. */ +- schedule_timeout_interruptible(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout_interruptible(50); + + if (kthread_should_stop()) { + jffs2_dbg(1, "%s(): kthread_stop() called\n", __func__); +diff --git a/fs/proc/base.c b/fs/proc/base.c +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -513,7 +513,7 @@ static int proc_pid_schedstat(struct seq_file *m, struct pid_namespace *ns, + seq_puts(m, "0 0 0\n"); + else + seq_printf(m, "%llu %llu %lu\n", +- (unsigned long long)task->se.sum_exec_runtime, ++ (unsigned long long)tsk_seruntime(task), + (unsigned long long)task->sched_info.run_delay, + task->sched_info.pcount); + +@@ -521,6 +521,108 @@ static int proc_pid_schedstat(struct seq_file *m, struct pid_namespace *ns, + } + #endif + ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * Provides /proc/PID/iotime ++ * ++ * Block device time consumed on this task's behalf, in nanoseconds. ++ * ++ * io_latency_ns is end to end per bio and so includes queue wait: it is what ++ * this task waited for. io_occupancy_ns is device service time per request: ++ * it is what this task cost everybody else, and is what scheduling charges ++ * are based on. io_debt_ns is occupancy not yet charged to the deadline. ++ * See block/blk-iotime.c. ++ * ++ * kern_time_ns is CPU time rather than device time: kernel work done for this ++ * task in another thread's context, which today means kworkers running work ++ * items it queued. kern_debt_ns is the part of it not yet charged. Work the ++ * task asks for in its own context is not counted here, because that already ++ * comes out of its own timeslice. ++ * ++ * Access is restricted exactly as /proc/PID/io is, and for the same reason: ++ * these counters say when a task used the device and for how long, which is ++ * enough to infer a good deal about what it is doing. The exec_update_lock ++ * keeps the permission check and the read on the same side of an exec, so ++ * credentials cannot change between the two. ++ * ++ * Also as /proc/PID/io: the tgid file sums the thread group and the tid file ++ * reports one thread. Charging is per thread, since each has its own deadline, ++ * but a multithreaded writer's total is only meaningful summed. ++ */ ++struct iotime_acct { ++ u64 latency_ns; ++ u64 count; ++ u64 occupancy_ns; ++ u64 debt_ns; ++ u64 kern_time_ns; ++ u64 kern_debt_ns; ++}; ++ ++static void iotime_add(struct iotime_acct *acct, struct task_struct *task) ++{ ++ acct->latency_ns += atomic64_read(&task->io_latency_ns); ++ acct->count += atomic64_read(&task->io_count); ++ acct->occupancy_ns += atomic64_read(&task->io_occupancy_ns); ++ acct->debt_ns += atomic64_read(&task->io_debt_ns); ++ acct->kern_time_ns += atomic64_read(&task->kern_time_ns); ++ acct->kern_debt_ns += atomic64_read(&task->kern_debt_ns); ++} ++ ++static int do_iotime_accounting(struct task_struct *task, struct seq_file *m, ++ int whole) ++{ ++ struct iotime_acct acct = { }; ++ int result; ++ ++ result = down_read_killable(&task->signal->exec_update_lock); ++ if (result) ++ return result; ++ ++ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { ++ result = -EACCES; ++ goto out_unlock; ++ } ++ ++ if (whole) { ++ struct task_struct *t; ++ ++ rcu_read_lock(); ++ for_each_thread(task, t) ++ iotime_add(&acct, t); ++ rcu_read_unlock(); ++ } else { ++ iotime_add(&acct, task); ++ } ++ ++ seq_printf(m, ++ "io_latency_ns %llu\n" ++ "io_count %llu\n" ++ "io_occupancy_ns %llu\n" ++ "io_debt_ns %llu\n" ++ "kern_time_ns %llu\n" ++ "kern_debt_ns %llu\n", ++ acct.latency_ns, acct.count, acct.occupancy_ns, ++ acct.debt_ns, acct.kern_time_ns, acct.kern_debt_ns); ++ ++out_unlock: ++ up_read(&task->signal->exec_update_lock); ++ return result; ++} ++ ++static int proc_tgid_iotime(struct seq_file *m, struct pid_namespace *ns, ++ struct pid *pid, struct task_struct *task) ++{ ++ return do_iotime_accounting(task, m, 1); ++} ++ ++static int proc_tid_iotime(struct seq_file *m, struct pid_namespace *ns, ++ struct pid *pid, struct task_struct *task) ++{ ++ return do_iotime_accounting(task, m, 0); ++} ++ ++#endif ++ + #ifdef CONFIG_LATENCYTOP + static int lstats_show_proc(struct seq_file *m, void *v) + { +@@ -3336,6 +3438,9 @@ static const struct pid_entry tgid_base_stuff[] = { + #ifdef CONFIG_SCHED_INFO + ONE("schedstat", S_IRUGO, proc_pid_schedstat), + #endif ++#ifdef CONFIG_MUQSS_IOTIME ++ ONE("iotime", S_IRUSR, proc_tgid_iotime), ++#endif + #ifdef CONFIG_LATENCYTOP + REG("latency", S_IRUGO, proc_lstats_operations), + #endif +@@ -3691,6 +3796,9 @@ static const struct pid_entry tid_base_stuff[] = { + #ifdef CONFIG_SCHED_INFO + ONE("schedstat", S_IRUGO, proc_pid_schedstat), + #endif ++#ifdef CONFIG_MUQSS_IOTIME ++ ONE("iotime", S_IRUSR, proc_tid_iotime), ++#endif + #ifdef CONFIG_LATENCYTOP + REG("latency", S_IRUGO, proc_lstats_operations), + #endif +diff --git a/fs/xfs/xfs_trans_ail.c b/fs/xfs/xfs_trans_ail.c +--- a/fs/xfs/xfs_trans_ail.c ++++ b/fs/xfs/xfs_trans_ail.c +@@ -714,7 +714,7 @@ xfsaild( + spin_unlock(&ailp->ail_lock); + + if (tout) +- schedule_timeout(msecs_to_jiffies(tout)); ++ schedule_msec_hrtimeout(tout); + + __set_current_state(TASK_RUNNING); + +diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h +--- a/include/linux/blk-mq.h ++++ b/include/linux/blk-mq.h +@@ -137,6 +137,23 @@ struct request { + /* Time that I/O was submitted to the device. */ + u64 io_start_time_ns; + ++#ifdef CONFIG_MUQSS_IOTIME ++ /* ++ * Task this request is being served for, holding a reference. Set ++ * once from the first bio and cleared if a merge brings in a bio ++ * belonging to somebody else, since occupancy cannot be split ++ * between owners at completion time. ++ */ ++ struct task_struct *muqss_owner; ++ /* ++ * Completion time, captured where the block layer already takes it. ++ * rq_qos_done() runs after blk_update_request() has completed every ++ * bio, so a timestamp taken there would include the completion path ++ * and overstate device service time. ++ */ ++ u64 muqss_done_ns; ++#endif ++ + #ifdef CONFIG_BLK_WBT + unsigned short wbt_flags; + #endif +diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h +--- a/include/linux/blk_types.h ++++ b/include/linux/blk_types.h +@@ -252,13 +252,25 @@ struct bio { + * on release of the bio. + */ + struct blkcg_gq *bi_blkg; +- /* Time that this bio was issued. */ +- u64 issue_time_ns; + #ifdef CONFIG_BLK_CGROUP_IOCOST + u64 bi_iocost_cost; + #endif + #endif + ++ /* ++ * Time that this bio was issued. Wanted by blkcg accounting and by ++ * MuQSS I/O aware scheduling, which must work on kernels built ++ * without any block cgroup support. ++ */ ++#if defined(CONFIG_BLK_CGROUP) || defined(CONFIG_MUQSS_IOTIME) ++ u64 issue_time_ns; ++#endif ++ ++#ifdef CONFIG_MUQSS_IOTIME ++ /* Task this I/O is being done for. Holds a reference. */ ++ struct task_struct *bi_muqss_owner; ++#endif ++ + #ifdef CONFIG_BLK_INLINE_ENCRYPTION + struct bio_crypt_ctx *bi_crypt_context; + #endif +diff --git a/include/linux/freezer.h b/include/linux/freezer.h +--- a/include/linux/freezer.h ++++ b/include/linux/freezer.h +@@ -91,6 +91,7 @@ static inline bool try_to_freeze(void) { return false; } + + static inline void set_freezable(void) {} + ++#define pm_freezing (false) + #endif /* !CONFIG_FREEZER */ + + #endif /* FREEZER_H_INCLUDED */ +diff --git a/include/linux/ioprio.h b/include/linux/ioprio.h +--- a/include/linux/ioprio.h ++++ b/include/linux/ioprio.h +@@ -29,6 +29,8 @@ static inline bool ioprio_valid(unsigned short ioprio) + */ + static inline int task_nice_ioprio(struct task_struct *task) + { ++ if (iso_task(task)) ++ return 0; + return (task_nice(task) + 20) / 5; + } + +diff --git a/include/linux/mm.h b/include/linux/mm.h +--- a/include/linux/mm.h ++++ b/include/linux/mm.h +@@ -2460,6 +2460,61 @@ static inline bool folio_use_access_time(struct folio *folio) + } + #endif /* CONFIG_NUMA_BALANCING */ + ++#if MUQSS_IOWNER_WIDTH ++/* ++ * MuQSS I/O owner tag: an index into the owner table in block/blk-iotime.c, ++ * recorded when a folio is dirtied so that the writeback issued for it later ++ * by a flusher thread can still be charged to the task responsible. Zero ++ * means no owner. ++ * ++ * The tag itself is lossy by design: which of two racing dirtiers wins the ++ * field does not matter, and a wrong answer costs one misattributed ++ * writeback. The *word* is not ours to be lossy with, though. Other fields in ++ * folio->flags are updated by atomic bit operations from contexts that do not ++ * hold the folio lock -- PG_waiters is set by lock waiters, and losing it ++ * skips the wakeup on unlock -- so a plain read-modify-write store here would ++ * discard concurrent updates to unrelated bits. Update the field the way ++ * folio_xchg_last_cpupid() does, with a cmpxchg loop that touches only our ++ * own bits. ++ */ ++static inline unsigned int folio_io_owner(const struct folio *folio) ++{ ++ return (READ_ONCE(folio->flags.f) >> MUQSS_IOWNER_PGSHIFT) & ++ MUQSS_IOWNER_MASK; ++} ++ ++static inline void folio_set_io_owner(struct folio *folio, unsigned int slot) ++{ ++ unsigned long old_flags, flags; ++ ++ old_flags = READ_ONCE(folio->flags.f); ++ do { ++ flags = old_flags; ++ flags &= ~(MUQSS_IOWNER_MASK << MUQSS_IOWNER_PGSHIFT); ++ flags |= ((unsigned long)slot & MUQSS_IOWNER_MASK) << ++ MUQSS_IOWNER_PGSHIFT; ++ } while (unlikely(!try_cmpxchg(&folio->flags.f, &old_flags, flags))); ++} ++ ++static inline void page_io_owner_reset(struct page *page) ++{ ++ page->flags.f &= ~(MUQSS_IOWNER_MASK << MUQSS_IOWNER_PGSHIFT); ++} ++#else ++static inline unsigned int folio_io_owner(const struct folio *folio) ++{ ++ return 0; ++} ++ ++static inline void folio_set_io_owner(struct folio *folio, unsigned int slot) ++{ ++} ++ ++static inline void page_io_owner_reset(struct page *page) ++{ ++} ++#endif /* MUQSS_IOWNER_WIDTH */ ++ + #if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS) + + /* +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1240,6 +1240,7 @@ static inline bool zone_is_empty(const struct zone *zone) + #define KASAN_TAG_PGOFF (LAST_CPUPID_PGOFF - KASAN_TAG_WIDTH) + #define LRU_GEN_PGOFF (KASAN_TAG_PGOFF - LRU_GEN_WIDTH) + #define LRU_REFS_PGOFF (LRU_GEN_PGOFF - LRU_REFS_WIDTH) ++#define MUQSS_IOWNER_PGOFF (LRU_REFS_PGOFF - MUQSS_IOWNER_WIDTH) + + /* + * Define the bit shifts to access each section. For non-existent +@@ -1251,6 +1252,7 @@ static inline bool zone_is_empty(const struct zone *zone) + #define ZONES_PGSHIFT (ZONES_PGOFF * (ZONES_WIDTH != 0)) + #define LAST_CPUPID_PGSHIFT (LAST_CPUPID_PGOFF * (LAST_CPUPID_WIDTH != 0)) + #define KASAN_TAG_PGSHIFT (KASAN_TAG_PGOFF * (KASAN_TAG_WIDTH != 0)) ++#define MUQSS_IOWNER_PGSHIFT (MUQSS_IOWNER_PGOFF * (MUQSS_IOWNER_WIDTH != 0)) + + /* NODE:ZONE or SECTION:ZONE is used to ID a zone for the buddy allocator */ + #ifdef NODE_NOT_IN_PAGE_FLAGS +@@ -1269,6 +1271,7 @@ static inline bool zone_is_empty(const struct zone *zone) + #define NODES_MASK ((1UL << NODES_WIDTH) - 1) + #define SECTIONS_MASK ((1UL << SECTIONS_WIDTH) - 1) + #define LAST_CPUPID_MASK ((1UL << LAST_CPUPID_SHIFT) - 1) ++#define MUQSS_IOWNER_MASK ((1UL << MUQSS_IOWNER_WIDTH) - 1) + #define KASAN_TAG_MASK ((1UL << KASAN_TAG_WIDTH) - 1) + #define ZONEID_MASK ((1UL << ZONEID_SHIFT) - 1) + +diff --git a/include/linux/muqss.h b/include/linux/muqss.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/muqss.h +@@ -0,0 +1,51 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Scheduler-neutral accessors for task fields that MuQSS keeps somewhere ++ * other than the mainline scheduling entities. ++ * ++ * Kept out of so that enabling MuQSS costs that header only ++ * the task_struct members themselves. Include this after task_struct is ++ * defined; already does so on your behalf. ++ */ ++#ifndef _LINUX_MUQSS_H ++#define _LINUX_MUQSS_H ++ ++#ifdef CONFIG_SCHED_MUQSS ++ ++/* MuQSS accounts runtime and RT timeouts directly on the task. */ ++#define tsk_seruntime(t) ((t)->sched_time) ++#define tsk_rttimeout(t) ((t)->rt_timeout) ++ ++/* ++ * The rtmutex PI chain sorts equal-priority waiters by SCHED_DEADLINE ++ * deadline. MuQSS implements no deadline class, and that tie-break is only ++ * consulted when dl_prio(prio) holds - i.e. prio < MAX_DL_PRIO, which is 0 - ++ * so it is unreachable here. Report 0 rather than carry a dl entity. ++ */ ++#define tsk_dl_deadline(t) (0) ++ ++/* No deadline class: no budget to overrun and no bandwidth to migrate. */ ++#define tsk_dl_overrun(t) (0) ++#define tsk_dl_bw(t) (0) ++ ++static inline bool iso_task(struct task_struct *p) ++{ ++ return (p->policy == SCHED_ISO); ++} ++ ++#else /* CONFIG_SCHED_MUQSS */ ++ ++#define tsk_seruntime(t) ((t)->se.sum_exec_runtime) ++#define tsk_rttimeout(t) ((t)->rt.timeout) ++#define tsk_dl_deadline(t) ((t)->dl.deadline) ++#define tsk_dl_overrun(t) ((t)->dl.dl_overrun) ++#define tsk_dl_bw(t) ((t)->dl.dl_bw) ++ ++static inline bool iso_task(struct task_struct *p) ++{ ++ return false; ++} ++ ++#endif /* CONFIG_SCHED_MUQSS */ ++ ++#endif /* _LINUX_MUQSS_H */ +diff --git a/include/linux/muqss_iotime.h b/include/linux/muqss_iotime.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/muqss_iotime.h +@@ -0,0 +1,168 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * I/O aware scheduling support for MuQSS. ++ * ++ * Attributes block device time back to the task that caused the I/O, so the ++ * CPU scheduler can see what a task costs the storage device rather than only ++ * what it costs the CPU. ++ * ++ * The attributed time is charged against the task's virtual deadline one for ++ * one, a nanosecond of deadline for each nanosecond of device time, so that ++ * I/O and CPU cost a task the same. ++ * ++ * The same attribution carries CPU time as well as device time. A kworker ++ * running a work item declares itself to be acting for whoever queued that ++ * work, so the time it spends is charged back to them rather than lost to a ++ * kernel thread. That half is charged one for one: a nanosecond of CPU time ++ * costs the same wherever the kernel chose to spend it, and work a task does ++ * in its own context already costs it exactly that. ++ * ++ * See MuQSS-iotime-design.md. ++ */ ++#ifndef _LINUX_MUQSS_IOTIME_H ++#define _LINUX_MUQSS_IOTIME_H ++ ++#include ++ ++struct bio; ++struct folio; ++struct request; ++struct gendisk; ++struct task_struct; ++struct work_struct; ++ ++#ifdef CONFIG_MUQSS_IOTIME ++ ++/* ++ * A window during which current is running on behalf of another task, opened ++ * by a kworker around one work item. Lives on the stack of the thread doing ++ * the work; see muqss_kerntime_begin(). ++ */ ++struct muqss_kern_window { ++ struct task_struct *owner; /* who to charge, NULL for nobody */ ++ struct task_struct *prev; /* override to restore on close */ ++ u64 start; /* current's runtime when opened */ ++}; ++ ++/* ++ * Tag @bio with the task that caused it. Called from the block layer submit ++ * path, where current is the originating task for reads, readahead and ++ * synchronous writes alike. Takes a reference that is dropped by ++ * muqss_iotime_put_owner(). ++ */ ++void muqss_iotime_set_owner(struct bio *bio); ++ ++/* ++ * Drop the owner reference taken above. Idempotent, so it can be called from ++ * every site that releases bio state without tracking which ran first. ++ */ ++void muqss_iotime_put_owner(struct bio *bio); ++ ++/* ++ * Propagate ownership from @bio_src to @bio. Stacked drivers (md, dm, loop) ++ * resubmit clones from their own threads, where current is a kernel thread ++ * rather than the originating task, so without this attribution is lost on ++ * every RAID and LUKS setup. ++ */ ++void muqss_iotime_clone_owner(struct bio *bio, struct bio *bio_src); ++ ++/* Attach the accounting policy to a newly registered queue. */ ++void muqss_iotime_enable(struct gendisk *disk); ++ ++/* Zero a new task's counters so nothing is inherited across fork. */ ++void muqss_iotime_task_init(struct task_struct *p); ++ ++/* ++ * Owner table, used to attribute writeback. A dirtying task is given a slot, ++ * whose index is small enough to store in spare page->flags bits; writeback ++ * issued later by a flusher thread reads the index back off the folio and ++ * resolves it to the task that dirtied it. ++ */ ++ ++/* Slot for current, allocating one on first use. 0 if none is available. */ ++unsigned int muqss_iotime_owner_slot(void); ++ ++/* Resolve a slot to its task, taking a reference. NULL if the slot is stale. */ ++struct task_struct *muqss_iotime_owner_task(unsigned int slot); ++ ++/* Give up a task's slot. Called from exit. */ ++void muqss_iotime_release_slot(struct task_struct *p); ++ ++/* Record current as the dirtier of @folio, if it can be attributed. */ ++void muqss_iotime_dirty_folio(struct folio *folio); ++ ++/* ++ * Declare that current is about to do I/O on behalf of @owner, and undo it. ++ * Used by io_uring's io-wq workers, which issue requests that another task ++ * queued. Returns the previous value, to be handed back to _proxy_end(). ++ * ++ * The caller must keep a reference to @owner across the window. ++ */ ++struct task_struct *muqss_iotime_proxy_begin(struct task_struct *owner); ++void muqss_iotime_proxy_end(struct task_struct *prev); ++ ++/* ++ * Two requests are being merged into one. Disowns @rq if @next belongs to a ++ * different task, so the survivor is not charged for both. ++ */ ++void muqss_iotime_merge_requests(struct request *rq, struct request *next); ++ ++/* ++ * Record who queued @work, so the kworker that eventually runs it can be ++ * charged to them. Called from the queueing task's own context, which rules ++ * out the deferred re-entries into __queue_work() made by the delayed work ++ * timer and the rcu_work callback: those run in interrupt and softirq ++ * context, where current is whoever was unlucky enough to be interrupted. ++ */ ++void muqss_work_set_owner(struct work_struct *work); ++ ++/* ++ * Open and close a window in which current is running work on behalf of the ++ * task in @slot. The CPU time current consumes across the window is charged ++ * to that task, and any I/O the work issues is attributed to it too. ++ * ++ * Cheap when there is nobody to charge, which is the common case: an unowned ++ * slot costs one comparison and reads no clocks. ++ */ ++void muqss_kerntime_begin(struct muqss_kern_window *w, unsigned int slot); ++void muqss_kerntime_end(struct muqss_kern_window *w); ++ ++/* ++ * The CPU time current has consumed so far, including the part not yet banked ++ * into ->sched_time. Implemented by the scheduler; see MuQSS.c. ++ */ ++u64 muqss_task_runtime_live(void); ++ ++#else /* !CONFIG_MUQSS_IOTIME */ ++ ++struct muqss_kern_window { }; ++ ++static inline void muqss_iotime_set_owner(struct bio *bio) { } ++static inline void muqss_iotime_put_owner(struct bio *bio) { } ++static inline void muqss_iotime_clone_owner(struct bio *bio, ++ struct bio *bio_src) { } ++static inline void muqss_iotime_enable(struct gendisk *disk) { } ++static inline void muqss_iotime_task_init(struct task_struct *p) { } ++static inline unsigned int muqss_iotime_owner_slot(void) { return 0; } ++static inline struct task_struct *muqss_iotime_owner_task(unsigned int slot) ++{ ++ return NULL; ++} ++static inline void muqss_iotime_release_slot(struct task_struct *p) { } ++static inline void muqss_iotime_dirty_folio(struct folio *folio) { } ++static inline struct task_struct * ++muqss_iotime_proxy_begin(struct task_struct *owner) ++{ ++ return NULL; ++} ++static inline void muqss_iotime_proxy_end(struct task_struct *prev) { } ++static inline void muqss_iotime_merge_requests(struct request *rq, ++ struct request *next) { } ++static inline void muqss_work_set_owner(struct work_struct *work) { } ++static inline void muqss_kerntime_begin(struct muqss_kern_window *w, ++ unsigned int slot) { } ++static inline void muqss_kerntime_end(struct muqss_kern_window *w) { } ++ ++#endif /* CONFIG_MUQSS_IOTIME */ ++ ++#endif /* _LINUX_MUQSS_IOTIME_H */ +diff --git a/include/linux/page-flags-layout.h b/include/linux/page-flags-layout.h +--- a/include/linux/page-flags-layout.h ++++ b/include/linux/page-flags-layout.h +@@ -113,9 +113,39 @@ + ZONES_WIDTH - LRU_GEN_WIDTH - SECTIONS_WIDTH - \ + NODES_WIDTH - KASAN_TAG_WIDTH - LAST_CPUPID_WIDTH) + ++/* ++ * MuQSS I/O aware scheduling stores an index into its owner table here, so ++ * that a page dirtied now can be charged to the task that dirtied it when it ++ * is written back later by a flusher thread. ++ * ++ * These bits are affordable because MuQSS excludes NUMA balancing, which is ++ * the only user of LAST_CPUPID: with CONFIG_NUMA_BALANCING off, ++ * LAST_CPUPID_SHIFT is 0 and its share of page->flags is unclaimed. ++ * ++ * Taken last so nothing else has to shrink. __LRU_REFS_WIDTH rather than ++ * LRU_REFS_WIDTH is used below because the latter is a min() expression and ++ * so cannot be evaluated by the preprocessor; the two are equal whenever ++ * there is room for MGLRU's full allocation, which is the only case in which ++ * anything is left over for us anyway. ++ */ ++#ifdef CONFIG_MUQSS_IOTIME ++#define MUQSS_IOWNER_SHIFT 8 ++#else ++#define MUQSS_IOWNER_SHIFT 0 ++#endif ++ ++#if ZONES_WIDTH + LRU_GEN_WIDTH + SECTIONS_WIDTH + NODES_WIDTH + \ ++ KASAN_TAG_WIDTH + LAST_CPUPID_WIDTH + __LRU_REFS_WIDTH + \ ++ MUQSS_IOWNER_SHIFT <= BITS_PER_LONG - NR_PAGEFLAGS ++#define MUQSS_IOWNER_WIDTH MUQSS_IOWNER_SHIFT ++#else ++#define MUQSS_IOWNER_WIDTH 0 ++#endif ++ + #define NR_NON_PAGEFLAG_BITS (SECTIONS_WIDTH + NODES_WIDTH + ZONES_WIDTH + \ + LAST_CPUPID_SHIFT + KASAN_TAG_WIDTH + \ +- LRU_GEN_WIDTH + LRU_REFS_WIDTH) ++ LRU_GEN_WIDTH + LRU_REFS_WIDTH + \ ++ MUQSS_IOWNER_WIDTH) + + #define NR_UNUSED_PAGEFLAG_BITS (BITS_PER_LONG - \ + (NR_NON_PAGEFLAG_BITS + NR_PAGEFLAGS)) +diff --git a/include/linux/sched.h b/include/linux/sched.h +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -44,6 +44,9 @@ + #include + #include + #include ++#ifdef CONFIG_SCHED_MUQSS ++#include ++#endif + #include + #include + #include +@@ -334,6 +337,12 @@ extern long schedule_timeout_interruptible(long timeout); + extern long schedule_timeout_killable(long timeout); + extern long schedule_timeout_uninterruptible(long timeout); + extern long schedule_timeout_idle(long timeout); ++ ++extern long schedule_msec_hrtimeout(long timeout); ++extern long schedule_min_hrtimeout(void); ++extern long schedule_msec_hrtimeout_interruptible(long timeout); ++extern long schedule_msec_hrtimeout_uninterruptible(long timeout); ++ + asmlinkage void schedule(void); + extern void schedule_preempt_disabled(void); + asmlinkage void preempt_schedule_irq(void); +@@ -877,6 +886,79 @@ struct task_struct { + int normal_prio; + unsigned int rt_priority; + ++#ifdef CONFIG_SCHED_MUQSS ++ int time_slice; ++ u64 deadline; ++ skiplist_node node; /* Skip list node */ ++ u64 last_ran; ++ u64 sched_time; /* sched_clock time spent running */ ++#ifdef CONFIG_SMT_NICE ++ int smt_bias; /* Policy/nice level bias across smt siblings */ ++#endif ++#ifdef CONFIG_HOTPLUG_CPU ++ bool zerobound; /* Bound to CPU0 for hotplug */ ++#endif ++ unsigned long rt_timeout; ++ /* Unbanked cpu time */ ++ unsigned long utime_ns, stime_ns; ++#ifdef CONFIG_MUQSS_IOTIME ++ /* ++ * Block device time consumed on this task's behalf. Updated from ++ * I/O completion, which may be any CPU, so these are atomic. Read ++ * via /proc//iotime. Not inherited across fork. Bytes are not ++ * counted here, task_io_accounting already has them. ++ * ++ * io_latency_ns is end to end per bio, so it includes queue wait and ++ * is what a waiting task experiences. io_occupancy_ns is device ++ * service time per request, which is what this task cost everybody ++ * else, and is the only one of the two fit to charge for. ++ * ++ * io_debt_ns is occupancy not yet charged to the deadline. The ++ * scheduler consumes and zeroes it; see consume_iotime_penalty(). ++ */ ++ atomic64_t io_latency_ns; ++ atomic64_t io_count; ++ atomic64_t io_occupancy_ns; ++ atomic64_t io_debt_ns; ++ ++ /* ++ * CPU time burnt in some other thread's context on this task's ++ * behalf, currently kworkers running work items it queued. Unlike the ++ * I/O counters above this is CPU time, which the task would have been ++ * charged against its own time_slice had the kernel done the work ++ * synchronously instead of handing it to a worker. ++ * ++ * kern_time_ns is cumulative and only for reporting. kern_debt_ns is ++ * the part not yet charged to the deadline; the scheduler consumes ++ * and zeroes it, see consume_kerntime_penalty(). ++ */ ++ atomic64_t kern_time_ns; ++ atomic64_t kern_debt_ns; ++ ++ /* ++ * Index of this task's slot in the I/O owner table, stamped into ++ * page->flags when it dirties a folio so writeback can be charged ++ * back to it. 0 means no slot; allocated lazily on first dirty and ++ * released on exit. ++ */ ++ unsigned int io_owner_slot; ++ ++ /* ++ * The task this one is currently working on behalf of, or NULL. ++ * io_uring's io-wq workers publish the task that queued the request ++ * here for the duration of the issue, so a submission punted to a ++ * worker is still charged to the task that asked for it. kworkers do ++ * the same around each work item, which additionally attributes any ++ * I/O the work item submits, and any pages it dirties, to the task ++ * that queued the work rather than losing them to a kernel thread. ++ * ++ * Only ever written by the task itself and only read while it is ++ * running, so it needs no locking. The publisher holds a reference ++ * to the pointee for the whole window. ++ */ ++ struct task_struct *io_owner_override; ++#endif ++#else /* CONFIG_SCHED_MUQSS */ + struct sched_entity se; + struct sched_rt_entity rt; + struct sched_dl_entity dl; +@@ -885,6 +967,7 @@ struct task_struct { + struct sched_ext_entity scx; + #endif + const struct sched_class *sched_class; ++#endif /* CONFIG_SCHED_MUQSS */ + + #ifdef CONFIG_SCHED_CORE + struct rb_node core_node; +@@ -1677,6 +1760,8 @@ struct task_struct { + randomized_struct_fields_end + } __attribute__ ((aligned (64))); + ++#include ++ + #ifdef CONFIG_SCHED_PROXY_EXEC + DECLARE_STATIC_KEY_TRUE(__sched_proxy_exec); + static inline bool sched_proxy_exec(void) +@@ -2293,7 +2378,12 @@ static inline void set_task_cpu(struct task_struct *p, unsigned int cpu) + + static inline bool task_is_runnable(struct task_struct *p) + { ++#ifdef CONFIG_SCHED_MUQSS ++ /* MuQSS has no delayed dequeue: queued means runnable. */ ++ return p->on_rq; ++#else + return p->on_rq && !p->se.sched_delayed; ++#endif + } + + extern bool sched_task_on_rq(struct task_struct *p); +diff --git a/include/linux/sched/deadline.h b/include/linux/sched/deadline.h +--- a/include/linux/sched/deadline.h ++++ b/include/linux/sched/deadline.h +@@ -30,6 +30,24 @@ static inline bool dl_time_before(u64 a, u64 b) + } + + struct root_domain; ++#ifdef CONFIG_SCHED_MUQSS ++/* ++ * MuQSS implements no deadline scheduling class, so there is never any DL ++ * bandwidth to account for or migrate between root domains. ++ */ ++static inline void dl_add_task_root_domain(struct task_struct *p) { } ++static inline void dl_clear_root_domain(struct root_domain *rd) { } ++static inline void dl_clear_root_domain_cpu(int cpu) { } ++static inline bool dl_task_needs_bw_move(struct task_struct *p, ++ const struct cpumask *new_mask) ++{ ++ return false; ++} ++static inline bool dl_bw_visited(int cpu, u64 cookie) ++{ ++ return false; ++} ++#else /* CONFIG_SCHED_MUQSS */ + extern void dl_add_task_root_domain(struct task_struct *p); + extern void dl_clear_root_domain(struct root_domain *rd); + extern void dl_clear_root_domain_cpu(int cpu); +@@ -43,19 +61,23 @@ extern void dl_clear_root_domain_cpu(int cpu); + extern bool dl_task_needs_bw_move(struct task_struct *p, + const struct cpumask *new_mask); + +-extern u64 dl_cookie; + extern bool dl_bw_visited(int cpu, u64 cookie); ++#endif /* CONFIG_SCHED_MUQSS */ ++ ++extern u64 dl_cookie; + + static inline bool dl_server(struct sched_dl_entity *dl_se) + { + return dl_se->dl_server; + } + ++#ifndef CONFIG_SCHED_MUQSS + static inline struct task_struct *dl_task_of(struct sched_dl_entity *dl_se) + { + BUG_ON(dl_server(dl_se)); + return container_of(dl_se, struct task_struct, dl); + } ++#endif + + /* + * Regarding the deadline, a task with implicit deadline has a relative +diff --git a/include/linux/sched/nohz.h b/include/linux/sched/nohz.h +--- a/include/linux/sched/nohz.h ++++ b/include/linux/sched/nohz.h +@@ -13,7 +13,7 @@ extern int get_nohz_timer_target(void); + static inline void nohz_balance_enter_idle(int cpu) { } + #endif + +-#ifdef CONFIG_NO_HZ_COMMON ++#if defined(CONFIG_NO_HZ_COMMON) && !defined(CONFIG_SCHED_MUQSS) + void calc_load_nohz_start(void); + void calc_load_nohz_remote(struct rq *rq); + void calc_load_nohz_stop(void); +diff --git a/include/linux/sched/prio.h b/include/linux/sched/prio.h +--- a/include/linux/sched/prio.h ++++ b/include/linux/sched/prio.h +@@ -16,6 +16,21 @@ + #define MAX_RT_PRIO 100 + #define MAX_DL_PRIO 0 + ++#ifdef CONFIG_SCHED_MUQSS ++/* ++ * MuQSS ranks its non-RT policies immediately below the RT range, and has no ++ * deadline class. SCHED_IDLEPRIO is an alias for the SCHED_IDLE policy value. ++ */ ++#define ISO_PRIO (MAX_RT_PRIO) ++#define NORMAL_PRIO (MAX_RT_PRIO + 1) ++#define IDLE_PRIO (MAX_RT_PRIO + 2) ++#define PRIO_LIMIT ((IDLE_PRIO) + 1) ++ ++#define SCHED_IDLEPRIO SCHED_IDLE ++#define SCHED_MAX (SCHED_IDLEPRIO) ++#define SCHED_RANGE(policy) ((policy) <= SCHED_MAX) ++#endif /* CONFIG_SCHED_MUQSS */ ++ + #define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH) + #define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2) + +diff --git a/include/linux/skip_list.h b/include/linux/skip_list.h +new file mode 100644 +--- /dev/null ++++ b/include/linux/skip_list.h +@@ -0,0 +1,41 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef _LINUX_SKIP_LISTS_H ++#define _LINUX_SKIP_LISTS_H ++ ++/* ++ * Four levels is enough: randomLevel() returns 0..3, and insert only ++ * grows the list one level at a time, so next/prev[4..] were unused. ++ */ ++#define SKIPLIST_MAXLEVEL 4 ++ ++typedef u64 keyType; ++ ++typedef struct nodeStructure skiplist_node; ++ ++struct nodeStructure { ++ keyType key; ++ skiplist_node *next[SKIPLIST_MAXLEVEL]; ++ skiplist_node *prev[SKIPLIST_MAXLEVEL]; ++}; ++ ++typedef struct listStructure { ++ int entries; ++ int level; /* Maximum level of the list ++ (1 more than the number of levels in the list) */ ++ u64 best_key; /* Earliest queued key; lockless hint for EDT */ ++ skiplist_node *header; /* pointer to header */ ++} skiplist; ++ ++void skiplist_init(skiplist_node *slnode); ++void skiplist_cache_init(void); ++skiplist *new_skiplist(skiplist_node *slnode); ++void skiplist_free(skiplist *l); ++void free_skiplist(skiplist *l); ++void skiplist_node_init(skiplist_node *node); ++void skiplist_insert(skiplist *l, skiplist_node *node, keyType key, unsigned int randseed); ++bool skiplist_delete(skiplist *l, skiplist_node *node); ++ ++static inline bool skiplist_node_empty(skiplist_node *node) { ++ return (!node->next[0]); ++} ++#endif /* _LINUX_SKIP_LISTS_H */ +diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h +--- a/include/linux/workqueue.h ++++ b/include/linux/workqueue.h +@@ -288,6 +288,7 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; } + lockdep_init_map(&(_work)->lockdep_map, "(work_completion)"#_work, (_key), 0); \ + INIT_LIST_HEAD(&(_work)->entry); \ + (_work)->func = (_func); \ ++ muqss_work_init_owner(_work); \ + } while (0) + #else + #define __INIT_WORK_KEY(_work, _func, _onstack, _key) \ +@@ -296,6 +297,7 @@ static inline unsigned int work_static(struct work_struct *work) { return 0; } + (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \ + INIT_LIST_HEAD(&(_work)->entry); \ + (_work)->func = (_func); \ ++ muqss_work_init_owner(_work); \ + } while (0) + #endif + +diff --git a/include/linux/workqueue_types.h b/include/linux/workqueue_types.h +--- a/include/linux/workqueue_types.h ++++ b/include/linux/workqueue_types.h +@@ -17,9 +17,51 @@ struct work_struct { + atomic_long_t data; + struct list_head entry; + work_func_t func; ++#ifdef CONFIG_MUQSS_IOTIME ++ /* ++ * Index in the I/O owner table of the task that queued this work, so ++ * the CPU time the kworker spends running it can be charged back to ++ * whoever asked for it. 0 means nobody to charge, which is the case ++ * for the great deal of work queued by kernel threads on their own ++ * account. ++ * ++ * A slot index rather than a task pointer deliberately: work items ++ * are cancelled and disabled through half a dozen paths that clear ++ * the pending bit without ever running the work, and none of them ++ * would drop a reference taken here. A stale slot simply resolves to ++ * NULL and the work is charged to nobody. ++ */ ++ unsigned int muqss_owner_slot; ++#endif + #ifdef CONFIG_LOCKDEP + struct lockdep_map lockdep_map; + #endif + }; + ++#ifdef CONFIG_MUQSS_IOTIME ++static inline unsigned int muqss_work_owner(struct work_struct *work) ++{ ++ return work->muqss_owner_slot; ++} ++ ++/* ++ * Every queueing path stamps the owner, so this only matters for a work item ++ * initialised on memory that was never zeroed and then reaching execution by ++ * some path that does not. It is here so that such a path, if one exists, ++ * charges nobody rather than charging whichever unlucky task holds the slot ++ * the garbage happens to name. ++ */ ++static inline void muqss_work_init_owner(struct work_struct *work) ++{ ++ work->muqss_owner_slot = 0; ++} ++#else ++static inline unsigned int muqss_work_owner(struct work_struct *work) ++{ ++ return 0; ++} ++ ++static inline void muqss_work_init_owner(struct work_struct *work) { } ++#endif ++ + #endif /* _LINUX_WORKQUEUE_TYPES_H */ +diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h +--- a/include/uapi/linux/sched.h ++++ b/include/uapi/linux/sched.h +@@ -125,7 +125,7 @@ struct clone_args { + #define SCHED_FIFO 1 + #define SCHED_RR 2 + #define SCHED_BATCH 3 +-/* SCHED_ISO: reserved but not implemented yet */ ++#define SCHED_ISO 4 /* Implemented on MuQSS only */ + #define SCHED_IDLE 5 + #define SCHED_DEADLINE 6 + #define SCHED_EXT 7 +diff --git a/init/Kconfig b/init/Kconfig +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -218,6 +218,8 @@ config THREAD_INFO_IN_TASK + + menu "General setup" + ++source "kernel/Kconfig.MuQSS" ++ + config BROKEN + bool + help +@@ -887,6 +889,7 @@ menu "Scheduler features" + config UCLAMP_TASK + bool "Enable utilization clamping for RT/FAIR tasks" + depends on CPU_FREQ_GOV_SCHEDUTIL ++ depends on !SCHED_MUQSS + help + This feature enables the scheduler to track the clamped utilization + of each CPU based on RUNNABLE tasks scheduled on that CPU. +@@ -935,6 +938,8 @@ config UCLAMP_BUCKETS_COUNT + + config SCHED_PROXY_EXEC + bool "Proxy Execution" ++ # MuQSS has no sched_class donor/proxy plumbing ++ depends on !SCHED_MUQSS + # Avoid some build failures w/ PREEMPT_RT until it can be fixed + depends on !PREEMPT_RT + # Need to investigate how to inform sched_ext of split contexts +@@ -1012,6 +1017,7 @@ config ARCH_WANT_NUMA_VARIABLE_LOCALITY + + config NUMA_BALANCING + bool "Memory placement aware NUMA scheduler" ++ depends on !SCHED_MUQSS + depends on ARCH_SUPPORTS_NUMA_BALANCING + depends on !ARCH_WANT_NUMA_VARIABLE_LOCALITY + depends on SMP && NUMA_MIGRATION && !PREEMPT_RT +@@ -1026,6 +1032,8 @@ config SCHED_CACHE + bool "Cache aware load balance" + default y + depends on SMP ++ # Cache aware load balancing lives in CFS, which MuQSS replaces ++ depends on !SCHED_MUQSS + help + When enabled, the scheduler will attempt to aggregate tasks from + the same process onto a single Last Level Cache (LLC) domain when +@@ -1148,7 +1156,7 @@ menuconfig CGROUP_SCHED + bandwidth allocation to such task groups. It uses cgroups to group + tasks. + +-if CGROUP_SCHED ++if CGROUP_SCHED && !SCHED_MUQSS + config GROUP_SCHED_WEIGHT + def_bool n + +@@ -1210,7 +1218,7 @@ config EXT_SUB_SCHED + + config SCHED_MM_CID + def_bool y +- depends on SMP && RSEQ ++ depends on SMP && RSEQ && !SCHED_MUQSS + + config UCLAMP_TASK_GROUP + bool "Utilization clamping per group of tasks" +@@ -1337,6 +1345,7 @@ config CGROUP_DEVICE + + config CGROUP_CPUACCT + bool "Simple CPU accounting controller" ++ depends on !SCHED_MUQSS + help + Provides a simple controller for monitoring the + total CPU consumed by the tasks in a cgroup. +@@ -1482,6 +1491,7 @@ config CHECKPOINT_RESTORE + + config SCHED_AUTOGROUP + bool "Automatic process group scheduling" ++ depends on !SCHED_MUQSS + select CGROUPS + select CGROUP_SCHED + select FAIR_GROUP_SCHED +@@ -2005,6 +2015,7 @@ config RSEQ + config RSEQ_SLICE_EXTENSION + bool "Enable rseq-based time slice extension mechanism" + depends on RSEQ && HIGH_RES_TIMERS && GENERIC_ENTRY && HAVE_GENERIC_TIF_BITS ++ depends on !SCHED_MUQSS + help + Allows userspace to request a limited time slice extension when + returning from an interrupt to user space via the RSEQ shared +diff --git a/init/init_task.c b/init/init_task.c +--- a/init/init_task.c ++++ b/init/init_task.c +@@ -111,9 +111,17 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { + .stack = init_stack, + .usage = REFCOUNT_INIT(2), + .flags = PF_KTHREAD, ++#ifdef CONFIG_SCHED_MUQSS ++ .prio = NORMAL_PRIO, ++ .static_prio = MAX_PRIO - 20, ++ .normal_prio = NORMAL_PRIO, ++ .deadline = 0, ++ .time_slice = 1000000, ++#else + .prio = MAX_PRIO - 20, + .static_prio = MAX_PRIO - 20, + .normal_prio = MAX_PRIO - 20, ++#endif + .policy = SCHED_NORMAL, + .cpus_ptr = &init_task.cpus_mask, + .user_cpus_ptr = NULL, +@@ -126,6 +134,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { + .restart_block = { + .fn = do_no_restart_syscall, + }, ++#ifndef CONFIG_SCHED_MUQSS + .se = { + .group_node = LIST_HEAD_INIT(init_task.se.group_node), + }, +@@ -133,6 +142,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { + .run_list = LIST_HEAD_INIT(init_task.rt.run_list), + .time_slice = RR_TIMESLICE, + }, ++#endif + .tasks = LIST_HEAD_INIT(init_task.tasks), + #ifdef CONFIG_SMP + .pushable_tasks = PLIST_NODE_INIT(init_task.pushable_tasks, MAX_PRIO), +diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c +--- a/io_uring/io_uring.c ++++ b/io_uring/io_uring.c +@@ -56,6 +56,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1531,7 +1532,19 @@ void io_wq_submit_work(struct io_wq_work *work) + } + + do { ++ struct task_struct *prev; ++ ++ /* ++ * Anything this issue submits to the block layer is being done ++ * on behalf of the task that queued the request, not this ++ * worker. Publish it so the I/O is charged to the right task; ++ * req->tctx pins it for the duration. ++ */ ++ prev = muqss_iotime_proxy_begin(req->tctx ? req->tctx->task : ++ NULL); + ret = io_issue_sqe(req, issue_flags); ++ muqss_iotime_proxy_end(prev); ++ + if (ret != -EAGAIN) + break; + +diff --git a/kernel/Kconfig.MuQSS b/kernel/Kconfig.MuQSS +new file mode 100644 +--- /dev/null ++++ b/kernel/Kconfig.MuQSS +@@ -0,0 +1,220 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++# ++# MuQSS - The Multiple Queue Skiplist Scheduler by Con Kolivas. ++# ++# Everything MuQSS needs from Kconfig lives in this file, including the ++# negative dependencies that disable mainline scheduler features MuQSS ++# replaces or does not implement. Kconfig merges properties from repeated ++# "config" blocks, so those features are gated here rather than by editing ++# each mainline Kconfig in place. The only change required outside this file ++# is a single source line in init/Kconfig. ++ ++config SCHED_MUQSS ++ bool "MuQSS cpu scheduler" ++ # Architectures the port is built and tested on. Widening this needs ++ # arch review: gains a NORMAL_PRIO definition, ++ # which collides with the private one in ++ # arch/powerpc/platforms/cell/spufs/sched.c. ++ depends on X86 || ARM64 ++ select HIGH_RES_TIMERS ++ default y ++ help ++ The Multiple Queue Skiplist Scheduler for excellent interactivity and ++ responsiveness on the desktop and highly scalable deterministic ++ low latency on any hardware. ++ ++ Say Y here. ++ ++config SMT_NICE ++ bool "SMT (Hyperthreading) aware nice priority and policy support" ++ depends on SCHED_MUQSS && SCHED_SMT ++ default y ++ help ++ Enabling Hyperthreading on Intel CPUs decreases the effectiveness ++ of the use of 'nice' levels and different scheduling policies ++ (e.g. realtime) due to sharing of CPU power between hyperthreads. ++ SMT nice support makes each logical CPU aware of what is running on ++ its hyperthread siblings, maintaining appropriate distribution of ++ CPU according to nice levels and scheduling policies at the expense ++ of slightly increased overhead. ++ ++ If unsure say Y here. ++ ++choice ++ depends on SMP ++ prompt "CPU scheduler runqueue sharing" ++ default RQ_MC if SCHED_MUQSS ++ default RQ_NONE ++ ++config RQ_NONE ++ bool "No sharing" ++ help ++ This is the default behaviour where the CPU scheduler has one runqueue ++ per CPU, whether it is a physical or logical CPU (hyperthread). ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=none ++ ++ If unsure, say N. ++ ++config RQ_SMT ++ bool "SMT (hyperthread) siblings" ++ depends on SCHED_SMT && SCHED_MUQSS ++ ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ shared by SMT (hyperthread) siblings. As these logical cores share ++ one physical core, sharing the runqueue resource can lead to decreased ++ overhead, lower latency and higher throughput. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=smt ++ ++ If unsure, say N. ++ ++config RQ_MC ++ bool "Multicore siblings" ++ depends on SCHED_MC && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ shared by multicore siblings in addition to any SMT siblings. ++ As these physical cores share caches, sharing the runqueue resource ++ will lead to lower latency, but its effects on overhead and throughput ++ are less predictable. As a general rule, 6 or fewer cores will likely ++ benefit from this, while larger CPUs will only derive a latency ++ benefit. If your workloads are primarily single threaded, this will ++ possibly worsen throughput. If you are only concerned about latency ++ then enable this regardless of how many cores you have. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=mc ++ ++ If unsure, say Y. ++ ++config RQ_MC_LLC ++ bool "Multicore siblings (LLC)" ++ depends on SCHED_MC && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will behave similarly as ++ with "Multicore siblings". ++ This option takes LLC cache into account when scheduling tasks. ++ Option may benefit CPUs with multiple LLC caches, such as Ryzen ++ and Xeon CPUs. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=llc ++ ++ If unsure, say N. ++ ++config RQ_SMP ++ bool "Symmetric Multi-Processing" ++ depends on SMP && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ shared by all physical CPUs unless they are on separate NUMA nodes. ++ As physical CPUs usually do not share resources, sharing the runqueue ++ will normally worsen throughput but improve latency. If you only ++ care about latency enable this. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=smp ++ ++ If unsure, say N. ++ ++config RQ_ALL ++ bool "NUMA" ++ depends on SMP && SCHED_MUQSS ++ help ++ With this option enabled, the CPU scheduler will have one runqueue ++ regardless of the architecture configuration, including across NUMA ++ nodes. This can substantially decrease throughput in NUMA ++ configurations, but light NUMA designs will not be dramatically ++ affected. This option should only be chosen if latency is the prime ++ concern. ++ ++ This can still be enabled runtime with the boot parameter ++ rqshare=all ++ ++ If unsure, say N. ++endchoice ++ ++config SHARERQ ++ int ++ default 0 if RQ_NONE ++ default 1 if RQ_SMT ++ default 2 if RQ_MC ++ default 3 if RQ_MC_LLC ++ default 4 if RQ_SMP ++ default 5 if RQ_ALL ++ ++config MUQSS_IOTIME ++ bool "I/O aware CPU scheduling support" ++ depends on SCHED_MUQSS && BLOCK ++ default y ++ help ++ Ordinarily the CPU scheduler can see how much CPU a task uses, but it ++ is blind to how much block device time that task consumes. A task ++ that keeps a disk busy on everyone else's behalf looks identical to ++ one that is merely idle, so it keeps full CPU priority while other ++ tasks wait behind its I/O. ++ ++ This option attaches the scheduler to the block layer so that the ++ time a device spends servicing each request is attributed back to the ++ task that asked for it, and accumulates that as a per task I/O time ++ figure. It is exported per task through /proc//iotime and per ++ device through debugfs. ++ ++ That figure is charged against the task's virtual deadline, demoting a ++ task in proportion to the device time it consumed. This stops a task ++ that keeps a disk busy on everybody else's behalf from retaining full ++ CPU priority for free, while leaving tasks that merely wait on I/O as ++ responsive as they are now. ++ ++ The charge is one nanosecond of deadline per nanosecond of device ++ time, so I/O and CPU cost a task equally. That rate is what the ++ feature means rather than a setting within it, so there is nothing to ++ tune it by. ++ ++ The same machinery charges a task for kernel work done in another ++ thread's context on its behalf, which today means kworkers running ++ work items it queued. Kernel work a task asks for in its own context ++ needs no such help, since it already comes out of that task's own ++ timeslice; what escapes the scheduler is the work handed to somebody ++ else, and forcing IRQ threading moves more work into that blind spot. ++ That is charged one for one, since a nanosecond of CPU time costs the ++ same wherever the kernel chose to spend it. It is reported alongside ++ the I/O figures. ++ ++ Doing this costs a timestamp and an owner reference on every block ++ request, and an accounting update on every completion. On very fast ++ devices, or under heavy small random I/O, that overhead is measurable ++ and may reduce peak I/O throughput. Say N if you are tuning purely ++ for storage benchmarks. ++ ++ When disabled this is compiled out entirely and costs nothing. ++ ++ If unsure, say Y. ++ ++# ++# Mainline features MuQSS replaces or does not implement are gated with ++# "depends on !SCHED_MUQSS" at their point of definition. Kconfig scopes a ++# "depends on" to the properties declared in the same config block, so a ++# dependency cannot be attached to a symbol from here; each gate has to live ++# beside the definition it constrains. They are: ++# ++# init/Kconfig UCLAMP_TASK ++# SCHED_PROXY_EXEC ++# NUMA_BALANCING ++# SCHED_CACHE ++# SCHED_MM_CID ++# CGROUP_CPUACCT ++# SCHED_AUTOGROUP ++# RSEQ_SLICE_EXTENSION ++# if CGROUP_SCHED (group scheduling ++# block: FAIR_GROUP_SCHED, ++# CFS_BANDWIDTH, RT_GROUP_SCHED, ++# GROUP_SCHED_*, EXT_GROUP_SCHED) ++# kernel/Kconfig.preempt PREEMPT_RT ++# SCHED_CORE ++# SCHED_CLASS_EXT ++# kernel/trace/rv/monitors/deadline/Kconfig RV_MON_DEADLINE +diff --git a/kernel/Kconfig.hz b/kernel/Kconfig.hz +--- a/kernel/Kconfig.hz ++++ b/kernel/Kconfig.hz +@@ -3,9 +3,29 @@ + # Timer Interrupt Frequency Configuration + # + ++# ++# Silent symbols keep CONFIG_HZ_* available to the rest of the tree and to ++# inherited .configs, but they are not choice members. The promptable ++# *_NODEF names are what olddefconfig can stick on; renaming every rate ++# (including 100) means a saved "# CONFIG_HZ_100 is not set" cannot pin the ++# choice away from the MuQSS default. ++# ++config HZ_100 ++ bool ++ ++config HZ_250 ++ bool ++ ++config HZ_300 ++ bool ++ ++config HZ_1000 ++ bool ++ + choice + prompt "Timer frequency" +- default HZ_250 ++ default HZ_100_NODEF if SCHED_MUQSS ++ default HZ_250_NODEF if !SCHED_MUQSS + help + Allows the configuration of the timer frequency. It is customary + to have the timer interrupt run at 1000 Hz but 100 Hz may be more +@@ -17,31 +37,45 @@ choice + per second. + + +- config HZ_100 ++ config HZ_100_NODEF + bool "100 HZ" ++ select HZ_100 + help ++ 100 Hz is a suitable choice in combination with MuQSS which does ++ not rely on ticks for rescheduling interrupts, and is not Hz limited ++ for timeouts and sleeps from both the kernel and userspace. ++ This allows us to benefit from the lower overhead and higher ++ throughput of fewer timer ticks. ++ ++ Non-MuQSS kernels: + 100 Hz is a typical choice for servers, SMP and NUMA systems + with lots of processors that may show reduced performance if + too many timer interrupts are occurring. + +- config HZ_250 ++ config HZ_250_NODEF + bool "250 HZ" ++ select HZ_250 + help + 250 Hz is a good compromise choice allowing server performance + while also showing good interactive responsiveness even + on SMP and NUMA systems. If you are going to be using NTSC video + or multimedia, select 300Hz instead. + +- config HZ_300 ++ 250 Hz is the default choice for the mainline scheduler but not ++ advantageous in combination with MuQSS. ++ ++ config HZ_300_NODEF + bool "300 HZ" ++ select HZ_300 + help + 300 Hz is a good compromise choice allowing server performance + while also showing good interactive responsiveness even + on SMP and NUMA systems and exactly dividing by both PAL and + NTSC frame rates for video and multimedia work. + +- config HZ_1000 ++ config HZ_1000_NODEF + bool "1000 HZ" ++ select HZ_1000 + help + 1000 Hz is the preferred choice for desktop systems and other + systems requiring fast interactive responses to events. +diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt +--- a/kernel/Kconfig.preempt ++++ b/kernel/Kconfig.preempt +@@ -14,15 +14,35 @@ config PREEMPT_BUILD + config ARCH_HAS_PREEMPT_LAZY + bool + ++# ++# Silent symbols keep CONFIG_PREEMPT{,_NONE,_VOLUNTARY,_LAZY} available to ++# the rest of the tree and to inherited .configs, but they are not choice ++# members. The promptable *_NODEF names are what olddefconfig can stick on; ++# renaming every model (including PREEMPT) means a saved ++# "# CONFIG_PREEMPT is not set" or CONFIG_PREEMPT_{NONE,VOLUNTARY,LAZY}=y ++# cannot pin the choice away from full preempt. ++# ++config PREEMPT_NONE ++ bool ++ ++config PREEMPT_VOLUNTARY ++ bool ++ ++config PREEMPT ++ bool ++ ++config PREEMPT_LAZY ++ bool ++ + choice + prompt "Preemption Model" +- default PREEMPT_LAZY if ARCH_HAS_PREEMPT_LAZY +- default PREEMPT_NONE ++ default PREEMPT_NODEF + +-config PREEMPT_NONE ++config PREEMPT_NONE_NODEF + bool "No Forced Preemption (Server)" + depends on !PREEMPT_RT + depends on ARCH_NO_PREEMPT ++ select PREEMPT_NONE + select PREEMPT_NONE_BUILD if !PREEMPT_DYNAMIC + help + This is the traditional Linux preemption model, geared towards +@@ -35,11 +55,12 @@ config PREEMPT_NONE + raw processing power of the kernel, irrespective of scheduling + latencies. + +-config PREEMPT_VOLUNTARY +- bool "Voluntary Kernel Preemption (Desktop)" ++config PREEMPT_VOLUNTARY_NODEF ++ bool "Voluntary Kernel Preemption (Nothing)" + depends on !ARCH_HAS_PREEMPT_LAZY + depends on !ARCH_NO_PREEMPT + depends on !PREEMPT_RT ++ select PREEMPT_VOLUNTARY + select PREEMPT_VOLUNTARY_BUILD if !PREEMPT_DYNAMIC + help + This option reduces the latency of the kernel by adding more +@@ -54,11 +75,13 @@ config PREEMPT_VOLUNTARY + applications to run more 'smoothly' even when the system is + under load. + +- Select this if you are building a kernel for a desktop system. ++ Select this for no system in particular (choose Preemptible ++ instead on a desktop if you know what's good for you). + +-config PREEMPT ++config PREEMPT_NODEF + bool "Preemptible Kernel (Low-Latency Desktop)" + depends on !ARCH_NO_PREEMPT ++ select PREEMPT + select PREEMPT_BUILD if !PREEMPT_DYNAMIC + help + This option reduces the latency of the kernel by making +@@ -75,10 +98,11 @@ config PREEMPT + embedded system with latency requirements in the milliseconds + range. + +-config PREEMPT_LAZY ++config PREEMPT_LAZY_NODEF + bool "Scheduler controlled preemption model" + depends on !ARCH_NO_PREEMPT + depends on ARCH_HAS_PREEMPT_LAZY ++ select PREEMPT_LAZY + select PREEMPT_BUILD if !PREEMPT_DYNAMIC + help + This option provides a scheduler driven preemption model that +@@ -87,11 +111,17 @@ config PREEMPT_LAZY + reduce lock holder preemption and recover some of the performance + gains seen from using Voluntary preemption. + ++ It trades the latency that makes a desktop feel responsive for ++ throughput, so choose Preemptible instead unless you are building ++ for a throughput oriented system. ++ + endchoice + + config PREEMPT_RT + bool "Fully Preemptible Kernel (Real-Time)" + depends on EXPERT && ARCH_SUPPORTS_RT && !COMPILE_TEST ++ # MuQSS does not implement the PREEMPT_RT scheduler/locking contract ++ depends on !SCHED_MUQSS + select PREEMPTION + help + This option turns the kernel into a real-time kernel by replacing +@@ -150,7 +180,7 @@ config PREEMPT_DYNAMIC + + config SCHED_CORE + bool "Core Scheduling for SMT" +- depends on SCHED_SMT ++ depends on SCHED_SMT && !SCHED_MUQSS + help + This option permits Core Scheduling, a means of coordinated task + selection across SMT siblings. When enabled -- see +@@ -168,7 +198,7 @@ config SCHED_CORE + + config SCHED_CLASS_EXT + bool "Extensible Scheduling Class" +- depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF ++ depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF && !SCHED_MUQSS + select GENERIC_ALLOCATOR + select STACKTRACE if STACKTRACE_SUPPORT + help +diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c +--- a/kernel/cgroup/cpuset.c ++++ b/kernel/cgroup/cpuset.c +@@ -3083,7 +3083,7 @@ static int cpuset_can_attach(struct cgroup_taskset *tset) + */ + cs->nr_migrate_dl_tasks++; + if (dl_task_needs_bw_move(task, cs->effective_cpus)) +- cs->sum_migrate_dl_bw += task->dl.dl_bw; ++ cs->sum_migrate_dl_bw += tsk_dl_bw(task); + } + } + +diff --git a/kernel/delayacct.c b/kernel/delayacct.c +--- a/kernel/delayacct.c ++++ b/kernel/delayacct.c +@@ -170,7 +170,7 @@ int delayacct_add_tsk(struct taskstats *d, struct task_struct *tsk) + */ + t1 = tsk->sched_info.pcount; + t2 = tsk->sched_info.run_delay; +- t3 = tsk->se.sum_exec_runtime; ++ t3 = tsk_seruntime(tsk); + + d->cpu_count += t1; + +diff --git a/kernel/exit.c b/kernel/exit.c +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -34,6 +34,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -207,7 +208,7 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts + sig->inblock += task_io_get_inblock(tsk); + sig->oublock += task_io_get_oublock(tsk); + task_io_accounting_add(&sig->ioac, &tsk->ioac); +- sig->sum_sched_runtime += tsk->se.sum_exec_runtime; ++ sig->sum_sched_runtime += tsk_seruntime(tsk); + sig->nr_threads--; + __unhash_process(post, tsk, group_dead); + write_sequnlock(&sig->stats_lock); +@@ -298,8 +299,8 @@ void release_task(struct task_struct *p) + proc_flush_pid(thread_pid); + put_pid(thread_pid); + exit_cred_namespaces(p); +- add_device_randomness(&p->se.sum_exec_runtime, +- sizeof(p->se.sum_exec_runtime)); ++ add_device_randomness(&tsk_seruntime(p), ++ sizeof(tsk_seruntime(p))); + free_pids(post.pids); + release_thread(p); + /* +@@ -1037,6 +1038,9 @@ void __noreturn do_exit(long code) + if (tsk->io_context) + exit_io_context(tsk); + ++ /* Give up the I/O owner table slot used to attribute writeback. */ ++ muqss_iotime_release_slot(tsk); ++ + if (tsk->splice_pipe) + free_pipe_info(tsk->splice_pipe); + +diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c +--- a/kernel/locking/rtmutex.c ++++ b/kernel/locking/rtmutex.c +@@ -372,7 +372,7 @@ waiter_update_prio(struct rt_mutex_waiter *waiter, struct task_struct *task) + lockdep_assert(RB_EMPTY_NODE(&waiter->tree.entry)); + + waiter->tree.prio = __waiter_prio(task); +- waiter->tree.deadline = task->dl.deadline; ++ waiter->tree.deadline = tsk_dl_deadline(task); + } + + /* +@@ -393,7 +393,7 @@ waiter_clone_prio(struct rt_mutex_waiter *waiter, struct task_struct *task) + * Only use with rt_waiter_node_{less,equal}() + */ + #define task_to_waiter_node(p) \ +- &(struct rt_waiter_node){ .prio = __waiter_prio(p), .deadline = (p)->dl.deadline } ++ &(struct rt_waiter_node){ .prio = __waiter_prio(p), .deadline = tsk_dl_deadline(p) } + #define task_to_waiter(p) \ + &(struct rt_mutex_waiter){ .tree = *task_to_waiter_node(p) } + +diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile +--- a/kernel/sched/Makefile ++++ b/kernel/sched/Makefile +@@ -23,12 +23,14 @@ ifneq ($(CONFIG_SCHED_OMIT_FRAME_POINTER),y) + # I turn this off for IA-64 only. Andreas Schwab says it's also needed on m68k + # to get a correct value for the wait-channel (WCHAN in ps). --davidm + CFLAGS_core.o := $(PROFILING) -fno-omit-frame-pointer ++CFLAGS_MuQSS.o := $(PROFILING) -fno-omit-frame-pointer + endif + + # Branch profiling isn't noinstr-safe + ifdef CONFIG_TRACE_BRANCH_PROFILING + CFLAGS_build_policy.o += -DDISABLE_BRANCH_PROFILING + CFLAGS_build_utility.o += -DDISABLE_BRANCH_PROFILING ++CFLAGS_build_muqss.o += -DDISABLE_BRANCH_PROFILING + endif + # + # Build efficiency: +@@ -36,7 +38,12 @@ endif + # These compilation units have roughly the same size and complexity - so their + # build parallelizes well and finishes roughly at once: + # ++ifdef CONFIG_SCHED_MUQSS ++obj-y += MuQSS.o ++obj-y += build_muqss.o ++else + obj-y += core.o + obj-y += fair.o + obj-y += build_policy.o + obj-y += build_utility.o ++endif +diff --git a/kernel/sched/MuQSS.c b/kernel/sched/MuQSS.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/MuQSS.c +@@ -0,0 +1,10795 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * kernel/sched/MuQSS.c, was kernel/sched.c ++ * ++ * Kernel scheduler and related syscalls ++ * ++ * Copyright (C) 1991-2002 Linus Torvalds ++ * ++ * 1996-12-23 Modified by Dave Grothe to fix bugs in semaphores and ++ * make semaphores SMP safe ++ * 1998-11-19 Implemented schedule_timeout() and related stuff ++ * by Andrea Arcangeli ++ * 2002-01-04 New ultra-scalable O(1) scheduler by Ingo Molnar: ++ * hybrid priority-list and round-robin design with ++ * an array-switch method of distributing timeslices ++ * and per-CPU runqueues. Cleanups and useful suggestions ++ * by Davide Libenzi, preemptible kernel bits by Robert Love. ++ * 2003-09-03 Interactivity tuning by Con Kolivas. ++ * 2004-04-02 Scheduler domains code by Nick Piggin ++ * 2007-04-15 Work begun on replacing all interactivity tuning with a ++ * fair scheduling design by Con Kolivas. ++ * 2007-05-05 Load balancing (smp-nice) and other improvements ++ * by Peter Williams ++ * 2007-05-06 Interactivity improvements to CFS by Mike Galbraith ++ * 2007-07-01 Group scheduling enhancements by Srivatsa Vaddagiri ++ * 2007-11-29 RT balancing improvements by Steven Rostedt, Gregory Haskins, ++ * Thomas Gleixner, Mike Kravetz ++ * 2009-08-13 Brainfuck deadline scheduling policy by Con Kolivas deletes ++ * a whole lot of those previous things. ++ * 2016-10-01 Multiple Queue Skiplist Scheduler scalable evolution of BFS ++ * scheduler by Con Kolivas. ++ * 2019-08-31 LLC bits by Eduards Bezverhijs ++ */ ++/* Out-of-line migrate_{disable,enable} for modules; see linux/sched.h. */ ++#define INSTANTIATE_EXPORTED_MIGRATE_DISABLE ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "../workqueue_internal.h" ++#include "../../io_uring/io-wq.h" ++#include "../smpboot.h" ++ ++#define CREATE_TRACE_POINTS ++#include ++#include ++#include ++#undef CREATE_TRACE_POINTS ++ ++#include "MuQSS.h" ++#include "smp.h" ++ ++/* Modules probe these; core.c exports the same set under CFS. */ ++EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpu); ++EXPORT_TRACEPOINT_SYMBOL_GPL(ipi_send_cpumask); ++EXPORT_TRACEPOINT_SYMBOL(sched_set_state_tp); ++ ++#define rt_prio(prio) unlikely((prio) < MAX_RT_PRIO) ++#define rt_task(p) rt_prio((p)->prio) ++#define batch_task(p) (unlikely((p)->policy == SCHED_BATCH)) ++#define is_rt_policy(policy) ((policy) == SCHED_FIFO || \ ++ (policy) == SCHED_RR) ++#define has_rt_policy(p) unlikely(is_rt_policy((p)->policy)) ++ ++#define is_idle_policy(policy) ((policy) == SCHED_IDLEPRIO) ++#define idleprio_task(p) unlikely(is_idle_policy((p)->policy)) ++#define task_running_idle(p) unlikely((p)->prio == IDLE_PRIO) ++ ++#define is_iso_policy(policy) ((policy) == SCHED_ISO) ++#define iso_task(p) unlikely(is_iso_policy((p)->policy)) ++#define task_running_iso(p) unlikely((p)->prio == ISO_PRIO) ++ ++#define rq_idle(rq) ((rq)->rq_prio == PRIO_LIMIT) ++ ++#define ISO_PERIOD (5 * HZ) ++ ++/* ++ * 'User priority' is the nice value converted to something we ++ * can work with better when scaling various scheduler parameters, ++ * it's a [ 0 ... 39 ] range. ++ */ ++#define USER_PRIO(p) ((p)-MAX_RT_PRIO) ++#define TASK_USER_PRIO(p) USER_PRIO((p)->static_prio) ++#define MAX_USER_PRIO (USER_PRIO(MAX_PRIO)) ++#define STOP_PRIO (MAX_RT_PRIO - 1) ++ ++/* ++ * Some helpers for converting to/from various scales. Use shifts to get ++ * approximate multiples of ten for less overhead. These are internal scales ++ * only - niffies themselves are real nanoseconds, so anything converting the ++ * tick into niffies must use TICK_NSEC rather than the approximations here. ++ */ ++#define APPROX_NS_PS (1073741824) /* Approximate ns per second */ ++#define JIFFY_NS (APPROX_NS_PS / HZ) ++#define NS_TO_JIFFIES(TIME) ((TIME) / JIFFY_NS) ++#define HALF_JIFFY_NS (APPROX_NS_PS / HZ / 2) ++/* ++ * time_slice is banked in NS_TO_US() of real nanoseconds, so half a tick in ++ * those units has to come from TICK_NSEC, not from the approximate scale. ++ */ ++#define HALF_JIFFY_US (NS_TO_US(TICK_NSEC) / 2) ++#define MS_TO_NS(TIME) ((TIME) << 20) ++#define MS_TO_US(TIME) ((TIME) << 10) ++#define NS_TO_MS(TIME) ((TIME) >> 20) ++#define NS_TO_US(TIME) ((TIME) >> 10) ++#define US_TO_NS(TIME) ((TIME) << 10) ++#define TICK_APPROX_NS ((APPROX_NS_PS+HZ/2)/HZ) ++ ++#define RESCHED_US (100) /* Reschedule if less than this many μs left */ ++ ++static void print_scheduler_version(void) ++{ ++ printk(KERN_INFO "MuQSS CPU scheduler v0.31 by Con Kolivas.\n"); ++} ++ ++/* ++ * This is the time all tasks within the same priority round robin. ++ * Value is in ms and set to a minimum of 6ms. ++ * Tunable via /proc interface. ++ */ ++int rr_interval __read_mostly = 6; ++ ++/* ++ * Tunable to choose whether to prioritise latency or throughput, simple ++ * binary yes or no ++ */ ++int sched_interactive __read_mostly = 1; ++ ++/* ++ * sched_iso_cpu - sysctl which determines the cpu percentage SCHED_ISO tasks ++ * are allowed to run five seconds as real time tasks. This is the total over ++ * all online cpus. ++ */ ++int sched_iso_cpu __read_mostly = 70; ++ ++/* ++ * sched_yield_type - Choose what sort of yield sched_yield will perform. ++ * 0: No yield. ++ * 1: Yield only to better priority/deadline tasks. (default) ++ * 2: Expire timeslice and recalculate deadline. ++ */ ++int sched_yield_type __read_mostly = 1; ++ ++/* ++ * The relative length of deadline for each priority(nice) level. ++ */ ++static int prio_ratios[NICE_WIDTH] __read_mostly; ++ ++ ++/* ++ * The quota handed out to tasks of all priority levels when refilling their ++ * time_slice. ++ */ ++static inline int timeslice(void) ++{ ++ return MS_TO_US(rr_interval); ++} ++ ++DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); ++ ++#ifdef CONFIG_SMP ++ ++/* Define RQ share levels */ ++#define RQSHARE_NONE 0 ++#define RQSHARE_SMT 1 ++#define RQSHARE_MC 2 ++#define RQSHARE_MC_LLC 3 ++#define RQSHARE_SMP 4 ++#define RQSHARE_ALL 5 ++ ++/* Define locality levels */ ++#define LOCALITY_SAME 0 ++#define LOCALITY_SMT 1 ++#define LOCALITY_MC_LLC 2 ++#define LOCALITY_MC 3 ++#define LOCALITY_SMP 4 ++#define LOCALITY_DISTANT 5 ++ ++/* ++ * This determines what level of runqueue sharing will be done and is ++ * configurable at boot time with the bootparam rqshare = ++ */ ++static int rqshare __read_mostly = CONFIG_SHARERQ; /* Default RQSHARE_MC */ ++ ++static int __init set_rqshare(char *str) ++{ ++ if (!strncmp(str, "none", 4)) { ++ rqshare = RQSHARE_NONE; ++ return 1; ++ } ++ if (!strncmp(str, "smt", 3)) { ++ rqshare = RQSHARE_SMT; ++ return 1; ++ } ++ if (!strncmp(str, "mc", 2)) { ++ rqshare = RQSHARE_MC; ++ return 1; ++ } ++ if (!strncmp(str, "llc", 3)) { ++ rqshare = RQSHARE_MC_LLC; ++ return 1; ++ } ++ if (!strncmp(str, "smp", 3)) { ++ rqshare = RQSHARE_SMP; ++ return 1; ++ } ++ if (!strncmp(str, "all", 3)) { ++ rqshare = RQSHARE_ALL; ++ return 1; ++ } ++ return 0; ++} ++__setup("rqshare=", set_rqshare); ++ ++/* ++ * Total number of runqueues. Equals number of CPUs when there is no runqueue ++ * sharing but is usually less with SMT/MC sharing of runqueues. ++ */ ++static int total_runqueues __read_mostly = 1; ++ ++static cpumask_t cpu_idle_map ____cacheline_aligned_in_smp; ++ ++/* ++ * For asym packing, by default the lower numbered cpu has higher priority. ++ */ ++int __weak arch_asym_cpu_priority(int cpu) ++{ ++ return -cpu; ++} ++ ++#else ++struct rq *uprq; ++#endif /* CONFIG_SMP */ ++ ++/* ++ * sched_smt_active() is unconditionally available now that cpu_smt_mask() is ++ * cpumask_of(cpu) for !CONFIG_SCHED_SMT, so the key must always be defined. ++ */ ++DEFINE_STATIC_KEY_FALSE(sched_smt_present); ++EXPORT_SYMBOL_GPL(sched_smt_present); ++ ++#include "stats.h" ++ ++/* ++ * All common locking functions performed on rq->lock. rq->clock is local to ++ * the CPU accessing it so it can be modified just with interrupts disabled ++ * when we're not updating niffies. ++ * Looking up task_rq must be done under rq->lock to be safe. ++ */ ++ ++/* ++ * RQ-clock updating methods: ++ */ ++ ++#ifdef HAVE_SCHED_AVG_IRQ ++static void update_irq_load_avg(struct rq *rq, long delta); ++#else ++static inline void update_irq_load_avg(struct rq *rq, long delta) {} ++#endif ++ ++/* Use CONFIG_PARAVIRT as this will avoid more #ifdef in arch code. */ ++#ifdef CONFIG_PARAVIRT ++struct static_key paravirt_steal_rq_enabled; ++#endif ++ ++static void update_rq_clock_task(struct rq *rq, s64 delta) ++{ ++/* ++ * In theory, the compile should just see 0 here, and optimize out the call ++ * to sched_rt_avg_update. But I don't trust it... ++ */ ++ s64 __maybe_unused steal = 0, irq_delta = 0; ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++ irq_delta = irq_time_read(cpu_of(rq)) - rq->prev_irq_time; ++ ++ /* ++ * Since irq_time is only updated on {soft,}irq_exit, we might run into ++ * this case when a previous update_rq_clock() happened inside a ++ * {soft,}irq region. ++ * ++ * When this happens, we stop ->clock_task and only update the ++ * prev_irq_time stamp to account for the part that fit, so that a next ++ * update will consume the rest. This ensures ->clock_task is ++ * monotonic. ++ * ++ * It does however cause some slight miss-attribution of {soft,}irq ++ * time, a more accurate solution would be to update the irq_time using ++ * the current rq->clock timestamp, except that would require using ++ * atomic ops. ++ */ ++ if (irq_delta > delta) ++ irq_delta = delta; ++ ++ rq->prev_irq_time += irq_delta; ++ delta -= irq_delta; ++#endif ++#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING ++ if (static_key_false((¶virt_steal_rq_enabled))) { ++ steal = paravirt_steal_clock(cpu_of(rq)); ++ steal -= rq->prev_steal_time_rq; ++ ++ if (unlikely(steal > delta)) ++ steal = delta; ++ ++ rq->prev_steal_time_rq += steal; ++ delta -= steal; ++ } ++#endif ++ rq->clock_task += delta; ++ ++#ifdef CONFIG_HAVE_SCHED_AVG_IRQ ++ if (irq_delta + steal) ++ update_irq_load_avg(rq, irq_delta + steal); ++#endif ++} ++ ++static inline void update_rq_clock(struct rq *rq) ++{ ++ s64 delta = sched_clock_cpu(cpu_of(rq)) - rq->clock; ++ ++ if (unlikely(delta < 0)) ++ return; ++ rq->clock += delta; ++ update_rq_clock_task(rq, delta); ++} ++ ++/* ++ * Niffies are a globally increasing nanosecond counter. They're only used by ++ * update_load_avg and time_slice_expired, however deadlines are based on them ++ * across CPUs. Update them whenever we will call one of those functions, and ++ * synchronise them across CPUs whenever we hold both runqueue locks. ++ * ++ * Niffies are the highest of two absolute values that each advance at real ++ * time: this runqueue's clock, and a tick line advanced by whole jiffies so ++ * that we keep counting if the rq clock stalls. Because both are absolute, ++ * time contributed by the tick line, or imported by synchronise_niffies(), is ++ * never counted again when the rq clock catches up - niffies simply stall ++ * until it does. Accumulating the maximum of the two *deltas* instead keeps ++ * every overshoot for good and drifts upwards without bound. ++ */ ++static inline void update_clocks(struct rq *rq) ++{ ++ long jdiff; ++ ++ update_rq_clock(rq); ++ jdiff = jiffies - rq->last_jiffy; ++ if (jdiff > 0) { ++ rq->last_jiffy += jdiff; ++ rq->jiffy_niffies += (u64)jdiff * TICK_NSEC; ++ } ++ if (rq->niffies < rq->jiffy_niffies) ++ rq->niffies = rq->jiffy_niffies; ++ if (rq->niffies < rq->clock) ++ rq->niffies = rq->clock; ++} ++ ++/* ++ * Any time we have two runqueues locked we use that as an opportunity to ++ * synchronise niffies to the highest value as idle ticks may have artificially ++ * kept niffies low on one CPU and the truth can only be later. ++ */ ++static inline void synchronise_niffies(struct rq *rq1, struct rq *rq2) ++{ ++ if (rq1->niffies > rq2->niffies) ++ rq2->niffies = rq1->niffies; ++ else ++ rq1->niffies = rq2->niffies; ++} ++ ++/* ++ * double_rq_lock - safely lock two runqueues ++ * ++ * Note this does not disable interrupts like task_rq_lock, ++ * you need to do so manually before calling. ++ */ ++ ++/* For when we know rq1 != rq2 */ ++static inline void __double_rq_lock(struct rq *rq1, struct rq *rq2) ++ __acquires(rq1->lock) ++ __acquires(rq2->lock) ++{ ++ if (rq1 < rq2) { ++ raw_spin_lock(rq1->lock); ++ raw_spin_lock_nested(rq2->lock, SINGLE_DEPTH_NESTING); ++ } else { ++ raw_spin_lock(rq2->lock); ++ raw_spin_lock_nested(rq1->lock, SINGLE_DEPTH_NESTING); ++ } ++} ++ ++static inline void double_rq_lock(struct rq *rq1, struct rq *rq2) ++ __acquires(rq1->lock) ++ __acquires(rq2->lock) ++{ ++ BUG_ON(!irqs_disabled()); ++ if (rq1->lock == rq2->lock) { ++ raw_spin_lock(rq1->lock); ++ __acquire(rq2->lock); /* Fake it out ;) */ ++ } else ++ __double_rq_lock(rq1, rq2); ++ synchronise_niffies(rq1, rq2); ++} ++ ++/* ++ * double_rq_unlock - safely unlock two runqueues ++ * ++ * Note this does not restore interrupts like task_rq_unlock, ++ * you need to do so manually after calling. ++ */ ++static inline void double_rq_unlock(struct rq *rq1, struct rq *rq2) ++ __releases(rq1->lock) ++ __releases(rq2->lock) ++{ ++ raw_spin_unlock(rq1->lock); ++ if (rq1->lock != rq2->lock) ++ raw_spin_unlock(rq2->lock); ++ else ++ __release(rq2->lock); ++} ++ ++static inline void lock_all_rqs(void) ++{ ++ int cpu; ++ ++ preempt_disable(); ++ for_each_possible_cpu(cpu) { ++ struct rq *rq = cpu_rq(cpu); ++ ++ do_raw_spin_lock(rq->lock); ++ } ++} ++ ++static inline void unlock_all_rqs(void) ++{ ++ int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct rq *rq = cpu_rq(cpu); ++ ++ do_raw_spin_unlock(rq->lock); ++ } ++ preempt_enable(); ++} ++ ++/* Specially nest trylock an rq */ ++static inline bool trylock_rq(struct rq *this_rq, struct rq *rq) ++{ ++ if (unlikely(!do_raw_spin_trylock(rq->lock))) ++ return false; ++ spin_acquire(&rq->lock->dep_map, SINGLE_DEPTH_NESTING, 1, _RET_IP_); ++ synchronise_niffies(this_rq, rq); ++ return true; ++} ++ ++/* Unlock a specially nested trylocked rq */ ++static inline void unlock_rq(struct rq *rq) ++{ ++ spin_release(&rq->lock->dep_map, _RET_IP_); ++ do_raw_spin_unlock(rq->lock); ++} ++ ++/* ++ * cmpxchg based fetch_or, macro so it works for different integer types ++ */ ++#define fetch_or(ptr, mask) \ ++ ({ \ ++ typeof(ptr) _ptr = (ptr); \ ++ typeof(mask) _mask = (mask); \ ++ typeof(*_ptr) _old, _val = *_ptr; \ ++ \ ++ for (;;) { \ ++ _old = cmpxchg(_ptr, _val, _val | _mask); \ ++ if (_old == _val) \ ++ break; \ ++ _val = _old; \ ++ } \ ++ _old; \ ++}) ++ ++#if defined(CONFIG_SMP) && defined(TIF_POLLING_NRFLAG) ++/* ++ * Atomically set TIF_NEED_RESCHED and test for TIF_POLLING_NRFLAG, ++ * this avoids any races wrt polling state changes and thereby avoids ++ * spurious IPIs. ++ */ ++static bool set_nr_and_not_polling(struct task_struct *p) ++{ ++ struct thread_info *ti = task_thread_info(p); ++ return !(fetch_or(&ti->flags, _TIF_NEED_RESCHED) & _TIF_POLLING_NRFLAG); ++} ++ ++/* ++ * Atomically set TIF_NEED_RESCHED if TIF_POLLING_NRFLAG is set. ++ * ++ * If this returns true, then the idle task promises to call ++ * sched_ttwu_pending() and reschedule soon. ++ */ ++static bool set_nr_if_polling(struct task_struct *p) ++{ ++ struct thread_info *ti = task_thread_info(p); ++ typeof(ti->flags) old, val = READ_ONCE(ti->flags); ++ ++ for (;;) { ++ if (!(val & _TIF_POLLING_NRFLAG)) ++ return false; ++ if (val & _TIF_NEED_RESCHED) ++ return true; ++ old = cmpxchg(&ti->flags, val, val | _TIF_NEED_RESCHED); ++ if (old == val) ++ break; ++ val = old; ++ } ++ return true; ++} ++ ++#else ++static bool set_nr_and_not_polling(struct task_struct *p) ++{ ++ set_tsk_need_resched(p); ++ return true; ++} ++ ++#ifdef CONFIG_SMP ++static bool set_nr_if_polling(struct task_struct *p) ++{ ++ return false; ++} ++#endif ++#endif ++ ++static bool __wake_q_add(struct wake_q_head *head, struct task_struct *task) ++{ ++ struct wake_q_node *node = &task->wake_q; ++ ++ /* ++ * Atomically grab the task, if ->wake_q is !nil already it means ++ * it's already queued (either by us or someone else) and will get the ++ * wakeup due to that. ++ * ++ * In order to ensure that a pending wakeup will observe our pending ++ * state, even in the failed case, an explicit smp_mb() must be used. ++ */ ++ smp_mb__before_atomic(); ++ if (unlikely(cmpxchg_relaxed(&node->next, NULL, WAKE_Q_TAIL))) ++ return false; ++ ++ /* ++ * The head is context local, there can be no concurrency. ++ */ ++ *head->lastp = node; ++ head->lastp = &node->next; ++ return true; ++} ++ ++/** ++ * wake_q_add() - queue a wakeup for 'later' waking. ++ * @head: the wake_q_head to add @task to ++ * @task: the task to queue for 'later' wakeup ++ * ++ * Queue a task for later wakeup, most likely by the wake_up_q() call in the ++ * same context, _HOWEVER_ this is not guaranteed, the wakeup can come ++ * instantly. ++ * ++ * This function must be used as-if it were wake_up_process(); IOW the task ++ * must be ready to be woken at this location. ++ */ ++void wake_q_add(struct wake_q_head *head, struct task_struct *task) ++{ ++ if (__wake_q_add(head, task)) ++ get_task_struct(task); ++} ++ ++/** ++ * wake_q_add_safe() - safely queue a wakeup for 'later' waking. ++ * @head: the wake_q_head to add @task to ++ * @task: the task to queue for 'later' wakeup ++ * ++ * Queue a task for later wakeup, most likely by the wake_up_q() call in the ++ * same context, _HOWEVER_ this is not guaranteed, the wakeup can come ++ * instantly. ++ * ++ * This function must be used as-if it were wake_up_process(); IOW the task ++ * must be ready to be woken at this location. ++ * ++ * This function is essentially a task-safe equivalent to wake_q_add(). Callers ++ * that already hold reference to @task can call the 'safe' version and trust ++ * wake_q to do the right thing depending whether or not the @task is already ++ * queued for wakeup. ++ */ ++void wake_q_add_safe(struct wake_q_head *head, struct task_struct *task) ++{ ++ if (!__wake_q_add(head, task)) ++ put_task_struct(task); ++} ++ ++void wake_up_q(struct wake_q_head *head) ++{ ++ struct wake_q_node *node = head->first; ++ ++ while (node != WAKE_Q_TAIL) { ++ struct task_struct *task; ++ ++ task = container_of(node, struct task_struct, wake_q); ++ BUG_ON(!task); ++ /* Task can safely be re-inserted now */ ++ node = node->next; ++ task->wake_q.next = NULL; ++ ++ /* ++ * wake_up_process() executes a full barrier, which pairs with ++ * the queueing in wake_q_add() so as not to miss wakeups. ++ */ ++ wake_up_process(task); ++ put_task_struct(task); ++ } ++} ++ ++static inline void smp_sched_reschedule(int cpu) ++{ ++ if (likely(cpu_online(cpu))) ++ smp_send_reschedule(cpu); ++} ++ ++/* ++ * resched_task - mark a task 'to be rescheduled now'. ++ * ++ * On UP this means the setting of the need_resched flag, on SMP it ++ * might also involve a cross-CPU call to trigger the scheduler on ++ * the target CPU. ++ */ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++static DEFINE_STATIC_KEY_FALSE(sk_dynamic_preempt_lazy); ++static __always_inline bool dynamic_preempt_lazy(void) ++{ ++ return static_branch_unlikely(&sk_dynamic_preempt_lazy); ++} ++#else ++static __always_inline bool dynamic_preempt_lazy(void) ++{ ++ return IS_ENABLED(CONFIG_PREEMPT_LAZY); ++} ++#endif ++ ++static void resched_task(struct task_struct *p) ++{ ++ int cpu; ++#ifdef CONFIG_LOCKDEP ++ /* Kernel threads call this when creating workqueues while still ++ * inactive from __kthread_bind_mask, holding only the pi_lock */ ++ if (!(p->flags & PF_KTHREAD)) { ++ struct rq *rq = task_rq(p); ++ ++ lockdep_assert_held(rq->lock); ++ } ++#endif ++ if (test_tsk_need_resched(p)) ++ return; ++ ++ cpu = task_cpu(p); ++ if (cpu == smp_processor_id()) { ++ set_tsk_need_resched(p); ++ set_preempt_need_resched(); ++ return; ++ } ++ ++ if (set_nr_and_not_polling(p)) ++ smp_sched_reschedule(cpu); ++ else ++ trace_sched_wake_idle_without_ipi(cpu); ++} ++ ++/* ++ * A task that is not running or queued will not have a node set. ++ * A task that is queued but not running will have a node set. ++ * A task that is currently running will have ->on_cpu set but no node set. ++ */ ++static inline bool task_queued(struct task_struct *p) ++{ ++ return !skiplist_node_empty(&p->node); ++} ++ ++static void enqueue_task(struct rq *rq, struct task_struct *p, int flags); ++static inline void resched_if_idle(struct rq *rq); ++ ++static inline bool deadline_before(u64 deadline, u64 time) ++{ ++ return (deadline < time); ++} ++ ++/* ++ * Deadline is "now" in niffies + (offset by priority). Setting the deadline ++ * is the key to everything. It distributes cpu fairly amongst tasks of the ++ * same nice value, it proportions cpu according to nice level, it means the ++ * task that last woke up the longest ago has the earliest deadline, thus ++ * ensuring that interactive tasks get low latency on wake up. The CPU ++ * proportion works out to the square of the virtual deadline difference, so ++ * this equation will give nice 19 3% CPU compared to nice 0. ++ */ ++static inline u64 prio_deadline_diff(int user_prio) ++{ ++ return (prio_ratios[user_prio] * rr_interval * (MS_TO_NS(1) / 128)); ++} ++ ++static inline u64 task_deadline_diff(struct task_struct *p) ++{ ++ return prio_deadline_diff(TASK_USER_PRIO(p)); ++} ++ ++static inline u64 static_deadline_diff(int static_prio) ++{ ++ return prio_deadline_diff(USER_PRIO(static_prio)); ++} ++ ++static inline int longest_deadline_diff(void) ++{ ++ return prio_deadline_diff(39); ++} ++ ++static inline int ms_longest_deadline_diff(void) ++{ ++ return NS_TO_MS(longest_deadline_diff()); ++} ++ ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * Scale an I/O time penalty by nice level, exactly as prio_deadline_diff() ++ * scales rr_interval. A deadline is virtual time, already stretched by ++ * prio_ratios[] before it means anything; adding raw nanoseconds of device ++ * time to it mixes two different currencies. Passing the penalty through the ++ * same ratio makes it commensurate with the deadline it is added to, so a ++ * given amount of I/O costs a task the same share of its own allotment ++ * whatever its nice level, and a niced down task is pushed back further in ++ * absolute terms than a niced up one for the same device time. ++ * ++ * The multiply is 64 bit, as it is in prio_deadline_diff(). Wrapping it would ++ * need a penalty over U64_MAX / prio_ratios[39], which is some forty days of ++ * device occupancy charged to one task between two scheduling events, so it ++ * is not a case worth writing code for. ++ */ ++static inline u64 prio_penalty_diff(int user_prio, u64 penalty) ++{ ++ return penalty * prio_ratios[user_prio] / 128; ++} ++ ++/* ++ * Idleprio tasks are charged at the highest nice level whatever nice value ++ * they happen to carry. SCHED_IDLEPRIO is a class beneath the whole nice ++ * range rather than a position within it, and a task there has said it should ++ * run only when nothing else wants the CPU. Scaling by its own nice would let ++ * an idleprio task sitting at nice -20 be charged the lightest rate of all ++ * for keeping the disk busy, which is backwards. Their deadlines already sort ++ * below everything else in enqueue_task(); this keeps what they are charged ++ * for I/O consistent with that. ++ */ ++static inline u64 task_penalty_diff(struct task_struct *p, u64 penalty) ++{ ++ if (idleprio_task(p)) ++ return prio_penalty_diff(39, penalty); ++ ++ return prio_penalty_diff(TASK_USER_PRIO(p), penalty); ++} ++#endif ++ ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * Take the block device time accumulated on this task's behalf since it was ++ * last charged, and convert it to an amount of virtual deadline to push the ++ * task back by. The debt is consumed as it is read, so each nanosecond of ++ * device time demotes the task exactly once no matter which caller gets to it ++ * first. The charge is added to the deadline, so it is a demotion, not a ++ * boost: a task that keeps a device busy for everybody else stops getting ++ * full CPU priority for free. ++ * ++ * Charged one for one, a nanosecond of deadline for each nanosecond of device ++ * time consumed. That rate says device time and CPU time cost a task the ++ * same, which is the premise of the feature rather than a setting within it, ++ * so there is nothing to weight one against the other with. ++ * ++ * The charge is then scaled by nice through task_penalty_diff(), the same way ++ * rr_interval is, so that it is in the same virtual currency as the deadline ++ * it is added to. One for one is therefore one for one in deadline terms ++ * rather than in raw nanoseconds: at nice 0 the ratio is prio_ratios[20] / ++ * 128, about 6.7, exactly as an ordinary nice 0 timeslice is worth 6.7 ++ * rr_intervals of deadline. ++ * ++ * There is no ceiling. A task that keeps a device busy can be demoted ++ * arbitrarily far behind, past the deadline of the lowest nice level. See ++ * MuQSS-iotime-design.md. ++ * ++ * This has to be consumed from both time_slice_expired() and enqueue_task(). ++ * Expiry alone misses the streaming reader, which blocks before exhausting its ++ * slice and so never refreshes its deadline. Enqueue alone is not enough ++ * either: time_slice_expired() assigns the deadline absolutely, resetting the ++ * task to an uncharged baseline, and two of its callers (sched_yield() and ++ * yield_to()) have no enqueue behind them to reapply the charge. ++ */ ++static inline u64 consume_iotime_penalty(struct task_struct *p) ++{ ++ u64 debt = atomic64_xchg(&p->io_debt_ns, 0); ++ ++ if (!debt) ++ return 0; ++ ++ return task_penalty_diff(p, debt); ++} ++ ++/* ++ * The same for CPU time spent in another thread's context on this task's ++ * behalf, which today means kworkers running work items it queued. ++ * ++ * Charged one for one, as device time is. A nanosecond of CPU time is a ++ * nanosecond of CPU time wherever it was spent, and the whole premise ++ * here is that the thread it was spent in should not decide whether it counts. ++ * Work a task asks for in its own context is already charged at exactly that ++ * rate: update_cpu_clock_switch() and update_cpu_clock_tick() deduct it from ++ * ->time_slice with no regard for which mode it was spent in, so a task that ++ * burns its whole quantum inside a syscall is demoted as surely as one that ++ * spun in userspace. Charging deferred work at anything other than parity ++ * would say that where the kernel chose to do the work changes what it cost, ++ * which is the bug this exists to fix. Forcing IRQ threading, as -ck does, ++ * only moves more work into that blind spot. ++ * ++ * It is still scaled by nice through task_penalty_diff(), like every other ++ * addition to a deadline, so that it is in the same virtual currency as the ++ * deadline it lands on. ++ * ++ * As with iotime there is no ceiling. The runaway this invites is a different ++ * shape to the I/O case and worth naming, because here CPU consumption is ++ * being punished with CPU demotion: a demoted task need not stop generating ++ * the work, so it can be starved while kworkers keep charging it. It is ++ * bounded in practice because the debt a task can accrue is bounded by the ++ * work it queued, and a task demoted enough to stop running stops queueing ++ * more. ++ * ++ * Kept as a separate counter from io_debt_ns rather than folded into it, ++ * because the two are quantities of very different size: work item runtimes ++ * are microseconds where device occupancy is milliseconds, and sharing an ++ * accumulator would let one vanish into the rounding of the other. Separate ++ * also keeps them separable in /proc//iotime when the question is why a ++ * task was demoted. ++ */ ++static inline u64 consume_kerntime_penalty(struct task_struct *p) ++{ ++ u64 debt = atomic64_xchg(&p->kern_debt_ns, 0); ++ ++ if (!debt) ++ return 0; ++ ++ return task_penalty_diff(p, debt); ++} ++#else ++static inline u64 consume_iotime_penalty(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static inline u64 consume_kerntime_penalty(struct task_struct *p) ++{ ++ return 0; ++} ++#endif ++ ++/* ++ * Everything charged against a task's deadline that did not come out of its ++ * own time_slice. ++ */ ++static inline u64 consume_task_penalty(struct task_struct *p) ++{ ++ return consume_iotime_penalty(p) + consume_kerntime_penalty(p); ++} ++ ++static inline bool rq_local(struct rq *rq); ++ ++#ifndef SCHED_CAPACITY_SCALE ++#define SCHED_CAPACITY_SCALE 1024 ++#endif ++ ++static inline int rq_load(struct rq *rq) ++{ ++ return rq->nr_running; ++} ++ ++/* ++ * Update the load average for feeding into cpu frequency governors. Use a ++ * rough estimate of a rolling average with ~ time constant of 32ms. ++ * 80/128 ~ 0.63. * 80 / 32768 / 128 == * 5 / 262144 ++ * Make sure a call to update_clocks has been made before calling this to get ++ * an updated rq->niffies. ++ */ ++static void update_load_avg(struct rq *rq, unsigned int flags) ++{ ++ long us_interval, load; ++ ++ us_interval = NS_TO_US(rq->niffies - rq->load_update); ++ if (unlikely(us_interval <= 0)) ++ return; ++ ++ load = rq->load_avg - (rq->load_avg * us_interval * 5 / 262144); ++ if (unlikely(load < 0)) ++ load = 0; ++ load += rq_load(rq) * SCHED_CAPACITY_SCALE * us_interval * 5 / 262144; ++ rq->load_avg = load; ++ ++ rq->load_update = rq->niffies; ++ update_irq_load_avg(rq, 0); ++ if (likely(rq_local(rq))) ++ cpufreq_trigger(rq, flags); ++} ++ ++#ifdef HAVE_SCHED_AVG_IRQ ++/* ++ * IRQ variant of update_load_avg below. delta is actually time in nanoseconds ++ * here so we scale curload to how long it's been since the last update. ++ */ ++static void update_irq_load_avg(struct rq *rq, long delta) ++{ ++ long us_interval, load; ++ ++ us_interval = NS_TO_US(rq->niffies - rq->irq_load_update); ++ if (unlikely(us_interval <= 0)) ++ return; ++ ++ load = rq->irq_load_avg - (rq->irq_load_avg * us_interval * 5 / 262144); ++ if (unlikely(load < 0)) ++ load = 0; ++ load += NS_TO_US(delta) * SCHED_CAPACITY_SCALE * 5 / 262144; ++ rq->irq_load_avg = load; ++ ++ rq->irq_load_update = rq->niffies; ++} ++#endif ++ ++static inline void update_best_key(struct rq *rq) ++{ ++ WRITE_ONCE(rq->sl->best_key, rq->node->next[0]->key); ++} ++ ++/* ++ * Removing from the runqueue. Enter with rq locked. Deleting a task ++ * from the skip list is done via the stored node reference in the task struct ++ * and does not require a full look up. Thus it occurs in O(k) time where k ++ * is the "level" of the list the task was stored at - usually 0, max 3. ++ */ ++static void dequeue_task(struct rq *rq, struct task_struct *p, int flags) ++{ ++ /* ++ * Everything below is accounting for @p having been on @rq, so none of ++ * it may happen if it was not: a task whose node is unlinked is on no ++ * runqueue at all and skiplist_delete() has left the list untouched. ++ * ++ * nr_running in particular is unsigned, so a single spurious decrement ++ * wraps it to ~0 and the runqueue never looks empty again. That wedges ++ * sched_cpu_wait_empty(), whose only exit is nr_running <= 1, and a CPU ++ * offline then hangs forever with the dying CPU idle. psi_dequeue() ++ * would likewise clear state that is counted somewhere else. Leaving ++ * best_key alone is right for the same reason - the list did not ++ * change. ++ * ++ * skiplist_delete() has already warned; this only keeps a lost race in ++ * the caller from becoming permanent corruption of the runqueue. ++ */ ++ if (unlikely(!skiplist_delete(rq->sl, &p->node))) ++ return; ++ ++ update_best_key(rq); ++ update_clocks(rq); ++ ++ if (!(flags & DEQUEUE_SAVE)) { ++ sched_info_dequeue(rq, p); ++ /* Pass full flags: DEQUEUE_SLEEP vs migration clear all state. */ ++ psi_dequeue(p, flags); ++ } ++ rq->nr_running--; ++ if (rt_task(p)) ++ rq->rt_nr_running--; ++ update_load_avg(rq, flags); ++} ++ ++#ifdef CONFIG_PREEMPT_RCU ++static bool rcu_read_critical(struct task_struct *p) ++{ ++ return p->rcu_read_unlock_special.b.blocked; ++} ++#else /* CONFIG_PREEMPT_RCU */ ++#define rcu_read_critical(p) (false) ++#endif /* CONFIG_PREEMPT_RCU */ ++ ++/* ++ * To determine if it's safe for a task of SCHED_IDLEPRIO to actually run as ++ * an idle task, we ensure none of the following conditions are met. ++ */ ++static bool idleprio_suitable(struct task_struct *p) ++{ ++ return (!(p->sched_contributes_to_load) && !(p->flags & (PF_EXITING)) && ++ !signal_pending(p) && !rcu_read_critical(p) && !freezing(p)); ++} ++ ++/* ++ * To determine if a task of SCHED_ISO can run in pseudo-realtime, we check ++ * that the iso_refractory flag is not set. ++ */ ++static inline bool isoprio_suitable(struct rq *rq) ++{ ++ return !rq->iso_refractory; ++} ++ ++static inline void inc_nr_running(struct rq *rq) ++{ ++ rq->nr_running++; ++ if (trace_sched_update_nr_running_tp_enabled()) { ++ call_trace_sched_update_nr_running(rq, 1); ++ } ++} ++ ++static inline void dec_nr_running(struct rq *rq) ++{ ++ rq->nr_running--; ++ if (trace_sched_update_nr_running_tp_enabled()) { ++ call_trace_sched_update_nr_running(rq, -1); ++ } ++} ++ ++/* ++ * A running task is off the skiplist, so its share of rt_nr_running is not ++ * maintained by the enqueue/dequeue pair; __schedule() hands that slot over on ++ * every switch instead. When a *running* task's priority crosses the rt ++ * boundary neither happens, so fix the count up here. ++ */ ++static inline void rt_running_reprio(struct rq *rq, int oldprio, int newprio) ++{ ++ if (!rt_prio(newprio) == !rt_prio(oldprio)) ++ return; ++ ++ if (rt_prio(newprio)) ++ rq->rt_nr_running++; ++ else ++ rq->rt_nr_running--; ++} ++ ++/* ++ * Adding to the runqueue. Enter with rq locked. ++ */ ++static void enqueue_task(struct rq *rq, struct task_struct *p, int flags) ++{ ++ unsigned int randseed, cflags = 0; ++ u64 sl_id, penalty; ++ ++ if (!rt_task(p)) { ++ /* Check it hasn't gotten rt from PI */ ++ if ((idleprio_task(p) && idleprio_suitable(p)) || ++ (iso_task(p) && isoprio_suitable(rq))) ++ p->prio = p->normal_prio; ++ else ++ p->prio = NORMAL_PRIO; ++ } else ++ rq->rt_nr_running++; ++ /* ++ * The sl_id key passed to the skiplist generates a sorted list. ++ * Realtime and sched iso tasks run FIFO so they only need be sorted ++ * according to priority. The skiplist will put tasks of the same ++ * key inserted later in FIFO order. Tasks of sched normal, batch ++ * and idleprio are sorted according to their deadlines. Idleprio ++ * tasks are offset by an impossibly large deadline value ensuring ++ * they get sorted into last positions, but still according to their ++ * own deadlines. This creates a "landscape" of skiplists running ++ * from priority 0 realtime in first place to the lowest priority ++ * idleprio tasks last. Skiplist insertion is an O(log n) process. ++ */ ++ /* ++ * Charge for device time and kernel work consumed since this task was ++ * last queued. Consumed unconditionally so debt cannot accumulate ++ * while a task is realtime, but only applied below, since realtime and ++ * iso tasks sort by priority rather than by deadline and so are never ++ * demoted. ++ */ ++ penalty = consume_task_penalty(p); ++ ++ if (p->prio <= ISO_PRIO) { ++ sl_id = p->prio; ++ } else { ++ p->deadline += penalty; ++ sl_id = p->deadline; ++ if (idleprio_task(p)) { ++ if (p->prio == IDLE_PRIO) ++ sl_id |= 0xF000000000000000; ++ else ++ sl_id += longest_deadline_diff(); ++ } ++ } ++ /* ++ * Some architectures don't have better than microsecond resolution ++ * so mask out ~microseconds as the random seed for skiplist insertion. ++ */ ++ update_clocks(rq); ++ if (!(flags & ENQUEUE_RESTORE)) { ++ sched_info_enqueue(rq, p); ++ /* Full flags so ENQUEUE_MIGRATED is visible to psi_enqueue(). */ ++ psi_enqueue(p, flags); ++ } ++ ++ randseed = (rq->niffies >> 10) & 0xFFFFFFFF; ++ skiplist_insert(rq->sl, &p->node, sl_id, randseed); ++ update_best_key(rq); ++ if (p->in_iowait) ++ cflags |= SCHED_CPUFREQ_IOWAIT; ++ inc_nr_running(rq); ++ update_load_avg(rq, cflags); ++} ++ ++/* ++ * Returns the relative length of deadline all compared to the shortest ++ * deadline which is that of nice -20. ++ */ ++static inline int task_prio_ratio(struct task_struct *p) ++{ ++ return prio_ratios[TASK_USER_PRIO(p)]; ++} ++ ++/* ++ * task_timeslice - all tasks of all priorities get the exact same timeslice ++ * length. CPU distribution is handled by giving different deadlines to ++ * tasks of different priorities. Use 128 as the base value for fast shifts. ++ */ ++static inline int task_timeslice(struct task_struct *p) ++{ ++ return (rr_interval * task_prio_ratio(p) / 128); ++} ++ ++#ifdef CONFIG_SMP ++/* Entered with rq locked */ ++static inline void resched_if_idle(struct rq *rq) ++{ ++ if (rq_idle(rq)) ++ resched_task(rq->curr); ++} ++ ++static inline bool rq_local(struct rq *rq) ++{ ++ return (rq->cpu == smp_processor_id()); ++} ++#ifdef CONFIG_SMT_NICE ++static const cpumask_t *thread_cpumask(int cpu); ++ ++/* Find the best real time priority running on any SMT siblings of cpu and if ++ * none are running, the static priority of the best deadline task running. ++ * The lookups to the other runqueues is done lockless as the occasional wrong ++ * value would be harmless. */ ++static int best_smt_bias(struct rq *this_rq) ++{ ++ int other_cpu, best_bias = 0; ++ ++ for_each_cpu(other_cpu, &this_rq->thread_mask) { ++ struct rq *rq = cpu_rq(other_cpu); ++ ++ if (rq_idle(rq)) ++ continue; ++ if (unlikely(!rq->online)) ++ continue; ++ if (!rq->rq_mm) ++ continue; ++ if (likely(rq->rq_smt_bias > best_bias)) ++ best_bias = rq->rq_smt_bias; ++ } ++ return best_bias; ++} ++ ++static int task_prio_bias(struct task_struct *p) ++{ ++ if (rt_task(p)) ++ return 1 << 30; ++ else if (task_running_iso(p)) ++ return 1 << 29; ++ else if (task_running_idle(p)) ++ return 0; ++ return MAX_PRIO - p->static_prio; ++} ++ ++static DEFINE_STATIC_KEY_FALSE(smt_nice_enabled); ++ ++/* We've already decided p can run on CPU, now test if it shouldn't for SMT ++ * nice reasons. */ ++static bool smt_should_schedule(struct task_struct *p, struct rq *this_rq) ++{ ++ int best_bias, task_bias; ++ ++ if (!idleprio_suitable(p)) ++ return true; ++ best_bias = best_smt_bias(this_rq); ++ /* The smt siblings are all idle or running IDLEPRIO */ ++ if (best_bias < 1) ++ return true; ++ task_bias = task_prio_bias(p); ++ if (task_bias < 1) ++ return false; ++ if (task_bias >= best_bias) ++ return true; ++ /* Dither 25% cpu of normal tasks regardless of nice difference */ ++ if (best_bias % 4 == 1) ++ return true; ++ /* Sorry, you lose */ ++ return false; ++} ++ ++static inline bool smt_schedule(struct task_struct *p, struct rq *this_rq) ++{ ++ if (!static_branch_unlikely(&smt_nice_enabled)) ++ return true; ++ /* Kernel threads and RT tasks always run */ ++ if (unlikely(!p->mm) || rt_task(p)) ++ return true; ++ return smt_should_schedule(p, this_rq); ++} ++#else /* CONFIG_SMT_NICE */ ++#define smt_schedule(p, this_rq) (true) ++#endif /* CONFIG_SMT_NICE */ ++ ++static inline void atomic_set_cpu(int cpu, cpumask_t *cpumask) ++{ ++ set_bit(cpu, (volatile unsigned long *)cpumask); ++} ++ ++/* ++ * The cpu_idle_map stores a bitmap of all the CPUs currently idle to ++ * allow easy lookup of whether any suitable idle CPUs are available. ++ * It's cheaper to maintain a binary yes/no if there are any idle CPUs on the ++ * idle_cpus variable than to do a full bitmask check when we are busy. The ++ * bits are set atomically but read locklessly as occasional false positive / ++ * negative is harmless. ++ */ ++static inline void set_cpuidle_map(int cpu) ++{ ++ /* ++ * Only an active CPU may advertise itself as a wakeup target. ++ * select_best_cpu() returns resched_best_idle()'s pick directly, ++ * and that pick comes straight out of this map without consulting ++ * is_cpu_allowed(), so a CPU on its way down (online but no longer ++ * active) would keep re-arming itself here every time it idled and ++ * collect fresh wakeups that sched_cpu_wait_empty() has to chase. ++ * set_rq_offline() clears the map; this keeps it clear. ++ */ ++ if (likely(cpu_active(cpu))) ++ atomic_set_cpu(cpu, &cpu_idle_map); ++} ++ ++static inline void atomic_clear_cpu(int cpu, cpumask_t *cpumask) ++{ ++ clear_bit(cpu, (volatile unsigned long *)cpumask); ++} ++ ++static inline void clear_cpuidle_map(int cpu) ++{ ++ atomic_clear_cpu(cpu, &cpu_idle_map); ++} ++ ++static bool suitable_idle_cpus(struct task_struct *p) ++{ ++ return (cpumask_intersects(p->cpus_ptr, &cpu_idle_map)); ++} ++ ++/* ++ * Resched current on rq. We don't know if rq is local to this CPU nor if it ++ * is locked so we do not use an intermediate variable for the task to avoid ++ * having it dereferenced. ++ */ ++static void resched_curr(struct rq *rq) ++{ ++ int cpu; ++ ++ if (test_tsk_need_resched(rq->curr)) ++ return; ++ ++ rq->preempt = rq->curr; ++ cpu = rq->cpu; ++ ++ /* We're doing this without holding the rq lock if it's not task_rq */ ++ ++ if (cpu == smp_processor_id()) { ++ set_tsk_need_resched(rq->curr); ++ set_preempt_need_resched(); ++ return; ++ } ++ ++ if (set_nr_and_not_polling(rq->curr)) ++ smp_sched_reschedule(cpu); ++ else ++ trace_sched_wake_idle_without_ipi(cpu); ++} ++ ++#define CPUIDLE_NO_SIBLING (1) ++#define CPUIDLE_DIFF_THREAD (2) ++#define CPUIDLE_DIFF_CORE_LLC (4) ++#define CPUIDLE_DIFF_CORE (8) ++#define CPUIDLE_CACHE_BUSY (16) ++#define CPUIDLE_DIFF_CPU (32) ++#define CPUIDLE_THREAD_BUSY (64) ++#define CPUIDLE_DIFF_NODE (128) ++ ++/* ++ * The best idle CPU is chosen according to the CPUIDLE ranking above where the ++ * lowest value would give the most suitable CPU to schedule p onto next. The ++ * order works out to be the following: ++ * ++ * Same thread, idle or busy cache, idle or busy threads ++ * Other core, same cache, idle or busy cache, idle threads. ++ * Same node, other CPU, idle cache, idle threads. ++ * Same node, other CPU, busy cache, idle threads. ++ * Other core, same cache, busy threads. ++ * Same node, other CPU, busy threads. ++ * Other node, other CPU, idle cache, idle threads. ++ * Other node, other CPU, busy cache, idle threads. ++ * Other node, other CPU, busy threads. ++ * ++ * CPUIDLE_NO_SIBLING is the least significant rank so it only separates cores ++ * that are otherwise equal, preferring a core with an idle SMT sibling over one ++ * with no siblings at all. On hybrid CPUs that indirectly prefers P cores over ++ * E cores, since only the former have siblings. A core whose sibling is busy ++ * still ranks below a core with no siblings as it only offers half a core. ++ * This does not treat CPUs with a single offline sibling as idle for ++ * simplicity. ++ */ ++static int best_mask_cpu(int best_cpu, struct rq *rq, cpumask_t *tmpmask) ++{ ++ int best_ranking = CPUIDLE_DIFF_NODE | CPUIDLE_THREAD_BUSY | ++ CPUIDLE_DIFF_CPU | CPUIDLE_CACHE_BUSY | CPUIDLE_DIFF_CORE | ++ CPUIDLE_DIFF_CORE_LLC | CPUIDLE_DIFF_THREAD | CPUIDLE_NO_SIBLING; ++ unsigned long best_idle_jiffy = 0; ++ int cpu_tmp; ++ ++ if (cpumask_test_cpu(best_cpu, tmpmask)) ++ goto out; ++ ++ for_each_cpu(cpu_tmp, tmpmask) { ++ int ranking, locality; ++ struct rq *tmp_rq; ++ ++ ranking = 0; ++ tmp_rq = cpu_rq(cpu_tmp); ++ ++ locality = rq->cpu_locality[cpu_tmp]; ++#ifdef CONFIG_NUMA ++ if (locality > LOCALITY_SMP) ++ ranking |= CPUIDLE_DIFF_NODE; ++ else ++#endif ++ if (locality > LOCALITY_MC) ++ ranking |= CPUIDLE_DIFF_CPU; ++#ifdef CONFIG_SCHED_MC ++ else if (locality == LOCALITY_MC_LLC) ++ ranking |= CPUIDLE_DIFF_CORE_LLC; ++ else if (locality == LOCALITY_MC) ++ ranking |= CPUIDLE_DIFF_CORE; ++ if (!(tmp_rq->cache_idle(tmp_rq))) ++ ranking |= CPUIDLE_CACHE_BUSY; ++#endif ++#ifdef CONFIG_SCHED_SMT ++ if (locality == LOCALITY_SMT) ++ ranking |= CPUIDLE_DIFF_THREAD; ++ if (!tmp_rq->has_smt_sibling) ++ ranking |= CPUIDLE_NO_SIBLING; ++ else if (!cpumask_subset(&tmp_rq->thread_mask, &cpu_idle_map)) ++ ranking |= CPUIDLE_THREAD_BUSY; ++#endif ++ /* Also look for the most recently idled CPU as it will likely ++ * be still at a higher CPU frequency */ ++ if (ranking < best_ranking || ++ (ranking == best_ranking && tmp_rq->idle_jiffy > best_idle_jiffy)) { ++ best_cpu = cpu_tmp; ++ best_ranking = ranking; ++ best_idle_jiffy = tmp_rq->idle_jiffy; ++ } ++ } ++out: ++ return best_cpu; ++} ++ ++bool cpus_share_cache(int this_cpu, int that_cpu) ++{ ++ struct rq *this_rq = cpu_rq(this_cpu); ++ ++ return (this_rq->cpu_locality[that_cpu] < LOCALITY_SMP); ++} ++ ++/* As per resched_curr but only will resched idle task */ ++static inline void resched_idle(struct rq *rq) ++{ ++ if (test_tsk_need_resched(rq->idle)) ++ return; ++ ++ rq->preempt = rq->idle; ++ ++ if (rq_local(rq)) { ++ set_tsk_need_resched(rq->idle); ++ set_preempt_need_resched(); ++ return; ++ } ++ ++ /* ++ * Atomically set NEED_RESCHED and only IPI if the idle task is not ++ * polling — same protocol as resched_curr / wake_up_idle_cpu. ++ */ ++ if (set_nr_and_not_polling(rq->idle)) ++ smp_sched_reschedule(rq->cpu); ++ else ++ trace_sched_wake_idle_without_ipi(rq->cpu); ++} ++ ++DEFINE_PER_CPU(cpumask_t, idlemask); ++ ++static struct rq *resched_best_idle(struct task_struct *p, int cpu) ++{ ++ cpumask_t *tmpmask = &(per_cpu(idlemask, cpu)); ++ struct rq *rq; ++ int best_cpu; ++ ++ cpumask_and(tmpmask, p->cpus_ptr, &cpu_idle_map); ++ best_cpu = best_mask_cpu(cpu, task_rq(p), tmpmask); ++ rq = cpu_rq(best_cpu); ++ if (!smt_schedule(p, rq)) ++ return NULL; ++ rq->preempt = p; ++ resched_idle(rq); ++ return rq; ++} ++ ++static inline void resched_suitable_idle(struct task_struct *p) ++{ ++ if (suitable_idle_cpus(p)) ++ resched_best_idle(p, task_cpu(p)); ++} ++ ++static inline struct rq *rq_order(struct rq *rq, int cpu) ++{ ++ return rq->rq_order[cpu]; ++} ++#else /* CONFIG_SMP */ ++static inline void set_cpuidle_map(int cpu) ++{ ++} ++ ++static inline void clear_cpuidle_map(int cpu) ++{ ++} ++ ++static inline bool suitable_idle_cpus(struct task_struct *p) ++{ ++ return uprq->curr == uprq->idle; ++} ++ ++static inline void resched_suitable_idle(struct task_struct *p) ++{ ++} ++ ++static inline void resched_curr(struct rq *rq) ++{ ++ resched_task(rq->curr); ++} ++ ++static inline void resched_if_idle(struct rq *rq) ++{ ++} ++ ++static inline bool rq_local(struct rq *rq) ++{ ++ return true; ++} ++ ++static inline struct rq *rq_order(struct rq *rq, int cpu) ++{ ++ return rq; ++} ++ ++static inline bool smt_schedule(struct task_struct *p, struct rq *rq) ++{ ++ return true; ++} ++ ++/* One CPU shares its cache with itself. */ ++bool cpus_share_cache(int this_cpu, int that_cpu) ++{ ++ return true; ++} ++#endif /* CONFIG_SMP */ ++ ++static inline int normal_prio(struct task_struct *p) ++{ ++ if (has_rt_policy(p)) ++ return MAX_RT_PRIO - 1 - p->rt_priority; ++ if (idleprio_task(p)) ++ return IDLE_PRIO; ++ if (iso_task(p)) ++ return ISO_PRIO; ++ return NORMAL_PRIO; ++} ++ ++/* ++ * Calculate the current priority, i.e. the priority ++ * taken into account by the scheduler. This value might ++ * be boosted by RT tasks as it will be RT if the task got ++ * RT-boosted. If not then it returns p->normal_prio. ++ */ ++static int effective_prio(struct task_struct *p) ++{ ++ p->normal_prio = normal_prio(p); ++ /* ++ * If we are RT tasks or we were boosted to RT priority, ++ * keep the priority unchanged. Otherwise, update priority ++ * to the normal priority: ++ */ ++ if (!rt_prio(p->prio)) ++ return p->normal_prio; ++ return p->prio; ++} ++ ++/* ++ * activate_task - move a task to the runqueue. Enter with rq locked. ++ */ ++static void activate_task(struct rq *rq, struct task_struct *p, int flags) ++{ ++ resched_if_idle(rq); ++ ++ /* Sleep profiling (SLEEP_PROFILING) was removed upstream. */ ++ ++ p->prio = effective_prio(p); ++ enqueue_task(rq, p, flags); ++ p->on_rq = TASK_ON_RQ_QUEUED; ++} ++ ++/* ++ * deactivate_task - If it's running, it's not on the runqueue and we can just ++ * decrement the nr_running. Enter with rq locked. ++ */ ++static inline void deactivate_task(struct task_struct *p, struct rq *rq) ++{ ++ p->on_rq = 0; ++ sched_info_dequeue(rq, p); ++ /* deactivate_task is always DEQUEUE_SLEEP in muqss */ ++ psi_dequeue(p, DEQUEUE_SLEEP); ++} ++ ++/* ++ * PSI counts the state a task is in per CPU, indexed by task_cpu(), so ++ * whenever task_cpu() changes for a task that still carries state, that state ++ * has to be moved with it. That is TSK_RUNNING for a task being taken to ++ * another runqueue, and TSK_IOWAIT for one that is still asleep. Whoever ++ * clears them next does so against task_cpu(p), and if that is no longer the ++ * CPU they were counted on the per-CPU counter underflows. ++ * ++ * TSK_ONCPU is deliberately not moved. It belongs to psi_sched_switch(), and ++ * a task whose CPU is being changed here is by definition not on one. ++ */ ++#ifdef CONFIG_PSI ++static inline unsigned int psi_migrate_begin(struct task_struct *p) ++{ ++ unsigned int migrate = p->psi_flags & ~TSK_ONCPU; ++ ++ if (migrate) ++ psi_task_change(p, migrate, 0); ++ return migrate; ++} ++ ++static inline void psi_migrate_end(struct task_struct *p, unsigned int migrate) ++{ ++ if (migrate) ++ psi_task_change(p, 0, migrate); ++} ++#else /* !CONFIG_PSI */ ++static inline unsigned int psi_migrate_begin(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static inline void psi_migrate_end(struct task_struct *p, unsigned int migrate) ++{ ++} ++#endif /* CONFIG_PSI */ ++ ++#ifdef CONFIG_SMP ++void set_task_cpu(struct task_struct *p, unsigned int new_cpu) ++{ ++ unsigned int migrate; ++ struct rq *rq; ++ ++ if (task_cpu(p) == new_cpu) ++ return; ++ ++ /* Do NOT call set_task_cpu on a currently queued task as we will not ++ * be reliably holding the rq lock after changing CPU. */ ++ BUG_ON(task_queued(p)); ++ rq = task_rq(p); ++ ++#ifdef CONFIG_LOCKDEP ++ /* ++ * The caller should hold either p->pi_lock or rq->lock, when changing ++ * a task's CPU. ->pi_lock for waking tasks, rq->lock for runnable tasks. ++ * ++ * Furthermore, all task_rq users should acquire both locks, see ++ * task_rq_lock(). ++ */ ++ WARN_ON_ONCE(debug_locks && !(lockdep_is_held(&p->pi_lock) || ++ lockdep_is_held(rq->lock))); ++#endif ++ ++ trace_sched_migrate_task(p, new_cpu); ++ rseq_sched_set_ids_changed(p); ++ perf_event_task_migrate(p); ++ ++ /* ++ * After ->cpu is set up to a new value, task_rq_lock(p, ...) can be ++ * successfully executed on another CPU. We must ensure that updates of ++ * per-task data have been completed by this moment. ++ */ ++ smp_wmb(); ++ ++ p->wake_cpu = new_cpu; ++ ++ if (task_running(rq, p)) { ++ /* ++ * We should only be calling this on a running task if we're ++ * holding rq lock. ++ */ ++ lockdep_assert_held(rq->lock); ++ ++ /* ++ * We can't change the task_thread_info CPU on a running task ++ * as p will still be protected by the rq lock of the CPU it ++ * is still running on so we only set the wake_cpu for it to be ++ * lazily updated once off the CPU. ++ */ ++ return; ++ } ++ ++ migrate = psi_migrate_begin(p); ++ WRITE_ONCE(task_thread_info(p)->cpu, new_cpu); ++ psi_migrate_end(p, migrate); ++ /* We're no longer protecting p after this point since we're holding ++ * the wrong runqueue lock. */ ++} ++#endif /* CONFIG_SMP */ ++ ++/* ++ * Move a task off the runqueue and take it to a cpu for it will ++ * become the running task. ++ */ ++static inline void take_task(struct rq *rq, int cpu, struct task_struct *p) ++{ ++ struct rq *p_rq = task_rq(p); ++ ++ dequeue_task(p_rq, p, DEQUEUE_SAVE); ++ if (p_rq != rq) { ++ sched_info_dequeue(p_rq, p); ++ sched_info_enqueue(rq, p); ++ } ++ /* ++ * DEQUEUE_SAVE left TSK_RUNNING in place, and set_task_cpu() moves it ++ * to @cpu along with the task. ++ */ ++ set_task_cpu(p, cpu); ++} ++ ++/* ++ * Returns a descheduling task to the runqueue unless it is being ++ * deactivated. ++ */ ++static inline void return_task(struct task_struct *p, struct rq *rq, ++ int cpu, bool deactivate) ++{ ++ if (deactivate) ++ deactivate_task(p, rq); ++ else { ++#ifdef CONFIG_SMP ++ /* ++ * set_task_cpu was called on the running task that doesn't ++ * want to deactivate so it has to be enqueued to a different ++ * CPU and we need its lock. Tag it to be moved with as the ++ * lock is dropped in finish_lock_switch. ++ */ ++ if (unlikely(p->wake_cpu != cpu)) ++ WRITE_ONCE(p->on_rq, TASK_ON_RQ_MIGRATING); ++ else ++#endif ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ } ++} ++ ++/* Enter with rq lock held. We know p is on the local cpu */ ++static inline void __set_tsk_resched(struct task_struct *p) ++{ ++ set_tsk_need_resched(p); ++ set_preempt_need_resched(); ++} ++ ++/** ++ * task_curr - is this task currently executing on a CPU? ++ * @p: the task in question. ++ * ++ * Return: 1 if the task is currently executing. 0 otherwise. ++ */ ++inline int task_curr(const struct task_struct *p) ++{ ++ return cpu_curr(task_cpu(p)) == p; ++} ++ ++static __always_inline ++int __task_state_match(struct task_struct *p, unsigned int state) ++{ ++ if (READ_ONCE(p->__state) & state) ++ return 1; ++ ++ if (READ_ONCE(p->saved_state) & state) ++ return -1; ++ ++ return 0; ++} ++ ++static __always_inline ++int task_state_match(struct task_struct *p, unsigned int state) ++{ ++ int match; ++ ++ /* ++ * Serialize against current_save_and_set_rtlock_wait_state(), ++ * current_restore_rtlock_saved_state(), and __refrigerator(). ++ */ ++ raw_spin_lock_irq(&p->pi_lock); ++ match = __task_state_match(p, state); ++ raw_spin_unlock_irq(&p->pi_lock); ++ ++ return match; ++} ++ ++/* ++ * wait_task_inactive - wait for a thread to unschedule. ++ * ++ * Wait for the thread to block in any of the states set in @match_state. ++ * If it changes, i.e. @p might have woken up, then return zero. When we ++ * succeed in waiting for @p to be off its CPU, we return a positive number ++ * (its total switch count). If a second call a short while later returns ++ * the same number, the caller can be sure that @p has remained unscheduled ++ * the whole time. ++ * ++ * The caller must ensure that the task *will* unschedule sometime soon, ++ * else this function might spin for a *long* time. This function can't ++ * be called with interrupts off, or it may introduce deadlock with ++ * smp_call_function() if an IPI is sent by the same process we are ++ * waiting to become inactive. ++ */ ++unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state) ++{ ++ int running, queued, match; ++ struct rq_flags rf; ++ unsigned long ncsw; ++ struct rq *rq; ++ ++ for (;;) { ++ rq = task_rq(p); ++ ++ /* ++ * If the task is actively running on another CPU ++ * still, just relax and busy-wait without holding ++ * any locks. ++ * ++ * NOTE! Since we don't hold any locks, it's not ++ * even sure that "rq" stays as the right runqueue! ++ * But we don't care, since this will return false ++ * if the runqueue has changed and p is actually now ++ * running somewhere else! ++ */ ++ while (task_running(rq, p)) { ++ if (!task_state_match(p, match_state)) ++ return 0; ++ cpu_relax(); ++ } ++ ++ /* ++ * Ok, time to look more closely! We need the rq ++ * lock now, to be *sure*. If we're wrong, we'll ++ * just go back and repeat. ++ */ ++ rq = task_rq_lock(p, &rf); ++ trace_sched_wait_task(p); ++ running = task_running(rq, p); ++ queued = task_on_rq_queued(p); ++ ncsw = 0; ++ if ((match = __task_state_match(p, match_state))) { ++ /* ++ * When matching on p->saved_state, consider this task ++ * still queued so it will wait. ++ */ ++ if (match < 0) ++ queued = 1; ++ ncsw = p->nvcsw | LONG_MIN; /* sets MSB */ ++ } ++ task_rq_unlock(rq, p, &rf); ++ ++ /* ++ * If it changed from the expected state, bail out now. ++ */ ++ if (unlikely(!ncsw)) ++ break; ++ ++ /* ++ * Was it really running after all now that we ++ * checked with the proper locks actually held? ++ * ++ * Oops. Go back and try again.. ++ */ ++ if (unlikely(running)) { ++ cpu_relax(); ++ continue; ++ } ++ ++ /* ++ * It's not enough that it's not actively running, ++ * it must be off the runqueue _entirely_, and not ++ * preempted! ++ * ++ * So if it was still runnable (but just not actively ++ * running right now), it's preempted, and we should ++ * yield - it could be a while. ++ */ ++ if (unlikely(queued)) { ++ ktime_t to = NSEC_PER_SEC / HZ; ++ ++ set_current_state(TASK_UNINTERRUPTIBLE); ++ schedule_hrtimeout(&to, HRTIMER_MODE_REL); ++ continue; ++ } ++ ++ /* ++ * Ahh, all good. It wasn't running, and it wasn't ++ * runnable, which means that it will never become ++ * running in the future either. We're all done! ++ */ ++ break; ++ } ++ ++ return ncsw; ++} ++ ++/*** ++ * kick_process - kick a running thread to enter/exit the kernel ++ * @p: the to-be-kicked thread ++ * ++ * Cause a process which is running on another CPU to enter ++ * kernel-mode, without any delay. (to get signals handled.) ++ * ++ * NOTE: this function doesn't have to take the runqueue lock, ++ * because all it wants to ensure is that the remote task enters ++ * the kernel. If the IPI races and the task has been migrated ++ * to another CPU then no harm is done and the purpose has been ++ * achieved as well. ++ */ ++#ifdef CONFIG_SMP ++void kick_process(struct task_struct *p) ++{ ++ int cpu; ++ ++ preempt_disable(); ++ cpu = task_cpu(p); ++ if ((cpu != smp_processor_id()) && task_curr(p)) ++ smp_sched_reschedule(cpu); ++ preempt_enable(); ++} ++#else /* !CONFIG_SMP */ ++/* @p can only be running on the CPU we are already running on. */ ++void kick_process(struct task_struct *p) ++{ ++} ++#endif /* CONFIG_SMP */ ++EXPORT_SYMBOL_GPL(kick_process); ++ ++/* ++ * RT tasks preempt purely on priority. SCHED_NORMAL tasks preempt on the ++ * basis of earlier deadlines. SCHED_IDLEPRIO don't preempt anything else or ++ * between themselves, they cooperatively multitask. An idle rq scores as ++ * prio PRIO_LIMIT so it is always preempted. ++ */ ++static inline bool ++can_preempt(struct task_struct *p, int prio, u64 deadline) ++{ ++ /* Better static priority RT task or better policy preemption */ ++ if (p->prio < prio) ++ return true; ++ if (p->prio > prio) ++ return false; ++ if (p->policy == SCHED_BATCH) ++ return false; ++ /* SCHED_NORMAL and ISO will preempt based on deadline */ ++ if (!deadline_before(p->deadline, deadline)) ++ return false; ++ return true; ++} ++ ++#ifdef CONFIG_SMP ++ ++/* ++ * Per-CPU kthreads are allowed to run on !active && online CPUs, see ++ * __set_cpus_allowed_ptr(). ++ */ ++static inline bool is_cpu_allowed(struct task_struct *p, int cpu) ++{ ++ if (!cpumask_test_cpu(cpu, p->cpus_ptr)) ++ return false; ++ ++ /* migrate_disable() must be allowed to finish on an online CPU. */ ++ if (is_migration_disabled(p)) ++ return cpu_online(cpu); ++ ++ if (!(p->flags & PF_KTHREAD)) ++ return cpu_active(cpu); ++ ++ /* KTHREAD_IS_PER_CPU is always allowed. */ ++ if (kthread_is_per_cpu(p)) ++ return cpu_online(cpu); ++ ++ /* Regular kernel threads don't get to stay during offline. */ ++ if (cpu_dying(cpu)) ++ return false; ++ ++ /* But are allowed during online. */ ++ return cpu_online(cpu); ++} ++ ++/* ++ * Check to see if p can run on cpu, and if not, whether there are any online ++ * CPUs it can run on instead. This only happens with the hotplug threads that ++ * bring up the CPUs. ++ */ ++static inline bool sched_other_cpu(struct task_struct *p, int cpu) ++{ ++ if (unlikely(is_migration_disabled(p) && task_cpu(p) != cpu)) ++ return true; ++ /* ++ * Defer to the single placement predicate: userspace is refused a ++ * deactivated CPU, a regular kthread is refused a dying one, while ++ * per-CPU kthreads and a migrate_disable() pin on this CPU still ++ * may run there. ++ */ ++ if (likely(cpumask_test_cpu(cpu, p->cpus_ptr))) ++ return !is_cpu_allowed(p, cpu); ++ if (p->nr_cpus_allowed == 1) { ++ cpumask_t valid_mask; ++ ++ /* ++ * Nowhere left to send it, so it may as well run here. The mask ++ * has to be the same one valid_task_cpu() uses to pick the ++ * destination, or the two disagree and the task is placed on a ++ * CPU that then refuses to run it. ++ * ++ * That is what a regular kthread affine to a single dying CPU ++ * hits: the CPU is still online, so intersecting with ++ * cpu_online_mask alone leaves it non-empty and this escape ++ * hatch never fires, while is_cpu_allowed() has already refused ++ * the dying CPU itself. It ends up runnable with no CPU willing ++ * to pick it. bind_zero() would free it by overriding the ++ * affinity, but that only runs from sched_cpu_wait_empty(), ++ * several hotplug states later - and anything the CPU going ++ * down waits for in between, such as blk_mq_hctx_notify_offline() ++ * waiting on a threaded completion interrupt, deadlocks against ++ * it. ++ */ ++ cpumask_and(&valid_mask, p->cpus_ptr, cpu_online_mask); ++ if (!kthread_is_per_cpu(p)) ++ cpumask_andnot(&valid_mask, &valid_mask, cpu_dying_mask); ++ if (unlikely(cpumask_empty(&valid_mask))) ++ return false; ++ } ++ return true; ++} ++ ++static inline bool needs_other_cpu(struct task_struct *p, int cpu) ++{ ++ if (unlikely(is_migration_disabled(p))) ++ return task_cpu(p) != cpu; ++ return !is_cpu_allowed(p, cpu); ++} ++ ++/* ++ * Pin p->cpus_ptr to this CPU so EDT / select_best_cpu cannot steal a ++ * preempted migrate_disable() task. Called under rq->lock on prev. ++ */ ++static void migrate_disable_switch(struct rq *rq, struct task_struct *p) ++{ ++ if (likely(!p->migration_disabled)) ++ return; ++ if (p->cpus_ptr != &p->cpus_mask) ++ return; ++ p->cpus_ptr = cpumask_of(cpu_of(rq)); ++} ++ ++#define cpu_online_map (*(cpumask_t *)cpu_online_mask) ++ ++static void try_preempt(struct task_struct *p, struct rq *this_rq) ++{ ++ int i, this_entries = rq_load(this_rq); ++ cpumask_t tmp; ++ ++ /* ++ * An idle CPU first: waking one costs less than bouncing a task ++ * that is already running, and it is what keeps work spread out. ++ * Only if none is available do we consider preemption, and the ++ * loop below starts at cpu_order[0] - this_rq - so @p still gets ++ * to preempt its own dest before any remote runqueue. ++ */ ++ if (suitable_idle_cpus(p) && resched_best_idle(p, task_cpu(p))) ++ return; ++ ++ /* IDLEPRIO tasks never preempt anything but idle */ ++ if (p->policy == SCHED_IDLEPRIO) ++ return; ++ ++ cpumask_and(&tmp, &cpu_online_map, p->cpus_ptr); ++ ++ for (i = 0; i < num_online_cpus(); i++) { ++ struct rq *rq = this_rq->cpu_order[i]; ++ ++ if (!cpumask_test_cpu(rq->cpu, &tmp)) ++ continue; ++ ++ if (!sched_interactive && rq != this_rq && rq_load(rq) <= this_entries) ++ continue; ++ if (smt_schedule(p, rq) && can_preempt(p, rq->rq_prio, rq->rq_deadline)) { ++ /* We set rq->preempting lockless, it's a hint only */ ++ rq->preempting = p; ++ resched_curr(rq); ++ return; ++ } ++ } ++} ++ ++static int __set_cpus_allowed_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ u32 flags); ++#else /* CONFIG_SMP */ ++static inline bool needs_other_cpu(struct task_struct *p, int cpu) ++{ ++ return false; ++} ++ ++static void try_preempt(struct task_struct *p, struct rq *this_rq) ++{ ++ if (p->policy == SCHED_IDLEPRIO) ++ return; ++ if (can_preempt(p, uprq->rq_prio, uprq->rq_deadline)) ++ resched_curr(uprq); ++} ++ ++static inline int __set_cpus_allowed_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ u32 __always_unused flags) ++{ ++ return set_cpus_allowed_ptr(p, new_mask); ++} ++#endif /* CONFIG_SMP */ ++ ++static void ++ttwu_stat(struct task_struct *p, int cpu, int wake_flags) ++{ ++ struct rq *rq; ++ ++ if (!schedstat_enabled()) ++ return; ++ ++ rq = this_rq(); ++ ++#ifdef CONFIG_SMP ++ if (cpu == rq->cpu) { ++ __schedstat_inc(rq->ttwu_local); ++ } else { ++ struct sched_domain *sd; ++ ++ rcu_read_lock(); ++ for_each_domain(rq->cpu, sd) { ++ if (cpumask_test_cpu(cpu, sched_domain_span(sd))) { ++ __schedstat_inc(sd->ttwu_wake_remote); ++ break; ++ } ++ } ++ rcu_read_unlock(); ++ } ++ ++#endif /* CONFIG_SMP */ ++ ++ __schedstat_inc(rq->ttwu_count); ++} ++ ++/* ++ * Mark the task runnable and perform wakeup-preemption. ++ */ ++static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags) ++{ ++ /* ++ * WF_SYNC means the waker will leave the CPU shortly. Prefer an ++ * idle CPU and do not preempt the waker itself — it will pick the ++ * wakee up on the next schedule(). A remote dest still needs ++ * kicking, or the wakee sits until dest's next tick (or forever ++ * on a nohz_full hog). ++ */ ++ if ((wake_flags & WF_SYNC) && rq == this_rq()) ++ resched_suitable_idle(p); ++ else ++ try_preempt(p, rq); ++ WRITE_ONCE(p->__state, TASK_RUNNING); ++ trace_sched_wakeup(p); ++} ++ ++static void ++ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags) ++{ ++ int en_flags = ENQUEUE_WAKEUP; ++ ++ lockdep_assert_held(rq->lock); ++ ++ if (p->sched_contributes_to_load) ++ rq->nr_uninterruptible--; ++ ++#ifdef CONFIG_SMP ++ if (wake_flags & WF_MIGRATED) ++ en_flags |= ENQUEUE_MIGRATED; ++ else ++#endif ++ if (p->in_iowait) { ++ delayacct_blkio_end(p); ++ atomic_dec(&task_rq(p)->nr_iowait); ++ } ++ ++ activate_task(rq, p, en_flags); ++ ttwu_do_wakeup(rq, p, wake_flags); ++} ++ ++/* ++ * Consider @p being inside a wait loop: ++ * ++ * for (;;) { ++ * set_current_state(TASK_UNINTERRUPTIBLE); ++ * ++ * if (CONDITION) ++ * break; ++ * ++ * schedule(); ++ * } ++ * __set_current_state(TASK_RUNNING); ++ * ++ * between set_current_state() and schedule(). In this case @p is still ++ * runnable, so all that needs doing is change p->__state back to TASK_RUNNING in ++ * an atomic manner. ++ * ++ * By taking task_rq(p)->lock we serialize against schedule(), if @p->on_rq ++ * then schedule() must still happen and p->__state can be changed to ++ * TASK_RUNNING. Otherwise we lost the race, schedule() has happened, and we ++ * need to do a full wakeup with enqueue. ++ * ++ * Returns: %true when the wakeup is done, ++ * %false otherwise. ++ */ ++static int ttwu_runnable(struct task_struct *p, int wake_flags) ++{ ++ struct rq *rq; ++ int ret = 0; ++ ++ rq = __task_rq_lock(p, NULL); ++ if (likely(task_on_rq_queued(p))) { ++ ttwu_do_wakeup(rq, p, wake_flags); ++ ret = 1; ++ } ++ __task_rq_unlock(rq, p, NULL); ++ ++ return ret; ++} ++ ++#ifdef CONFIG_SMP ++void sched_ttwu_pending(void *arg) ++{ ++ struct llist_node *llist = arg; ++ struct rq *rq = this_rq(); ++ struct task_struct *p, *t; ++ struct rq_flags rf; ++ ++ if (!llist) ++ return; ++ ++ /* ++ * rq::ttwu_pending racy indication of out-standing wakeups. ++ * Races such that false-negatives are possible, since they ++ * are shorter lived that false-positives would be. ++ */ ++ WRITE_ONCE(rq->ttwu_pending, 0); ++ ++ rq_lock_irqsave(rq, &rf); ++ ++ llist_for_each_entry_safe(p, t, llist, wake_entry.llist) { ++ if (WARN_ON_ONCE(p->on_cpu)) ++ smp_cond_load_acquire(&p->on_cpu, !VAL); ++ ++ if (WARN_ON_ONCE(task_cpu(p) != cpu_of(rq))) ++ set_task_cpu(p, cpu_of(rq)); ++ ++ ttwu_do_activate(rq, p, p->sched_remote_wakeup ? WF_MIGRATED : 0); ++ } ++ ++ rq_unlock_irqrestore(rq, &rf); ++} ++ ++/* ++ * Called from smp.c: report whether the caller still needs to send the IPI, ++ * having woken a polling idle task in passing. ++ */ ++bool call_function_single_prep_ipi(int cpu) ++{ ++ if (set_nr_if_polling(cpu_rq(cpu)->idle)) { ++ trace_sched_wake_idle_without_ipi(cpu); ++ return false; ++ } ++ ++ return true; ++} ++ ++/* ++ * Queue a task on the target CPUs wake_list and wake the CPU via IPI if ++ * necessary. The wakee CPU on receipt of the IPI will queue the task ++ * via sched_ttwu_wakeup() for activation so the wakee incurs the cost ++ * of the wakeup instead of the waker. ++ */ ++static void __ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ /* ++ * Carry WF_MIGRATED across to the CPU that will do the enqueue. Losing ++ * it there makes ttwu_do_activate() treat an already migrated wakeup ++ * as a local one, decrementing rq->nr_iowait a second time and telling ++ * PSI to clear an iowait that psi_ttwu_dequeue() has already dropped. ++ */ ++ p->sched_remote_wakeup = !!(wake_flags & WF_MIGRATED); ++ ++ WRITE_ONCE(rq->ttwu_pending, 1); ++ __smp_call_single_queue(cpu, &p->wake_entry.llist); ++} ++ ++void wake_up_if_idle(int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ rcu_read_lock(); ++ ++ if (!is_idle_task(rcu_dereference(rq->curr))) ++ goto out; ++ ++ /* ++ * Match mainline: always go through resched_curr so TIF_NEED_RESCHED ++ * is set before any IPI. Sending a reschedule IPI alone does nothing ++ * useful — scheduler_ipi() only folds an already-set need_resched. ++ */ ++ rq_lock_irqsave(rq, &rf); ++ if (is_idle_task(rq->curr)) ++ resched_curr(rq); ++ rq_unlock_irqrestore(rq, &rf); ++ ++out: ++ rcu_read_unlock(); ++} ++ ++static inline bool ttwu_queue_cond(int cpu, int wake_flags) ++{ ++ /* ++ * Do not complicate things with the async wake_list while the CPU is ++ * in hotplug state. ++ */ ++ if (!cpu_active(cpu)) ++ return false; ++ ++ /* ++ * If the CPU does not share cache, then queue the task on the ++ * remote rqs wakelist to avoid accessing remote data. ++ */ ++ if (!cpus_share_cache(smp_processor_id(), cpu)) ++ return true; ++ ++ /* ++ * If the task is descheduling and the only running task on the ++ * CPU then use the wakelist to offload the task activation to ++ * the soon-to-be-idle CPU as the current CPU is likely busy. ++ * nr_running is checked to avoid unnecessary task stacking. ++ */ ++ if ((wake_flags & WF_ON_CPU) && cpu_rq(cpu)->nr_running <= 1) ++ return true; ++ ++ return false; ++} ++ ++static bool ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) ++{ ++ /* CFS would require sched_feat(TTWU_QUEUE) here but that is ++ * fixed enabled */ ++ if (ttwu_queue_cond(cpu, wake_flags)) { ++ if (WARN_ON_ONCE(cpu == smp_processor_id())) ++ return false; ++ ++ sched_clock_cpu(cpu); /* Sync clocks across CPUs */ ++ __ttwu_queue_wakelist(p, cpu, wake_flags); ++ return true; ++ } ++ ++ return false; ++} ++ ++static int valid_task_cpu(struct task_struct *p) ++{ ++ cpumask_t valid_mask; ++ ++ if (unlikely(is_migration_disabled(p))) ++ return task_cpu(p); ++ ++ /* ++ * Kthreads may be bound to a CPU that is not yet online (per-CPU ++ * hotplug threads created during smp_init). Placement for running ++ * must still pick an online CPU — sched_other_cpu() allows them to ++ * be selected despite the affinity mismatch until their CPU is up. ++ * Userspace tasks are restricted to the active mask as usual. ++ */ ++ if (p->flags & PF_KTHREAD) { ++ cpumask_and(&valid_mask, p->cpus_ptr, cpu_online_mask); ++ /* ++ * Only KTHREAD_IS_PER_CPU gets to stay on a CPU that is on ++ * its way down, so do not hand a regular kthread back the ++ * CPU bind_zero() just moved it off. Mirrors is_cpu_allowed(). ++ */ ++ if (!kthread_is_per_cpu(p)) ++ cpumask_andnot(&valid_mask, &valid_mask, cpu_dying_mask); ++ } else ++ cpumask_and(&valid_mask, p->cpus_ptr, cpu_active_mask); ++ ++ if (unlikely(!cpumask_weight(&valid_mask))) { ++ if ((p->flags & PF_KTHREAD) && num_online_cpus()) ++ return cpumask_any(cpu_online_mask); ++ /* Defer console access while holding the task's PI lock. */ ++ printk_deferred(KERN_WARNING "SCHED: No cpumask for %s/%d weight %d\n", ++ p->comm, p->pid, cpumask_weight(p->cpus_ptr)); ++ return cpumask_any(p->cpus_ptr); ++ } ++ return cpumask_any(&valid_mask); ++} ++ ++/* ++ * For a task that's just being woken up we have a valuable balancing ++ * opportunity so choose the nearest cache most lightly loaded runqueue. ++ * Entered with rq locked and returns with the chosen runqueue locked. ++ */ ++static inline int select_best_cpu(struct task_struct *p) ++{ ++ unsigned int idlest = ~0U; ++ struct rq *rq = NULL; ++ int i; ++ ++ if (suitable_idle_cpus(p)) { ++ int cpu = task_cpu(p); ++ ++ if (unlikely(needs_other_cpu(p, cpu))) ++ cpu = valid_task_cpu(p); ++ rq = resched_best_idle(p, cpu); ++ if (likely(rq)) ++ return rq->cpu; ++ } ++ ++ for (i = 0; i < num_online_cpus(); i++) { ++ struct rq *other_rq = task_rq(p)->cpu_order[i]; ++ int entries; ++ ++ if (!other_rq->online) ++ continue; ++ if (needs_other_cpu(p, other_rq->cpu)) ++ continue; ++ entries = rq_load(other_rq); ++ if (entries >= idlest) ++ continue; ++ idlest = entries; ++ rq = other_rq; ++ } ++ if (unlikely(!rq)) { ++ /* ++ * The walk found nowhere to put @p. Its affinity may contain ++ * only offline CPUs (hotplug kthreads bound before their CPU ++ * is up), or the walk may simply not have reached an allowed ++ * one: cpu_order[] is built once at boot, so once any CPU is ++ * offline the num_online_cpus() bound above stops short of its ++ * tail and never examines what is there. ++ * ++ * Staying put is only an answer if this CPU is one @p is ++ * allowed to run on. Otherwise defer to valid_task_cpu(), ++ * which intersects the mask directly and so does not depend on ++ * that ordering at all. Never place a wakeup on a CPU the task ++ * is not allowed on, whether because it is offline or because ++ * it is not in the mask; a blocked task keeps a stale ++ * task_cpu() across an affinity change, so the latter is the ++ * common case here. ++ */ ++ if (unlikely(needs_other_cpu(p, task_cpu(p)))) ++ return valid_task_cpu(p); ++ return task_cpu(p); ++ } ++ return rq->cpu; ++} ++#else /* CONFIG_SMP */ ++ ++static inline bool ttwu_queue_wakelist(struct task_struct *p, int cpu, int wake_flags) ++{ ++ return false; ++} ++ ++static int valid_task_cpu(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static inline int select_best_cpu(struct task_struct *p) ++{ ++ return 0; ++} ++ ++static struct rq *resched_best_idle(struct task_struct *p, int cpu) ++{ ++ return NULL; ++} ++#endif /* CONFIG_SMP */ ++ ++static void ttwu_queue(struct task_struct *p, int cpu, int wake_flags) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ if (ttwu_queue_wakelist(p, cpu, wake_flags)) ++ return; ++ ++ rq_lock(rq); ++ update_rq_clock(rq); ++ ttwu_do_activate(rq, p, wake_flags); ++ rq_unlock(rq); ++} ++ ++/* ++ * Consider @state matched against @p, taking p->saved_state into account. ++ * ++ * The caller holds p::pi_lock if p != current or has preemption disabled ++ * when p == current. ++ * ++ * The rules of saved_state: ++ * ++ * The related locking code always holds p::pi_lock when updating ++ * p::saved_state, which means the code is fully serialized in both cases. ++ * ++ * For PREEMPT_RT, the lock wait and lock wakeups happen via TASK_RTLOCK_WAIT. ++ * No other bits set. This allows to distinguish all wakeup scenarios. ++ * ++ * For FREEZER, the wakeup happens via TASK_FROZEN. No other bits set. This ++ * allows us to prevent early wakeup of tasks before they can be run on ++ * asymmetric ISA architectures (eg ARMv9). ++ */ ++static __always_inline ++bool ttwu_state_match(struct task_struct *p, unsigned int state, int *success) ++{ ++ int match; ++ ++ if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)) { ++ WARN_ON_ONCE((state & TASK_RTLOCK_WAIT) && ++ state != TASK_RTLOCK_WAIT); ++ } ++ ++ *success = !!(match = __task_state_match(p, state)); ++ ++ /* ++ * Saved state preserves the task state across blocking on ++ * an RT lock or TASK_FREEZABLE tasks. If the state matches, ++ * set p::saved_state to TASK_RUNNING, but do not wake the task ++ * because it waits for a lock wakeup or __thaw_task(). Also ++ * indicate success because from the regular waker's point of ++ * view this has succeeded. ++ * ++ * After acquiring the lock the task will restore p::__state ++ * from p::saved_state which ensures that the regular ++ * wakeup is not lost. The restore will also set ++ * p::saved_state to TASK_RUNNING so any further tests will ++ * not result in false positives vs. @success ++ */ ++ if (match < 0) ++ p->saved_state = TASK_RUNNING; ++ ++ return match > 0; ++} ++ ++/*** ++ * try_to_wake_up - wake up a thread ++ * @p: the thread to be awakened ++ * @state: the mask of task states that can be woken ++ * @wake_flags: wake modifier flags (WF_*) ++ * ++ * Put it on the run-queue if it's not already there. The "current" ++ * thread is always on the run-queue (except when the actual ++ * re-schedule is in progress), and as such you're allowed to do ++ * the simpler "current->__state = TASK_RUNNING" to mark yourself ++ * runnable without the overhead of this. ++ * ++ * Return: %true if @p was woken up, %false if it was already running. ++ * or @state didn't match @p's state. ++ */ ++int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags) ++{ ++ unsigned long flags; ++ int cpu, success = 0; ++ ++ preempt_disable(); ++ if (p == current) { ++ /* ++ * We're waking current, this means 'p->on_rq' and 'task_cpu(p) ++ * == smp_processor_id()'. Together this means we can special ++ * case the whole 'p->on_rq && ttwu_runnable()' case below ++ * without taking any locks. ++ * ++ * In particular: ++ * - we rely on Program-Order guarantees for all the ordering, ++ * - we're serialized against set_special_state() by virtue of ++ * it disabling IRQs (this allows not taking ->pi_lock). ++ */ ++ if (!ttwu_state_match(p, state, &success)) ++ goto out; ++ ++ trace_sched_waking(p); ++ p->__state = TASK_RUNNING; ++ trace_sched_wakeup(p); ++ goto out; ++ } ++ ++ /* ++ * If we are going to wake up a thread waiting for CONDITION we ++ * need to ensure that CONDITION=1 done by the caller can not be ++ * reordered with p->__state check below. This pairs with smp_store_mb() ++ * in set_current_state() that the waiting thread does. ++ */ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ smp_mb__after_spinlock(); ++ if (!ttwu_state_match(p, state, &success)) ++ goto unlock; ++ ++ trace_sched_waking(p); ++ ++ /* ++ * Ensure we load p->on_rq _after_ p->__state, otherwise it would ++ * be possible to, falsely, observe p->on_rq == 0 and get stuck ++ * in smp_cond_load_acquire() below. ++ * ++ * sched_ttwu_pending() try_to_wake_up() ++ * STORE p->on_rq = 1 LOAD p->__state ++ * UNLOCK rq->lock ++ * ++ * __schedule() (switch to task 'p') ++ * LOCK rq->lock smp_rmb(); ++ * smp_mb__after_spinlock(); ++ * UNLOCK rq->lock ++ * ++ * [task p] ++ * STORE p->__state = UNINTERRUPTIBLE LOAD p->on_rq ++ * ++ * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in ++ * __schedule(). See the comment for smp_mb__after_spinlock(). ++ */ ++ smp_rmb(); ++ if (READ_ONCE(p->on_rq) && ttwu_runnable(p, wake_flags)) ++ goto unlock; ++ ++#ifdef CONFIG_SMP ++ /* ++ * Ensure we load p->on_cpu _after_ p->on_rq, otherwise it would be ++ * possible to, falsely, observe p->on_cpu == 0. ++ * ++ * One must be running (->on_cpu == 1) in order to remove oneself ++ * from the runqueue. ++ * ++ * __schedule() (switch to task 'p') try_to_wake_up() ++ * STORE p->on_cpu = 1 LOAD p->on_rq ++ * UNLOCK rq->lock ++ * ++ * __schedule() (put 'p' to sleep) ++ * LOCK rq->lock smp_rmb(); ++ * smp_mb__after_spinlock(); ++ * STORE p->on_rq = 0 LOAD p->on_cpu ++ * ++ * Pairs with the LOCK+smp_mb__after_spinlock() on rq->lock in ++ * __schedule(). See the comment for smp_mb__after_spinlock(). ++ * ++ * Form a control-dep-acquire with p->on_rq == 0 above, to ensure ++ * schedule()'s deactivate_task() has 'happened' and p will no longer ++ * care about it's own p->__state. See the comment in __schedule(). ++ */ ++ smp_acquire__after_ctrl_dep(); ++ ++ /* ++ * We're doing the wakeup (@success == 1), they did a dequeue (p->on_rq ++ * == 0), which means we need to do an enqueue, change p->__state to ++ * TASK_WAKING such that we can unlock p->pi_lock before doing the ++ * enqueue, such as ttwu_queue_wakelist(). ++ */ ++ p->__state = TASK_WAKING; ++ ++ /* ++ * If the owning (remote) CPU is still in the middle of schedule() with ++ * this task as prev, considering queueing p on the remote CPUs wake_list ++ * which potentially sends an IPI instead of spinning on p->on_cpu to ++ * let the waker make forward progress. This is safe because IRQs are ++ * disabled and the IPI will deliver after on_cpu is cleared. ++ * ++ * Ensure we load task_cpu(p) after p->on_cpu: ++ * ++ * set_task_cpu(p, cpu); ++ * STORE task_cpu(p) = @cpu ++ * __schedule() (switch to task 'p') ++ * LOCK rq->lock ++ * smp_mb__after_spin_lock() smp_cond_load_acquire(&p->on_cpu) ++ * STORE p->on_cpu = 1 LOAD task_cpu(p) ++ * ++ * to ensure we observe the correct CPU on which the task is currently ++ * scheduling. ++ */ ++ if (smp_load_acquire(&p->on_cpu) && ++ ttwu_queue_wakelist(p, task_cpu(p), wake_flags | WF_ON_CPU)) ++ goto unlock; ++ ++ /* ++ * If the owning (remote) CPU is still in the middle of schedule() with ++ * this task as prev, wait until it's done referencing the task. ++ * ++ * Pairs with the smp_store_release() in finish_task(). ++ * ++ * This ensures that tasks getting woken will be fully ordered against ++ * their previous state and preserve Program Order. ++ */ ++ smp_cond_load_acquire(&p->on_cpu, !VAL); ++ ++ cpu = select_best_cpu(p); ++ if (task_cpu(p) != cpu) { ++ if (p->in_iowait) { ++ delayacct_blkio_end(p); ++ atomic_dec(&task_rq(p)->nr_iowait); ++ } ++ ++ wake_flags |= WF_MIGRATED; ++ psi_ttwu_dequeue(p); ++ set_task_cpu(p, cpu); ++ } ++ ++#else ++ cpu = task_cpu(p); ++#endif /* CONFIG_SMP */ ++ ++ ttwu_queue(p, cpu, wake_flags); ++unlock: ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++out: ++ if (success) ++ ttwu_stat(p, task_cpu(p), wake_flags); ++ preempt_enable(); ++ ++ return success; ++} ++ ++static bool __task_needs_rq_lock(struct task_struct *p) ++{ ++ unsigned int state = READ_ONCE(p->__state); ++ ++ /* ++ * Since pi->lock blocks try_to_wake_up(), we don't need rq->lock when ++ * the task is blocked. Make sure to check @state since ttwu() can drop ++ * locks at the end, see ttwu_queue_wakelist(). ++ */ ++ if (state == TASK_RUNNING || state == TASK_WAKING) ++ return true; ++ ++ /* ++ * Ensure we load p->on_rq after p->__state, otherwise it would be ++ * possible to, falsely, observe p->on_rq == 0. ++ * ++ * See try_to_wake_up() for a longer comment. ++ */ ++ smp_rmb(); ++ if (p->on_rq) ++ return true; ++ ++ /* ++ * Ensure the task has finished __schedule() and will not be referenced ++ * anymore. Again, see try_to_wake_up() for a longer comment. ++ */ ++ smp_rmb(); ++ smp_cond_load_acquire(&p->on_cpu, !VAL); ++ ++ return false; ++} ++ ++/** ++ * task_call_func - Invoke a function on task in fixed state ++ * @p: Process for which the function is to be invoked, can be @current. ++ * @func: Function to invoke. ++ * @arg: Argument to function. ++ * ++ * Fix the task in it's current state by avoiding wakeups and or rq operations ++ * and call @func(@arg) on it. This function can use task_is_runnable() and ++ * task_curr() to work out what the state is, if required. Given that @func ++ * can be invoked with a runqueue lock held, it had better be quite ++ * lightweight. ++ * ++ * Returns: ++ * Whatever @func returns ++ */ ++int task_call_func(struct task_struct *p, task_call_f func, void *arg) ++{ ++ struct rq_flags rf; ++ int ret; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, rf.flags); ++ ++ if (__task_needs_rq_lock(p)) { ++ struct rq *rq = __task_rq_lock(p, &rf); ++ ++ /* ++ * At this point the task is pinned; either: ++ * - blocked and we're holding off wakeups (pi->lock) ++ * - woken, and we're holding off enqueue (rq->lock) ++ * - queued, and we're holding off schedule (rq->lock) ++ * - running, and we're holding off de-schedule (rq->lock) ++ * ++ * The called function (@func) can use: task_curr(), p->on_rq and ++ * p->__state to differentiate between these states. ++ */ ++ ret = func(p, arg); ++ ++ __task_rq_unlock(rq, p, &rf); ++ } else { ++ ret = func(p, arg); ++ } ++ ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags); ++ return ret; ++} ++ ++/** ++ * wake_up_process - Wake up a specific process ++ * @p: The process to be woken up. ++ * ++ * Attempt to wake up the nominated process and move it to the set of runnable ++ * processes. ++ * ++ * Return: 1 if the process was woken up, 0 if it was already running. ++ * ++ * This function executes a full memory barrier before accessing the task state. ++ */ ++int wake_up_process(struct task_struct *p) ++{ ++ return try_to_wake_up(p, TASK_NORMAL, 0); ++} ++EXPORT_SYMBOL(wake_up_process); ++ ++int wake_up_state(struct task_struct *p, unsigned int state) ++{ ++ return try_to_wake_up(p, state, 0); ++} ++ ++static void time_slice_expired(struct task_struct *p, struct rq *rq); ++ ++/* ++ * Perform scheduler related setup for a newly forked process p. ++ * p is forked by current. ++ */ ++int sched_fork(u64 __maybe_unused clone_flags, struct task_struct *p) ++{ ++ unsigned long flags; ++ ++#ifdef CONFIG_PREEMPT_NOTIFIERS ++ INIT_HLIST_HEAD(&p->preempt_notifiers); ++#endif ++ ++#ifdef CONFIG_COMPACTION ++ p->capture_control = NULL; ++#endif ++ ++#ifdef CONFIG_SMP ++ p->wake_entry.u_flags = CSD_TYPE_TTWU; ++#endif ++ /* A new task starts with no I/O history of its own. */ ++ muqss_iotime_task_init(p); ++ ++ /* ++ * We mark the process as NEW here. This guarantees that ++ * nobody will actually run it, and a signal or other external ++ * event cannot wake it up and insert it on the runqueue either. ++ */ ++ p->__state = TASK_NEW; ++ ++ /* ++ * The process state is set to the same value of the process executing ++ * do_fork() code. That is running. This guarantees that nobody will ++ * actually run it, and a signal or other external event cannot wake ++ * it up and insert it on the runqueue either. ++ */ ++ ++ /* Should be reset in fork.c but done here for ease of MuQSS patching */ ++ p->on_cpu = ++ p->on_rq = ++ p->utime = ++ p->stime = ++ p->sched_time = ++ p->stime_ns = ++ p->utime_ns = 0; ++ skiplist_node_init(&p->node); ++ ++ /* ++ * Revert to default priority/policy on fork if requested. ++ */ ++ if (unlikely(p->sched_reset_on_fork)) { ++ if (p->policy == SCHED_FIFO || p->policy == SCHED_RR || p-> policy == SCHED_ISO) { ++ /* ++ * __setscheduler() zeroes timer_slack_ns for rt tasks, ++ * so restore it when demoting back to SCHED_NORMAL, ++ * otherwise the child inherits zero slack forever. ++ */ ++ if (has_rt_policy(p)) ++ p->timer_slack_ns = p->default_timer_slack_ns; ++ p->policy = SCHED_NORMAL; ++ p->normal_prio = normal_prio(p); ++ } ++ ++ if (PRIO_TO_NICE(p->static_prio) < 0) { ++ p->static_prio = NICE_TO_PRIO(0); ++ p->normal_prio = p->static_prio; ++ } ++ ++ /* ++ * We don't need the reset flag anymore after the fork. It has ++ * fulfilled its duty: ++ */ ++ p->sched_reset_on_fork = 0; ++ } ++ ++ /* ++ * Silence PROVE_RCU. ++ */ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ rseq_sched_set_ids_changed(p); ++ set_task_cpu(p, smp_processor_id()); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++ ++#ifdef CONFIG_SCHED_INFO ++ if (unlikely(sched_info_on())) ++ memset(&p->sched_info, 0, sizeof(p->sched_info)); ++#endif ++ init_task_preempt_count(p); ++ ++ return 0; ++} ++ ++void sched_post_fork(struct task_struct *p) ++{ ++} ++ ++#ifdef CONFIG_SCHEDSTATS ++ ++DEFINE_STATIC_KEY_FALSE(sched_schedstats); ++static bool __initdata __sched_schedstats = false; ++ ++static void set_schedstats(bool enabled) ++{ ++ if (enabled) ++ static_branch_enable(&sched_schedstats); ++ else ++ static_branch_disable(&sched_schedstats); ++} ++ ++void force_schedstat_enabled(void) ++{ ++ if (!schedstat_enabled()) { ++ pr_info("kernel profiling enabled schedstats, disable via kernel.sched_schedstats.\n"); ++ static_branch_enable(&sched_schedstats); ++ } ++} ++ ++static int __init setup_schedstats(char *str) ++{ ++ int ret = 0; ++ if (!str) ++ goto out; ++ ++ /* ++ * This code is called before jump labels have been set up, so we can't ++ * change the static branch directly just yet. Instead set a temporary ++ * variable so init_schedstats() can do it later. ++ */ ++ if (!strcmp(str, "enable")) { ++ __sched_schedstats = true; ++ ret = 1; ++ } else if (!strcmp(str, "disable")) { ++ __sched_schedstats = false; ++ ret = 1; ++ } ++out: ++ if (!ret) ++ pr_warn("Unable to parse schedstats=\n"); ++ ++ return ret; ++} ++__setup("schedstats=", setup_schedstats); ++ ++static void __init init_schedstats(void) ++{ ++ set_schedstats(__sched_schedstats); ++} ++ ++#ifdef CONFIG_SYSCTL ++static int sysctl_schedstats(const struct ctl_table *table, int write, void *buffer, ++ size_t *lenp, loff_t *ppos) ++{ ++ struct ctl_table t; ++ int err; ++ int state = static_branch_likely(&sched_schedstats); ++ ++ if (write && !capable(CAP_SYS_ADMIN)) ++ return -EPERM; ++ ++ t = *table; ++ t.data = &state; ++ err = proc_dointvec_minmax(&t, write, buffer, lenp, ppos); ++ if (err < 0) ++ return err; ++ if (write) ++ set_schedstats(state); ++ return err; ++} ++#endif /* CONFIG_SYSCTL */ ++#else /* !CONFIG_SCHEDSTATS */ ++static inline void init_schedstats(void) {} ++#endif /* CONFIG_SCHEDSTATS */ ++ ++#ifdef CONFIG_SYSCTL ++/* ++ * MuQSS tunables used to live in kernel/sysctl.c. Mainline moved scheduler ++ * sysctls next to their implementation via register_sysctl_init(), so register ++ * ours the same way. CFS/rt/fair knobs are not present under MuQSS. ++ */ ++static const struct ctl_table muqss_sysctls[] = { ++ { ++ .procname = "rr_interval", ++ .data = &rr_interval, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ONE, ++ .extra2 = SYSCTL_ONE_THOUSAND, ++ }, ++ { ++ .procname = "interactive", ++ .data = &sched_interactive, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++ { ++ .procname = "iso_cpu", ++ .data = &sched_iso_cpu, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE_HUNDRED, ++ }, ++ { ++ .procname = "yield_type", ++ .data = &sched_yield_type, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_TWO, ++ }, ++#ifdef CONFIG_SCHEDSTATS ++ { ++ .procname = "sched_schedstats", ++ .data = NULL, ++ .maxlen = sizeof(unsigned int), ++ .mode = 0644, ++ .proc_handler = sysctl_schedstats, ++ .extra1 = SYSCTL_ZERO, ++ .extra2 = SYSCTL_ONE, ++ }, ++#endif ++}; ++ ++static int __init muqss_sysctl_init(void) ++{ ++ register_sysctl_init("kernel", muqss_sysctls); ++ return 0; ++} ++late_initcall(muqss_sysctl_init); ++#endif /* CONFIG_SYSCTL */ ++ ++static void update_cpu_clock_switch(struct rq *rq, struct task_struct *p); ++ ++static void account_task_cpu(struct rq *rq, struct task_struct *p) ++{ ++ update_clocks(rq); ++ /* This isn't really a context switch but accounting is the same */ ++ update_cpu_clock_switch(rq, p); ++ p->last_ran = rq->niffies; ++} ++ ++bool sched_smp_initialized __read_mostly; ++ ++/* ++ * High-resolution timeslice expiry (MuQSS counterpart of mainline hrtick). ++ * ++ * Reprogramming a oneshot clockevent from set_rq_task() on every context ++ * switch under the rq lock races with the tick/timer softirq path on SMP ++ * and starves TIMER_SOFTIRQ (RCU "timer wakeup didn't happen"). Mirror ++ * mainline: HARD + LAZY_REARM setup, needs_rearm 5us threshold, and defer ++ * the actual start/cancel until hrexpiry_schedule_exit() after the pick. ++ */ ++#ifdef CONFIG_HIGH_RES_TIMERS ++ ++enum { ++ HREXPIRE_SCHED_NONE = 0, ++ HREXPIRE_SCHED_DEFER = BIT(1), ++ HREXPIRE_SCHED_START = BIT(2), ++ HREXPIRE_SCHED_REARM_HRTIMER = BIT(3), ++}; ++ ++static inline int hrexpiry_enabled(struct rq *rq) ++{ ++ /* ++ * 5.12 used hrtimer_is_hres_active(); 7.1 exposes the same idea as ++ * hrtimer_resolution != LOW_RES_NSEC once highres has switched on. ++ */ ++ return hrtimer_resolution != LOW_RES_NSEC; ++} ++ ++static inline bool hrexpiry_needs_rearm(struct hrtimer *timer, ktime_t expires) ++{ ++ /* ++ * Queued is false when not started or the callback is running. If ++ * already queued, only reprogram when the expiry moves substantially. ++ */ ++ return !hrtimer_is_queued(timer) || ++ abs(expires - hrtimer_get_expires(timer)) > 5000; ++} ++ ++static void hrexpiry_cond_restart(struct rq *rq) ++{ ++ struct hrtimer *timer = &rq->hrexpiry_timer; ++ ktime_t time = rq->hrexpiry_time; ++ ++ if (hrexpiry_needs_rearm(timer, time)) ++ hrtimer_start(timer, time, HRTIMER_MODE_ABS_PINNED_HARD); ++} ++ ++/* ++ * Remote start IPI — wake_up_new_task may shorten a parent on another CPU. ++ * Runs hardirq/IPI context; take the rq lock like mainline __hrtick_start. ++ */ ++static void __hrexpiry_start(void *arg) ++{ ++ struct rq *rq = arg; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(rq->lock, flags); ++ hrexpiry_cond_restart(rq); ++ raw_spin_unlock_irqrestore(rq->lock, flags); ++} ++ ++static inline void hrexpiry_clear(struct rq *rq) ++{ ++ if (!hrexpiry_enabled(rq)) ++ return; ++ ++ /* ++ * Inside __schedule() only drop a pending deferred start; the actual ++ * cancel happens once in hrexpiry_schedule_exit(). ++ */ ++ if (rq->hrexpiry_sched) { ++ rq->hrexpiry_sched &= ~HREXPIRE_SCHED_START; ++ return; ++ } ++ ++ hrtimer_try_to_cancel(&rq->hrexpiry_timer); ++} ++ ++/* ++ * High-resolution time_slice expiry. ++ * Runs from hardirq context with interrupts disabled. ++ */ ++static enum hrtimer_restart hrexpiry(struct hrtimer *timer) ++{ ++ struct rq *rq = container_of(timer, struct rq, hrexpiry_timer); ++ struct task_struct *p; ++ ++ /* This can happen during CPU hotplug / resume */ ++ if (unlikely(cpu_of(rq) != smp_processor_id())) ++ goto out; ++ ++ /* ++ * Local CPU only; no rq lock. Force a reschedule when the slice ++ * expires — __schedule() will pick the next deadline task. ++ */ ++ p = rq->curr; ++ p->time_slice = 0; ++ __set_tsk_resched(p); ++out: ++ return HRTIMER_NORESTART; ++} ++ ++/* ++ * Called with irqs disabled under the rq lock (set_rq_task / fork path). ++ * May target a remote rq — then arm via CSD like mainline hrtick_start. ++ */ ++static void hrexpiry_start(struct rq *rq, u64 delay) ++{ ++ s64 delta; ++ ++ if (!hrexpiry_enabled(rq)) ++ return; ++ ++ /* Slices < 10us are not useful and can DoS the timer subsystem. */ ++ delta = max_t(s64, delay, 10000LL); ++ ++ /* ++ * Mid-schedule: note the delay and let hrexpiry_schedule_exit() ++ * program the clockevent once. ++ */ ++ if (rq->hrexpiry_sched) { ++ rq->hrexpiry_sched |= HREXPIRE_SCHED_START; ++ rq->hrexpiry_delay = delta; ++ return; ++ } ++ ++ rq->hrexpiry_time = ktime_add_ns(ktime_get(), delta); ++ if (!hrexpiry_needs_rearm(&rq->hrexpiry_timer, rq->hrexpiry_time)) ++ return; ++ ++ if (rq == this_rq()) ++ hrtimer_start(&rq->hrexpiry_timer, rq->hrexpiry_time, ++ HRTIMER_MODE_ABS_PINNED_HARD); ++ else ++ smp_call_function_single_async(cpu_of(rq), &rq->hrexpiry_csd); ++} ++ ++static inline void hrexpiry_schedule_enter(struct rq *rq) ++{ ++ rq->hrexpiry_sched = HREXPIRE_SCHED_DEFER; ++ if (hrtimer_test_and_clear_rearm_deferred()) ++ rq->hrexpiry_sched |= HREXPIRE_SCHED_REARM_HRTIMER; ++} ++ ++static inline void hrexpiry_schedule_exit(struct rq *rq) ++{ ++ if (rq->hrexpiry_sched & HREXPIRE_SCHED_START) { ++ rq->hrexpiry_time = ktime_add_ns(ktime_get(), rq->hrexpiry_delay); ++ hrexpiry_cond_restart(rq); ++ } else if (rq->curr == rq->idle || rq->curr->policy == SCHED_FIFO) { ++ /* ++ * No slice timer needed. Local CPU, IRQs off: the HARD ++ * callback cannot be running, so cancel is safe. ++ */ ++ if (hrtimer_is_queued(&rq->hrexpiry_timer)) ++ hrtimer_cancel(&rq->hrexpiry_timer); ++ } ++ ++ if (rq->hrexpiry_sched & HREXPIRE_SCHED_REARM_HRTIMER) ++ __hrtimer_rearm_deferred(); ++ ++ rq->hrexpiry_sched = HREXPIRE_SCHED_NONE; ++} ++ ++static void init_rq_hrexpiry(struct rq *rq) ++{ ++ INIT_CSD(&rq->hrexpiry_csd, __hrexpiry_start, rq); ++ rq->hrexpiry_sched = HREXPIRE_SCHED_NONE; ++ hrtimer_setup(&rq->hrexpiry_timer, hrexpiry, CLOCK_MONOTONIC, ++ HRTIMER_MODE_REL_HARD | HRTIMER_MODE_LAZY_REARM); ++} ++ ++#else /* !CONFIG_HIGH_RES_TIMERS */ ++ ++static inline int hrexpiry_enabled(struct rq *rq) ++{ ++ return 0; ++} ++static inline void hrexpiry_clear(struct rq *rq) { } ++static inline void hrexpiry_start(struct rq *rq, u64 delay) { } ++static inline void hrexpiry_schedule_enter(struct rq *rq) { } ++static inline void hrexpiry_schedule_exit(struct rq *rq) { } ++static inline void init_rq_hrexpiry(struct rq *rq) { } ++ ++#endif /* CONFIG_HIGH_RES_TIMERS */ ++ ++static inline int rq_dither(struct rq *rq) ++{ ++ if (!hrexpiry_enabled(rq)) ++ return HALF_JIFFY_US; ++ return 0; ++} ++ ++/* ++ * wake_up_new_task - wake up a newly created task for the first time. ++ * ++ * This function will do some initial scheduler statistics housekeeping ++ * that must be done for every newly created context, then puts the task ++ * on the runqueue and wakes it. ++ */ ++void wake_up_new_task(struct task_struct *p) ++{ ++ struct task_struct *parent, *rq_curr; ++ struct rq *rq, *new_rq; ++ unsigned long flags; ++ ++ parent = p->parent; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ p->__state = TASK_RUNNING; ++ /* Task_rq can't change yet on a new task */ ++ new_rq = rq = task_rq(p); ++ if (unlikely(needs_other_cpu(p, task_cpu(p)))) { ++ set_task_cpu(p, valid_task_cpu(p)); ++ new_rq = task_rq(p); ++ } ++ ++ double_rq_lock(rq, new_rq); ++ rq_curr = rq->curr; ++ ++ /* ++ * Make sure we do not leak PI boosting priority to the child. ++ */ ++ p->prio = rq_curr->normal_prio; ++ ++ trace_sched_wakeup_new(p); ++ ++ /* ++ * Share the timeslice between parent and child, thus the ++ * total amount of pending timeslices in the system doesn't change, ++ * resulting in more scheduling fairness. If it's negative, it won't ++ * matter since that's the same as being 0. rq->rq_deadline is only ++ * modified within schedule() so it is always equal to ++ * current->deadline. ++ */ ++ account_task_cpu(rq, rq_curr); ++ p->last_ran = rq_curr->last_ran; ++ if (likely(rq_curr->policy != SCHED_FIFO)) { ++ rq_curr->time_slice /= 2; ++ if (rq_curr->time_slice < RESCHED_US) { ++ /* ++ * Forking task has run out of timeslice. Reschedule it and ++ * start its child with a new time slice and deadline. The ++ * child will end up running first because its deadline will ++ * be slightly earlier. ++ */ ++ __set_tsk_resched(rq_curr); ++ time_slice_expired(p, new_rq); ++ if (suitable_idle_cpus(p)) ++ resched_best_idle(p, task_cpu(p)); ++ else if (unlikely(rq != new_rq)) ++ try_preempt(p, new_rq); ++ } else { ++ p->time_slice = rq_curr->time_slice; ++ if (rq_curr == parent && rq == new_rq && !suitable_idle_cpus(p)) { ++ /* ++ * The VM isn't cloned, so we're in a good position to ++ * do child-runs-first in anticipation of an exec. This ++ * usually avoids a lot of COW overhead. ++ */ ++ __set_tsk_resched(rq_curr); ++ } else { ++ /* ++ * Adjust the hrexpiry since rq_curr will keep ++ * running and its timeslice has been shortened. ++ */ ++ hrexpiry_start(rq, US_TO_NS(rq_curr->time_slice)); ++ try_preempt(p, new_rq); ++ } ++ } ++ } else { ++ time_slice_expired(p, new_rq); ++ try_preempt(p, new_rq); ++ } ++ activate_task(new_rq, p, 0); ++ double_rq_unlock(rq, new_rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++} ++ ++#ifdef CONFIG_PREEMPT_NOTIFIERS ++ ++static DEFINE_STATIC_KEY_FALSE(preempt_notifier_key); ++ ++void preempt_notifier_inc(void) ++{ ++ static_branch_inc(&preempt_notifier_key); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_inc); ++ ++void preempt_notifier_dec(void) ++{ ++ static_branch_dec(&preempt_notifier_key); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_dec); ++ ++/** ++ * preempt_notifier_register - tell me when current is being preempted & rescheduled ++ * @notifier: notifier struct to register ++ */ ++void preempt_notifier_register(struct preempt_notifier *notifier) ++{ ++ if (!static_branch_unlikely(&preempt_notifier_key)) ++ WARN(1, "registering preempt_notifier while notifiers disabled\n"); ++ ++ hlist_add_head(¬ifier->link, ¤t->preempt_notifiers); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_register); ++ ++/** ++ * preempt_notifier_unregister - no longer interested in preemption notifications ++ * @notifier: notifier struct to unregister ++ * ++ * This is *not* safe to call from within a preemption notifier. ++ */ ++void preempt_notifier_unregister(struct preempt_notifier *notifier) ++{ ++ hlist_del(¬ifier->link); ++} ++EXPORT_SYMBOL_GPL(preempt_notifier_unregister); ++ ++static void __fire_sched_in_preempt_notifiers(struct task_struct *curr) ++{ ++ struct preempt_notifier *notifier; ++ ++ hlist_for_each_entry(notifier, &curr->preempt_notifiers, link) ++ notifier->ops->sched_in(notifier, raw_smp_processor_id()); ++} ++ ++static __always_inline void fire_sched_in_preempt_notifiers(struct task_struct *curr) ++{ ++ if (static_branch_unlikely(&preempt_notifier_key)) ++ __fire_sched_in_preempt_notifiers(curr); ++} ++ ++static void ++__fire_sched_out_preempt_notifiers(struct task_struct *curr, ++ struct task_struct *next) ++{ ++ struct preempt_notifier *notifier; ++ ++ hlist_for_each_entry(notifier, &curr->preempt_notifiers, link) ++ notifier->ops->sched_out(notifier, next); ++} ++ ++static __always_inline void ++fire_sched_out_preempt_notifiers(struct task_struct *curr, ++ struct task_struct *next) ++{ ++ if (static_branch_unlikely(&preempt_notifier_key)) ++ __fire_sched_out_preempt_notifiers(curr, next); ++} ++ ++#else /* !CONFIG_PREEMPT_NOTIFIERS */ ++ ++static inline void fire_sched_in_preempt_notifiers(struct task_struct *curr) ++{ ++} ++ ++static inline void ++fire_sched_out_preempt_notifiers(struct task_struct *curr, ++ struct task_struct *next) ++{ ++} ++ ++#endif /* CONFIG_PREEMPT_NOTIFIERS */ ++ ++static inline void prepare_task(struct task_struct *next) ++{ ++ /* ++ * Claim the task as running, we do this before switching to it ++ * such that any running task will have this set. ++ * ++ * See the ttwu() WF_ON_CPU case and its ordering comment. ++ */ ++ WRITE_ONCE(next->on_cpu, 1); ++} ++ ++static inline void finish_task(struct task_struct *prev) ++{ ++ /* ++ * This must be the very last reference to @prev from this CPU. After ++ * p->on_cpu is cleared, the task can be moved to a different CPU. We ++ * must ensure this doesn't happen until the switch is completely ++ * finished. ++ * ++ * In particular, the load of prev->__state in finish_task_switch() must ++ * happen before this. ++ * ++ * Pairs with the smp_cond_load_acquire() in try_to_wake_up() and ++ * __task_needs_rq_lock(). ++ */ ++ smp_store_release(&prev->on_cpu, 0); ++} ++ ++static inline void ++prepare_lock_switch(struct rq *rq, struct task_struct *next) ++{ ++ /* ++ * Since the runqueue lock will be released by the next ++ * task (which is an invalid locking op but in the case ++ * of the scheduler it's an obvious special-case), so we ++ * do an early lockdep release here: ++ */ ++ spin_release(&rq->lock->dep_map, _THIS_IP_); ++#ifdef CONFIG_DEBUG_SPINLOCK ++ /* this is a valid case when another task releases the spinlock */ ++ rq->lock->owner = next; ++#endif ++} ++ ++static inline void finish_lock_switch(struct rq *rq, struct task_struct *prev) ++{ ++ /* ++ * If we are tracking spinlock dependencies then we have to ++ * fix up the runqueue lock - which gets 'carried over' from ++ * prev into current: ++ */ ++ spin_acquire(&rq->lock->dep_map, 0, 0, _THIS_IP_); ++ ++#ifdef CONFIG_SMP ++ /* ++ * If prev was marked as migrating to another CPU in return_task, drop ++ * the local runqueue lock but leave interrupts disabled and grab the ++ * remote lock we're migrating it to before enabling them. ++ */ ++ if (unlikely(task_on_rq_migrating(prev))) { ++ unsigned int migrate; ++ ++ /* ++ * Program/cancel hrexpiry on this CPU before dropping its ++ * rq lock; after the unlock `rq` may become the remote one. ++ */ ++ hrexpiry_schedule_exit(rq); ++ sched_info_dequeue(rq, prev); ++ /* ++ * We move the ownership of prev to the new cpu now. Note that ++ * this does not lock ttwu out: pointing task_cpu() at wake_cpu ++ * below sends a concurrent ttwu_runnable() to the *new* ++ * runqueue's lock rather than the one dropped here, and ++ * __task_rq_lock() does not spin on task_on_rq_migrating() the ++ * way mainline's does. See the re-check before enqueueing. ++ * ++ * This bypasses set_task_cpu(), so any PSI state prev is still ++ * counted for has to be moved by hand, exactly as that does. ++ * psi_sched_switch() saw prev off the CPU as a sleep, since it ++ * is no longer queued, so a task in iowait is carrying ++ * TSK_IOWAIT here and enqueue_task() below would clear it on ++ * the new CPU that never counted it. ++ */ ++ migrate = psi_migrate_begin(prev); ++ task_thread_info(prev)->cpu = prev->wake_cpu; ++ psi_migrate_end(prev, migrate); ++ raw_spin_unlock(rq->lock); ++ ++ raw_spin_lock(&prev->pi_lock); ++ rq = __task_rq_lock(prev, NULL); ++ /* ++ * Complete the handover only while it is still ours to ++ * complete. ttwu() reaches prev here despite the comment ++ * above: ttwu_runnable() only accepts TASK_ON_RQ_QUEUED, so a ++ * wakeup that finds prev still TASK_ON_RQ_MIGRATING falls ++ * through it and enqueues prev itself. Once it has, prev can be ++ * picked and run on another CPU, and take_task() empties its ++ * skiplist node again. ++ * ++ * task_queued() only asks whether that node is linked, so at ++ * that point it reads "prev still needs enqueueing" when it ++ * means "prev is running elsewhere", and puts a task that is on ++ * a CPU back on a runqueue for a second CPU to pick up. One ++ * task then runs on two CPUs off one stack, which shows up ++ * downstream as skiplist corruption, PSI counting the task ++ * twice, and a scribbled kernel stack. ++ * ++ * on_rq is what actually tracks the handover - return_task() ++ * set TASK_ON_RQ_MIGRATING and whoever takes prev over clears ++ * it - and pi_lock, held here and taken by ttwu() before it ++ * does anything, serialises the two. ++ */ ++ if (likely(task_on_rq_migrating(prev))) { ++ enqueue_task(rq, prev, 0); ++ prev->on_rq = TASK_ON_RQ_QUEUED; ++ /* Wake up the CPU if it's not already running */ ++ resched_if_idle(rq); ++ } ++ raw_spin_unlock(&prev->pi_lock); ++ raw_spin_unlock_irq(rq->lock); ++ return; ++ } ++#endif ++ hrexpiry_schedule_exit(rq); ++ raw_spin_unlock_irq(rq->lock); ++} ++ ++#ifndef prepare_arch_switch ++# define prepare_arch_switch(next) do { } while (0) ++#endif ++#ifndef finish_arch_switch ++# define finish_arch_switch(prev) do { } while (0) ++#endif ++#ifndef finish_arch_post_lock_switch ++# define finish_arch_post_lock_switch() do { } while (0) ++#endif ++ ++static inline void kmap_local_sched_out(void) ++{ ++#ifdef CONFIG_KMAP_LOCAL ++ if (unlikely(current->kmap_ctrl.idx)) ++ __kmap_local_sched_out(); ++#endif ++} ++ ++static inline void kmap_local_sched_in(void) ++{ ++#ifdef CONFIG_KMAP_LOCAL ++ if (unlikely(current->kmap_ctrl.idx)) ++ __kmap_local_sched_in(); ++#endif ++} ++ ++/** ++ * prepare_task_switch - prepare to switch tasks ++ * @rq: the runqueue preparing to switch ++ * @next: the task we are going to switch to. ++ * ++ * This is called with the rq lock held and interrupts off. It must ++ * be paired with a subsequent finish_task_switch after the context ++ * switch. ++ * ++ * prepare_task_switch sets up locking and calls architecture specific ++ * hooks. ++ */ ++static inline void ++prepare_task_switch(struct rq *rq, struct task_struct *prev, ++ struct task_struct *next) ++{ ++ kcov_prepare_switch(prev); ++ sched_info_switch(rq, prev, next); ++ perf_event_task_sched_out(prev, next); ++ /* ++ * rseq_preempt(prev) became rseq_sched_switch_event(next): rseq is now ++ * told about the task being scheduled *in*. ++ */ ++ rseq_sched_switch_event(next); ++ fire_sched_out_preempt_notifiers(prev, next); ++ kmap_local_sched_out(); ++ prepare_task(next); ++ prepare_arch_switch(next); ++} ++ ++/** ++ * finish_task_switch - clean up after a task-switch ++ * @rq: runqueue associated with task-switch ++ * @prev: the thread we just switched away from. ++ * ++ * finish_task_switch must be called after the context switch, paired ++ * with a prepare_task_switch call before the context switch. ++ * finish_task_switch will reconcile locking set up by prepare_task_switch, ++ * and do any other architecture-specific cleanup actions. ++ * ++ * Note that we may have delayed dropping an mm in context_switch(). If ++ * so, we finish that here outside of the runqueue lock. (Doing it ++ * with the lock held can cause deadlocks; see schedule() for ++ * details.) ++ * ++ * The context switch have flipped the stack from under us and restored the ++ * local variables which were saved when this task called schedule() in the ++ * past. prev == current is still correct but we need to recalculate this_rq ++ * because prev may have moved to another CPU. ++ */ ++static void finish_task_switch(struct task_struct *prev) ++ __releases(rq->lock) ++{ ++ struct rq *rq = this_rq(); ++ struct mm_struct *mm = rq->prev_mm; ++ long prev_state; ++ ++ /* ++ * The previous task will have left us with a preempt_count of 2 ++ * because it left us after: ++ * ++ * schedule() ++ * preempt_disable(); // 1 ++ * __schedule() ++ * raw_spin_lock_irq(rq->lock) // 2 ++ * ++ * Also, see FORK_PREEMPT_COUNT. ++ */ ++ if (WARN_ONCE(preempt_count() != 2*PREEMPT_DISABLE_OFFSET, ++ "corrupted preempt_count: %s/%d/0x%x\n", ++ current->comm, current->pid, preempt_count())) ++ preempt_count_set(FORK_PREEMPT_COUNT); ++ ++ rq->prev_mm = NULL; ++ ++ /* ++ * A task struct has one reference for the use as "current". ++ * If a task dies, then it sets TASK_DEAD in tsk->__state and calls ++ * schedule one last time. The schedule call will never return, and ++ * the scheduled task must drop that reference. ++ * ++ * We must observe prev->__state before clearing prev->on_cpu (in ++ * finish_task), otherwise a concurrent wakeup can get prev ++ * running on another CPU and we could rave with its RUNNING -> DEAD ++ * transition, resulting in a double drop. ++ */ ++ prev_state = prev->__state; ++ vtime_task_switch(prev); ++ perf_event_task_sched_in(prev, current); ++ finish_task(prev); ++ finish_lock_switch(rq, prev); ++ finish_arch_post_lock_switch(); ++ kcov_finish_switch(current); ++ /* ++ * kmap_local_sched_out() is invoked with rq::lock held and ++ * interrupts disabled. There is no requirement for that, but the ++ * sched out code does not have an interrupt enabled section. ++ * Restoring the maps on sched in does not require interrupts being ++ * disabled either. ++ */ ++ kmap_local_sched_in(); ++ ++ /* ++ * Any cached block-layer timestamp (plug->cur_ktime) is stale now, ++ * invalidate it. ++ */ ++ blk_plug_invalidate_ts(); ++ ++ fire_sched_in_preempt_notifiers(current); ++ /* ++ * When switching through a kernel thread, the loop in ++ * membarrier_{private,global}_expedited() may have observed that ++ * kernel thread and not issued an IPI. It is therefore possible to ++ * schedule between user->kernel->user threads without passing though ++ * switch_mm(). Membarrier requires a barrier after storing to ++ * rq->curr, before returning to userspace, so provide them here: ++ * ++ * - a full memory barrier for {PRIVATE,GLOBAL}_EXPEDITED, implicitly ++ * provided by mmdrop_lazy_tlb(), ++ * - a sync_core for SYNC_CORE. ++ */ ++ if (mm) { ++ membarrier_mm_sync_core_before_usermode(mm); ++ mmdrop_lazy_tlb_sched(mm); ++ } ++ if (unlikely(prev_state == TASK_DEAD)) { ++ /* ++ * Remove function-return probe instances associated with this ++ * task and put them back on the free list. ++ */ ++ kprobe_flush_task(prev); ++ ++ /* ++ * Unlink from css_set after the final switch so rmdir can ++ * complete. Must precede put_task_struct_rcu_user(). ++ */ ++ cgroup_task_dead(prev); ++ ++ /* Task is done with its stack. */ ++ put_task_stack(prev); ++ ++ put_task_struct_rcu_user(prev); ++ } ++} ++ ++/** ++ * schedule_tail - first thing a freshly forked thread must call. ++ * @prev: the thread we just switched away from. ++ */ ++asmlinkage __visible void schedule_tail(struct task_struct *prev) ++{ ++ /* ++ * New tasks start with FORK_PREEMPT_COUNT, see there and ++ * finish_task_switch() for details. ++ * ++ * finish_task_switch() will drop rq->lock() and lower preempt_count ++ * and the preempt_enable() will end up enabling preemption (on ++ * PREEMPT_COUNT kernels). ++ */ ++ ++ finish_task_switch(prev); ++ preempt_enable(); ++ ++ if (current->set_child_tid) ++ put_user(task_pid_vnr(current), current->set_child_tid); ++ ++ calculate_sigpending(); ++} ++ ++/* ++ * context_switch - switch to the new MM and the new thread's register state. ++ */ ++static __always_inline void ++context_switch(struct rq *rq, struct task_struct *prev, ++ struct task_struct *next) ++{ ++ prepare_task_switch(rq, prev, next); ++ ++ /* ++ * For paravirt, this is coupled with an exit in switch_to to ++ * combine the page table reload and the switch backend into ++ * one hypercall. ++ */ ++ arch_start_context_switch(prev); ++ ++ /* ++ * kernel -> kernel lazy + transfer active ++ * user -> kernel lazy + mmgrab_lazy_tlb() active ++ * ++ * kernel -> user switch + mmdrop_lazy_tlb() active ++ * user -> user switch ++ */ ++ if (!next->mm) { // to kernel ++ enter_lazy_tlb(prev->active_mm, next); ++ ++ next->active_mm = prev->active_mm; ++ if (prev->mm) // from user ++ mmgrab_lazy_tlb(prev->active_mm); ++ else ++ prev->active_mm = NULL; ++ } else { // to user ++ membarrier_switch_mm(rq, prev->active_mm, next->mm); ++ /* ++ * sys_membarrier() requires an smp_mb() between setting ++ * rq->curr / membarrier_switch_mm() and returning to userspace. ++ * ++ * The below provides this either through switch_mm(), or in ++ * case 'prev->active_mm == next->mm' through ++ * finish_task_switch()'s mmdrop_lazy_tlb(). ++ */ ++ switch_mm_irqs_off(prev->active_mm, next->mm, next); ++ lru_gen_use_mm(next->mm); ++ ++ if (!prev->mm) { // from kernel ++ /* will mmdrop_lazy_tlb() in finish_task_switch(). */ ++ rq->prev_mm = prev->active_mm; ++ prev->active_mm = NULL; ++ } ++ } ++ prepare_lock_switch(rq, next); ++ ++ /* Here we just switch the register state and the stack. */ ++ switch_to(prev, next, prev); ++ barrier(); ++ ++ finish_task_switch(prev); ++} ++ ++/* ++ * nr_running, nr_uninterruptible and nr_context_switches: ++ * ++ * externally visible scheduler statistics: current number of runnable ++ * threads, total number of context switches performed since bootup. ++ */ ++unsigned int nr_running(void) ++{ ++ unsigned long i, sum = 0; ++ ++ for_each_online_cpu(i) ++ sum += cpu_rq(i)->nr_running; ++ ++ return sum; ++} ++ ++static unsigned long nr_uninterruptible(void) ++{ ++ unsigned long i, sum = 0; ++ ++ for_each_online_cpu(i) ++ sum += cpu_rq(i)->nr_uninterruptible; ++ ++ return sum; ++} ++ ++/* ++ * Check if only the current task is running on the CPU. ++ * ++ * Caution: this function does not check that the caller has disabled ++ * preemption, thus the result might have a time-of-check-to-time-of-use ++ * race. The caller is responsible to use it correctly, for example: ++ * ++ * - from a non-preemptible section (of course) ++ * ++ * - from a thread that is bound to a single CPU ++ * ++ * - in a loop with very short iterations (e.g. a polling loop) ++ */ ++bool single_task_running(void) ++{ ++ if (rq_load(raw_rq()) == 1) ++ return true; ++ else ++ return false; ++} ++EXPORT_SYMBOL(single_task_running); ++ ++unsigned long long nr_context_switches(void) ++{ ++ int cpu; ++ unsigned long long sum = 0; ++ ++ for_each_possible_cpu(cpu) ++ sum += cpu_rq(cpu)->nr_switches; ++ ++ return sum; ++} ++ ++/* ++ * Consumers of these two interfaces, like for example the cpufreq menu ++ * governor are using nonsensical data. Boosting frequency for a CPU that has ++ * IO-wait which might not even end up running the task when it does become ++ * runnable. ++ */ ++ ++unsigned int nr_iowait_cpu(int cpu) ++{ ++ return atomic_read(&cpu_rq(cpu)->nr_iowait); ++} ++ ++/* ++ * IO-wait accounting, and how it's mostly bollocks (on SMP). ++ * ++ * The idea behind IO-wait account is to account the idle time that we could ++ * have spend running if it were not for IO. That is, if we were to improve the ++ * storage performance, we'd have a proportional reduction in IO-wait time. ++ * ++ * This all works nicely on UP, where, when a task blocks on IO, we account ++ * idle time as IO-wait, because if the storage were faster, it could've been ++ * running and we'd not be idle. ++ * ++ * This has been extended to SMP, by doing the same for each CPU. This however ++ * is broken. ++ * ++ * Imagine for instance the case where two tasks block on one CPU, only the one ++ * CPU will have IO-wait accounted, while the other has regular idle. Even ++ * though, if the storage were faster, both could've ran at the same time, ++ * utilising both CPUs. ++ * ++ * This means, that when looking globally, the current IO-wait accounting on ++ * SMP is a lower bound, by reason of under accounting. ++ * ++ * Worse, since the numbers are provided per CPU, they are sometimes ++ * interpreted per CPU, and that is nonsensical. A blocked task isn't strictly ++ * associated with any one particular CPU, it can wake to another CPU than it ++ * blocked on. This means the per CPU IO-wait number is meaningless. ++ * ++ * Task CPU affinities can make all that even more 'interesting'. ++ */ ++ ++unsigned int nr_iowait(void) ++{ ++ unsigned long cpu, sum = 0; ++ ++ for_each_possible_cpu(cpu) ++ sum += nr_iowait_cpu(cpu); ++ ++ return sum; ++} ++ ++static unsigned long nr_active(void) ++{ ++ return nr_running() + nr_uninterruptible(); ++} ++ ++/* Variables and functions for calc_load */ ++static unsigned long calc_load_update; ++unsigned long avenrun[3]; ++EXPORT_SYMBOL(avenrun); ++ ++/** ++ * get_avenrun - get the load average array ++ * @loads: pointer to dest load array ++ * @offset: offset to add ++ * @shift: shift count to shift the result left ++ * ++ * These values are estimates at best, so no need for locking. ++ */ ++void get_avenrun(unsigned long *loads, unsigned long offset, int shift) ++{ ++ loads[0] = (avenrun[0] + offset) << shift; ++ loads[1] = (avenrun[1] + offset) << shift; ++ loads[2] = (avenrun[2] + offset) << shift; ++} ++ ++/* ++ * calc_load - update the avenrun load estimates every LOAD_FREQ seconds. ++ */ ++void calc_global_load(void) ++{ ++ long active; ++ ++ if (time_before(jiffies, READ_ONCE(calc_load_update))) ++ return; ++ active = nr_active() * FIXED_1; ++ ++ avenrun[0] = calc_load(avenrun[0], EXP_1, active); ++ avenrun[1] = calc_load(avenrun[1], EXP_5, active); ++ avenrun[2] = calc_load(avenrun[2], EXP_15, active); ++ ++ calc_load_update = jiffies + LOAD_FREQ; ++} ++ ++/** ++ * fixed_power_int - compute: x^n, in O(log n) time ++ * ++ * @x: base of the power ++ * @frac_bits: fractional bits of @x ++ * @n: power to raise @x to. ++ * ++ * By exploiting the relation between the definition of the natural power ++ * function: x^n := x*x*...*x (x multiplied by itself for n times), and ++ * the binary encoding of numbers used by computers: n := \Sum n_i * 2^i, ++ * (where: n_i \elem {0, 1}, the binary vector representing n), ++ * we find: x^n := x^(\Sum n_i * 2^i) := \Prod x^(n_i * 2^i), which is ++ * of course trivially computable in O(log_2 n), the length of our binary ++ * vector. ++ */ ++static unsigned long ++fixed_power_int(unsigned long x, unsigned int frac_bits, unsigned int n) ++{ ++ unsigned long result = 1UL << frac_bits; ++ ++ if (n) { ++ for (;;) { ++ if (n & 1) { ++ result *= x; ++ result += 1UL << (frac_bits - 1); ++ result >>= frac_bits; ++ } ++ n >>= 1; ++ if (!n) ++ break; ++ x *= x; ++ x += 1UL << (frac_bits - 1); ++ x >>= frac_bits; ++ } ++ } ++ ++ return result; ++} ++ ++/* ++ * a1 = a0 * e + a * (1 - e) ++ * ++ * a2 = a1 * e + a * (1 - e) ++ * = (a0 * e + a * (1 - e)) * e + a * (1 - e) ++ * = a0 * e^2 + a * (1 - e) * (1 + e) ++ * ++ * a3 = a2 * e + a * (1 - e) ++ * = (a0 * e^2 + a * (1 - e) * (1 + e)) * e + a * (1 - e) ++ * = a0 * e^3 + a * (1 - e) * (1 + e + e^2) ++ * ++ * ... ++ * ++ * an = a0 * e^n + a * (1 - e) * (1 + e + ... + e^n-1) [1] ++ * = a0 * e^n + a * (1 - e) * (1 - e^n)/(1 - e) ++ * = a0 * e^n + a * (1 - e^n) ++ * ++ * [1] application of the geometric series: ++ * ++ * n 1 - x^(n+1) ++ * S_n := \Sum x^i = ------------- ++ * i=0 1 - x ++ */ ++unsigned long ++calc_load_n(unsigned long load, unsigned long exp, ++ unsigned long active, unsigned int n) ++{ ++ return calc_load(load, fixed_power_int(exp, FSHIFT, n), active); ++} ++ ++DEFINE_PER_CPU(struct kernel_stat, kstat); ++DEFINE_PER_CPU(struct kernel_cpustat, kernel_cpustat) = { ++#ifdef CONFIG_NO_HZ_COMMON ++ .idle_sleeptime_seq = SEQCNT_ZERO(kernel_cpustat.idle_sleeptime_seq) ++#endif ++}; ++ ++EXPORT_PER_CPU_SYMBOL(kstat); ++EXPORT_PER_CPU_SYMBOL(kernel_cpustat); ++ ++#ifdef CONFIG_PARAVIRT ++static inline u64 steal_ticks(u64 steal) ++{ ++ if (unlikely(steal > NSEC_PER_SEC)) ++ return div_u64(steal, TICK_NSEC); ++ ++ return __iter_div_u64_rem(steal, TICK_NSEC, &steal); ++} ++#endif ++ ++#ifndef nsecs_to_cputime ++# define nsecs_to_cputime(__nsecs) nsecs_to_jiffies(__nsecs) ++#endif ++ ++/* ++ * On each tick, add the number of nanoseconds to the unbanked variables and ++ * once one tick's worth has accumulated, account it allowing for accurate ++ * sub-tick accounting and totals. Use the TICK_APPROX_NS to match the way we ++ * deduct nanoseconds. ++ */ ++static void pc_idle_time(struct rq *rq, struct task_struct *idle, unsigned long ns) ++{ ++ u64 *cpustat = kcpustat_this_cpu->cpustat; ++ unsigned long ticks; ++ ++ if (atomic_read(&rq->nr_iowait) > 0) { ++ rq->iowait_ns += ns; ++ if (rq->iowait_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->iowait_ns); ++ cpustat[CPUTIME_IOWAIT] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->iowait_ns %= JIFFY_NS; ++ } ++ } else { ++ rq->idle_ns += ns; ++ if (rq->idle_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->idle_ns); ++ cpustat[CPUTIME_IDLE] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->idle_ns %= JIFFY_NS; ++ } ++ } ++ acct_update_integrals(idle); ++} ++ ++static void pc_system_time(struct rq *rq, struct task_struct *p, ++ int hardirq_offset, unsigned long ns) ++{ ++ u64 *cpustat = kcpustat_this_cpu->cpustat; ++ unsigned long ticks; ++ ++ p->stime_ns += ns; ++ if (p->stime_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(p->stime_ns); ++ p->stime_ns %= JIFFY_NS; ++ p->stime += (__force u64)TICK_APPROX_NS * ticks; ++ account_group_system_time(p, TICK_APPROX_NS * ticks); ++ } ++ p->sched_time += ns; ++ account_group_exec_runtime(p, ns); ++ ++ if (hardirq_count() - hardirq_offset) { ++ rq->irq_ns += ns; ++ if (rq->irq_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->irq_ns); ++ cpustat[CPUTIME_IRQ] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->irq_ns %= JIFFY_NS; ++ } ++ } else if (in_serving_softirq() || this_cpu_ksoftirqd() == p) { ++ /* ++ * ksoftirqd time does not get accounted in cpu_softirq_time, so ++ * it has to be handled separately here. Naming it explicitly ++ * also catches it between batches, where it has reenabled bh ++ * and in_serving_softirq() no longer holds - which is where the ++ * context switch that lands us here happens. ++ */ ++ rq->softirq_ns += ns; ++ if (rq->softirq_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->softirq_ns); ++ cpustat[CPUTIME_SOFTIRQ] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->softirq_ns %= JIFFY_NS; ++ } ++ } else { ++ rq->system_ns += ns; ++ if (rq->system_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->system_ns); ++ cpustat[CPUTIME_SYSTEM] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->system_ns %= JIFFY_NS; ++ } ++ } ++ acct_update_integrals(p); ++} ++ ++static void pc_user_time(struct rq *rq, struct task_struct *p, unsigned long ns) ++{ ++ u64 *cpustat = kcpustat_this_cpu->cpustat; ++ unsigned long ticks; ++ ++ p->utime_ns += ns; ++ if (p->utime_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(p->utime_ns); ++ p->utime_ns %= JIFFY_NS; ++ p->utime += (__force u64)TICK_APPROX_NS * ticks; ++ account_group_user_time(p, TICK_APPROX_NS * ticks); ++ } ++ p->sched_time += ns; ++ account_group_exec_runtime(p, ns); ++ ++ if (task_nice(p) > 0 || idleprio_task(p)) { ++ rq->nice_ns += ns; ++ if (rq->nice_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->nice_ns); ++ cpustat[CPUTIME_NICE] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->nice_ns %= JIFFY_NS; ++ } ++ } else { ++ rq->user_ns += ns; ++ if (rq->user_ns >= JIFFY_NS) { ++ ticks = NS_TO_JIFFIES(rq->user_ns); ++ cpustat[CPUTIME_USER] += (__force u64)TICK_APPROX_NS * ticks; ++ rq->user_ns %= JIFFY_NS; ++ } ++ } ++ acct_update_integrals(p); ++} ++ ++/* ++ * This is called on clock ticks. ++ * Bank in p->sched_time the ns elapsed since the last tick or switch. ++ * CPU scheduler quota accounting is also performed here in microseconds. ++ */ ++static void update_cpu_clock_tick(struct rq *rq, struct task_struct *p) ++{ ++ s64 account_ns = rq->niffies - p->last_ran; ++ struct task_struct *idle = rq->idle; ++ ++ /* Accurate tick timekeeping */ ++ if (user_mode(get_irq_regs())) ++ pc_user_time(rq, p, account_ns); ++ else if (p != idle || (irq_count() != HARDIRQ_OFFSET)) { ++ pc_system_time(rq, p, HARDIRQ_OFFSET, account_ns); ++ } else ++ pc_idle_time(rq, idle, account_ns); ++ ++ /* time_slice accounting is done in usecs to avoid overflow on 32bit */ ++ if (p->policy != SCHED_FIFO && p != idle) ++ p->time_slice -= NS_TO_US(account_ns); ++ ++ p->last_ran = rq->niffies; ++} ++ ++/* ++ * This is called on context switches. ++ * Bank in p->sched_time the ns elapsed since the last tick or switch. ++ * CPU scheduler quota accounting is also performed here in microseconds. ++ */ ++static void update_cpu_clock_switch(struct rq *rq, struct task_struct *p) ++{ ++ s64 account_ns = rq->niffies - p->last_ran; ++ struct task_struct *idle = rq->idle; ++ ++ /* ++ * Accurate subtick timekeeping. There is no interrupt frame to sample ++ * here the way update_cpu_clock_tick() samples one: the entry code has ++ * already restored the previous value by the time irqentry_exit() ++ * reaches preempt_schedule_irq(), and a task that called schedule() ++ * itself never had a frame at all. What can be said for certain is that ++ * a task which never executes user code cannot have spent this interval ++ * in userspace, so charge all of those as system time. Without this the ++ * whole of every kernel thread's runtime that lands between two ticks ++ * is booked as user time, which is how irq threads, kworkers and ++ * ksoftirqd end up carrying utime they cannot possibly have accrued. ++ * ++ * A user task that blocks in a syscall genuinely does split its ++ * interval between the two modes, and knowing where it crossed over ++ * needs timestamped user/kernel transitions - vtime - that MuQSS ++ * deliberately does not take. Those keep the historical assumption. ++ */ ++ if (p == idle) ++ pc_idle_time(rq, idle, account_ns); ++ else if (is_user_task(p)) ++ pc_user_time(rq, p, account_ns); ++ else ++ pc_system_time(rq, p, 0, account_ns); ++ ++ /* time_slice accounting is done in usecs to avoid overflow on 32bit */ ++ if (p->policy != SCHED_FIFO && p != idle) ++ p->time_slice -= NS_TO_US(account_ns); ++} ++ ++#ifdef CONFIG_MUQSS_IOTIME ++/* ++ * The CPU time current has consumed so far, for measuring how long a stretch ++ * of work took the thread doing it. ++ * ++ * ->sched_time alone is not enough. It is banked at ticks and at context ++ * switches, and a kworker's switches happen at the ends of a whole batch of ++ * work items, so the delta across any one of them is usually a flat zero. The ++ * unbanked remainder has to be added in, and that means reading a clock: ++ * sched_clock_cpu() rather than rq->niffies, which is only refreshed under ++ * the rq lock and would be stale by up to a tick here. ++ * ++ * niffies is monotonised forward of the raw clock, so last_ran can be ahead ++ * of what we read and the remainder can come out negative. Drop it in that ++ * case; it is bounded by the skew between the two and the banked figure is ++ * still right. ++ * ++ * Interrupts are off across the read so the tick cannot land between taking ++ * ->sched_time and taking ->last_ran and have the interval counted in both. ++ * That also pins us to this CPU, which is what makes the subtraction a ++ * same-clock one. ++ */ ++u64 muqss_task_runtime_live(void) ++{ ++ struct task_struct *p = current; ++ unsigned long flags; ++ s64 remainder; ++ u64 ns, ran; ++ ++ local_irq_save(flags); ++ ns = p->sched_time; ++ ran = p->last_ran; ++ remainder = sched_clock_cpu(smp_processor_id()) - ran; ++ local_irq_restore(flags); ++ ++ if (likely(remainder > 0)) ++ ns += remainder; ++ ++ return ns; ++} ++#endif ++ ++/* ++ * Return any ns on the sched_clock that have not yet been accounted in ++ * @p in case that task is currently running. ++ * ++ * Called with task_rq_lock(p) held. ++ */ ++static inline u64 do_task_delta_exec(struct task_struct *p, struct rq *rq) ++{ ++ u64 ns = 0; ++ ++ /* ++ * Must be ->curr _and_ ->on_rq. If dequeued, we would ++ * project cycles that may never be accounted to this ++ * thread, breaking clock_gettime(). ++ */ ++ if (p == rq->curr && task_on_rq_queued(p)) { ++ update_clocks(rq); ++ ns = rq->niffies - p->last_ran; ++ } ++ ++ return ns; ++} ++ ++/* ++ * Return accounted runtime for the task. ++ * Return separately the current's pending runtime that have not been ++ * accounted yet. ++ */ ++unsigned long long task_sched_runtime(struct task_struct *p) ++{ ++ struct rq_flags rf; ++ struct rq *rq; ++ u64 ns; ++ ++#if defined(CONFIG_64BIT) && defined(CONFIG_SMP) ++ /* ++ * 64-bit doesn't need locks to atomically read a 64-bit value. ++ * So we have a optimisation chance when the task's delta_exec is 0. ++ * Reading ->on_cpu is racy, but this is ok. ++ * ++ * If we race with it leaving CPU, we'll take a lock. So we're correct. ++ * If we race with it entering CPU, unaccounted time is 0. This is ++ * indistinguishable from the read occurring a few cycles earlier. ++ * If we see ->on_cpu without ->on_rq, the task is leaving, and has ++ * been accounted, so we're correct here as well. ++ */ ++ if (!p->on_cpu || !task_on_rq_queued(p)) ++ return tsk_seruntime(p); ++#endif ++ ++ rq = task_rq_lock(p, &rf); ++ ns = p->sched_time + do_task_delta_exec(p, rq); ++ task_rq_unlock(rq, p, &rf); ++ ++ return ns; ++} ++ ++/* ++ * Functions to test for when SCHED_ISO tasks have used their allocated ++ * quota as real time scheduling and convert them back to SCHED_NORMAL. All ++ * data is modified only by the local runqueue during sched_tick with ++ * interrupts disabled. ++ */ ++ ++/* ++ * Test if SCHED_ISO tasks have run longer than their alloted period as RT ++ * tasks and set the refractory flag if necessary. There is 10% hysteresis ++ * for unsetting the flag. 115/128 is ~90/100 as a fast shift instead of a ++ * slow division. ++ */ ++static inline void iso_tick(struct rq *rq) ++{ ++ rq->iso_ticks = rq->iso_ticks * (ISO_PERIOD - 1) / ISO_PERIOD; ++ rq->iso_ticks += 100; ++ if (rq->iso_ticks > ISO_PERIOD * sched_iso_cpu) { ++ rq->iso_refractory = true; ++ if (unlikely(rq->iso_ticks > ISO_PERIOD * 100)) ++ rq->iso_ticks = ISO_PERIOD * 100; ++ } ++} ++ ++/* No SCHED_ISO task was running so decrease rq->iso_ticks */ ++static inline void no_iso_tick(struct rq *rq, int ticks) ++{ ++ if (rq->iso_ticks > 0 || rq->iso_refractory) { ++ rq->iso_ticks = rq->iso_ticks * (ISO_PERIOD - ticks) / ISO_PERIOD; ++ if (rq->iso_ticks < ISO_PERIOD * (sched_iso_cpu * 115 / 128)) { ++ rq->iso_refractory = false; ++ if (unlikely(rq->iso_ticks < 0)) ++ rq->iso_ticks = 0; ++ } ++ } ++} ++ ++/* This manages tasks that have run out of timeslice during a sched_tick */ ++static void task_running_tick(struct rq *rq) ++{ ++ struct task_struct *p = rq->curr; ++ ++ /* ++ * If a SCHED_ISO task is running we increment the iso_ticks. In ++ * order to prevent SCHED_ISO tasks from causing starvation in the ++ * presence of true RT tasks we account those as iso_ticks as well. ++ */ ++ if (rt_task(p) || task_running_iso(p)) ++ iso_tick(rq); ++ else ++ no_iso_tick(rq, 1); ++ ++ /* SCHED_FIFO tasks never run out of timeslice. */ ++ if (p->policy == SCHED_FIFO) ++ return; ++ ++ if (iso_task(p)) { ++ if (task_running_iso(p)) { ++ if (rq->iso_refractory) { ++ /* ++ * SCHED_ISO task is running as RT and limit ++ * has been hit. Force it to reschedule as ++ * SCHED_NORMAL by zeroing its time_slice ++ */ ++ p->time_slice = 0; ++ } ++ } else if (!rq->iso_refractory) { ++ /* Can now run again ISO. Reschedule to pick up prio */ ++ goto out_resched; ++ } ++ } ++ ++ /* ++ * Tasks that were scheduled in the first half of a tick are not ++ * allowed to run into the 2nd half of the next tick if they will ++ * run out of time slice in the interim. Otherwise, if they have ++ * less than RESCHED_US μs of time slice left they will be rescheduled. ++ * Dither is used as a backup for when hrexpiry is disabled or high res ++ * timers not configured in. ++ */ ++ if (p->time_slice - rq->dither >= RESCHED_US) ++ return; ++out_resched: ++ rq_lock(rq); ++ __set_tsk_resched(p); ++ rq_unlock(rq); ++} ++ ++static inline void task_tick(struct rq *rq) ++{ ++ if (!rq_idle(rq)) ++ task_running_tick(rq); ++ else if (rq->last_jiffy > rq->last_scheduler_tick) ++ no_iso_tick(rq, rq->last_jiffy - rq->last_scheduler_tick); ++} ++ ++#ifdef CONFIG_NO_HZ_FULL ++/* ++ * We can stop the timer tick any time highres timers are active since ++ * we rely entirely on highres timeouts for task expiry rescheduling. ++ */ ++static void sched_stop_tick(struct rq *rq, int cpu) ++{ ++ if (!hrexpiry_enabled(rq)) ++ return; ++ if (!tick_nohz_full_enabled()) ++ return; ++ if (!tick_nohz_full_cpu(cpu)) ++ return; ++ tick_nohz_dep_clear_cpu(cpu, TICK_DEP_BIT_SCHED); ++} ++ ++static inline void sched_start_tick(struct rq *rq, int cpu) ++{ ++ tick_nohz_dep_set_cpu(cpu, TICK_DEP_BIT_SCHED); ++} ++ ++struct tick_work { ++ int cpu; ++ atomic_t state; ++ struct delayed_work work; ++}; ++/* Values for ->state, see diagram below. */ ++#define TICK_SCHED_REMOTE_OFFLINE 0 ++#define TICK_SCHED_REMOTE_OFFLINING 1 ++#define TICK_SCHED_REMOTE_RUNNING 2 ++ ++/* ++ * State diagram for ->state: ++ * ++ * ++ * TICK_SCHED_REMOTE_OFFLINE ++ * | ^ ++ * | | ++ * | | sched_tick_remote() ++ * | | ++ * | | ++ * +--TICK_SCHED_REMOTE_OFFLINING ++ * | ^ ++ * | | ++ * sched_tick_start() | | sched_tick_stop() ++ * | | ++ * V | ++ * TICK_SCHED_REMOTE_RUNNING ++ * ++ * ++ * Other transitions get WARN_ON_ONCE(), except that sched_tick_remote() ++ * and sched_tick_start() are happy to leave the state in RUNNING. ++ */ ++ ++static struct tick_work __percpu *tick_work_cpu; ++ ++static void sched_tick_remote(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct tick_work *twork = container_of(dwork, struct tick_work, work); ++ int cpu = twork->cpu; ++ struct rq *rq = cpu_rq(cpu); ++ struct task_struct *curr; ++ u64 delta; ++ int os; ++ ++ /* ++ * Handle the tick only if it appears the remote CPU is running in full ++ * dynticks mode. The check is racy by nature, but missing a tick or ++ * having one too much is no big deal because the scheduler tick updates ++ * statistics and checks timeslices in a time-independent way, regardless ++ * of when exactly it is running. ++ */ ++ if (!tick_nohz_tick_stopped_cpu(cpu)) ++ goto out_requeue; ++ ++ rq_lock_irq(rq); ++ if (cpu_is_offline(cpu)) ++ goto out_unlock; ++ ++ curr = rq->curr; ++ update_rq_clock(rq); ++ ++ if (!is_idle_task(curr)) { ++ /* ++ * Make sure the next tick runs within a reasonable ++ * amount of time. ++ */ ++ delta = rq_clock_task(rq) - curr->last_ran; ++ WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 3); ++ } ++ /* ++ * task_tick() takes the rq lock itself when it needs to force a ++ * reschedule so we must drop it here, keeping interrupts disabled ++ * to match the context it is called in from sched_tick(). ++ */ ++ rq_unlock(rq); ++ task_tick(rq); ++ local_irq_enable(); ++ goto out_requeue; ++ ++out_unlock: ++ rq_unlock_irq(rq, NULL); ++ ++out_requeue: ++ ++ /* ++ * Run the remote tick once per second (1Hz). This arbitrary ++ * frequency is large enough to avoid overload but short enough ++ * to keep scheduler internal stats reasonably up to date. But ++ * first update state to reflect hotplug activity if required. ++ */ ++ os = atomic_fetch_add_unless(&twork->state, -1, TICK_SCHED_REMOTE_RUNNING); ++ WARN_ON_ONCE(os == TICK_SCHED_REMOTE_OFFLINE); ++ if (os == TICK_SCHED_REMOTE_RUNNING) ++ queue_delayed_work(system_unbound_wq, dwork, HZ); ++} ++ ++static void sched_tick_start(int cpu) ++{ ++ struct tick_work *twork; ++ int os; ++ ++ if (housekeeping_cpu(cpu, HK_TYPE_TICK)) ++ return; ++ ++ WARN_ON_ONCE(!tick_work_cpu); ++ ++ twork = per_cpu_ptr(tick_work_cpu, cpu); ++ os = atomic_xchg(&twork->state, TICK_SCHED_REMOTE_RUNNING); ++ WARN_ON_ONCE(os == TICK_SCHED_REMOTE_RUNNING); ++ if (os == TICK_SCHED_REMOTE_OFFLINE) { ++ twork->cpu = cpu; ++ INIT_DELAYED_WORK(&twork->work, sched_tick_remote); ++ queue_delayed_work(system_unbound_wq, &twork->work, HZ); ++ } ++} ++ ++#ifdef CONFIG_HOTPLUG_CPU ++static void sched_tick_stop(int cpu) ++{ ++ struct tick_work *twork; ++ int os; ++ ++ if (housekeeping_cpu(cpu, HK_TYPE_TICK)) ++ return; ++ ++ WARN_ON_ONCE(!tick_work_cpu); ++ ++ twork = per_cpu_ptr(tick_work_cpu, cpu); ++ /* There cannot be competing actions, but don't rely on stop-machine. */ ++ os = atomic_xchg(&twork->state, TICK_SCHED_REMOTE_OFFLINING); ++ WARN_ON_ONCE(os != TICK_SCHED_REMOTE_RUNNING); ++ /* Don't cancel, as this would mess up the state machine. */ ++} ++#endif /* CONFIG_HOTPLUG_CPU */ ++ ++int __init sched_tick_offload_init(void) ++{ ++ tick_work_cpu = alloc_percpu(struct tick_work); ++ BUG_ON(!tick_work_cpu); ++ return 0; ++} ++ ++#else /* !CONFIG_NO_HZ_FULL */ ++static inline void sched_stop_tick(struct rq *rq, int cpu) {} ++static inline void sched_start_tick(struct rq *rq, int cpu) {} ++static inline void sched_tick_start(int cpu) { } ++static inline void sched_tick_stop(int cpu) { } ++#endif ++ ++/* ++ * This function gets called by the timer code, with HZ frequency. ++ * We call it with interrupts disabled. ++ */ ++void sched_tick(void) ++{ ++ int cpu __maybe_unused = smp_processor_id(); ++ struct rq *rq = cpu_rq(cpu); ++ ++ arch_scale_freq_tick(); ++ sched_clock_tick(); ++ update_clocks(rq); ++ update_load_avg(rq, 0); ++ update_cpu_clock_tick(rq, rq->curr); ++ task_tick(rq); ++ rq->last_scheduler_tick = rq->last_jiffy; ++ rq->last_tick = rq->clock; ++ perf_event_task_tick(); ++ sched_stop_tick(rq, cpu); ++} ++ ++#if defined(CONFIG_PREEMPTION) && (defined(CONFIG_DEBUG_PREEMPT) || \ ++ defined(CONFIG_TRACE_PREEMPT_TOGGLE)) ++/* ++ * If the value passed in is equal to the current preempt count ++ * then we just disabled preemption. Start timing the latency. ++ */ ++static inline void preempt_latency_start(int val) ++{ ++ if (preempt_count() == val) { ++ unsigned long ip = get_lock_parent_ip(); ++#ifdef CONFIG_DEBUG_PREEMPT ++ current->preempt_disable_ip = ip; ++#endif ++ trace_preempt_off(CALLER_ADDR0, ip); ++ } ++} ++ ++void preempt_count_add(int val) ++{ ++#ifdef CONFIG_DEBUG_PREEMPT ++ /* ++ * Underflow? ++ */ ++ if (DEBUG_LOCKS_WARN_ON((preempt_count() < 0))) ++ return; ++#endif ++ __preempt_count_add(val); ++#ifdef CONFIG_DEBUG_PREEMPT ++ /* ++ * Spinlock count overflowing soon? ++ */ ++ DEBUG_LOCKS_WARN_ON((preempt_count() & PREEMPT_MASK) >= ++ PREEMPT_MASK - 10); ++#endif ++ preempt_latency_start(val); ++} ++EXPORT_SYMBOL(preempt_count_add); ++NOKPROBE_SYMBOL(preempt_count_add); ++ ++/* ++ * If the value passed in equals to the current preempt count ++ * then we just enabled preemption. Stop timing the latency. ++ */ ++static inline void preempt_latency_stop(int val) ++{ ++ if (preempt_count() == val) ++ trace_preempt_on(CALLER_ADDR0, get_lock_parent_ip()); ++} ++ ++void preempt_count_sub(int val) ++{ ++#ifdef CONFIG_DEBUG_PREEMPT ++ /* ++ * Underflow? ++ */ ++ if (DEBUG_LOCKS_WARN_ON(val > preempt_count())) ++ return; ++ /* ++ * Is the spinlock portion underflowing? ++ */ ++ if (DEBUG_LOCKS_WARN_ON((val < PREEMPT_MASK) && ++ !(preempt_count() & PREEMPT_MASK))) ++ return; ++#endif ++ ++ preempt_latency_stop(val); ++ __preempt_count_sub(val); ++} ++EXPORT_SYMBOL(preempt_count_sub); ++NOKPROBE_SYMBOL(preempt_count_sub); ++ ++#else ++static inline void preempt_latency_start(int val) { } ++static inline void preempt_latency_stop(int val) { } ++#endif ++ ++static inline unsigned long get_preempt_disable_ip(struct task_struct *p) ++{ ++#ifdef CONFIG_DEBUG_PREEMPT ++ return p->preempt_disable_ip; ++#else ++ return 0; ++#endif ++} ++ ++/* ++ * The time_slice is only refilled when it is empty and that is when we set a ++ * new deadline. Make sure update_clocks has been called recently to update ++ * rq->niffies. ++ */ ++static void time_slice_expired(struct task_struct *p, struct rq *rq) ++{ ++ p->time_slice = timeslice(); ++ /* ++ * This assignment is absolute, so the charge has to be folded in here ++ * rather than left to enqueue_task(), or expiry would reset the task ++ * to an uncharged baseline. ++ */ ++ p->deadline = rq->niffies + task_deadline_diff(p) + ++ consume_task_penalty(p); ++#ifdef CONFIG_SMT_NICE ++ if (!p->mm) ++ p->smt_bias = 0; ++ else if (rt_task(p)) ++ p->smt_bias = 1 << 30; ++ else if (task_running_iso(p)) ++ p->smt_bias = 1 << 29; ++ else if (idleprio_task(p)) { ++ if (task_running_idle(p)) ++ p->smt_bias = 0; ++ else ++ p->smt_bias = 1; ++ } else if (--p->smt_bias < 1) ++ p->smt_bias = MAX_PRIO - p->static_prio; ++#endif ++} ++ ++/* ++ * Timeslices below RESCHED_US are considered as good as expired as there's no ++ * point rescheduling when there's so little time left. SCHED_BATCH tasks ++ * have been flagged be not latency sensitive and likely to be fully CPU ++ * bound so every time they're rescheduled they have their time_slice ++ * refilled, but get a new later deadline to have little effect on ++ * SCHED_NORMAL tasks. ++ ++ */ ++static inline void check_deadline(struct task_struct *p, struct rq *rq) ++{ ++ if (p->time_slice < RESCHED_US || batch_task(p)) ++ time_slice_expired(p, rq); ++} ++ ++/* ++ * Task selection with skiplists is a simple matter of picking off the first ++ * task in the sorted list, an O(1) operation. The lookup is amortised O(1) ++ * being bound to the number of processors. ++ * ++ * Runqueues are selectively locked based on their unlocked data and then ++ * unlocked if not needed. At most 3 locks will be held at any time and are ++ * released as soon as they're no longer needed. All balancing between CPUs ++ * is thus done here in an extremely simple first come best fit manner. ++ * ++ * This iterates over runqueues in cache locality order. In interactive mode ++ * it iterates over all CPUs and finds the task with the best key/deadline. ++ * In non-interactive mode it will only take a task if it's from the current ++ * runqueue or a runqueue with more tasks than the current one with a better ++ * key/deadline. ++ */ ++#ifdef CONFIG_SMP ++static inline struct task_struct ++*earliest_deadline_task(struct rq *rq, int cpu, struct task_struct *idle) ++{ ++ struct rq *locked = NULL, *chosen = NULL; ++ struct task_struct *edt = idle; ++ int i, best_entries = 0; ++ u64 best_key = ~0ULL; ++ ++ for (i = 0; i < total_runqueues; i++) { ++ skiplist *sl = rq->sl_order[i]; ++ struct rq *other_rq = NULL; ++ skiplist_node *next; ++ int entries; ++ ++ entries = READ_ONCE(sl->entries); ++ /* ++ * Check for queued entres lockless first. The local runqueue ++ * is locked so entries will always be accurate. ++ */ ++ if (!sched_interactive) { ++ /* ++ * Don't reschedule balance across nodes unless the CPU ++ * is idle. Non-interactive needs the rq: cpu is read ++ * before the entries filter. ++ */ ++ other_rq = rq_order(rq, i); ++ if (edt != idle && rq->cpu_locality[other_rq->cpu] > LOCALITY_SMP) ++ break; ++ if (entries <= best_entries) ++ continue; ++ } else if (!entries) ++ continue; ++ ++ /* if (i) implies other_rq != rq */ ++ if (i) { ++ /* Check for best id queued lockless first */ ++ if (READ_ONCE(sl->best_key) >= best_key) ++ continue; ++ ++ other_rq = rq_order(rq, i); ++ if (unlikely(!trylock_rq(rq, other_rq))) ++ continue; ++ ++ /* Need to reevaluate entries after locking */ ++ entries = sl->entries; ++ if (unlikely(!entries)) { ++ unlock_rq(other_rq); ++ continue; ++ } ++ } ++ ++ next = sl->header; ++ /* ++ * In interactive mode we check beyond the best entry on other ++ * runqueues if we can't get the best for smt or affinity ++ * reasons. ++ */ ++ while ((next = next->next[0]) != sl->header) { ++ struct task_struct *p; ++ u64 key = next->key; ++ ++ /* Reevaluate key after locking */ ++ if (key >= best_key) ++ break; ++ ++ p = container_of(next, struct task_struct, node); ++ if (!smt_schedule(p, rq)) { ++ if (i && !sched_interactive) ++ break; ++ continue; ++ } ++ ++ if (sched_other_cpu(p, cpu)) { ++ if (sched_interactive || !i) ++ continue; ++ break; ++ } ++ /* Make sure affinity is ok */ ++ if (i) { ++ /* From this point on p is the best so far */ ++ if (locked) ++ unlock_rq(locked); ++ chosen = locked = other_rq; ++ } ++ best_entries = entries; ++ best_key = key; ++ edt = p; ++ break; ++ } ++ /* rq->preempting is a hint only as the state may have changed ++ * since it was set with the resched call but if we have met ++ * the condition we can break out here. */ ++ if (edt == rq->preempting) ++ break; ++ if (i && other_rq != chosen) ++ unlock_rq(other_rq); ++ } ++ ++ if (likely(edt != idle)) ++ take_task(rq, cpu, edt); ++ ++ if (locked) ++ unlock_rq(locked); ++ ++ rq->preempting = NULL; ++ ++ return edt; ++} ++#else /* CONFIG_SMP */ ++static inline struct task_struct ++*earliest_deadline_task(struct rq *rq, int cpu, struct task_struct *idle) ++{ ++ struct task_struct *edt; ++ ++ if (unlikely(!rq->sl->entries)) ++ return idle; ++ edt = container_of(rq->node->next[0], struct task_struct, node); ++ take_task(rq, cpu, edt); ++ return edt; ++} ++#endif /* CONFIG_SMP */ ++ ++/* ++ * Print scheduling while atomic bug: ++ */ ++static noinline void __schedule_bug(struct task_struct *prev) ++{ ++ /* Save this before calling printk(), since that will clobber it */ ++ unsigned long preempt_disable_ip = get_preempt_disable_ip(current); ++ ++ if (oops_in_progress) ++ return; ++ ++ printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n", ++ prev->comm, prev->pid, preempt_count()); ++ ++ debug_show_held_locks(prev); ++ print_modules(); ++ if (irqs_disabled()) ++ print_irqtrace_events(prev); ++ if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) ++ && in_atomic_preempt_off()) { ++ pr_err("Preemption disabled at:"); ++ print_ip_sym(KERN_ERR, preempt_disable_ip); ++ } ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++ ++/* ++ * Various schedule()-time debugging checks and statistics: ++ */ ++static inline void schedule_debug(struct task_struct *prev, bool preempt) ++{ ++#ifdef CONFIG_SCHED_STACK_END_CHECK ++ if (task_stack_end_corrupted(prev)) ++ panic("corrupted stack end detected inside scheduler\n"); ++ ++ if (task_scs_end_corrupted(prev)) ++ panic("corrupted shadow stack detected inside scheduler\n"); ++#endif ++ ++#ifdef CONFIG_DEBUG_ATOMIC_SLEEP ++ if (!preempt && prev->__state && prev->non_block_count) { ++ printk(KERN_ERR "BUG: scheduling in a non-blocking section: %s/%d/%i\n", ++ prev->comm, prev->pid, prev->non_block_count); ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++ } ++#endif ++ ++ if (unlikely(in_atomic_preempt_off())) { ++ __schedule_bug(prev); ++ preempt_count_set(PREEMPT_DISABLED); ++ } ++ rcu_sleep_check(); ++ SCHED_WARN_ON(ct_state() == CT_STATE_USER); ++ ++ profile_hit(SCHED_PROFILING, __builtin_return_address(0)); ++ ++ schedstat_inc(this_rq()->sched_count); ++} ++ ++/* ++ * The currently running task's information is all stored in rq local data ++ * which is only modified by the local CPU. ++ */ ++static inline void set_rq_task(struct rq *rq, struct task_struct *p) ++{ ++ if (p == rq->idle || p->policy == SCHED_FIFO) ++ hrexpiry_clear(rq); ++ else ++ hrexpiry_start(rq, US_TO_NS(p->time_slice)); ++ if (rq->clock - rq->last_tick > HALF_JIFFY_NS) ++ rq->dither = 0; ++ else ++ rq->dither = rq_dither(rq); ++ ++ rq->rq_deadline = p->deadline; ++ rq->rq_prio = p->prio; ++#ifdef CONFIG_SMT_NICE ++ rq->rq_mm = p->mm; ++ rq->rq_smt_bias = p->smt_bias; ++#endif ++} ++ ++#ifdef CONFIG_SMT_NICE ++static void check_no_siblings(struct rq __maybe_unused *this_rq) {} ++static void wake_no_siblings(struct rq __maybe_unused *this_rq) {} ++static void (*check_siblings)(struct rq *this_rq) = &check_no_siblings; ++static void (*wake_siblings)(struct rq *this_rq) = &wake_no_siblings; ++ ++/* Iterate over smt siblings when we've scheduled a process on cpu and decide ++ * whether they should continue running or be descheduled. */ ++static void check_smt_siblings(struct rq *this_rq) ++{ ++ int other_cpu; ++ ++ for_each_cpu(other_cpu, &this_rq->thread_mask) { ++ struct task_struct *p; ++ struct rq *rq; ++ ++ rq = cpu_rq(other_cpu); ++ if (rq_idle(rq)) ++ continue; ++ p = rq->curr; ++ if (!smt_schedule(p, this_rq)) ++ resched_curr(rq); ++ } ++} ++ ++static void wake_smt_siblings(struct rq *this_rq) ++{ ++ int other_cpu; ++ ++ for_each_cpu(other_cpu, &this_rq->thread_mask) { ++ struct rq *rq; ++ ++ rq = cpu_rq(other_cpu); ++ if (rq_idle(rq)) ++ resched_idle(rq); ++ } ++} ++#else ++static void check_siblings(struct rq __maybe_unused *this_rq) {} ++static void wake_siblings(struct rq __maybe_unused *this_rq) {} ++#endif ++ ++/* ++ * schedule() is the main scheduler function. ++ * ++ * The main means of driving the scheduler and thus entering this function are: ++ * ++ * 1. Explicit blocking: mutex, semaphore, waitqueue, etc. ++ * ++ * 2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return ++ * paths. For example, see arch/x86/entry_64.S. ++ * ++ * To drive preemption between tasks, the scheduler sets the flag in timer ++ * interrupt handler sched_tick(). ++ * ++ * 3. Wakeups don't really cause entry into schedule(). They add a ++ * task to the run-queue and that's it. ++ * ++ * Now, if the new task added to the run-queue preempts the current ++ * task, then the wakeup sets TIF_NEED_RESCHED and schedule() gets ++ * called on the nearest possible occasion: ++ * ++ * - If the kernel is preemptible (CONFIG_PREEMPTION=y): ++ * ++ * - in syscall or exception context, at the next outmost ++ * preempt_enable(). (this might be as soon as the wake_up()'s ++ * spin_unlock()!) ++ * ++ * - in IRQ context, return from interrupt-handler to ++ * preemptible context ++ * ++ * - If the kernel is not preemptible (CONFIG_PREEMPTION is not set) ++ * then at the next: ++ * ++ * - cond_resched() call ++ * - explicit schedule() call ++ * - return from syscall or exception to user-space ++ * - return from interrupt-handler to user-space ++ * ++ * WARNING: must be called with preemption disabled! ++ */ ++#define SM_IDLE (-1) ++#define SM_NONE 0 ++#define SM_PREEMPT 1 ++#define SM_RTLOCK_WAIT 2 ++ ++static void __sched notrace __schedule(int sched_mode) ++{ ++ struct task_struct *prev, *next, *idle; ++ unsigned long *switch_count; ++ unsigned long prev_state; ++ bool deactivate = false; ++ struct rq *rq; ++ u64 niffies; ++ int cpu; ++ /* ++ * On PREEMPT_RT, SM_RTLOCK_WAIT is noted as a preemption by ++ * schedule_debug() and RCU. Task-state changes still treat ++ * only SM_PREEMPT as preemption so a sleeping lock wait can ++ * deactivate. ++ */ ++ bool preempt = sched_mode > SM_NONE; ++ ++ cpu = smp_processor_id(); ++ rq = cpu_rq(cpu); ++ prev = rq->curr; ++ idle = rq->idle; ++ ++ schedule_debug(prev, preempt); ++ ++ klp_sched_try_switch(prev); ++ ++ local_irq_disable(); ++ rcu_note_context_switch(preempt); ++ ++ /* ++ * Make sure that signal_pending_state()->signal_pending() below ++ * can't be reordered with __set_current_state(TASK_INTERRUPTIBLE) ++ * done by the caller to avoid the race with signal_wake_up(): ++ * ++ * __set_current_state(@state) signal_wake_up() ++ * schedule() set_tsk_thread_flag(p, TIF_SIGPENDING) ++ * wake_up_state(p, state) ++ * LOCK rq->lock LOCK p->pi_state ++ * smp_mb__after_spinlock() smp_mb__after_spinlock() ++ * if (signal_pending_state()) if (p->__state & @state) ++ * ++ * Also, the membarrier system call requires a full memory barrier ++ * after coming from user-space, before storing to rq->curr. ++ */ ++ rq_lock(rq); ++ smp_mb__after_spinlock(); ++#ifdef CONFIG_SMP ++ if (rq->preempt) { ++ /* ++ * Make sure resched_curr hasn't triggered a preemption ++ * locklessly on a task that has since scheduled away. Spurious ++ * wakeup of idle is okay though. ++ */ ++ if (unlikely(sched_mode == SM_PREEMPT && prev != idle && ++ !test_tsk_need_resched(prev))) { ++ rq->preempt = NULL; ++ clear_preempt_need_resched(); ++ rq_unlock_irq(rq, NULL); ++ return; ++ } ++ rq->preempt = NULL; ++ } ++ migrate_disable_switch(rq, prev); ++#endif ++ ++ /* ++ * Defer hrexpiry start/cancel until we leave __schedule so we do not ++ * thrash the oneshot clockevent under the rq lock (mainline hrtick). ++ */ ++ hrexpiry_schedule_enter(rq); ++ ++ switch_count = &prev->nivcsw; ++ ++ /* Task state changes only consider SM_PREEMPT as preemption */ ++ preempt = sched_mode == SM_PREEMPT; ++ ++ /* ++ * We must load prev->__state once (task_struct::state is volatile), such ++ * that: ++ * ++ * - we form a control dependency vs deactivate_task() below. ++ * - ptrace_{,un}freeze_traced() can change ->state underneath us. ++ */ ++ prev_state = prev->__state; ++ if (!preempt && prev_state) { ++ if (signal_pending_state(prev_state, prev)) { ++ prev->__state = TASK_RUNNING; ++ } else { ++ prev->sched_contributes_to_load = ++ (prev_state & TASK_UNINTERRUPTIBLE) && ++ !(prev_state & TASK_NOLOAD) && ++ !(prev_state & TASK_FROZEN); ++ ++ if (prev->sched_contributes_to_load) ++ rq->nr_uninterruptible++; ++ ++ /* ++ * __schedule() ttwu() ++ * prev_state = prev->__state; if (p->on_rq && ...) ++ * if (prev_state) goto out; ++ * p->on_rq = 0; smp_acquire__after_ctrl_dep(); ++ * p->__state = TASK_WAKING ++ * ++ * Where __schedule() and ttwu() have matching control dependencies. ++ * ++ * After this, schedule() must not care about p->__state any more. ++ */ ++ deactivate = true; ++ ++ if (prev->in_iowait) { ++ atomic_inc(&rq->nr_iowait); ++ delayacct_blkio_start(); ++ } ++ } ++ switch_count = &prev->nvcsw; ++ } ++ ++ /* ++ * Store the niffy value here for use by the next task's last_ran ++ * below to avoid losing niffies due to update_clocks being called ++ * again after this point. ++ */ ++ update_clocks(rq); ++ niffies = rq->niffies; ++ update_cpu_clock_switch(rq, prev); ++ ++ clear_tsk_need_resched(prev); ++ clear_preempt_need_resched(); ++ ++ if (idle != prev) { ++ check_deadline(prev, rq); ++ return_task(prev, rq, cpu, deactivate); ++ } ++ ++ next = earliest_deadline_task(rq, cpu, idle); ++ if (likely(next->prio != PRIO_LIMIT)) ++ clear_cpuidle_map(cpu); ++ else { ++#ifdef CONFIG_SMP ++ if (prev != idle) ++ rq->idle_jiffy = jiffies; ++#endif ++ set_cpuidle_map(cpu); ++ update_load_avg(rq, 0); ++ } ++ ++ set_rq_task(rq, next); ++ next->last_ran = niffies; ++ ++ if (likely(prev != next)) { ++ /* ++ * Don't reschedule an idle task or deactivated tasks ++ */ ++ if (prev == idle) ++ inc_nr_running(rq); ++ else if (!deactivate) ++ resched_suitable_idle(prev); ++ /* ++ * The task on the CPU is not on the skiplist, so it holds a ++ * count of its own on top of the queued ones. nr_running only ++ * changes when that slot is created or destroyed - i.e. on the ++ * idle transitions above and below - but rt_nr_running also ++ * depends on *which* task holds it, so hand it over on every ++ * switch. Leaving that to the idle transitions leaked a count ++ * on every rt -> non-rt switch and underflowed on the reverse. ++ */ ++ if (prev != idle && rt_task(prev)) ++ rq->rt_nr_running--; ++ if (next != idle && rt_task(next)) ++ rq->rt_nr_running++; ++ if (unlikely(next == idle)) { ++ dec_nr_running(rq); ++ wake_siblings(rq); ++ } else ++ check_siblings(rq); ++ rq->nr_switches++; ++ /* ++ * RCU users of rcu_dereference(rq->curr) may not see ++ * changes to task_struct made by pick_next_task(). ++ */ ++ RCU_INIT_POINTER(rq->curr, next); ++ /* ++ * The membarrier system call requires each architecture ++ * to have a full memory barrier after updating ++ * rq->curr, before returning to user-space. ++ * ++ * Here are the schemes providing that barrier on the ++ * various architectures: ++ * - mm ? switch_mm() : mmdrop() for x86, s390, sparc, PowerPC. ++ * switch_mm() rely on membarrier_arch_switch_mm() on PowerPC. ++ * - finish_lock_switch() for weakly-ordered ++ * architectures where spin_unlock is a full barrier, ++ * - switch_to() for arm64 (weakly-ordered, spin_unlock ++ * is a RELEASE barrier), ++ */ ++ ++*switch_count; ++ ++ psi_sched_switch(prev, next, !task_on_rq_queued(prev)); ++ ++ trace_sched_switch(preempt, prev, next, prev->__state); ++ context_switch(rq, prev, next); /* unlocks the rq via finish_lock_switch */ ++ } else { ++ check_siblings(rq); ++ hrexpiry_schedule_exit(rq); ++ rq_unlock(rq); ++ local_irq_enable(); ++ } ++} ++ ++void __noreturn do_task_dead(void) ++{ ++ /* Causes final put_task_struct in finish_task_switch(). */ ++ set_special_state(TASK_DEAD); ++ ++ /* Tell freezer to ignore us: */ ++ current->flags |= PF_NOFREEZE; ++ __schedule(SM_NONE); ++ BUG(); ++ ++ /* Avoid "noreturn function does return" - but don't continue if BUG() is a NOP: */ ++ for (;;) ++ cpu_relax(); ++} ++ ++static inline void sched_submit_work(struct task_struct *tsk) ++{ ++ unsigned int task_flags; ++ ++ if (!tsk->__state) ++ return; ++ ++ task_flags = tsk->flags; ++ /* ++ * If a worker went to sleep, notify and ask workqueue whether ++ * it wants to wake up a task to maintain concurrency. ++ * As this function is called inside the schedule() context, ++ * we disable preemption to avoid it calling schedule() again ++ * in the possible wakeup of a kworker and because wq_worker_sleeping() ++ * requires it. ++ */ ++ if (task_flags & (PF_WQ_WORKER | PF_IO_WORKER)) { ++ preempt_disable(); ++ if (task_flags & PF_WQ_WORKER) ++ wq_worker_sleeping(tsk); ++ else ++ io_wq_worker_sleeping(tsk); ++ preempt_enable_no_resched(); ++ } ++ ++ if (tsk->pi_blocked_on) ++ return; ++ ++ /* ++ * If we are going to sleep and we have plugged IO queued, ++ * make sure to submit it to avoid deadlocks. ++ */ ++ blk_flush_plug(tsk->plug, true); ++} ++ ++static inline void sched_update_worker(struct task_struct *tsk) ++{ ++ if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER)) { ++ if (tsk->flags & PF_WQ_WORKER) ++ wq_worker_running(tsk); ++ else ++ io_wq_worker_running(tsk); ++ } ++} ++ ++static __always_inline void __schedule_loop(int sched_mode) ++{ ++ do { ++ preempt_disable(); ++ __schedule(sched_mode); ++ sched_preempt_enable_no_resched(); ++ } while (need_resched()); ++} ++ ++asmlinkage __visible void __sched schedule(void) ++{ ++ struct task_struct *tsk = current; ++ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(!tsk->sched_rt_mutex); ++#endif ++ sched_submit_work(tsk); ++ __schedule_loop(SM_NONE); ++ sched_update_worker(tsk); ++} ++ ++EXPORT_SYMBOL(schedule); ++ ++/* ++ * synchronize_rcu_tasks() makes sure that no task is stuck in preempted ++ * state (have scheduled out non-voluntarily) by making sure that all ++ * tasks have either left the run queue or have gone into user space. ++ * As idle tasks do not do either, they must not ever be preempted ++ * (schedule out non-voluntarily). ++ * ++ * schedule_idle() is similar to schedule_preempt_disable() except that it ++ * never enables preemption because it does not call sched_submit_work(). ++ */ ++void __sched schedule_idle(void) ++{ ++ /* ++ * As this skips calling sched_submit_work(), which the idle task does ++ * regardless because that function is a nop when the task is in a ++ * TASK_RUNNING state, make sure this isn't used someplace that the ++ * current task can be in any other state. Note, idle is always in the ++ * TASK_RUNNING state. ++ */ ++ WARN_ON_ONCE(current->__state); ++ do { ++ __schedule(SM_IDLE); ++ } while (need_resched()); ++} ++ ++#if defined(CONFIG_CONTEXT_TRACKING) && !defined(CONFIG_HAVE_CONTEXT_TRACKING_OFFSTACK) ++asmlinkage __visible void __sched schedule_user(void) ++{ ++ /* ++ * If we come here after a random call to set_need_resched(), ++ * or we have been woken up remotely but the IPI has not yet arrived, ++ * we haven't yet exited the RCU idle mode. Do it here manually until ++ * we find a better solution. ++ * ++ * NB: There are buggy callers of this function. Ideally we ++ * should warn if prev_state != IN_USER, but that will trigger ++ * too frequently to make sense yet. ++ */ ++ enum ctx_state prev_state = exception_enter(); ++ schedule(); ++ exception_exit(prev_state); ++} ++#endif ++ ++/** ++ * schedule_preempt_disabled - called with preemption disabled ++ * ++ * Returns with preemption disabled. Note: preempt_count must be 1 ++ */ ++void __sched schedule_preempt_disabled(void) ++{ ++ sched_preempt_enable_no_resched(); ++ schedule(); ++ preempt_disable(); ++} ++ ++#ifdef CONFIG_PREEMPT_RT ++void __sched notrace schedule_rtlock(void) ++{ ++ __schedule_loop(SM_RTLOCK_WAIT); ++} ++NOKPROBE_SYMBOL(schedule_rtlock); ++#endif ++ ++static void __sched notrace preempt_schedule_common(void) ++{ ++ do { ++ /* ++ * Because the function tracer can trace preempt_count_sub() ++ * and it also uses preempt_enable/disable_notrace(), if ++ * NEED_RESCHED is set, the preempt_enable_notrace() called ++ * by the function tracer will call this function again and ++ * cause infinite recursion. ++ * ++ * Preemption must be disabled here before the function ++ * tracer can trace. Break up preempt_disable() into two ++ * calls. One to disable preemption without fear of being ++ * traced. The other to still record the preemption latency, ++ * which can also be traced by the function tracer. ++ */ ++ preempt_disable_notrace(); ++ preempt_latency_start(1); ++ __schedule(SM_PREEMPT); ++ preempt_latency_stop(1); ++ preempt_enable_no_resched_notrace(); ++ ++ /* ++ * Check again in case we missed a preemption opportunity ++ * between schedule and now. ++ */ ++ } while (need_resched()); ++} ++ ++#ifdef CONFIG_PREEMPTION ++/* ++ * This is the entry point to schedule() from in-kernel preemption ++ * off of preempt_enable. ++ */ ++asmlinkage __visible void __sched notrace preempt_schedule(void) ++{ ++ /* ++ * If there is a non-zero preempt_count or interrupts are disabled, ++ * we do not want to preempt the current task. Just return.. ++ */ ++ if (likely(!preemptible())) ++ return; ++ ++ preempt_schedule_common(); ++} ++NOKPROBE_SYMBOL(preempt_schedule); ++EXPORT_SYMBOL(preempt_schedule); ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ++# ifndef preempt_schedule_dynamic_enabled ++# define preempt_schedule_dynamic_enabled preempt_schedule ++# define preempt_schedule_dynamic_disabled NULL ++# endif ++DEFINE_STATIC_CALL(preempt_schedule, preempt_schedule_dynamic_enabled); ++EXPORT_STATIC_CALL_TRAMP(preempt_schedule); ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule); ++void __sched notrace dynamic_preempt_schedule(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_preempt_schedule)) ++ return; ++ preempt_schedule(); ++} ++NOKPROBE_SYMBOL(dynamic_preempt_schedule); ++EXPORT_SYMBOL(dynamic_preempt_schedule); ++# endif ++#endif ++ ++ ++/** ++ * preempt_schedule_notrace - preempt_schedule called by tracing ++ * ++ * The tracing infrastructure uses preempt_enable_notrace to prevent ++ * recursion and tracing preempt enabling caused by the tracing ++ * infrastructure itself. But as tracing can happen in areas coming ++ * from userspace or just about to enter userspace, a preempt enable ++ * can occur before user_exit() is called. This will cause the scheduler ++ * to be called when the system is still in usermode. ++ * ++ * To prevent this, the preempt_enable_notrace will use this function ++ * instead of preempt_schedule() to exit user context if needed before ++ * calling the scheduler. ++ */ ++asmlinkage __visible void __sched notrace preempt_schedule_notrace(void) ++{ ++ enum ctx_state prev_ctx; ++ ++ if (likely(!preemptible())) ++ return; ++ ++ do { ++ /* ++ * Because the function tracer can trace preempt_count_sub() ++ * and it also uses preempt_enable/disable_notrace(), if ++ * NEED_RESCHED is set, the preempt_enable_notrace() called ++ * by the function tracer will call this function again and ++ * cause infinite recursion. ++ * ++ * Preemption must be disabled here before the function ++ * tracer can trace. Break up preempt_disable() into two ++ * calls. One to disable preemption without fear of being ++ * traced. The other to still record the preemption latency, ++ * which can also be traced by the function tracer. ++ */ ++ preempt_disable_notrace(); ++ preempt_latency_start(1); ++ /* ++ * Needs preempt disabled in case user_exit() is traced ++ * and the tracer calls preempt_enable_notrace() causing ++ * an infinite recursion. ++ */ ++ prev_ctx = exception_enter(); ++ __schedule(SM_PREEMPT); ++ exception_exit(prev_ctx); ++ ++ preempt_latency_stop(1); ++ preempt_enable_no_resched_notrace(); ++ } while (need_resched()); ++} ++EXPORT_SYMBOL_GPL(preempt_schedule_notrace); ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++# if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) ++# ifndef preempt_schedule_notrace_dynamic_enabled ++# define preempt_schedule_notrace_dynamic_enabled preempt_schedule_notrace ++# define preempt_schedule_notrace_dynamic_disabled NULL ++# endif ++DEFINE_STATIC_CALL(preempt_schedule_notrace, preempt_schedule_notrace_dynamic_enabled); ++EXPORT_STATIC_CALL_TRAMP(preempt_schedule_notrace); ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule_notrace); ++void __sched notrace dynamic_preempt_schedule_notrace(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_preempt_schedule_notrace)) ++ return; ++ preempt_schedule_notrace(); ++} ++NOKPROBE_SYMBOL(dynamic_preempt_schedule_notrace); ++EXPORT_SYMBOL(dynamic_preempt_schedule_notrace); ++# endif ++#endif ++ ++#endif /* CONFIG_PREEMPTION */ ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++/* ++ * These aliases sit beside __cond_resched() in core.c, which MuQSS.c defines ++ * much further down; declare them here so sched_dynamic_update() can see them. ++ */ ++# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ++# define cond_resched_dynamic_enabled __cond_resched ++# define cond_resched_dynamic_disabled ((void *)&__static_call_return0) ++# define might_resched_dynamic_enabled __cond_resched ++# define might_resched_dynamic_disabled ((void *)&__static_call_return0) ++# endif ++#endif ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++ ++# ifdef CONFIG_GENERIC_IRQ_ENTRY ++# include ++# endif ++ ++/* ++ * SC:cond_resched ++ * SC:might_resched ++ * SC:preempt_schedule ++ * SC:preempt_schedule_notrace ++ * SC:irqentry_exit_cond_resched ++ * ++ * ++ * NONE: ++ * cond_resched <- __cond_resched ++ * might_resched <- RET0 ++ * preempt_schedule <- NOP ++ * preempt_schedule_notrace <- NOP ++ * irqentry_exit_cond_resched <- NOP ++ * dynamic_preempt_lazy <- false ++ * ++ * VOLUNTARY: ++ * cond_resched <- __cond_resched ++ * might_resched <- __cond_resched ++ * preempt_schedule <- NOP ++ * preempt_schedule_notrace <- NOP ++ * irqentry_exit_cond_resched <- NOP ++ * dynamic_preempt_lazy <- false ++ * ++ * FULL: ++ * cond_resched <- RET0 ++ * might_resched <- RET0 ++ * preempt_schedule <- preempt_schedule ++ * preempt_schedule_notrace <- preempt_schedule_notrace ++ * irqentry_exit_cond_resched <- irqentry_exit_cond_resched ++ * dynamic_preempt_lazy <- false ++ * ++ * LAZY: ++ * cond_resched <- RET0 ++ * might_resched <- RET0 ++ * preempt_schedule <- preempt_schedule ++ * preempt_schedule_notrace <- preempt_schedule_notrace ++ * irqentry_exit_cond_resched <- irqentry_exit_cond_resched ++ * dynamic_preempt_lazy <- true ++ */ ++ ++enum { ++ preempt_dynamic_undefined = -1, ++ preempt_dynamic_none, ++ preempt_dynamic_voluntary, ++ preempt_dynamic_full, ++ preempt_dynamic_lazy, ++}; ++ ++int preempt_dynamic_mode = preempt_dynamic_undefined; ++ ++int sched_dynamic_mode(const char *str) ++{ ++# if !(defined(CONFIG_PREEMPT_RT) || defined(CONFIG_ARCH_HAS_PREEMPT_LAZY)) ++ if (!strcmp(str, "none")) ++ return preempt_dynamic_none; ++ ++ if (!strcmp(str, "voluntary")) ++ return preempt_dynamic_voluntary; ++# endif ++ ++ if (!strcmp(str, "full")) ++ return preempt_dynamic_full; ++ ++# ifdef CONFIG_ARCH_HAS_PREEMPT_LAZY ++ if (!strcmp(str, "lazy")) ++ return preempt_dynamic_lazy; ++# endif ++ ++ return -EINVAL; ++} ++ ++# define preempt_dynamic_key_enable(f) static_key_enable(&sk_dynamic_##f.key) ++# define preempt_dynamic_key_disable(f) static_key_disable(&sk_dynamic_##f.key) ++ ++# if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) ++# define preempt_dynamic_enable(f) static_call_update(f, f##_dynamic_enabled) ++# define preempt_dynamic_disable(f) static_call_update(f, f##_dynamic_disabled) ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++# define preempt_dynamic_enable(f) preempt_dynamic_key_enable(f) ++# define preempt_dynamic_disable(f) preempt_dynamic_key_disable(f) ++# else ++# error "Unsupported PREEMPT_DYNAMIC mechanism" ++# endif ++ ++static DEFINE_MUTEX(sched_dynamic_mutex); ++ ++static void __sched_dynamic_update(int mode) ++{ ++ /* ++ * Avoid {NONE,VOLUNTARY} -> FULL transitions from ever ending up in ++ * the ZERO state, which is invalid. ++ */ ++ preempt_dynamic_enable(cond_resched); ++ preempt_dynamic_enable(might_resched); ++ preempt_dynamic_enable(preempt_schedule); ++ preempt_dynamic_enable(preempt_schedule_notrace); ++ preempt_dynamic_enable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ ++ switch (mode) { ++ case preempt_dynamic_none: ++ preempt_dynamic_enable(cond_resched); ++ preempt_dynamic_disable(might_resched); ++ preempt_dynamic_disable(preempt_schedule); ++ preempt_dynamic_disable(preempt_schedule_notrace); ++ preempt_dynamic_disable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: none\n"); ++ break; ++ ++ case preempt_dynamic_voluntary: ++ preempt_dynamic_enable(cond_resched); ++ preempt_dynamic_enable(might_resched); ++ preempt_dynamic_disable(preempt_schedule); ++ preempt_dynamic_disable(preempt_schedule_notrace); ++ preempt_dynamic_disable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: voluntary\n"); ++ break; ++ ++ case preempt_dynamic_full: ++ preempt_dynamic_disable(cond_resched); ++ preempt_dynamic_disable(might_resched); ++ preempt_dynamic_enable(preempt_schedule); ++ preempt_dynamic_enable(preempt_schedule_notrace); ++ preempt_dynamic_enable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_disable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: full\n"); ++ break; ++ ++ case preempt_dynamic_lazy: ++ preempt_dynamic_disable(cond_resched); ++ preempt_dynamic_disable(might_resched); ++ preempt_dynamic_enable(preempt_schedule); ++ preempt_dynamic_enable(preempt_schedule_notrace); ++ preempt_dynamic_enable(irqentry_exit_cond_resched); ++ preempt_dynamic_key_enable(preempt_lazy); ++ if (mode != preempt_dynamic_mode) ++ pr_info("Dynamic Preempt: lazy\n"); ++ break; ++ } ++ ++ WRITE_ONCE(preempt_dynamic_mode, mode); ++} ++ ++void sched_dynamic_update(int mode) ++{ ++ mutex_lock(&sched_dynamic_mutex); ++ __sched_dynamic_update(mode); ++ mutex_unlock(&sched_dynamic_mutex); ++} ++ ++static int __init setup_preempt_mode(char *str) ++{ ++ int mode = sched_dynamic_mode(str); ++ if (mode < 0) { ++ pr_warn("Dynamic Preempt: unsupported mode: %s\n", str); ++ return 0; ++ } ++ ++ sched_dynamic_update(mode); ++ return 1; ++} ++__setup("preempt=", setup_preempt_mode); ++ ++static void __init preempt_dynamic_init(void) ++{ ++ if (preempt_dynamic_mode == preempt_dynamic_undefined) { ++ if (IS_ENABLED(CONFIG_PREEMPT_NONE)) { ++ sched_dynamic_update(preempt_dynamic_none); ++ } else if (IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY)) { ++ sched_dynamic_update(preempt_dynamic_voluntary); ++ } else if (IS_ENABLED(CONFIG_PREEMPT_LAZY)) { ++ sched_dynamic_update(preempt_dynamic_lazy); ++ } else { ++ /* Default static call setting, nothing to do */ ++ WARN_ON_ONCE(!IS_ENABLED(CONFIG_PREEMPT)); ++ preempt_dynamic_mode = preempt_dynamic_full; ++ pr_info("Dynamic Preempt: full\n"); ++ } ++ } ++} ++ ++# define PREEMPT_MODEL_ACCESSOR(mode) \ ++ bool preempt_model_##mode(void) \ ++ { \ ++ int mode = READ_ONCE(preempt_dynamic_mode); \ ++ WARN_ON_ONCE(mode == preempt_dynamic_undefined); \ ++ return mode == preempt_dynamic_##mode; \ ++ } \ ++ EXPORT_SYMBOL_GPL(preempt_model_##mode) ++ ++PREEMPT_MODEL_ACCESSOR(none); ++PREEMPT_MODEL_ACCESSOR(voluntary); ++PREEMPT_MODEL_ACCESSOR(full); ++PREEMPT_MODEL_ACCESSOR(lazy); ++ ++#else /* !CONFIG_PREEMPT_DYNAMIC: */ ++ ++#define preempt_dynamic_mode -1 ++ ++static inline void preempt_dynamic_init(void) { } ++ ++#endif /* CONFIG_PREEMPT_DYNAMIC */ ++ ++const char *preempt_modes[] = { ++ "none", "voluntary", "full", "lazy", NULL, ++}; ++/* ++ * This is the entry point to schedule() from kernel preemption ++ * off of irq context. ++ * Note, that this is called and return with irqs disabled. This will ++ * protect us against recursive calling from irq. ++ */ ++asmlinkage __visible void __sched preempt_schedule_irq(void) ++{ ++ enum ctx_state prev_state; ++ ++ /* Catch callers which need to be fixed */ ++ BUG_ON(preempt_count() || !irqs_disabled()); ++ ++ prev_state = exception_enter(); ++ ++ do { ++ preempt_disable(); ++ local_irq_enable(); ++ __schedule(SM_PREEMPT); ++ local_irq_disable(); ++ sched_preempt_enable_no_resched(); ++ } while (need_resched()); ++ ++ exception_exit(prev_state); ++} ++ ++int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags, ++ void *key) ++{ ++ WARN_ON_ONCE(IS_ENABLED(CONFIG_SCHED_DEBUG) && wake_flags & ~WF_SYNC); ++ return try_to_wake_up(curr->private, mode, wake_flags); ++} ++EXPORT_SYMBOL(default_wake_function); ++ ++#ifdef CONFIG_RT_MUTEXES ++ ++static inline int __rt_effective_prio(struct task_struct *pi_task, int prio) ++{ ++ if (pi_task) ++ prio = min(prio, pi_task->prio); ++ ++ return prio; ++} ++ ++static inline int rt_effective_prio(struct task_struct *p, int prio) ++{ ++ struct task_struct *pi_task = rt_mutex_get_top_task(p); ++ ++ return __rt_effective_prio(pi_task, prio); ++} ++ ++/* ++ * rt_mutex_setprio - set the current priority of a task ++ * @p: task to boost ++ * @pi_task: donor task ++ * ++ * This function changes the 'effective' priority of a task. It does ++ * not touch ->normal_prio like __setscheduler(). ++ * ++ * Used by the rt_mutex code to implement priority inheritance ++ * logic. Call site only calls if the priority of the task changed. ++ */ ++void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task) ++{ ++ int prio, oldprio; ++ struct rq *rq; ++ ++ /* XXX used to be waiter->prio, not waiter->task->prio */ ++ prio = __rt_effective_prio(pi_task, p->normal_prio); ++ ++ /* ++ * If nothing changed; bail early. ++ */ ++ if (p->pi_top_task == pi_task && prio == p->prio) ++ return; ++ ++ rq = __task_rq_lock(p, NULL); ++ update_rq_clock(rq); ++ /* ++ * Set under pi_lock && rq->lock, such that the value can be used under ++ * either lock. ++ * ++ * Note that there is loads of tricky to make this pointer cache work ++ * right. rt_mutex_slowunlock()+rt_mutex_postunlock() work together to ++ * ensure a task is de-boosted (pi_task is set to NULL) before the ++ * task is allowed to run again (and can exit). This ensures the pointer ++ * points to a blocked task -- which guarantees the task is present. ++ */ ++ p->pi_top_task = pi_task; ++ ++ /* ++ * For FIFO/RR we only need to set prio, if that matches we're done. ++ */ ++ if (prio == p->prio) ++ goto out_unlock; ++ ++ /* ++ * Idle task boosting is a nono in general. There is one ++ * exception, when PREEMPT_RT and NOHZ is active: ++ * ++ * The idle task calls get_next_timer_interrupt() and holds ++ * the timer wheel base->lock on the CPU and another CPU wants ++ * to access the timer (probably to cancel it). We can safely ++ * ignore the boosting request, as the idle CPU runs this code ++ * with interrupts disabled and will complete the lock ++ * protected section without being interrupted. So there is no ++ * real need to boost. ++ */ ++ if (unlikely(p == rq->idle)) { ++ WARN_ON(p != rq->curr); ++ WARN_ON(p->pi_blocked_on); ++ goto out_unlock; ++ } ++ ++ trace_sched_pi_setprio(p, pi_task); ++ oldprio = p->prio; ++ p->prio = prio; ++ if (task_running(rq, p)){ ++ rt_running_reprio(rq, oldprio, prio); ++ if (prio > oldprio) ++ resched_task(p); ++ } else if (task_queued(p)) { ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ if (prio < oldprio) ++ try_preempt(p, rq); ++ } ++out_unlock: ++ /* Avoid rq from going away on us: */ ++ preempt_disable(); ++ __task_rq_unlock(rq, p, NULL); ++ ++ preempt_enable(); ++} ++#else ++static inline int rt_effective_prio(struct task_struct *p, int prio) ++{ ++ return prio; ++} ++#endif ++ ++/* ++ * Adjust the deadline for when the priority is to change, before it's ++ * changed. ++ */ ++static inline void adjust_deadline(struct task_struct *p, int new_prio) ++{ ++ p->deadline += static_deadline_diff(new_prio) - task_deadline_diff(p); ++} ++ ++void set_user_nice(struct task_struct *p, long nice) ++{ ++ int new_static, old_static; ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE) ++ return; ++ new_static = NICE_TO_PRIO(nice); ++ /* ++ * We have to be careful, if called from sys_setpriority(), ++ * the task might be in the middle of scheduling on another CPU. ++ */ ++ rq = task_rq_lock(p, &rf); ++ update_rq_clock(rq); ++ ++ /* ++ * The RT priorities are set via sched_setscheduler(), but we still ++ * allow the 'normal' nice value to be set - but as expected ++ * it won't have any effect on scheduling until the task is ++ * not SCHED_NORMAL/SCHED_BATCH: ++ */ ++ if (has_rt_policy(p)) { ++ p->static_prio = new_static; ++ goto out_unlock; ++ } ++ ++ adjust_deadline(p, new_static); ++ old_static = p->static_prio; ++ p->static_prio = new_static; ++ p->prio = effective_prio(p); ++ ++ if (task_queued(p)) { ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ if (new_static < old_static) ++ try_preempt(p, rq); ++ } else if (task_running(rq, p)) { ++ set_rq_task(rq, p); ++ if (old_static < new_static) ++ resched_task(p); ++ } ++out_unlock: ++ task_rq_unlock(rq, p, &rf); ++} ++EXPORT_SYMBOL(set_user_nice); ++ ++/* ++ * can_nice - check if a task can reduce its nice value ++ * @p: task ++ * @nice: nice value ++ */ ++int can_nice(const struct task_struct *p, const int nice) ++{ ++ /* Convert nice value [19,-20] to rlimit style value [1,40] */ ++ int nice_rlim = nice_to_rlimit(nice); ++ ++ return (nice_rlim <= task_rlimit(p, RLIMIT_NICE) || ++ capable(CAP_SYS_NICE)); ++} ++ ++#ifdef __ARCH_WANT_SYS_NICE ++ ++/* ++ * sys_nice - change the priority of the current process. ++ * @increment: priority increment ++ * ++ * sys_setpriority is a more generic, but much slower function that ++ * does similar things. ++ */ ++SYSCALL_DEFINE1(nice, int, increment) ++{ ++ long nice, retval; ++ ++ /* ++ * Setpriority might change our priority at the same moment. ++ * We don't have to worry. Conceptually one call occurs first ++ * and we have a single winner. ++ */ ++ ++ increment = clamp(increment, -NICE_WIDTH, NICE_WIDTH); ++ nice = task_nice(current) + increment; ++ ++ nice = clamp_val(nice, MIN_NICE, MAX_NICE); ++ if (increment < 0 && !can_nice(current, nice)) ++ return -EPERM; ++ ++ retval = security_task_setnice(current, nice); ++ if (retval) ++ return retval; ++ ++ set_user_nice(current, nice); ++ return 0; ++} ++ ++#endif ++ ++/** ++ * task_prio - return the priority value of a given task. ++ * @p: the task in question. ++ * ++ * Return: The priority value as seen by users in /proc. ++ * ++ * sched policy return value kernel prio user prio/nice ++ * ++ * normal, batch, [1 ... 41] 101 0/[-20 ... 19] ++ * idle [42 ... 81] 102 0/[-20 ... 19] ++ * iso [0 ... 41] 100 0/[-20 ... 19] ++ * fifo, rr [-2 ... -100] [98 ... 0] [1 ... 99] ++ */ ++int task_prio(const struct task_struct *p) ++{ ++ int delta, prio = p->prio - MAX_RT_PRIO; ++ ++ /* rt tasks and iso tasks */ ++ if (prio <= 0) ++ goto out; ++ ++ /* Convert to ms to avoid overflows */ ++ delta = NS_TO_MS(p->deadline - task_rq(p)->niffies); ++ if (unlikely(delta < 0)) ++ delta = 0; ++ delta = delta * 40 / ms_longest_deadline_diff(); ++ if (delta <= 80) ++ prio += delta; ++ if (idleprio_task(p)) ++ prio += 40; ++out: ++ return prio; ++} ++ ++#ifdef CONFIG_SMP ++static inline bool rt_rq_is_runnable(struct rq *rt_rq) ++{ ++ return rt_rq->rt_nr_running; ++} ++ ++/* ++ * This function computes an effective utilization for the given CPU, to be ++ * used for frequency selection given the linear relation: f = u * f_max. ++ * ++ * The scheduler tracks the following metrics: ++ * ++ * cpu_util_{cfs,rt,dl,irq}() ++ * cpu_bw_dl() ++ * ++ * Where the cfs,rt and dl util numbers are tracked with the same metric and ++ * synchronized windows and are thus directly comparable. ++ * ++ * The cfs,rt,dl utilization are the running times measured with rq->clock_task ++ * which excludes things like IRQ and steal-time. These latter are then accrued ++ * in the irq utilization. ++ * ++ * The DL bandwidth number otoh is not a measured metric but a value computed ++ * based on the task model parameters and gives the minimal utilization ++ * required to meet deadlines. ++ */ ++unsigned long effective_cpu_util(int cpu, unsigned long util_cfs, ++ unsigned long *min, unsigned long *max) ++{ ++ unsigned long util, irq, scale; ++ struct rq *rq = cpu_rq(cpu); ++ ++ scale = arch_scale_cpu_capacity(cpu); ++ ++ /* ++ * Early check to see if IRQ/steal time saturates the CPU, can be ++ * because of inaccuracies in how we track these. ++ */ ++ irq = cpu_util_irq(rq); ++ if (unlikely(irq >= scale)) { ++ if (min) ++ *min = scale; ++ if (max) ++ *max = scale; ++ return scale; ++ } ++ ++ /* ++ * MuQSS has no utilisation clamping and no deadline bandwidth, so the ++ * usable range is simply the whole capacity. A runnable realtime task ++ * asks for the maximum. ++ */ ++ if (min) ++ *min = rt_rq_is_runnable(rq) ? scale : 0; ++ if (max) ++ *max = scale; ++ ++ util = util_cfs + cpu_util_rt(rq); ++ util = scale_irq_capacity(util, irq, scale); ++ ++ return min_t(unsigned long, scale, util); ++} ++ ++/* ++ * Available idle CPU capacity, used by the energy model. MuQSS does not ++ * implement energy aware scheduling, but the interface is still called. ++ */ ++unsigned long sched_cpu_util(int cpu) ++{ ++ unsigned long min, max; ++ ++ return effective_cpu_util(cpu, cpu_util_cfs(cpu_rq(cpu)), &min, &max); ++} ++#endif /* CONFIG_SMP */ ++ ++/** ++ * idle_cpu - is a given CPU idle currently? ++ * @cpu: the processor in question. ++ * ++ * Return: 1 if the CPU is currently idle. 0 otherwise. ++ */ ++int idle_cpu(int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ if (rq->curr != rq->idle) ++ return 0; ++ ++ if (rq->nr_running) ++ return 0; ++ ++#ifdef CONFIG_SMP ++ if (rq->ttwu_pending) ++ return 0; ++#endif ++ ++ return 1; ++} ++ ++/** ++ * available_idle_cpu - is a given CPU idle for enqueuing work. ++ * @cpu: the CPU in question. ++ * ++ * Return: 1 if the CPU is currently idle. 0 otherwise. ++ */ ++bool available_idle_cpu(int cpu) ++{ ++ if (!idle_cpu(cpu)) ++ return 0; ++ ++ if (vcpu_is_preempted(cpu)) ++ return 0; ++ ++ return 1; ++} ++ ++/** ++ * idle_task - return the idle task for a given CPU. ++ * @cpu: the processor in question. ++ * ++ * Return: The idle task for the CPU @cpu. ++ */ ++struct task_struct *idle_task(int cpu) ++{ ++ return cpu_rq(cpu)->idle; ++} ++ ++/** ++ * find_process_by_pid - find a process with a matching PID value. ++ * @pid: the pid in question. ++ * ++ * The task of @pid, if found. %NULL otherwise. ++ */ ++static inline struct task_struct *find_process_by_pid(pid_t pid) ++{ ++ return pid ? find_task_by_vpid(pid) : current; ++} ++ ++/* Actually do priority change: must hold rq lock. */ ++static void __setscheduler(struct task_struct *p, struct rq *rq, int policy, ++ int prio, const struct sched_attr *attr, ++ bool keep_boost) ++{ ++ int oldrtprio, oldprio; ++ ++ /* ++ * If params can't change scheduling class changes aren't allowed ++ * either. ++ */ ++ if (attr->sched_flags & SCHED_FLAG_KEEP_PARAMS) ++ return; ++ ++ p->policy = policy; ++ oldrtprio = p->rt_priority; ++ p->rt_priority = prio; ++ ++ /* rt-policy tasks do not have a timerslack */ ++ if (has_rt_policy(p)) { ++ p->timer_slack_ns = 0; ++ } else if (p->timer_slack_ns == 0) { ++ /* when switching back to non-rt policy, restore timerslack */ ++ p->timer_slack_ns = p->default_timer_slack_ns; ++ } ++ ++ p->normal_prio = normal_prio(p); ++ oldprio = p->prio; ++ /* ++ * Keep a potential priority boosting if called from ++ * sched_setscheduler(). ++ */ ++ p->prio = normal_prio(p); ++ if (keep_boost) ++ p->prio = rt_effective_prio(p, p->prio); ++ ++ if (task_running(rq, p)) { ++ rt_running_reprio(rq, oldprio, p->prio); ++ set_rq_task(rq, p); ++ resched_task(p); ++ } else if (task_queued(p)) { ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(rq, p, ENQUEUE_RESTORE); ++ if (p->prio < oldprio || p->rt_priority > oldrtprio) ++ try_preempt(p, rq); ++ } ++} ++ ++/* ++ * Check the target process has a UID that matches the current process's ++ */ ++static bool check_same_owner(struct task_struct *p) ++{ ++ const struct cred *cred = current_cred(), *pcred; ++ bool match; ++ ++ rcu_read_lock(); ++ pcred = __task_cred(p); ++ match = (uid_eq(cred->euid, pcred->euid) || ++ uid_eq(cred->euid, pcred->uid)); ++ rcu_read_unlock(); ++ return match; ++} ++ ++static int __sched_setscheduler(struct task_struct *p, ++ const struct sched_attr *attr, ++ bool user, bool pi) ++{ ++ int retval, policy = attr->sched_policy, oldpolicy = -1, priority = attr->sched_priority; ++ unsigned long rlim_rtprio = 0; ++ struct rq_flags rf; ++ int reset_on_fork; ++ struct rq *rq; ++ ++ /* The pi code expects interrupts enabled */ ++ BUG_ON(pi && in_interrupt()); ++ ++ if (is_rt_policy(policy) && !capable(CAP_SYS_NICE)) { ++ unsigned long lflags; ++ ++ if (!lock_task_sighand(p, &lflags)) ++ return -ESRCH; ++ rlim_rtprio = task_rlimit(p, RLIMIT_RTPRIO); ++ unlock_task_sighand(p, &lflags); ++ if (rlim_rtprio) ++ goto recheck; ++ /* ++ * If the caller requested an RT policy without having the ++ * necessary rights, we downgrade the policy to SCHED_ISO. ++ * We also set the parameter to zero to pass the checks. ++ */ ++ policy = SCHED_ISO; ++ priority = 0; ++ } ++recheck: ++ /* Double check policy once rq lock held */ ++ if (policy < 0) { ++ reset_on_fork = p->sched_reset_on_fork; ++ policy = oldpolicy = p->policy; ++ } else { ++ reset_on_fork = !!(policy & SCHED_RESET_ON_FORK); ++ policy &= ~SCHED_RESET_ON_FORK; ++ ++ if (!SCHED_RANGE(policy)) ++ return -EINVAL; ++ } ++ ++ if (attr->sched_flags & ~(SCHED_FLAG_ALL | SCHED_FLAG_SUGOV)) ++ return -EINVAL; ++ ++ /* ++ * Valid priorities for SCHED_FIFO and SCHED_RR are ++ * 1..MAX_RT_PRIO-1, valid priority for SCHED_NORMAL and ++ * SCHED_BATCH is 0. ++ */ ++ if (priority > MAX_RT_PRIO-1) ++ return -EINVAL; ++ if (is_rt_policy(policy) != (priority != 0)) ++ return -EINVAL; ++ ++ /* ++ * Allow unprivileged RT tasks to decrease priority: ++ */ ++ if (user && !capable(CAP_SYS_NICE)) { ++ if (is_rt_policy(policy)) { ++ unsigned long rlim_rtprio = ++ task_rlimit(p, RLIMIT_RTPRIO); ++ ++ /* Can't set/change the rt policy */ ++ if (policy != p->policy && !rlim_rtprio) ++ return -EPERM; ++ ++ /* Can't increase priority */ ++ if (priority > p->rt_priority && ++ priority > rlim_rtprio) ++ return -EPERM; ++ } else { ++ switch (p->policy) { ++ /* ++ * Can only downgrade policies but not back to ++ * SCHED_NORMAL ++ */ ++ case SCHED_ISO: ++ if (policy == SCHED_ISO) ++ goto out; ++ if (policy != SCHED_NORMAL) ++ return -EPERM; ++ break; ++ case SCHED_BATCH: ++ if (policy == SCHED_BATCH) ++ goto out; ++ if (policy != SCHED_IDLEPRIO) ++ return -EPERM; ++ break; ++ case SCHED_IDLEPRIO: ++ if (policy == SCHED_IDLEPRIO) ++ goto out; ++ return -EPERM; ++ default: ++ break; ++ } ++ } ++ ++ /* Can't change other user's priorities */ ++ if (!check_same_owner(p)) ++ return -EPERM; ++ ++ /* Normal users shall not reset the sched_reset_on_fork flag: */ ++ if (p->sched_reset_on_fork && !reset_on_fork) ++ return -EPERM; ++ } ++ ++ if (user) { ++ retval = security_task_setscheduler(p); ++ if (retval) ++ return retval; ++ } ++ ++ if (pi) ++ cpuset_lock(); ++ ++ /* ++ * Make sure no PI-waiters arrive (or leave) while we are ++ * changing the priority of the task: ++ * ++ * To be able to change p->policy safely, the runqueue lock must be ++ * held. ++ */ ++ rq = task_rq_lock(p, &rf); ++ update_rq_clock(rq); ++ ++ /* ++ * Changing the policy of the stop threads its a very bad idea: ++ */ ++ if (p == rq->stop) { ++ retval = -EINVAL; ++ goto unlock; ++ } ++ ++ /* ++ * If not changing anything there's no need to proceed further, ++ * but store a possible modification of reset_on_fork. ++ */ ++ if (unlikely(policy == p->policy && (!is_rt_policy(policy) || ++ priority == p->rt_priority))) { ++ p->sched_reset_on_fork = reset_on_fork; ++ retval = 0; ++ goto unlock; ++ } ++ ++ /* Re-check policy now with rq lock held */ ++ if (unlikely(oldpolicy != -1 && oldpolicy != p->policy)) { ++ policy = oldpolicy = -1; ++ task_rq_unlock(rq, p, &rf); ++ if (pi) ++ cpuset_unlock(); ++ goto recheck; ++ } ++ p->sched_reset_on_fork = reset_on_fork; ++ ++ __setscheduler(p, rq, policy, priority, attr, pi); ++ ++ /* Avoid rq from going away on us: */ ++ preempt_disable(); ++ task_rq_unlock(rq, p, &rf); ++ ++ if (pi) { ++ cpuset_unlock(); ++ rt_mutex_adjust_pi(p); ++ } ++ preempt_enable(); ++out: ++ return 0; ++ ++unlock: ++ task_rq_unlock(rq, p, &rf); ++ if (pi) ++ cpuset_unlock(); ++ return retval; ++} ++ ++static int _sched_setscheduler(struct task_struct *p, int policy, ++ const struct sched_param *param, bool check) ++{ ++ struct sched_attr attr = { ++ .sched_policy = policy, ++ .sched_priority = param->sched_priority, ++ .sched_nice = PRIO_TO_NICE(p->static_prio), ++ }; ++ ++ return __sched_setscheduler(p, &attr, check, true); ++} ++/** ++ * sched_setscheduler - change the scheduling policy and/or RT priority of a thread. ++ * @p: the task in question. ++ * @policy: new policy. ++ * @param: structure containing the new RT priority. ++ * ++ * Use sched_set_fifo(), read its comment. ++ * ++ * Return: 0 on success. An error code otherwise. ++ * ++ * NOTE that the task may be already dead. ++ */ ++int sched_setscheduler(struct task_struct *p, int policy, ++ const struct sched_param *param) ++{ ++ return _sched_setscheduler(p, policy, param, true); ++} ++ ++ ++int sched_setattr(struct task_struct *p, const struct sched_attr *attr) ++{ ++ return __sched_setscheduler(p, attr, true, true); ++} ++ ++int sched_setattr_nocheck(struct task_struct *p, const struct sched_attr *attr) ++{ ++ return __sched_setscheduler(p, attr, false, true); ++} ++ ++/** ++ * sched_setscheduler_nocheck - change the scheduling policy and/or RT priority of a thread from kernelspace. ++ * @p: the task in question. ++ * @policy: new policy. ++ * @param: structure containing the new RT priority. ++ * ++ * Just like sched_setscheduler, only don't bother checking if the ++ * current context has permission. For example, this is needed in ++ * stop_machine(): we create temporary high priority worker threads, ++ * but our caller might not have that capability. ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++int sched_setscheduler_nocheck(struct task_struct *p, int policy, ++ const struct sched_param *param) ++{ ++ return _sched_setscheduler(p, policy, param, false); ++} ++ ++/* ++ * SCHED_FIFO is a broken scheduler model; that is, it is fundamentally ++ * incapable of resource management, which is the one thing an OS really should ++ * be doing. ++ * ++ * This is of course the reason it is limited to privileged users only. ++ * ++ * Worse still; it is fundamentally impossible to compose static priority ++ * workloads. You cannot take two correctly working static prio workloads ++ * and smash them together and still expect them to work. ++ * ++ * For this reason 'all' FIFO tasks the kernel creates are basically at: ++ * ++ * MAX_RT_PRIO / 2 ++ * ++ * The administrator _MUST_ configure the system, the kernel simply doesn't ++ * know enough information to make a sensible choice. ++ */ ++void sched_set_fifo(struct task_struct *p) ++{ ++ struct sched_param sp = { .sched_priority = MAX_RT_PRIO / 2 }; ++ WARN_ON_ONCE(sched_setscheduler_nocheck(p, SCHED_FIFO, &sp) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_fifo); ++ ++/* ++ * For when you don't much care about FIFO, but want to be above SCHED_NORMAL. ++ */ ++void sched_set_fifo_low(struct task_struct *p) ++{ ++ struct sched_param sp = { .sched_priority = 1 }; ++ WARN_ON_ONCE(sched_setscheduler_nocheck(p, SCHED_FIFO, &sp) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_fifo_low); ++ ++void sched_set_normal(struct task_struct *p, int nice) ++{ ++ struct sched_attr attr = { ++ .sched_policy = SCHED_NORMAL, ++ .sched_nice = nice, ++ }; ++ WARN_ON_ONCE(sched_setattr_nocheck(p, &attr) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_normal); ++ ++static int ++do_sched_setscheduler(pid_t pid, int policy, struct sched_param __user *param) ++{ ++ struct sched_param lparam; ++ struct task_struct *p; ++ int retval; ++ ++ if (!param || pid < 0) ++ return -EINVAL; ++ if (copy_from_user(&lparam, param, sizeof(struct sched_param))) ++ return -EFAULT; ++ ++ rcu_read_lock(); ++ retval = -ESRCH; ++ p = find_process_by_pid(pid); ++ if (likely(p)) ++ get_task_struct(p); ++ rcu_read_unlock(); ++ ++ if (likely(p)) { ++ retval = sched_setscheduler(p, policy, &lparam); ++ put_task_struct(p); ++ } ++ ++ return retval; ++} ++ ++/* ++ * Mimics kernel/events/core.c perf_copy_attr(). ++ */ ++static int sched_copy_attr(struct sched_attr __user *uattr, ++ struct sched_attr *attr) ++{ ++ u32 size; ++ int ret; ++ ++ /* Zero the full structure, so that a short copy will be nice: */ ++ memset(attr, 0, sizeof(*attr)); ++ ++ ret = get_user(size, &uattr->size); ++ if (ret) ++ return ret; ++ ++ /* ABI compatibility quirk: */ ++ if (!size) ++ size = SCHED_ATTR_SIZE_VER0; ++ ++ if (size < SCHED_ATTR_SIZE_VER0 || size > PAGE_SIZE) ++ goto err_size; ++ ++ ret = copy_struct_from_user(attr, sizeof(*attr), uattr, size); ++ if (ret) { ++ if (ret == -E2BIG) ++ goto err_size; ++ return ret; ++ } ++ ++ /* ++ * XXX: Do we want to be lenient like existing syscalls; or do we want ++ * to be strict and return an error on out-of-bounds values? ++ */ ++ attr->sched_nice = clamp(attr->sched_nice, -20, 19); ++ ++ /* sched/core.c uses zero here but we already know ret is zero */ ++ return 0; ++ ++err_size: ++ put_user(sizeof(*attr), &uattr->size); ++ return -E2BIG; ++} ++ ++/* ++ * sched_setparam() passes in -1 for its policy, to let the functions ++ * it calls know not to change it. ++ */ ++#define SETPARAM_POLICY -1 ++ ++/** ++ * sys_sched_setscheduler - set/change the scheduler policy and RT priority ++ * @pid: the pid in question. ++ * @policy: new policy. ++ * @param: structure containing the new RT priority. ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE3(sched_setscheduler, pid_t, pid, int, policy, struct sched_param __user *, param) ++{ ++ if (policy < 0) ++ return -EINVAL; ++ ++ return do_sched_setscheduler(pid, policy, param); ++} ++ ++/** ++ * sys_sched_setparam - set/change the RT priority of a thread ++ * @pid: the pid in question. ++ * @param: structure containing the new RT priority. ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE2(sched_setparam, pid_t, pid, struct sched_param __user *, param) ++{ ++ return do_sched_setscheduler(pid, SETPARAM_POLICY, param); ++} ++ ++/** ++ * sys_sched_setattr - same as above, but with extended sched_attr ++ * @pid: the pid in question. ++ * @uattr: structure containing the extended parameters. ++ */ ++SYSCALL_DEFINE3(sched_setattr, pid_t, pid, struct sched_attr __user *, uattr, ++ unsigned int, flags) ++{ ++ struct sched_attr attr; ++ struct task_struct *p; ++ int retval; ++ ++ if (!uattr || pid < 0 || flags) ++ return -EINVAL; ++ ++ retval = sched_copy_attr(uattr, &attr); ++ if (retval) ++ return retval; ++ ++ if ((int)attr.sched_policy < 0) ++ return -EINVAL; ++ if (attr.sched_flags & SCHED_FLAG_KEEP_POLICY) ++ attr.sched_policy = SETPARAM_POLICY; ++ ++ rcu_read_lock(); ++ retval = -ESRCH; ++ p = find_process_by_pid(pid); ++ if (likely(p)) ++ get_task_struct(p); ++ rcu_read_unlock(); ++ ++ if (likely(p)) { ++ retval = sched_setattr(p, &attr); ++ put_task_struct(p); ++ } ++ ++ return retval; ++} ++ ++/** ++ * sys_sched_getscheduler - get the policy (scheduling class) of a thread ++ * @pid: the pid in question. ++ * ++ * Return: On success, the policy of the thread. Otherwise, a negative error ++ * code. ++ */ ++SYSCALL_DEFINE1(sched_getscheduler, pid_t, pid) ++{ ++ struct task_struct *p; ++ int retval = -EINVAL; ++ ++ if (pid < 0) ++ goto out_nounlock; ++ ++ retval = -ESRCH; ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ if (p) { ++ retval = security_task_getscheduler(p); ++ if (!retval) ++ retval = p->policy; ++ } ++ rcu_read_unlock(); ++ ++out_nounlock: ++ return retval; ++} ++ ++/** ++ * sys_sched_getscheduler - get the RT priority of a thread ++ * @pid: the pid in question. ++ * @param: structure containing the RT priority. ++ * ++ * Return: On success, 0 and the RT priority is in @param. Otherwise, an error ++ * code. ++ */ ++SYSCALL_DEFINE2(sched_getparam, pid_t, pid, struct sched_param __user *, param) ++{ ++ struct sched_param lp = { .sched_priority = 0 }; ++ struct task_struct *p; ++ int retval = -EINVAL; ++ ++ if (!param || pid < 0) ++ goto out_nounlock; ++ ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ retval = -ESRCH; ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ if (has_rt_policy(p)) ++ lp.sched_priority = p->rt_priority; ++ rcu_read_unlock(); ++ ++ /* ++ * This one might sleep, we cannot do it with a spinlock held ... ++ */ ++ retval = copy_to_user(param, &lp, sizeof(*param)) ? -EFAULT : 0; ++ ++out_nounlock: ++ return retval; ++ ++out_unlock: ++ rcu_read_unlock(); ++ return retval; ++} ++ ++/* ++ * Copy the kernel size attribute structure (which might be larger ++ * than what user-space knows about) to user-space. ++ * ++ * Note that all cases are valid: user-space buffer can be larger or ++ * smaller than the kernel-space buffer. The usual case is that both ++ * have the same size. ++ */ ++static int ++sched_attr_copy_to_user(struct sched_attr __user *uattr, ++ struct sched_attr *kattr, ++ unsigned int usize) ++{ ++ unsigned int ksize = sizeof(*kattr); ++ ++ if (!access_ok(uattr, usize)) ++ return -EFAULT; ++ ++ /* ++ * sched_getattr() ABI forwards and backwards compatibility: ++ * ++ * If usize == ksize then we just copy everything to user-space and all is good. ++ * ++ * If usize < ksize then we only copy as much as user-space has space for, ++ * this keeps ABI compatibility as well. We skip the rest. ++ * ++ * If usize > ksize then user-space is using a newer version of the ABI, ++ * which part the kernel doesn't know about. Just ignore it - tooling can ++ * detect the kernel's knowledge of attributes from the attr->size value ++ * which is set to ksize in this case. ++ */ ++ kattr->size = min(usize, ksize); ++ ++ if (copy_to_user(uattr, kattr, kattr->size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++/** ++ * sys_sched_getattr - similar to sched_getparam, but with sched_attr ++ * @pid: the pid in question. ++ * @uattr: structure containing the extended parameters. ++ * @usize: sizeof(attr) for fwd/bwd comp. ++ * @flags: for future extension. ++ */ ++SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr, ++ unsigned int, usize, unsigned int, flags) ++{ ++ struct sched_attr kattr = { }; ++ struct task_struct *p; ++ int retval; ++ ++ if (!uattr || pid < 0 || usize > PAGE_SIZE || ++ usize < SCHED_ATTR_SIZE_VER0 || flags) ++ return -EINVAL; ++ ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ retval = -ESRCH; ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ kattr.sched_policy = p->policy; ++ if (rt_task(p)) ++ kattr.sched_priority = p->rt_priority; ++ else ++ kattr.sched_nice = task_nice(p); ++ ++ rcu_read_unlock(); ++ ++ return sched_attr_copy_to_user(uattr, &kattr, usize); ++ ++out_unlock: ++ rcu_read_unlock(); ++ return retval; ++} ++ ++long sched_setaffinity(pid_t pid, const struct cpumask *in_mask) ++{ ++ cpumask_var_t cpus_allowed, new_mask; ++ struct task_struct *p; ++ int retval; ++ ++ rcu_read_lock(); ++ ++ p = find_process_by_pid(pid); ++ if (!p) { ++ rcu_read_unlock(); ++ return -ESRCH; ++ } ++ ++ /* Prevent p going away */ ++ get_task_struct(p); ++ rcu_read_unlock(); ++ ++ if (p->flags & PF_NO_SETAFFINITY) { ++ retval = -EINVAL; ++ goto out_put_task; ++ } ++ if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL)) { ++ retval = -ENOMEM; ++ goto out_put_task; ++ } ++ if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) { ++ retval = -ENOMEM; ++ goto out_free_cpus_allowed; ++ } ++ retval = -EPERM; ++ if (!check_same_owner(p)) { ++ rcu_read_lock(); ++ if (!ns_capable(__task_cred(p)->user_ns, CAP_SYS_NICE)) { ++ rcu_read_unlock(); ++ goto out_unlock; ++ } ++ rcu_read_unlock(); ++ } ++ ++ retval = security_task_setscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ cpuset_cpus_allowed(p, cpus_allowed); ++ cpumask_and(new_mask, in_mask, cpus_allowed); ++again: ++ retval = __set_cpus_allowed_ptr(p, new_mask, SCA_CHECK | SCA_USER); ++ ++ if (!retval) { ++ cpuset_cpus_allowed(p, cpus_allowed); ++ if (!cpumask_subset(new_mask, cpus_allowed)) { ++ /* ++ * We must have raced with a concurrent cpuset ++ * update. Just reset the cpus_allowed to the ++ * cpuset's cpus_allowed ++ */ ++ cpumask_copy(new_mask, cpus_allowed); ++ goto again; ++ } ++ } ++out_unlock: ++ free_cpumask_var(new_mask); ++out_free_cpus_allowed: ++ free_cpumask_var(cpus_allowed); ++out_put_task: ++ put_task_struct(p); ++ return retval; ++} ++ ++static int get_user_cpu_mask(unsigned long __user *user_mask_ptr, unsigned len, ++ cpumask_t *new_mask) ++{ ++ if (len < cpumask_size()) ++ cpumask_clear(new_mask); ++ else if (len > cpumask_size()) ++ len = cpumask_size(); ++ ++ return copy_from_user(new_mask, user_mask_ptr, len) ? -EFAULT : 0; ++} ++ ++ ++/** ++ * sys_sched_setaffinity - set the CPU affinity of a process ++ * @pid: pid of the process ++ * @len: length in bytes of the bitmask pointed to by user_mask_ptr ++ * @user_mask_ptr: user-space pointer to the new CPU mask ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE3(sched_setaffinity, pid_t, pid, unsigned int, len, ++ unsigned long __user *, user_mask_ptr) ++{ ++ cpumask_var_t new_mask; ++ int retval; ++ ++ if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ retval = get_user_cpu_mask(user_mask_ptr, len, new_mask); ++ if (retval == 0) ++ retval = sched_setaffinity(pid, new_mask); ++ free_cpumask_var(new_mask); ++ return retval; ++} ++ ++long sched_getaffinity(pid_t pid, cpumask_t *mask) ++{ ++ struct task_struct *p; ++ unsigned long flags; ++ int retval; ++ ++ cpus_read_lock(); ++ rcu_read_lock(); ++ ++ retval = -ESRCH; ++ p = find_process_by_pid(pid); ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ cpumask_and(mask, &p->cpus_mask, cpu_active_mask); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++ ++out_unlock: ++ rcu_read_unlock(); ++ cpus_read_unlock(); ++ ++ return retval; ++} ++ ++/** ++ * sys_sched_getaffinity - get the CPU affinity of a process ++ * @pid: pid of the process ++ * @len: length in bytes of the bitmask pointed to by user_mask_ptr ++ * @user_mask_ptr: user-space pointer to hold the current CPU mask ++ * ++ * Return: 0 on success. An error code otherwise. ++ */ ++SYSCALL_DEFINE3(sched_getaffinity, pid_t, pid, unsigned int, len, ++ unsigned long __user *, user_mask_ptr) ++{ ++ int ret; ++ cpumask_var_t mask; ++ ++ if ((len * BITS_PER_BYTE) < nr_cpu_ids) ++ return -EINVAL; ++ if (len & (sizeof(unsigned long)-1)) ++ return -EINVAL; ++ ++ if (!alloc_cpumask_var(&mask, GFP_KERNEL)) ++ return -ENOMEM; ++ ++ ret = sched_getaffinity(pid, mask); ++ if (ret == 0) { ++ unsigned int retlen = min(len, cpumask_size()); ++ ++ if (copy_to_user(user_mask_ptr, mask, retlen)) ++ ret = -EFAULT; ++ else ++ ret = retlen; ++ } ++ free_cpumask_var(mask); ++ ++ return ret; ++} ++ ++static void do_sched_yield(void) ++{ ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ if (!sched_yield_type) ++ return; ++ ++ rq = this_rq_lock_irq(&rf); ++ ++ if (sched_yield_type > 1) ++ time_slice_expired(current, rq); ++ schedstat_inc(rq->yld_count); ++ ++ preempt_disable(); ++ rq_unlock_irq(rq, &rf); ++ sched_preempt_enable_no_resched(); ++ ++ schedule(); ++} ++ ++/** ++ * sys_sched_yield - yield the current processor to other threads. ++ * ++ * This function yields the current CPU to other tasks. If there are no ++ * other threads running on this CPU then this function will return. ++ * ++ * Return: 0. ++ */ ++SYSCALL_DEFINE0(sched_yield) ++{ ++ do_sched_yield(); ++ return 0; ++} ++ ++#if !defined(CONFIG_PREEMPTION) || defined(CONFIG_PREEMPT_DYNAMIC) ++int __sched __cond_resched(void) ++{ ++ if (should_resched(0)) { ++ preempt_schedule_common(); ++ return 1; ++ } ++#ifndef CONFIG_PREEMPT_RCU ++ rcu_all_qs(); ++#endif ++ return 0; ++} ++EXPORT_SYMBOL(__cond_resched); ++#endif ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL ++DEFINE_STATIC_CALL_RET0(cond_resched, __cond_resched); ++EXPORT_STATIC_CALL_TRAMP(cond_resched); ++ ++DEFINE_STATIC_CALL_RET0(might_resched, __cond_resched); ++EXPORT_STATIC_CALL_TRAMP(might_resched); ++# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) ++static DEFINE_STATIC_KEY_FALSE(sk_dynamic_cond_resched); ++int __sched dynamic_cond_resched(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_cond_resched)) ++ return 0; ++ return __cond_resched(); ++} ++EXPORT_SYMBOL(dynamic_cond_resched); ++ ++static DEFINE_STATIC_KEY_FALSE(sk_dynamic_might_resched); ++int __sched dynamic_might_resched(void) ++{ ++ if (!static_branch_unlikely(&sk_dynamic_might_resched)) ++ return 0; ++ return __cond_resched(); ++} ++EXPORT_SYMBOL(dynamic_might_resched); ++# endif ++#endif /* CONFIG_PREEMPT_DYNAMIC */ ++ ++/* ++ * __cond_resched_lock() - if a reschedule is pending, drop the given lock, ++ * call schedule, and on return reacquire the lock. ++ * ++ * This works OK both with and without CONFIG_PREEMPTION. We do strange low-level ++ * operations here to prevent schedule() from being called twice (once via ++ * spin_unlock(), once by hand). ++ */ ++int __cond_resched_lock(spinlock_t *lock) ++{ ++ int resched = should_resched(PREEMPT_LOCK_OFFSET); ++ int ret = 0; ++ ++ lockdep_assert_held(lock); ++ ++ if (spin_needbreak(lock) || resched) { ++ spin_unlock(lock); ++ if (resched) ++ preempt_schedule_common(); ++ else ++ cpu_relax(); ++ ret = 1; ++ spin_lock(lock); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__cond_resched_lock); ++ ++int __cond_resched_rwlock_read(rwlock_t *lock) ++{ ++ int resched = should_resched(PREEMPT_LOCK_OFFSET); ++ int ret = 0; ++ ++ lockdep_assert_held_read(lock); ++ ++ if (rwlock_needbreak(lock) || resched) { ++ read_unlock(lock); ++ if (resched) ++ preempt_schedule_common(); ++ else ++ cpu_relax(); ++ ret = 1; ++ read_lock(lock); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__cond_resched_rwlock_read); ++ ++int __cond_resched_rwlock_write(rwlock_t *lock) ++{ ++ int resched = should_resched(PREEMPT_LOCK_OFFSET); ++ int ret = 0; ++ ++ lockdep_assert_held_write(lock); ++ ++ if (rwlock_needbreak(lock) || resched) { ++ write_unlock(lock); ++ if (resched) ++ preempt_schedule_common(); ++ else ++ cpu_relax(); ++ ret = 1; ++ write_lock(lock); ++ } ++ return ret; ++} ++EXPORT_SYMBOL(__cond_resched_rwlock_write); ++ ++/** ++ * yield - yield the current processor to other threads. ++ * ++ * Do not ever use this function, there's a 99% chance you're doing it wrong. ++ * ++ * The scheduler is at all times free to pick the calling task as the most ++ * eligible task to run, if removing the yield() call from your code breaks ++ * it, it's already broken. ++ * ++ * Typical broken usage is: ++ * ++ * while (!event) ++ * yield(); ++ * ++ * where one assumes that yield() will let 'the other' process run that will ++ * make event true. If the current task is a SCHED_FIFO task that will never ++ * happen. Never use yield() as a progress guarantee!! ++ * ++ * If you want to use yield() to wait for something, use wait_event(). ++ * If you want to use yield() to be 'nice' for others, use cond_resched(). ++ * If you still want to use yield(), do not! ++ */ ++void __sched yield(void) ++{ ++ set_current_state(TASK_RUNNING); ++ do_sched_yield(); ++} ++EXPORT_SYMBOL(yield); ++ ++/** ++ * yield_to - yield the current processor to another thread in ++ * your thread group, or accelerate that thread toward the ++ * processor it's on. ++ * @p: target task ++ * @preempt: whether task preemption is allowed or not ++ * ++ * It's the caller's job to ensure that the target task struct ++ * can't go away on us before we can do any checks. ++ * ++ * Return: ++ * true (>0) if we indeed boosted the target task. ++ * false (0) if we failed to boost the target. ++ * -ESRCH if there's no task to yield to. ++ */ ++int __sched yield_to(struct task_struct *p, bool preempt) ++{ ++ struct task_struct *rq_p; ++ struct rq *rq, *p_rq; ++ unsigned long flags; ++ int yielded = 0; ++ ++ local_irq_save(flags); ++ rq = this_rq(); ++ ++again: ++ p_rq = task_rq(p); ++ /* ++ * If we're the only runnable task on the rq and target rq also ++ * has only one task, there's absolutely no point in yielding. ++ */ ++ if (task_running(p_rq, p) || p->__state) { ++ yielded = -ESRCH; ++ goto out_irq; ++ } ++ ++ double_rq_lock(rq, p_rq); ++ if (unlikely(task_rq(p) != p_rq)) { ++ double_rq_unlock(rq, p_rq); ++ goto again; ++ } ++ ++ yielded = 1; ++ schedstat_inc(rq->yld_count); ++ rq_p = rq->curr; ++ if (p->deadline > rq_p->deadline) ++ p->deadline = rq_p->deadline; ++ p->time_slice += rq_p->time_slice; ++ if (p->time_slice > timeslice()) ++ p->time_slice = timeslice(); ++ time_slice_expired(rq_p, rq); ++ if (preempt && rq != p_rq) ++ resched_task(p_rq->curr); ++ double_rq_unlock(rq, p_rq); ++out_irq: ++ local_irq_restore(flags); ++ ++ if (yielded > 0) ++ schedule(); ++ return yielded; ++} ++EXPORT_SYMBOL_GPL(yield_to); ++ ++int io_schedule_prepare(void) ++{ ++ int old_iowait = current->in_iowait; ++ ++ current->in_iowait = 1; ++ blk_flush_plug(current->plug, true); ++ ++ return old_iowait; ++} ++ ++void io_schedule_finish(int token) ++{ ++ current->in_iowait = token; ++} ++ ++/* ++ * This task is about to go to sleep on IO. Increment rq->nr_iowait so ++ * that process accounting knows that this is a task in IO wait state. ++ * ++ * But don't do that if it is a deliberate, throttling IO wait (this task ++ * has set its backing_dev_info: the queue against which it should throttle) ++ */ ++ ++long __sched io_schedule_timeout(long timeout) ++{ ++ int token; ++ long ret; ++ ++ token = io_schedule_prepare(); ++ ret = schedule_timeout(timeout); ++ io_schedule_finish(token); ++ ++ return ret; ++} ++EXPORT_SYMBOL(io_schedule_timeout); ++ ++void __sched io_schedule(void) ++{ ++ int token; ++ ++ token = io_schedule_prepare(); ++ schedule(); ++ io_schedule_finish(token); ++} ++EXPORT_SYMBOL(io_schedule); ++ ++/** ++ * sys_sched_get_priority_max - return maximum RT priority. ++ * @policy: scheduling class. ++ * ++ * Return: On success, this syscall returns the maximum ++ * rt_priority that can be used by a given scheduling class. ++ * On failure, a negative error code is returned. ++ */ ++SYSCALL_DEFINE1(sched_get_priority_max, int, policy) ++{ ++ int ret = -EINVAL; ++ ++ switch (policy) { ++ case SCHED_FIFO: ++ case SCHED_RR: ++ ret = MAX_RT_PRIO-1; ++ break; ++ case SCHED_NORMAL: ++ case SCHED_BATCH: ++ case SCHED_ISO: ++ case SCHED_IDLEPRIO: ++ ret = 0; ++ break; ++ } ++ return ret; ++} ++ ++/** ++ * sys_sched_get_priority_min - return minimum RT priority. ++ * @policy: scheduling class. ++ * ++ * Return: On success, this syscall returns the minimum ++ * rt_priority that can be used by a given scheduling class. ++ * On failure, a negative error code is returned. ++ */ ++SYSCALL_DEFINE1(sched_get_priority_min, int, policy) ++{ ++ int ret = -EINVAL; ++ ++ switch (policy) { ++ case SCHED_FIFO: ++ case SCHED_RR: ++ ret = 1; ++ break; ++ case SCHED_NORMAL: ++ case SCHED_BATCH: ++ case SCHED_ISO: ++ case SCHED_IDLEPRIO: ++ ret = 0; ++ break; ++ } ++ return ret; ++} ++ ++static int sched_rr_get_interval(pid_t pid, struct timespec64 *t) ++{ ++ struct task_struct *p; ++ unsigned int time_slice; ++ struct rq_flags rf; ++ struct rq *rq; ++ int retval; ++ ++ if (pid < 0) ++ return -EINVAL; ++ ++ retval = -ESRCH; ++ rcu_read_lock(); ++ p = find_process_by_pid(pid); ++ if (!p) ++ goto out_unlock; ++ ++ retval = security_task_getscheduler(p); ++ if (retval) ++ goto out_unlock; ++ ++ rq = task_rq_lock(p, &rf); ++ time_slice = p->policy == SCHED_FIFO ? 0 : MS_TO_NS(task_timeslice(p)); ++ task_rq_unlock(rq, p, &rf); ++ ++ rcu_read_unlock(); ++ *t = ns_to_timespec64(time_slice); ++ return 0; ++ ++out_unlock: ++ rcu_read_unlock(); ++ return retval; ++} ++ ++/** ++ * sys_sched_rr_get_interval - return the default timeslice of a process. ++ * @pid: pid of the process. ++ * @interval: userspace pointer to the timeslice value. ++ * ++ * this syscall writes the default timeslice value of a given process ++ * into the user-space timespec buffer. A value of '0' means infinity. ++ * ++ * Return: On success, 0 and the timeslice is in @interval. Otherwise, ++ * an error code. ++ */ ++SYSCALL_DEFINE2(sched_rr_get_interval, pid_t, pid, ++ struct __kernel_timespec __user *, interval) ++{ ++ struct timespec64 t; ++ int retval = sched_rr_get_interval(pid, &t); ++ ++ if (retval == 0) ++ retval = put_timespec64(&t, interval); ++ ++ return retval; ++} ++ ++#ifdef CONFIG_COMPAT_32BIT_TIME ++SYSCALL_DEFINE2(sched_rr_get_interval_time32, pid_t, pid, ++ struct old_timespec32 __user *, interval) ++{ ++ struct timespec64 t; ++ int retval = sched_rr_get_interval(pid, &t); ++ ++ if (retval == 0) ++ retval = put_old_timespec32(&t, interval); ++ return retval; ++} ++#endif ++ ++void sched_show_task(struct task_struct *p) ++{ ++ unsigned long free = 0; ++ int ppid; ++ ++ if (!try_get_task_stack(p)) ++ return; ++ ++ printk(KERN_INFO "%-15.15s %c", p->comm, task_state_to_char(p)); ++ ++ if (p->__state == TASK_RUNNING) ++ printk(KERN_CONT " running task "); ++#ifdef CONFIG_DEBUG_STACK_USAGE ++ free = stack_not_used(p); ++#endif ++ ppid = 0; ++ rcu_read_lock(); ++ if (pid_alive(p)) ++ ppid = task_pid_nr(rcu_dereference(p->real_parent)); ++ rcu_read_unlock(); ++ pr_cont(" stack:%5lu pid:%5d ppid:%6d flags:0x%08lx\n", ++ free, task_pid_nr(p), ppid, ++ (unsigned long)task_thread_info(p)->flags); ++ ++ print_worker_info(KERN_INFO, p); ++ print_stop_info(KERN_INFO, p); ++ show_stack(p, NULL, KERN_INFO); ++ put_task_stack(p); ++} ++EXPORT_SYMBOL_GPL(sched_show_task); ++ ++static inline bool ++state_filter_match(unsigned long state_filter, struct task_struct *p) ++{ ++ /* no filter, everything matches */ ++ if (!state_filter) ++ return true; ++ ++ /* filter, but doesn't match */ ++ if (!(p->__state & state_filter)) ++ return false; ++ ++ /* ++ * When looking for TASK_UNINTERRUPTIBLE skip TASK_IDLE (allows ++ * TASK_KILLABLE). ++ */ ++ if (state_filter == TASK_UNINTERRUPTIBLE && p->__state == TASK_IDLE) ++ return false; ++ ++ return true; ++} ++ ++void show_state_filter(unsigned int state_filter) ++{ ++ struct task_struct *g, *p; ++ ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ /* ++ * reset the NMI-timeout, listing all files on a slow ++ * console might take a lot of time: ++ * Also, reset softlockup watchdogs on all CPUs, because ++ * another CPU might be blocked waiting for us to process ++ * an IPI. ++ */ ++ touch_nmi_watchdog(); ++ touch_all_softlockup_watchdogs(); ++ if (state_filter_match(state_filter, p)) ++ sched_show_task(p); ++ } ++ ++ rcu_read_unlock(); ++ /* ++ * Only show locks if all tasks are dumped: ++ */ ++ if (!state_filter) ++ debug_show_all_locks(); ++} ++ ++void dump_cpu_task(int cpu) ++{ ++ pr_info("Task dump for CPU %d:\n", cpu); ++ sched_show_task(cpu_curr(cpu)); ++} ++ ++#ifdef CONFIG_SMP ++void set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx) ++{ ++ if (ctx->flags & (SCA_MIGRATE_ENABLE | SCA_MIGRATE_DISABLE)) { ++ p->cpus_ptr = ctx->new_mask; ++ return; ++ } ++ ++ cpumask_copy(&p->cpus_mask, ctx->new_mask); ++ p->nr_cpus_allowed = cpumask_weight(ctx->new_mask); ++} ++ ++void ++__do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx) ++{ ++ struct rq *rq = task_rq(p); ++ ++ lockdep_assert_held(&p->pi_lock); ++ ++ /* ++ * Keep cpus_mask and nr_cpus_allowed in lockstep. sched_other_cpu() ++ * relies on nr_cpus_allowed == 1 to allow hotplug kthreads bound to ++ * offline CPUs to still be picked on an online runqueue. ++ */ ++ set_cpus_allowed_common(p, ctx); ++ ++ if (task_queued(p)) { ++ /* ++ * Because __kthread_bind() calls this on blocked tasks without ++ * holding rq->lock. ++ */ ++ lockdep_assert_held(rq->lock); ++ } ++} ++ ++static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf, ++ struct task_struct *p, int new_cpu); ++ ++/* ++ * Calling do_set_cpus_allowed from outside the scheduler code should not be ++ * called on a running or queued task. We should be holding pi_lock. ++ */ ++void do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx) ++{ ++ __do_set_cpus_allowed(p, ctx); ++ ++ /* ++ * A mask forced by cpuset or kthread_bind is a change of what the ++ * task wants, so an override bind_zero() had to make must not be ++ * restored over the top of it later. What was saved is left in place ++ * rather than freed, which here would be under pi_lock. ++ */ ++ if (ctx->flags & SCA_USER) ++ p->zerobound = false; ++ ++ if (needs_other_cpu(p, task_cpu(p))) { ++ struct rq *rq; ++ ++ rq = __task_rq_lock(p, NULL); ++ /* ++ * A blocked task is on no runqueue, so task_cpu() only says ++ * which rq lock protects it and which CPU PSI counts its state ++ * on. It is left exactly as it is. ++ * ++ * Moving it would leave the TSK_IOWAIT it is carrying counted ++ * on the CPU it came from, and the wakeup would then clear it ++ * on a CPU that never counted it and underflow that counter. ++ * Nor may the destination be recorded in wake_cpu: that is not ++ * a hint, it is the bit that arms the lazy migration handshake. ++ * return_task() reads wake_cpu != task_cpu() as "set_task_cpu() ++ * moved this running task, hand it over in ++ * finish_lock_switch()", so stamping it here arms a migration ++ * no one arranged. Widening the mask again before the task ++ * wakes is enough to leave it armed, since ttwu only calls ++ * set_task_cpu() - the one place that resyncs wake_cpu - when ++ * it picks a different CPU, and a wakeup back onto task_cpu() ++ * therefore keeps the stale value until the next deschedule ++ * migrates the task out from under the switch. ++ * ++ * Nothing needs either. select_best_cpu() honours the new mask ++ * when the task wakes and set_task_cpu() takes the PSI state ++ * with it, which is why affine_move_task() leaves a blocked ++ * task alone here as well. ++ */ ++ if (task_running(rq, p)) { ++ /* ++ * A running task is not on any skiplist, so ++ * set_task_cpu() only tags wake_cpu for it; resched_task() ++ * gets it off the CPU so finish_lock_switch() can ++ * complete the move. ++ */ ++ set_task_cpu(p, valid_task_cpu(p)); ++ resched_task(p); ++ } else if (task_queued(p)) { ++ /* ++ * A queued task's skiplist node is linked into this ++ * rq's list, so its CPU must not be rewritten ++ * underneath it: task_rq(p) would then name a runqueue ++ * the node is not in, and the next dequeue_task() would ++ * try to unlink it from there and trip ++ * skiplist_delete()'s "m < 0" while leaving the node ++ * linked in the list it really is on. Unlink it first, ++ * exactly as every other migration of a queued task ++ * does. ++ */ ++ rq = move_queued_task(rq, NULL, p, valid_task_cpu(p)); ++ } ++ __task_rq_unlock(rq, p, NULL); ++ } ++} ++ ++/* migrate_disable()/migrate_enable() are inline in now. */ ++#endif ++ ++/** ++ * init_idle - set up an idle thread for a given CPU ++ * @idle: task in question ++ * @cpu: cpu the idle task belongs to ++ * ++ * NOTE: this function does not set the idle thread's NEED_RESCHED ++ * flag, to make booting more robust. ++ */ ++void init_idle(struct task_struct *idle, int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&idle->pi_lock, flags); ++ raw_spin_lock(rq->lock); ++ idle->last_ran = rq->niffies; ++ time_slice_expired(idle, rq); ++ idle->__state = TASK_RUNNING; ++ /* Setting prio to illegal value shouldn't matter when never queued */ ++ idle->prio = PRIO_LIMIT; ++ idle->flags |= PF_IDLE; ++ ++ scs_task_reset(idle); ++ kasan_unpoison_task_stack(idle); ++ ++#ifdef CONFIG_SMP ++ /* ++ * It's possible that init_idle() gets called multiple times on a task, ++ * in that case do_set_cpus_allowed() will not do the right thing. ++ * ++ * And since this is boot we can forgo the serialisation. ++ */ ++ { ++ struct affinity_context ac = { .new_mask = cpumask_of(cpu) }; ++ ++ set_cpus_allowed_common(idle, &ac); ++ } ++#ifdef CONFIG_SMT_NICE ++ idle->smt_bias = 0; ++#endif ++#endif ++ set_rq_task(rq, idle); ++ ++ /* Silence PROVE_RCU */ ++ rcu_read_lock(); ++ set_task_cpu(idle, cpu); ++ rcu_read_unlock(); ++ ++ rq->idle = idle; ++ rcu_assign_pointer(rq->curr, idle); ++ idle->on_rq = TASK_ON_RQ_QUEUED; ++ raw_spin_unlock(rq->lock); ++ raw_spin_unlock_irqrestore(&idle->pi_lock, flags); ++ ++ /* Set the preempt count _outside_ the spinlocks! */ ++ init_idle_preempt_count(idle, cpu); ++ ++ ftrace_graph_init_idle_task(idle, cpu); ++ vtime_init_idle(idle, cpu); ++#ifdef CONFIG_SMP ++ sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu); ++#endif ++} ++ ++int cpuset_cpumask_can_shrink(const struct cpumask __maybe_unused *cur, ++ const struct cpumask __maybe_unused *trial) ++{ ++ return 1; ++} ++ ++int task_can_attach(struct task_struct *p) ++{ ++ int ret = 0; ++ ++ /* ++ * Kthreads which disallow setaffinity shouldn't be moved ++ * to a new cpuset; we don't want to change their CPU ++ * affinity and isolating such threads by their set of ++ * allowed nodes is unnecessary. Thus, cpusets are not ++ * applicable for such threads. This prevents checking for ++ * success of set_cpus_allowed_ptr() on all attached tasks ++ * before cpus_mask may be changed. ++ */ ++ if (p->flags & PF_NO_SETAFFINITY) ++ ret = -EINVAL; ++ ++ return ret; ++} ++ ++void resched_cpu(int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ rq_lock_irqsave(rq, &rf); ++ if (cpu_online(cpu) || cpu == smp_processor_id()) ++ resched_curr(rq); ++ rq_unlock_irqrestore(rq, &rf); ++} ++ ++#ifdef CONFIG_SMP ++#ifdef CONFIG_NO_HZ_COMMON ++void nohz_balance_enter_idle(int cpu) {} ++ ++/* ++ * In the semi idle case, use the nearest busy CPU for migrating timers ++ * from an idle CPU. This is good for power-savings. ++ * ++ * We don't do similar optimization for completely idle system, as ++ * selecting an idle CPU will add more delays to the timers than intended ++ * (as that CPU's timer base may not be uptodate wrt jiffies etc). ++ */ ++int get_nohz_timer_target(void) ++{ ++ int i, cpu = smp_processor_id(), default_cpu = -1; ++ struct sched_domain *sd; ++ ++ if (housekeeping_cpu(cpu, HK_TYPE_TIMER)) { ++ if (!idle_cpu(cpu)) ++ return cpu; ++ default_cpu = cpu; ++ } ++ ++ rcu_read_lock(); ++ for_each_domain(cpu, sd) { ++ for_each_cpu_and(i, sched_domain_span(sd), ++ housekeeping_cpumask(HK_TYPE_TIMER)) { ++ if (cpu == i) ++ continue; ++ ++ if (!idle_cpu(i)) { ++ cpu = i; ++ goto unlock; ++ } ++ } ++ } ++ ++ if (default_cpu == -1) ++ default_cpu = housekeeping_any_cpu(HK_TYPE_TIMER); ++ cpu = default_cpu; ++unlock: ++ rcu_read_unlock(); ++ return cpu; ++} ++ ++/* ++ * When add_timer_on() enqueues a timer into the timer wheel of an ++ * idle CPU then this timer might expire before the next timer event ++ * which is scheduled to wake up that CPU. In case of a completely ++ * idle system the next event might even be infinite time into the ++ * future. wake_up_idle_cpu() ensures that the CPU is woken up and ++ * leaves the inner idle loop so the newly added timer is taken into ++ * account when the CPU goes back to idle and evaluates the timer ++ * wheel for the next timer event. ++ */ ++static void wake_up_idle_cpu(int cpu) ++{ ++ if (cpu == smp_processor_id()) ++ return; ++ ++ if (set_nr_and_not_polling(cpu_rq(cpu)->idle)) ++ smp_sched_reschedule(cpu); ++ else ++ trace_sched_wake_idle_without_ipi(cpu); ++} ++ ++static bool wake_up_full_nohz_cpu(int cpu) ++{ ++ /* ++ * We just need the target to call irq_exit() and re-evaluate ++ * the next tick. The nohz full kick at least implies that. ++ * If needed we can still optimize that later with an ++ * empty IRQ. ++ */ ++ if (cpu_is_offline(cpu)) ++ return true; /* Don't try to wake offline CPUs. */ ++ if (tick_nohz_full_cpu(cpu)) { ++ if (cpu != smp_processor_id() || ++ tick_nohz_tick_stopped()) ++ tick_nohz_full_kick_cpu(cpu); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * Wake up the specified CPU. If the CPU is going offline, it is the ++ * caller's responsibility to deal with the lost wakeup, for example, ++ * by hooking into the CPU_DEAD notifier like timers and hrtimers do. ++ */ ++void wake_up_nohz_cpu(int cpu) ++{ ++ if (!wake_up_full_nohz_cpu(cpu)) ++ wake_up_idle_cpu(cpu); ++} ++#endif /* CONFIG_NO_HZ_COMMON */ ++ ++/* ++ * This is how migration works: ++ * ++ * 1) we invoke migration_cpu_stop() on the target CPU using ++ * stop_one_cpu(). ++ * 2) stopper starts to run (implicitly forcing the migrated thread ++ * off the CPU) ++ * 3) it checks whether the migrated task is still in the wrong runqueue. ++ * 4) if it's in the wrong runqueue then the migration thread removes ++ * it and puts it into the right queue. ++ * 5) stopper completes and stop_one_cpu() returns and the migration ++ * is done. ++ */ ++ ++/* ++ * move_queued_task - move a queued task to new rq. ++ * ++ * Returns (locked) new rq. Old rq's lock is released. Running tasks ++ * are not on the skiplist, so callers must use task_queued(). ++ */ ++static struct rq *move_queued_task(struct rq *rq, ++ struct rq_flags __always_unused *rf, ++ struct task_struct *p, int new_cpu) ++{ ++ lockdep_assert_held(rq->lock); ++ ++ dequeue_task(rq, p, 0); ++ set_task_cpu(p, new_cpu); ++ rq_unlock(rq); ++ ++ rq = cpu_rq(new_cpu); ++ ++ rq_lock(rq); ++ WARN_ON_ONCE(task_cpu(p) != new_cpu); ++ enqueue_task(rq, p, ENQUEUE_MIGRATED); ++ try_preempt(p, rq); ++ ++ return rq; ++} ++ ++struct set_affinity_pending; ++ ++struct migration_arg { ++ struct task_struct *task; ++ int dest_cpu; ++ struct set_affinity_pending *pending; ++}; ++ ++/* ++ * @refs: number of wait_for_completion() ++ * @stop_pending: is @stop_work in use ++ */ ++struct set_affinity_pending { ++ refcount_t refs; ++ unsigned int stop_pending; ++ struct completion done; ++ struct cpu_stop_work stop_work; ++ struct migration_arg arg; ++}; ++ ++static struct rq *__migrate_task(struct rq *rq, struct rq_flags *rf, ++ struct task_struct *p, int dest_cpu) ++{ ++ /* Affinity changed (again). */ ++ if (!is_cpu_allowed(p, dest_cpu)) ++ return rq; ++ ++ return move_queued_task(rq, rf, p, dest_cpu); ++} ++ ++static int migration_cpu_stop(void *data) ++{ ++ struct migration_arg *arg = data; ++ struct set_affinity_pending *pending = arg->pending; ++ struct task_struct *p = arg->task; ++ struct rq *rq = this_rq(); ++ bool complete = false; ++ struct rq_flags rf; ++ ++ /* ++ * The original target CPU might have gone down and we might ++ * be on another CPU but it doesn't matter. ++ */ ++ local_irq_save(rf.flags); ++ /* ++ * Flush pending wakeups so we do not miss enforcing cpus_ptr, ++ * see set_cpus_allowed_ptr()'s TASK_WAKING test. ++ */ ++ flush_smp_call_function_queue(); ++ ++ raw_spin_lock(&p->pi_lock); ++ rq_lock(rq); ++ ++ /* ++ * If we were passed a pending, then ->stop_pending was set, thus ++ * p->migration_pending must have remained stable. ++ */ ++ WARN_ON_ONCE(pending && pending != p->migration_pending); ++ ++ /* ++ * If task_rq(p) != rq, it cannot be migrated here, because we're ++ * holding rq->lock. If p is not queued it cannot get enqueued ++ * because we're holding p->pi_lock. ++ */ ++ if (task_rq(p) == rq) { ++ if (is_migration_disabled(p)) ++ goto out; ++ ++ if (pending) { ++ p->migration_pending = NULL; ++ complete = true; ++ ++ if (cpumask_test_cpu(task_cpu(p), &p->cpus_mask)) ++ goto out; ++ } ++ ++ /* ++ * Mainline updates the rq clock before __migrate_task() here. ++ * MuQSS has no need to: dequeue_task() and enqueue_task() each ++ * call update_clocks() on the runqueue they touch, so niffies, ++ * which deadlines are based on, is current on both sides of ++ * the migration. Adding one here would only be redundant. ++ */ ++ if (task_queued(p)) ++ rq = __migrate_task(rq, &rf, p, arg->dest_cpu); ++ else ++ p->wake_cpu = arg->dest_cpu; ++ } else if (pending) { ++ /* ++ * The task moved before the stopper got to run. We're ++ * holding ->pi_lock, so the allowed mask is stable - if ++ * it got somewhere allowed, we're done. ++ */ ++ if (cpumask_test_cpu(task_cpu(p), p->cpus_ptr)) { ++ p->migration_pending = NULL; ++ complete = true; ++ goto out; ++ } ++ ++ /* ++ * When migrate_enable() hits a rq mis-match we can't ++ * reliably determine is_migration_disabled() and so ++ * have to chase after it. ++ */ ++ WARN_ON_ONCE(!pending->stop_pending); ++ preempt_disable(); ++ rq_unlock(rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags); ++ stop_one_cpu_nowait(task_cpu(p), migration_cpu_stop, ++ &pending->arg, &pending->stop_work); ++ preempt_enable(); ++ return 0; ++ } ++out: ++ if (pending) ++ pending->stop_pending = false; ++ rq_unlock(rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags); ++ ++ if (complete) ++ complete_all(&pending->done); ++ ++ return 0; ++} ++ ++/* ++ * When given a valid mask, __set_cpus_allowed_ptr() must block until ++ * the designated task is enqueued on an allowed CPU. A running task ++ * is kicked off with the CPU stopper. A migrate_disable() region on ++ * the target delays that move until the outermost migrate_enable(); ++ * concurrent waiters share one set_affinity_pending. ++ */ ++static int affine_move_task(struct rq *rq, struct task_struct *p, ++ struct rq_flags *rf, int dest_cpu, unsigned int flags) ++{ ++ struct set_affinity_pending my_pending = { }, *pending = NULL; ++ bool stop_pending, complete = false; ++ ++ /* Can the task run on the task's current CPU? If so, we're done */ ++ if (cpumask_test_cpu(task_cpu(p), &p->cpus_mask)) { ++ /* ++ * If there are pending waiters, but no pending stop_work, ++ * then complete now. ++ */ ++ pending = p->migration_pending; ++ if (pending && !pending->stop_pending) { ++ p->migration_pending = NULL; ++ complete = true; ++ } ++ ++ task_rq_unlock(rq, p, rf); ++ ++ if (complete) ++ complete_all(&pending->done); ++ ++ return 0; ++ } ++ ++ if (!(flags & SCA_MIGRATE_ENABLE)) { ++ /* serialized by p->pi_lock */ ++ if (!p->migration_pending) { ++ refcount_set(&my_pending.refs, 1); ++ init_completion(&my_pending.done); ++ my_pending.arg = (struct migration_arg) { ++ .task = p, ++ .dest_cpu = dest_cpu, ++ .pending = &my_pending, ++ }; ++ ++ p->migration_pending = &my_pending; ++ } else { ++ pending = p->migration_pending; ++ refcount_inc(&pending->refs); ++ /* ++ * Affinity has changed, but we've already installed a ++ * pending. migration_cpu_stop() *must* see this, else ++ * we risk completing despite a task on a disallowed ++ * CPU. Serialized by p->pi_lock. ++ */ ++ pending->arg.dest_cpu = dest_cpu; ++ } ++ } ++ pending = p->migration_pending; ++ /* ++ * !MIGRATE_ENABLE installs a pending if there wasn't one. ++ * MIGRATE_ENABLE only gets here because the current CPU no ++ * longer matches, so a concurrent set_cpus_allowed_ptr() ++ * should still be pending completion. ++ */ ++ if (WARN_ON_ONCE(!pending)) { ++ task_rq_unlock(rq, p, rf); ++ return -EINVAL; ++ } ++ ++ if (task_running(rq, p) || READ_ONCE(p->__state) == TASK_WAKING) { ++ /* ++ * MIGRATE_ENABLE gets here because 'p == current'. For ++ * anything else we cannot do is_migration_disabled(); ++ * punt and have the stopper handle it race-free. ++ */ ++ stop_pending = pending->stop_pending; ++ if (!stop_pending) ++ pending->stop_pending = true; ++ ++ preempt_disable(); ++ task_rq_unlock(rq, p, rf); ++ if (!stop_pending) { ++ stop_one_cpu_nowait(cpu_of(rq), migration_cpu_stop, ++ &pending->arg, &pending->stop_work); ++ } ++ preempt_enable(); ++ ++ if (flags & SCA_MIGRATE_ENABLE) ++ return 0; ++ } else { ++ if (!is_migration_disabled(p)) { ++ if (task_queued(p)) ++ rq = move_queued_task(rq, rf, p, dest_cpu); ++ ++ if (!pending->stop_pending) { ++ p->migration_pending = NULL; ++ complete = true; ++ } ++ } ++ task_rq_unlock(rq, p, rf); ++ ++ if (complete) ++ complete_all(&pending->done); ++ } ++ ++ wait_for_completion(&pending->done); ++ ++ if (refcount_dec_and_test(&pending->refs)) ++ wake_up_var(&pending->refs); /* No UaF, just an address */ ++ ++ /* ++ * Block the original owner of &pending until all subsequent ++ * callers have seen the completion and decremented the refcount ++ */ ++ wait_var_event(&my_pending.refs, !refcount_read(&my_pending.refs)); ++ ++ WARN_ON_ONCE(my_pending.stop_pending); ++ ++ return 0; ++} ++ ++/* ++ * Record @new_mask as the affinity userspace asked for, handing back the ++ * allocation it displaced for the caller to free once the locks are ++ * dropped. An explicit request also supersedes any override bind_zero() ++ * had to force, since this mask is the one that is wanted now. ++ */ ++static cpumask_t *set_user_cpus_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ cpumask_t *user_mask) ++{ ++ lockdep_assert_held(&p->pi_lock); ++ ++ if (user_mask) { ++ cpumask_copy(user_mask, new_mask); ++ swap(p->user_cpus_ptr, user_mask); ++ } ++ p->zerobound = false; ++ ++ return user_mask; ++} ++ ++/* ++ * Change a given task's CPU affinity. Migrate the thread to a ++ * proper CPU and schedule it away if the CPU it's executing on ++ * is removed from the allowed bitmask. ++ * ++ * NOTE: the caller must have a valid reference to the task, the ++ * task must not exit() & deallocate itself prematurely. The ++ * call is not atomic; no spinlocks may be held. ++ */ ++static int __set_cpus_allowed_ptr(struct task_struct *p, ++ const struct cpumask *new_mask, ++ u32 flags) ++{ ++ const struct cpumask *cpu_valid_mask = cpu_active_mask; ++ cpumask_t *user_mask = NULL; ++ unsigned int dest_cpu; ++ struct rq_flags rf; ++ struct rq *rq; ++ int ret = 0; ++ ++ /* ++ * user_cpus_ptr keeps what userspace asked for so that ++ * relax_compatible_cpus_allowed_ptr() and unbind_zero() have ++ * something to put back. Only sched_setaffinity() passes SCA_USER ++ * and it is always sleepable, so make room before taking any lock; ++ * failing here only costs the task that restore, not the syscall. ++ */ ++ if (flags & SCA_USER) ++ user_mask = kmalloc(cpumask_size(), GFP_KERNEL); ++ ++ rq = task_rq_lock(p, &rf); ++ update_rq_clock(rq); ++ ++ if ((p->flags & PF_KTHREAD) || is_migration_disabled(p)) { ++ /* ++ * Kernel threads are allowed on online && !active CPUs. ++ * migrate_disabled() tasks must not fail the dest pick ++ * on SCA_MIGRATE_ENABLE or we skip set_cpus_allowed_common() ++ * and never reset p->cpus_ptr. ++ */ ++ cpu_valid_mask = cpu_online_mask; ++ } ++ ++ /* ++ * Must re-check here, to close a race against __kthread_bind(), ++ * sched_setaffinity() is not guaranteed to observe the flag. ++ */ ++ if ((flags & SCA_CHECK) && (p->flags & PF_NO_SETAFFINITY)) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ if (!(flags & SCA_MIGRATE_ENABLE)) { ++ if (cpumask_equal(&p->cpus_mask, new_mask)) { ++ /* ++ * Nothing to change, but asking for exactly the mask ++ * bind_zero() forced is still userspace choosing it, ++ * and takes over from the restore. An internal caller ++ * changing nothing must not cost the task that. ++ */ ++ if (flags & SCA_USER) ++ user_mask = set_user_cpus_ptr(p, new_mask, ++ user_mask); ++ goto out; ++ } ++ ++ if (WARN_ON_ONCE(p == current && ++ is_migration_disabled(p) && ++ !cpumask_test_cpu(task_cpu(p), new_mask))) { ++ ret = -EBUSY; ++ goto out; ++ } ++ } ++ ++ /* ++ * Picking a ~random cpu helps in cases where we are changing affinity ++ * for groups of tasks (ie. cpuset), so that load balancing is not ++ * immediately required to distribute the tasks within their new mask. ++ */ ++ dest_cpu = cpumask_any_and_distribute(cpu_valid_mask, new_mask); ++ if (dest_cpu >= nr_cpu_ids) { ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ { ++ struct affinity_context ac = { ++ .new_mask = new_mask, ++ .flags = flags, ++ }; ++ ++ __do_set_cpus_allowed(p, &ac); ++ } ++ ++ /* ++ * migrate_enable() only reinstates cpus_mask, so it is not a change ++ * of what the task wants and must not disturb what is saved. ++ */ ++ if (!(flags & SCA_MIGRATE_ENABLE)) ++ user_mask = set_user_cpus_ptr(p, new_mask, user_mask); ++ ++ if (p->flags & PF_KTHREAD) { ++ /* ++ * For kernel threads that do indeed end up on online && ++ * !active we want to ensure they are strict per-CPU threads. ++ */ ++ WARN_ON(cpumask_intersects(new_mask, cpu_online_mask) && ++ !cpumask_intersects(new_mask, cpu_active_mask) && ++ p->nr_cpus_allowed != 1); ++ } ++ ++ ret = affine_move_task(rq, p, &rf, dest_cpu, flags); ++ kfree(user_mask); ++ ++ return ret; ++ ++out: ++ task_rq_unlock(rq, p, &rf); ++ kfree(user_mask); ++ ++ return ret; ++} ++ ++int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ return __set_cpus_allowed_ptr(p, new_mask, 0); ++} ++EXPORT_SYMBOL_GPL(set_cpus_allowed_ptr); ++ ++#ifdef CONFIG_HOTPLUG_CPU ++/* ++ * Skip affinity rewrite for tasks whose mask is managed elsewhere: the ++ * hotplug/stopper threads, idle, and kthreads that forbid setaffinity ++ * (including KTHREAD_IS_PER_CPU, rebound on unpark). ++ */ ++static bool bind_zero_skip_affinity(struct task_struct *p, struct rq *rq) ++{ ++ return p == current || is_idle_task(p) || p == rq->stop || ++ (p->flags & PF_NO_SETAFFINITY) || kthread_is_per_cpu(p); ++} ++ ++/* ++ * Move @p off @src_cpu under pi+rq lock, and only override its affinity ++ * if losing @src_cpu would leave it with no active CPU at all. ++ * ++ * Like mainline, the common case does not touch cpus_mask: an offline or ++ * deactivated CPU is already refused by is_cpu_allowed(), sched_other_cpu(), ++ * needs_other_cpu() and valid_task_cpu(), so the mask needs no editing and ++ * the task keeps the affinity userspace asked for across a hotplug cycle. ++ * Narrowing here would be permanent, as unbind_zero() only restores tasks ++ * that were forced. ++ * ++ * The desperate case still has to put the task somewhere: substitute an ++ * active fallback (usually CPU0) for @src_cpu and record that with ++ * zerobound, stashing the mask being replaced in user_cpus_ptr so ++ * unbind_zero() can put back exactly what userspace asked for once ++ * @src_cpu returns. ++ * ++ * migrate_disable() tasks keep their pin (cpus_ptr) and are left for ++ * wait_empty to wait out; only cpus_mask is updated so migrate_enable() ++ * will affine_move them. ++ */ ++static int bind_zero_one(struct task_struct *p, int src_cpu) ++{ ++ cpumask_t *user_mask, new_mask; ++ struct rq_flags rf; ++ struct rq *rq; ++ int dest, bound = 0; ++ ++ /* ++ * The override below cannot allocate under the runqueue lock, so ++ * speculatively provide it room for the old mask here. Almost every ++ * task takes the fast path and hands this straight back. ++ */ ++ user_mask = kmalloc(cpumask_size(), GFP_ATOMIC); ++ ++ rq = task_rq_lock(p, &rf); ++ ++ if (!bind_zero_skip_affinity(p, rq) && ++ cpumask_test_cpu(src_cpu, &p->cpus_mask)) { ++ cpumask_copy(&new_mask, &p->cpus_mask); ++ cpumask_clear_cpu(src_cpu, &new_mask); ++ if (!cpumask_intersects(&new_mask, cpu_active_mask)) { ++ dest = cpumask_any(cpu_active_mask); ++ if (dest >= nr_cpu_ids) ++ dest = cpumask_any(cpu_online_mask); ++ if (dest < nr_cpu_ids) { ++ struct affinity_context ac = { ++ .new_mask = &new_mask ++ }; ++ ++ /* ++ * Only the first override saves; a second one ++ * would only be replacing an already forced ++ * mask with another. What is being displaced ++ * is what has to come back, so it takes over ++ * any mask sched_setaffinity() left here - ++ * reusing its allocation - rather than let a ++ * request cpusets have since narrowed be ++ * restored wider than the cpuset allows. ++ */ ++ if (!p->zerobound) { ++ if (p->user_cpus_ptr) { ++ cpumask_copy(p->user_cpus_ptr, ++ &p->cpus_mask); ++ } else if (user_mask) { ++ cpumask_copy(user_mask, ++ &p->cpus_mask); ++ p->user_cpus_ptr = user_mask; ++ user_mask = NULL; ++ } ++ } ++ cpumask_set_cpu(dest, &new_mask); ++ p->zerobound = true; ++ __do_set_cpus_allowed(p, &ac); ++ bound = 1; ++ } ++ } ++ } ++ ++ if (task_cpu(p) == src_cpu && p != current && ++ !is_idle_task(p) && p != rq->stop && ++ !is_migration_disabled(p)) { ++ dest = valid_task_cpu(p); ++ if (dest != src_cpu) { ++ /* ++ * Only a queued task has to be moved. A blocked one is ++ * on no runqueue, so it does not hold @src_cpu up, and ++ * its destination must not be stamped into wake_cpu: ++ * that arms return_task()'s migration handshake, which ++ * would then fire on a task nobody is migrating. Its ++ * wakeup picks an allowed CPU by itself. ++ */ ++ if (task_queued(p)) ++ rq = move_queued_task(rq, &rf, p, dest); ++ } ++ } ++ ++ task_rq_unlock(rq, p, &rf); ++ kfree(user_mask); ++ return bound; ++} ++ ++/* ++ * Called from sched_cpu_wait_empty() (sleepable) on the outgoing CPU. ++ * Walk every task and move anyone still queued here. Affinity is only ++ * overridden for the few tasks @src_cpu leaving would otherwise strand, ++ * so on a normal offline this reports nothing. ++ */ ++static void bind_zero(int src_cpu) ++{ ++ struct task_struct *g, *p; ++ int bound = 0; ++ ++ if (src_cpu == 0) ++ return; ++ ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ if (task_cpu(p) != src_cpu && ++ !cpumask_test_cpu(src_cpu, p->cpus_ptr) && ++ !cpumask_test_cpu(src_cpu, &p->cpus_mask)) ++ continue; ++ bound += bind_zero_one(p, src_cpu); ++ } ++ rcu_read_unlock(); ++ ++ if (bound) { ++ printk(KERN_INFO "MuQSS overrode affinity for %d processes left with no active cpu by offlining cpu %d\n", ++ bound, src_cpu); ++ } ++} ++ ++/* ++ * Undo the affinity override bind_zero() had to force onto tasks that ++ * @src_cpu leaving would have stranded. Hold pi+rq so cpus_mask / ++ * nr_cpus_allowed stay in lockstep. Unbound kthreads are eligible — only ++ * per-CPU / PF_NO_SETAFFINITY threads were skipped on the way out. ++ * ++ * Restoring the saved mask can take the CPU the task is on right now away ++ * from it, so kick it off exactly like do_set_cpus_allowed() does. ++ */ ++static void unbind_zero(int src_cpu) ++{ ++ int restored = 0, unbound = 0; ++ struct task_struct *g, *p; ++ ++ if (src_cpu == 0) ++ return; ++ ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ if (!p->zerobound) ++ continue; ++ ++ rq = task_rq_lock(p, &rf); ++ if (!p->zerobound) { ++ task_rq_unlock(rq, p, &rf); ++ continue; ++ } ++ ++ if (likely(p->user_cpus_ptr)) { ++ /* ++ * Wait for one of the CPUs the task actually wanted, ++ * or restoring would strand it all over again. The ++ * saved mask stays put once copied back: it is still ++ * what the task wants, and what a later override or ++ * relax_compatible_cpus_allowed_ptr() restores. ++ */ ++ if (cpumask_test_cpu(src_cpu, p->user_cpus_ptr)) { ++ struct affinity_context ac = { ++ .new_mask = p->user_cpus_ptr ++ }; ++ ++ __do_set_cpus_allowed(p, &ac); ++ p->zerobound = false; ++ restored++; ++ } ++ } else if (!cpumask_test_cpu(src_cpu, &p->cpus_mask)) { ++ /* ++ * bind_zero() had no room to save the old mask, so ++ * hand @src_cpu back and stop there rather than ++ * accumulate every CPU that ever comes online. ++ */ ++ cpumask_set_cpu(src_cpu, &p->cpus_mask); ++ p->nr_cpus_allowed = cpumask_weight(&p->cpus_mask); ++ p->zerobound = false; ++ unbound++; ++ } ++ ++ if (!p->zerobound && needs_other_cpu(p, task_cpu(p))) { ++ int dest = valid_task_cpu(p); ++ ++ /* ++ * As in bind_zero_one(), a blocked task is left alone ++ * rather than having @dest stamped into its wake_cpu, ++ * which would arm return_task()'s migration handshake ++ * for a migration that is not happening. ++ */ ++ if (task_queued(p)) ++ rq = move_queued_task(rq, &rf, p, dest); ++ else if (task_running(rq, p)) { ++ set_task_cpu(p, dest); ++ resched_task(p); ++ } ++ } ++ ++ task_rq_unlock(rq, p, &rf); ++ } ++ rcu_read_unlock(); ++ ++ if (restored) { ++ printk(KERN_INFO "MuQSS restored the original affinity of %d processes by onlining cpu %d\n", ++ restored, src_cpu); ++ } ++ if (unbound) { ++ printk(KERN_INFO "MuQSS added affinity for %d processes to cpu %d\n", ++ unbound, src_cpu); ++ } ++} ++ ++/* ++ * idle_task_exit() is an empty inline in now; the ++ * outgoing CPU switches back to init_mm from sched_cpu_wait_empty() instead. ++ */ ++#else /* CONFIG_HOTPLUG_CPU */ ++static void unbind_zero(int src_cpu) {} ++#endif /* CONFIG_HOTPLUG_CPU */ ++ ++void sched_set_stop_task(int cpu, struct task_struct *stop) ++{ ++ struct sched_param stop_param = { .sched_priority = STOP_PRIO }; ++ struct sched_param start_param = { .sched_priority = 0 }; ++ struct task_struct *old_stop = cpu_rq(cpu)->stop; ++ ++ if (stop) { ++ /* ++ * Make it appear like a SCHED_FIFO task, its something ++ * userspace knows about and won't get confused about. ++ * ++ * Also, it will make PI more or less work without too ++ * much confusion -- but then, stop work should not ++ * rely on PI working anyway. ++ */ ++ sched_setscheduler_nocheck(stop, SCHED_FIFO, &stop_param); ++ } ++ ++ cpu_rq(cpu)->stop = stop; ++ ++ if (old_stop) { ++ /* ++ * Reset it back to a normal scheduling policy so that ++ * it can die in pieces. ++ */ ++ sched_setscheduler_nocheck(old_stop, SCHED_NORMAL, &start_param); ++ } ++} ++ ++#if defined(CONFIG_SCHED_DEBUG) && defined(CONFIG_SYSCTL) ++ ++static struct ctl_table sd_ctl_dir[] = { ++ { ++ .procname = "sched_domain", ++ .mode = 0555, ++ }, ++ {} ++}; ++ ++static struct ctl_table sd_ctl_root[] = { ++ { ++ .procname = "kernel", ++ .mode = 0555, ++ .child = sd_ctl_dir, ++ }, ++ {} ++}; ++ ++static struct ctl_table *sd_alloc_ctl_entry(int n) ++{ ++ struct ctl_table *entry = ++ kcalloc(n, sizeof(struct ctl_table), GFP_KERNEL); ++ ++ return entry; ++} ++ ++static void sd_free_ctl_entry(struct ctl_table **tablep) ++{ ++ struct ctl_table *entry; ++ ++ /* ++ * In the intermediate directories, both the child directory and ++ * procname are dynamically allocated and could fail but the mode ++ * will always be set. In the lowest directory the names are ++ * static strings and all have proc handlers. ++ */ ++ for (entry = *tablep; entry->mode; entry++) { ++ if (entry->child) ++ sd_free_ctl_entry(&entry->child); ++ if (entry->proc_handler == NULL) ++ kfree(entry->procname); ++ } ++ ++ kfree(*tablep); ++ *tablep = NULL; ++} ++ ++static void ++set_table_entry(struct ctl_table *entry, ++ const char *procname, void *data, int maxlen, ++ umode_t mode, proc_handler *proc_handler) ++{ ++ entry->procname = procname; ++ entry->data = data; ++ entry->maxlen = maxlen; ++ entry->mode = mode; ++ entry->proc_handler = proc_handler; ++} ++ ++static struct ctl_table * ++sd_alloc_ctl_domain_table(struct sched_domain *sd) ++{ ++ struct ctl_table *table = sd_alloc_ctl_entry(9); ++ ++ if (table == NULL) ++ return NULL; ++ ++ set_table_entry(&table[0], "min_interval", &sd->min_interval, sizeof(long), 0644, proc_doulongvec_minmax); ++ set_table_entry(&table[1], "max_interval", &sd->max_interval, sizeof(long), 0644, proc_doulongvec_minmax); ++ set_table_entry(&table[2], "busy_factor", &sd->busy_factor, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[3], "imbalance_pct", &sd->imbalance_pct, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[4], "cache_nice_tries", &sd->cache_nice_tries, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[5], "flags", &sd->flags, sizeof(int), 0644, proc_dointvec_minmax); ++ set_table_entry(&table[6], "max_newidle_lb_cost", &sd->max_newidle_lb_cost, sizeof(long), 0644, proc_doulongvec_minmax); ++ set_table_entry(&table[7], "name", sd->name, CORENAME_MAX_SIZE, 0444, proc_dostring); ++ /* &table[8] is terminator */ ++ ++ return table; ++} ++ ++static struct ctl_table *sd_alloc_ctl_cpu_table(int cpu) ++{ ++ struct ctl_table *entry, *table; ++ struct sched_domain *sd; ++ int domain_num = 0, i; ++ char buf[32]; ++ ++ for_each_domain(cpu, sd) ++ domain_num++; ++ entry = table = sd_alloc_ctl_entry(domain_num + 1); ++ if (table == NULL) ++ return NULL; ++ ++ i = 0; ++ for_each_domain(cpu, sd) { ++ snprintf(buf, 32, "domain%d", i); ++ entry->procname = kstrdup(buf, GFP_KERNEL); ++ entry->mode = 0555; ++ entry->child = sd_alloc_ctl_domain_table(sd); ++ entry++; ++ i++; ++ } ++ return table; ++} ++ ++static cpumask_var_t sd_sysctl_cpus; ++static struct ctl_table_header *sd_sysctl_header; ++ ++void register_sched_domain_sysctl(void) ++{ ++ static struct ctl_table *cpu_entries; ++ static struct ctl_table **cpu_idx; ++ char buf[32]; ++ int i; ++ ++ if (!cpu_entries) { ++ cpu_entries = sd_alloc_ctl_entry(num_possible_cpus() + 1); ++ if (!cpu_entries) ++ return; ++ ++ WARN_ON(sd_ctl_dir[0].child); ++ sd_ctl_dir[0].child = cpu_entries; ++ } ++ ++ if (!cpu_idx) { ++ struct ctl_table *e = cpu_entries; ++ ++ cpu_idx = kcalloc(nr_cpu_ids, sizeof(struct ctl_table*), GFP_KERNEL); ++ if (!cpu_idx) ++ return; ++ ++ /* deal with sparse possible map */ ++ for_each_possible_cpu(i) { ++ cpu_idx[i] = e; ++ e++; ++ } ++ } ++ ++ if (!cpumask_available(sd_sysctl_cpus)) { ++ if (!alloc_cpumask_var(&sd_sysctl_cpus, GFP_KERNEL)) ++ return; ++ ++ /* init to possible to not have holes in @cpu_entries */ ++ cpumask_copy(sd_sysctl_cpus, cpu_possible_mask); ++ } ++ ++ for_each_cpu(i, sd_sysctl_cpus) { ++ struct ctl_table *e = cpu_idx[i]; ++ ++ if (e->child) ++ sd_free_ctl_entry(&e->child); ++ ++ if (!e->procname) { ++ snprintf(buf, 32, "cpu%d", i); ++ e->procname = kstrdup(buf, GFP_KERNEL); ++ } ++ e->mode = 0555; ++ e->child = sd_alloc_ctl_cpu_table(i); ++ ++ __cpumask_clear_cpu(i, sd_sysctl_cpus); ++ } ++ ++ WARN_ON(sd_sysctl_header); ++ sd_sysctl_header = register_sysctl_table(sd_ctl_root); ++} ++ ++void dirty_sched_domain_sysctl(int cpu) ++{ ++ if (cpumask_available(sd_sysctl_cpus)) ++ __cpumask_set_cpu(cpu, sd_sysctl_cpus); ++} ++ ++/* may be called multiple times per register */ ++void unregister_sched_domain_sysctl(void) ++{ ++ unregister_sysctl_table(sd_sysctl_header); ++ sd_sysctl_header = NULL; ++} ++#endif /* CONFIG_SYSCTL */ ++ ++void set_rq_online(struct rq *rq) ++{ ++ if (!rq->online) { ++ cpumask_set_cpu(cpu_of(rq), rq->rd->online); ++ rq->online = true; ++ } ++} ++ ++void set_rq_offline(struct rq *rq) ++{ ++ if (rq->online) { ++ int cpu = cpu_of(rq); ++ ++ cpumask_clear_cpu(cpu, rq->rd->online); ++ rq->online = false; ++ clear_cpuidle_map(cpu); ++ } ++} ++ ++/* ++ * used to mark begin/end of suspend/resume: ++ */ ++static int num_cpus_frozen; ++ ++/* ++ * Update cpusets according to cpu_active mask. If cpusets are ++ * disabled, cpuset_update_active_cpus() becomes a simple wrapper ++ * around partition_sched_domains(). ++ * ++ * If we come here as part of a suspend/resume, don't touch cpusets because we ++ * want to restore it back to its original state upon resume anyway. ++ */ ++static void cpuset_cpu_active(void) ++{ ++ if (cpuhp_tasks_frozen) { ++ /* ++ * num_cpus_frozen tracks how many CPUs are involved in suspend ++ * resume sequence. As long as this is not the last online ++ * operation in the resume sequence, just build a single sched ++ * domain, ignoring cpusets. ++ */ ++ partition_sched_domains(1, NULL, NULL); ++ if (--num_cpus_frozen) ++ return; ++ /* ++ * This is the last CPU online operation. So fall through and ++ * restore the original sched domains by considering the ++ * cpuset configurations. ++ */ ++ cpuset_force_rebuild(); ++ } ++ ++ cpuset_update_active_cpus(); ++} ++ ++static int cpuset_cpu_inactive(unsigned int cpu) ++{ ++ if (!cpuhp_tasks_frozen) { ++ cpuset_update_active_cpus(); ++ } else { ++ num_cpus_frozen++; ++ partition_sched_domains(1, NULL, NULL); ++ } ++ return 0; ++} ++ ++int sched_cpu_activate(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ /* ++ * When going up, increment the number of cores with SMT present. ++ * cpu_smt_mask() is cpumask_of(cpu) when !CONFIG_SCHED_SMT, so this ++ * never fires there. ++ */ ++ if (cpumask_weight(cpu_smt_mask(cpu)) == 2) ++ static_branch_inc_cpuslocked(&sched_smt_present); ++ ++ set_cpu_active(cpu, true); ++ ++ if (sched_smp_initialized) { ++ sched_domains_numa_masks_set(cpu); ++ cpuset_cpu_active(); ++ } ++ ++ /* ++ * Put the rq online, if not already. This happens: ++ * ++ * 1) In the early boot process, because we build the real domains ++ * after all CPUs have been brought up. ++ * ++ * 2) At runtime, if cpuset_cpu_active() fails to rebuild the ++ * domains. ++ */ ++ rq_lock_irqsave(rq, &rf); ++ if (rq->rd) { ++ BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span)); ++ set_rq_online(rq); ++ } ++ rq_unlock_irqrestore(rq, &rf); ++ /* ++ * unbind_zero() takes each task's pi+rq lock; do not nest that ++ * under this CPU's rq lock. ++ */ ++ unbind_zero(cpu); ++ ++ return 0; ++} ++ ++int sched_cpu_deactivate(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ int ret; ++ ++ set_cpu_active(cpu, false); ++ /* ++ * We've cleared cpu_active_mask, wait for all preempt-disabled and RCU ++ * users of this state to go away such that all new such users will ++ * observe it. ++ * ++ * Do sync before park smpboot threads to take care the rcu boost case. ++ */ ++ synchronize_rcu(); ++ ++ sched_domains_free_llc_id(cpu); ++ ++ rq_lock_irqsave(rq, &rf); ++ if (rq->rd) { ++ update_rq_clock(rq); ++ BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span)); ++ set_rq_offline(rq); ++ } ++ rq_unlock_irqrestore(rq, &rf); ++ ++ /* ++ * When going down, decrement the number of cores with SMT present. ++ */ ++ if (cpumask_weight(cpu_smt_mask(cpu)) == 2) ++ static_branch_dec_cpuslocked(&sched_smt_present); ++ ++ if (!sched_smp_initialized) ++ return 0; ++ ++ ret = cpuset_cpu_inactive(cpu); ++ if (ret) { ++ set_cpu_active(cpu, true); ++ return ret; ++ } ++ sched_domains_numa_masks_clear(cpu); ++ return 0; ++} ++ ++int sched_cpu_starting(unsigned int cpu) ++{ ++ sched_tick_start(cpu); ++ return 0; ++} ++ ++#ifdef CONFIG_HOTPLUG_CPU ++/* ++ * Invoked on the outgoing CPU in context of the CPU hotplug thread after ++ * ensuring that there are no user space tasks left on the CPU. ++ * ++ * If there is a lazy mm in use on the hotplug thread, drop it and switch to ++ * init_mm. finish_cpu() on the control CPU drops the init_mm reference and ++ * WARNs if we left anything else behind. 5.12 did this from idle_task_exit(); ++ * mainline now does it here from sched_cpu_wait_empty(). ++ */ ++static void __sched_force_init_mm(void) ++{ ++ struct mm_struct *mm = current->active_mm; ++ ++ if (mm == &init_mm) ++ return; ++ ++ mmgrab_lazy_tlb(&init_mm); ++ current->active_mm = &init_mm; ++ switch_mm_irqs_off(mm, &init_mm, current); ++ finish_arch_post_lock_switch(); ++ mmdrop_lazy_tlb(mm); ++} ++ ++static void sched_force_init_mm(void) ++{ ++ unsigned long flags; ++ ++ local_irq_save(flags); ++ __sched_force_init_mm(); ++ local_irq_restore(flags); ++} ++ ++static void dump_rq_tasks(struct rq *rq, const char *loglvl) ++{ ++ struct task_struct *g, *p; ++ int cpu = cpu_of(rq); ++ ++ lockdep_assert_rq_held(rq); ++ ++ printk("%sCPU%d tasks (nr_running=%u nr_pinned=%u):\n", ++ loglvl, cpu, rq->nr_running, rq->nr_pinned); ++ rcu_read_lock(); ++ for_each_process_thread(g, p) { ++ if (task_cpu(p) != cpu) ++ continue; ++ printk("%s\tpid: %d, name: %s, queued: %d, on_rq: %d\n", ++ loglvl, p->pid, p->comm, task_queued(p), p->on_rq); ++ } ++ rcu_read_unlock(); ++} ++ ++/* ++ * Invoked on the outgoing CPU after per-CPU kthreads have been parked. ++ * bind_zero() takes affinity off this CPU and moves anyone still queued ++ * here. Then wait until only this hotplug thread remains and every ++ * migrate_disable() pin has dropped, so finish_cpu() cannot see a user mm. ++ */ ++int sched_cpu_wait_empty(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ ++ WARN_ON_ONCE(cpu != smp_processor_id()); ++ ++ for (;;) { ++ bind_zero(cpu); ++ if (READ_ONCE(rq->nr_running) <= 1 && !READ_ONCE(rq->nr_pinned)) ++ break; ++ schedule_timeout_uninterruptible(1); ++ } ++ ++ sched_force_init_mm(); ++ return 0; ++} ++ ++int sched_cpu_dying(unsigned int cpu) ++{ ++ struct rq *rq = cpu_rq(cpu); ++ struct rq_flags rf; ++ ++ /* Handle pending wakeups; tasks were already moved in wait_empty. */ ++ sched_tick_stop(cpu); ++ ++ rq_lock_irqsave(rq, &rf); ++ if (rq->rd) { ++ BUG_ON(!cpumask_test_cpu(cpu, rq->rd->span)); ++ set_rq_offline(rq); ++ } ++ if (rq->nr_running > 1 || rq->nr_pinned) { ++ WARN(true, "Dying CPU not properly vacated!"); ++ dump_rq_tasks(rq, KERN_WARNING); ++ } ++ rq_unlock_irqrestore(rq, &rf); ++ ++ sched_start_tick(rq, cpu); ++ hrexpiry_clear(rq); ++ /* ++ * Belt-and-suspenders: wait_empty already switched to init_mm, ++ * but a preempting migrate_disable() task could have left a ++ * lazy user mm on current. Drop it again now that this is the ++ * last thing to run before idle takes over. ++ */ ++ local_irq_disable(); ++ __sched_force_init_mm(); ++ local_irq_enable(); ++ ++ return 0; ++} ++#endif ++ ++#if defined(CONFIG_SCHED_SMT) || defined(CONFIG_SCHED_MC) ++/* ++ * Cheaper version of the below functions in case support for SMT and MC is ++ * compiled in but CPUs have no siblings. ++ */ ++static bool sole_cpu_idle(struct rq *rq) ++{ ++ return rq_idle(rq); ++} ++#endif ++#ifdef CONFIG_SCHED_SMT ++static const cpumask_t *thread_cpumask(int cpu) ++{ ++ return topology_sibling_cpumask(cpu); ++} ++#endif ++#ifdef CONFIG_SCHED_MC ++static const cpumask_t *core_cpumask(int cpu) ++{ ++ return topology_core_cpumask(cpu); ++} ++/* All this CPU's shared cache siblings are idle */ ++static bool cache_cpu_idle(struct rq *rq) ++{ ++ return cpumask_subset(&rq->core_mask, &cpu_idle_map); ++} ++/* MC siblings CPU mask which share the same LLC */ ++static const cpumask_t *llc_core_cpumask(int cpu) ++{ ++#ifdef CONFIG_X86 ++ return per_cpu(cpu_llc_shared_map, cpu); ++#else ++ return topology_core_cpumask(cpu); ++#endif ++} ++#endif ++ ++enum sched_domain_level { ++ SD_LV_NONE = 0, ++ SD_LV_SIBLING, ++ SD_LV_MC, ++ SD_LV_BOOK, ++ SD_LV_CPU, ++ SD_LV_NODE, ++ SD_LV_ALLNODES, ++ SD_LV_MAX ++}; ++ ++#ifdef CONFIG_SMT_NICE ++/* ++ * Recorded by select_leaders() and consumed by sched_init_smp() once the ++ * runqueue locks have been dropped. See the comment where it is set. ++ */ ++static bool __initdata smt_nice_needed; ++#endif ++ ++/* ++ * Set up the relative cache distance of each online cpu from each ++ * other in a simple array for quick lookup. Locality is determined ++ * by the closest sched_domain that CPUs are separated by. CPUs with ++ * shared cache in SMT and MC are treated as local. Separate CPUs ++ * (within the same package or physically) within the same node are ++ * treated as not local. CPUs not even in the same domain (different ++ * nodes) are treated as very distant. ++ * ++ * Called with interrupts disabled and every runqueue lock held, so nothing ++ * here may sleep or wait on another CPU. ++ */ ++static void __init select_leaders(void) ++{ ++ struct rq *rq, *other_rq, *leader; ++ struct sched_domain *sd; ++ int cpu, other_cpu; ++#ifdef CONFIG_SCHED_SMT ++ bool smt_threads = false; ++#endif ++ ++ for (cpu = 0; cpu < num_online_cpus(); cpu++) { ++ rq = cpu_rq(cpu); ++ leader = NULL; ++ /* First check if this cpu is in the same node */ ++ for_each_domain(cpu, sd) { ++ if (sd->level > SD_LV_MC) ++ continue; ++ if (rqshare != RQSHARE_ALL) ++ leader = NULL; ++ /* Set locality to local node if not already found lower */ ++ for_each_cpu(other_cpu, sched_domain_span(sd)) { ++ if (rqshare >= RQSHARE_SMP) { ++ other_rq = cpu_rq(other_cpu); ++ ++ /* Set the smp_leader to the first CPU */ ++ if (!leader) ++ leader = rq; ++ if (!other_rq->smp_leader) ++ other_rq->smp_leader = leader; ++ } ++ if (rq->cpu_locality[other_cpu] > LOCALITY_SMP) ++ rq->cpu_locality[other_cpu] = LOCALITY_SMP; ++ } ++ } ++ ++ /* ++ * Each runqueue has its own function in case it doesn't have ++ * siblings of its own allowing mixed topologies. ++ */ ++#ifdef CONFIG_SCHED_MC ++ leader = NULL; ++ if (cpumask_weight(core_cpumask(cpu)) > 1) { ++ cpumask_copy(&rq->core_mask, llc_core_cpumask(cpu)); ++ cpumask_clear_cpu(cpu, &rq->core_mask); ++ for_each_cpu(other_cpu, core_cpumask(cpu)) { ++ if (rqshare == RQSHARE_MC || ++ (rqshare == RQSHARE_MC_LLC && cpumask_test_cpu(other_cpu, llc_core_cpumask(cpu)))) { ++ other_rq = cpu_rq(other_cpu); ++ ++ /* Set the mc_leader to the first CPU */ ++ if (!leader) ++ leader = rq; ++ if (!other_rq->mc_leader) ++ other_rq->mc_leader = leader; ++ } ++ if (rq->cpu_locality[other_cpu] > LOCALITY_MC) { ++ /* this is to get LLC into play even in case LLC sharing is not used */ ++ if (cpumask_test_cpu(other_cpu, llc_core_cpumask(cpu))) ++ rq->cpu_locality[other_cpu] = LOCALITY_MC_LLC; ++ else ++ rq->cpu_locality[other_cpu] = LOCALITY_MC; ++ } ++ } ++ rq->cache_idle = cache_cpu_idle; ++ } ++#endif ++#ifdef CONFIG_SCHED_SMT ++ leader = NULL; ++ if (cpumask_weight(thread_cpumask(cpu)) > 1) { ++ cpumask_copy(&rq->thread_mask, thread_cpumask(cpu)); ++ cpumask_clear_cpu(cpu, &rq->thread_mask); ++ for_each_cpu(other_cpu, thread_cpumask(cpu)) { ++ if (rqshare == RQSHARE_SMT) { ++ other_rq = cpu_rq(other_cpu); ++ ++ /* Set the smt_leader to the first CPU */ ++ if (!leader) ++ leader = rq; ++ if (!other_rq->smt_leader) ++ other_rq->smt_leader = leader; ++ } ++ if (rq->cpu_locality[other_cpu] > LOCALITY_SMT) ++ rq->cpu_locality[other_cpu] = LOCALITY_SMT; ++ } ++ rq->has_smt_sibling = true; ++ smt_threads = true; ++ } ++#endif ++ } ++ ++#ifdef CONFIG_SMT_NICE ++ /* ++ * Only record it here; sched_init_smp() turns SMT nice on once it has ++ * dropped the runqueue locks and re-enabled interrupts. ++ * ++ * static_branch_enable() must not be called from this context. It ++ * takes cpus_read_lock() and jump_label_mutex, either of which can ++ * sleep - and scheduling from here would try to take a runqueue lock ++ * this CPU already holds - and it then patches the jump site with ++ * text_poke_bp(), which waits for every other CPU to answer a sync ++ * IPI. Any CPU spinning on one of the runqueue locks held here has ++ * interrupts disabled and can never answer, so the wait never ends and ++ * the machine is gone with nothing on the console. ++ */ ++ smt_nice_needed = smt_threads; ++#endif ++ ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ for_each_online_cpu(other_cpu) { ++ printk(KERN_DEBUG "MuQSS locality CPU %d to %d: %d\n", cpu, other_cpu, rq->cpu_locality[other_cpu]); ++ } ++ } ++} ++ ++/* ++ * Fold @rq into @leader while all runqueue locks are already held by ++ * lock_all_rqs(). Secondary CPUs may already have tasks queued by the time ++ * sched_init_smp() runs (hotplug kthreads, RCU, early kworkers), so the ++ * skiplist must be drained onto the leader before its storage is freed. ++ * ++ * Lock handoff: rq_lock() is a plain raw_spin_lock(rq->lock) with no re-check, ++ * so a CPU that had already read the follower's lock pointer would acquire the ++ * old lock after we drop it and then walk the leader's skiplist without the ++ * leader's lock. share_rqs() therefore runs from stop_machine(), where no ++ * other CPU can be inside or waiting on any rq lock, which is what makes ++ * repointing rq->lock and freeing the old one safe. ++ */ ++static void __init share_and_free_rq(struct rq *leader, struct rq *rq) ++{ ++ raw_spinlock_t *old_lock = rq->lock; ++ skiplist_node *old_node = rq->node; ++ skiplist *old_sl = rq->sl; ++ ++ /* ++ * Move every queued task onto the leader skiplist. nr_running is ++ * accounted against the rq the task belongs to (task_rq(p), which is ++ * unchanged here) rather than the rq owning the skiplist, so hand the ++ * counts straight back or the follower underflows on the next dequeue ++ * and the leader keeps a phantom entry forever. ++ */ ++ while (rq->sl->entries > 0) { ++ struct task_struct *p = container_of(rq->node->next[0], ++ struct task_struct, node); ++ ++ dequeue_task(rq, p, DEQUEUE_SAVE); ++ enqueue_task(leader, p, ENQUEUE_RESTORE); ++ leader->nr_running--; ++ rq->nr_running++; ++ if (rt_task(p)) { ++ leader->rt_nr_running--; ++ rq->rt_nr_running++; ++ } ++ } ++ ++ /* ++ * A non-idle curr is not on the skiplist but still accounts for one ++ * nr_running. Leave that count on @rq; only the shared skiplist and ++ * lock are merged. ++ */ ++ WARN_ON_ONCE(rq->sl->entries != 0); ++ ++ /* Point the follower at the leader's skiplist and lock. */ ++ rq->node = leader->node; ++ rq->sl = leader->sl; ++ rq->lock = leader->lock; ++ rq->is_leader = false; ++ ++ /* ++ * Drop the follower's private lock, taken by lock_all_rqs(). Only the ++ * leader lock remains held for @rq, and nothing can be waiting on the ++ * old one, so free it. ++ */ ++ do_raw_spin_unlock(old_lock); ++ ++ kfree(old_node); ++ skiplist_free(old_sl); ++ kfree(old_lock); ++} ++ ++/* ++ * Called with every runqueue lock held via lock_all_rqs() and IRQs off. ++ * Must not take locks again. After this, only leader rqs own a unique lock; ++ * unlock_all_rqs() is replaced by unlock_leader_rqs(). ++ */ ++static void __init share_rqs(void) ++{ ++ struct rq *rq, *leader; ++ int cpu; ++ ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ leader = rq->smp_leader; ++ ++ if (leader && rq != leader) { ++ printk(KERN_INFO "MuQSS sharing SMP runqueue from CPU %d to CPU %d\n", ++ leader->cpu, rq->cpu); ++ share_and_free_rq(leader, rq); ++ } ++ } ++ ++#ifdef CONFIG_SCHED_MC ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ leader = rq->mc_leader; ++ ++ if (leader && rq != leader) { ++ printk(KERN_INFO "MuQSS sharing MC runqueue from CPU %d to CPU %d\n", ++ leader->cpu, rq->cpu); ++ share_and_free_rq(leader, rq); ++ } ++ } ++#endif /* CONFIG_SCHED_MC */ ++ ++#ifdef CONFIG_SCHED_SMT ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ leader = rq->smt_leader; ++ ++ if (leader && rq != leader) { ++ printk(KERN_INFO "MuQSS sharing SMT runqueue from CPU %d to CPU %d\n", ++ leader->cpu, rq->cpu); ++ share_and_free_rq(leader, rq); ++ } ++ } ++#endif /* CONFIG_SCHED_SMT */ ++} ++ ++/* Unlock each unique runqueue lock once after share_rqs(). */ ++static inline void unlock_leader_rqs(void) ++{ ++ int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct rq *rq = cpu_rq(cpu); ++ ++ if (rq->is_leader) ++ do_raw_spin_unlock(rq->lock); ++ } ++ preempt_enable(); ++} ++ ++/* ++ * Fold the runqueues from stop_machine() context. Every other CPU is parked ++ * in the stopper with interrupts disabled, so none of them is inside an rq ++ * lock or spinning on one while share_and_free_rq() repoints a follower's ++ * rq->lock at its leader and frees the old lock. Any single CPU can run this. ++ */ ++static int __init share_rqs_stopper(void *unused) ++{ ++ lock_all_rqs(); ++ share_rqs(); ++ unlock_leader_rqs(); ++ ++ return 0; ++} ++ ++static void __init set_rq_order(struct rq *rq, int idx, struct rq *other) ++{ ++ rq->rq_order[idx] = other; ++ rq->sl_order[idx] = other->sl; ++} ++ ++static void __init setup_rq_orders(void) ++{ ++ int *selected_cpus, *ordered_cpus; ++ struct rq *rq, *other_rq; ++ int cpu, other_cpu, i; ++ ++ selected_cpus = kmalloc(sizeof(int) * NR_CPUS, GFP_ATOMIC); ++ ordered_cpus = kmalloc(sizeof(int) * NR_CPUS, GFP_ATOMIC); ++ ++ total_runqueues = 0; ++ for_each_online_cpu(cpu) { ++ int locality, total_rqs = 0, total_cpus = 0; ++ ++ rq = cpu_rq(cpu); ++ if (rq->is_leader) ++ total_runqueues++; ++ ++ for (locality = LOCALITY_SAME; locality <= LOCALITY_DISTANT; locality++) { ++ int selected_cpu_cnt, selected_cpu_idx, test_cpu_idx, cpu_idx, best_locality, test_cpu; ++ int ordered_cpus_idx; ++ ++ ordered_cpus_idx = -1; ++ selected_cpu_cnt = 0; ++ ++ for_each_online_cpu(test_cpu) { ++ if (cpu < num_online_cpus() / 2) ++ other_cpu = cpu + test_cpu; ++ else ++ other_cpu = cpu - test_cpu; ++ if (other_cpu < 0) ++ other_cpu += num_online_cpus(); ++ else ++ other_cpu %= num_online_cpus(); ++ /* gather CPUs of the same locality */ ++ if (rq->cpu_locality[other_cpu] == locality) { ++ selected_cpus[selected_cpu_cnt] = other_cpu; ++ selected_cpu_cnt++; ++ } ++ } ++ ++ /* reserve first CPU as starting point */ ++ if (selected_cpu_cnt > 0) { ++ ordered_cpus_idx++; ++ ordered_cpus[ordered_cpus_idx] = selected_cpus[ordered_cpus_idx]; ++ selected_cpus[ordered_cpus_idx] = -1; ++ } ++ ++ /* take each CPU and sort it within the same locality based on each inter-CPU localities */ ++ for (test_cpu_idx = 1; test_cpu_idx < selected_cpu_cnt; test_cpu_idx++) { ++ /* starting point with worst locality and current CPU */ ++ best_locality = LOCALITY_DISTANT; ++ selected_cpu_idx = test_cpu_idx; ++ ++ /* try to find the best locality within group */ ++ for (cpu_idx = 1; cpu_idx < selected_cpu_cnt; cpu_idx++) { ++ /* if CPU has not been used and locality is better */ ++ if (selected_cpus[cpu_idx] > -1) { ++ other_rq = cpu_rq(ordered_cpus[ordered_cpus_idx]); ++ if (best_locality > other_rq->cpu_locality[selected_cpus[cpu_idx]]) { ++ /* assign best locality and best CPU idx in array */ ++ best_locality = other_rq->cpu_locality[selected_cpus[cpu_idx]]; ++ selected_cpu_idx = cpu_idx; ++ } ++ } ++ } ++ ++ /* add our next best CPU to ordered list */ ++ ordered_cpus_idx++; ++ ordered_cpus[ordered_cpus_idx] = selected_cpus[selected_cpu_idx]; ++ /* mark this CPU as used */ ++ selected_cpus[selected_cpu_idx] = -1; ++ } ++ ++ /* set up RQ and CPU orders */ ++ for (test_cpu = 0; test_cpu <= ordered_cpus_idx; test_cpu++) { ++ other_rq = cpu_rq(ordered_cpus[test_cpu]); ++ /* set up cpu orders */ ++ rq->cpu_order[total_cpus++] = other_rq; ++ if (other_rq->is_leader) { ++ /* set up RQ orders */ ++ set_rq_order(rq, total_rqs++, other_rq); ++ } ++ } ++ } ++ } ++ ++ kfree(selected_cpus); ++ kfree(ordered_cpus); ++ ++#ifdef CONFIG_X86 ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ for (i = 0; i < total_runqueues; i++) { ++ printk(KERN_DEBUG "MuQSS CPU %d llc %d RQ order %d RQ %d llc %d\n", cpu, per_cpu_llc_id(cpu), i, ++ rq->rq_order[i]->cpu, per_cpu_llc_id(rq->rq_order[i]->cpu)); ++ } ++ } ++ ++ for_each_online_cpu(cpu) { ++ rq = cpu_rq(cpu); ++ for (i = 0; i < num_online_cpus(); i++) { ++ printk(KERN_DEBUG "MuQSS CPU %d llc %d CPU order %d RQ %d llc %d\n", cpu, per_cpu_llc_id(cpu), i, ++ rq->cpu_order[i]->cpu, per_cpu_llc_id(rq->cpu_order[i]->cpu)); ++ } ++ } ++#endif ++} ++ ++void __init sched_init_smp(void) ++{ ++ sched_init_numa(NUMA_NO_NODE); ++ ++ /* ++ * There's no userspace yet to cause hotplug operations; hence all the ++ * cpu masks are stable and all blatant races in the below code cannot ++ * happen. ++ */ ++ mutex_lock(&sched_domains_mutex); ++ sched_init_domains(cpu_active_mask); ++ mutex_unlock(&sched_domains_mutex); ++ ++ /* Move init over to a non-isolated CPU */ ++ if (set_cpus_allowed_ptr(current, housekeeping_cpumask(HK_TYPE_DOMAIN)) < 0) ++ BUG(); ++ ++ /* ++ * Take the sleeping lock before disabling interrupts - the 5.12 order ++ * trips "sleeping function called from invalid context" under ++ * CONFIG_DEBUG_ATOMIC_SLEEP. select_leaders() walks the domain tree, ++ * which is what the mutex is for. ++ */ ++ mutex_lock(&sched_domains_mutex); ++ local_irq_disable(); ++ lock_all_rqs(); ++ ++ printk(KERN_INFO "MuQSS possible/present/online CPUs: %d/%d/%d\n", ++ num_possible_cpus(), num_present_cpus(), num_online_cpus()); ++ ++ select_leaders(); ++ ++ unlock_all_rqs(); ++ local_irq_enable(); ++ mutex_unlock(&sched_domains_mutex); ++ ++#ifdef CONFIG_SMT_NICE ++ /* ++ * Safe to patch the jump site only now that the runqueue locks are ++ * dropped and interrupts are back on; see select_leaders(). Until ++ * this point smt_schedule() just returns true and check_siblings()/ ++ * wake_siblings() are the no-op variants, which is correct behaviour, ++ * merely without SMT nice. ++ */ ++ if (smt_nice_needed) { ++ check_siblings = &check_smt_siblings; ++ wake_siblings = &wake_smt_siblings; ++ static_branch_enable(&smt_nice_enabled); ++ } ++#endif ++ ++ /* ++ * Only now fold the runqueues together, and do it from stop_machine(): ++ * 7.1 has the secondary CPUs running by this point, so a follower's rq ++ * lock can be held - or waited on - by another CPU exactly while it is ++ * handed over to the leader and freed. Quiescing everybody closes that ++ * window without putting a re-check in the rq_lock() fast path. Note ++ * stop_machine() takes cpus_read_lock(), so it must not nest inside ++ * sched_domains_mutex. ++ */ ++ stop_machine(share_rqs_stopper, NULL, cpumask_of(raw_smp_processor_id())); ++ ++ setup_rq_orders(); ++ ++ switch (rqshare) { ++ case RQSHARE_ALL: ++ /* This should only ever read 1 */ ++ printk(KERN_INFO "MuQSS runqueue share type ALL total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_SMP: ++ printk(KERN_INFO "MuQSS runqueue share type SMP total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_MC: ++ printk(KERN_INFO "MuQSS runqueue share type MC total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_MC_LLC: ++ printk(KERN_INFO "MuQSS runqueue share type LLC total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_SMT: ++ printk(KERN_INFO "MuQSS runqueue share type SMT total runqueues: %d\n", ++ total_runqueues); ++ break; ++ case RQSHARE_NONE: ++ printk(KERN_INFO "MuQSS runqueue share type NONE total runqueues: %d\n", ++ total_runqueues); ++ break; ++ } ++ ++ sched_smp_initialized = true; ++} ++#else /* !CONFIG_SMP */ ++void __init sched_init_smp(void) ++{ ++ sched_smp_initialized = true; ++} ++ ++/* ++ * 7.1 dropped the !CONFIG_SMP inline stubs these used to have in ++ * , so the scheduler has to provide them on UP too. There is ++ * only ever cpu 0 to run on, so affinity is either trivially satisfied or ++ * impossible. ++ */ ++int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ if (!cpumask_test_cpu(0, new_mask)) ++ return -EINVAL; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(set_cpus_allowed_ptr); ++ ++void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ cpumask_copy(&p->cpus_mask, new_mask); ++ p->nr_cpus_allowed = cpumask_weight(new_mask); ++} ++ ++#ifdef CONFIG_NO_HZ_COMMON ++void nohz_balance_enter_idle(int cpu) {} ++ ++/* ++ * The only CPU is the one already running this, so it is by definition not ++ * idle and needs no kick. ++ */ ++void wake_up_nohz_cpu(int cpu) ++{ ++} ++#endif /* CONFIG_NO_HZ_COMMON */ ++ ++/* ++ * topology.c is not built on UP, but declares this ++ * unconditionally and amd-pstate calls it. There are no sched domains to ++ * update. ++ */ ++void sched_update_asym_prefer_cpu(int cpu, int old_prio, int new_prio) ++{ ++} ++#endif /* CONFIG_SMP */ ++ ++int in_sched_functions(unsigned long addr) ++{ ++ return in_lock_functions(addr) || ++ (addr >= (unsigned long)__sched_text_start ++ && addr < (unsigned long)__sched_text_end); ++} ++ ++#ifdef CONFIG_CGROUP_SCHED ++/* ++ * Default CFS bandwidth period (100ms). Used only for accept-and-ignore ++ * readback of cpu.max / cfs_period_us — MuQSS does not enforce quotas. ++ */ ++#define MUQSS_CGROUP_PERIOD_DFL_US 100000ULL ++ ++/* task group related information */ ++struct task_group { ++ struct cgroup_subsys_state css; ++ ++ struct rcu_head rcu; ++ struct list_head list; ++ ++ struct task_group *parent; ++ struct list_head siblings; ++ struct list_head children; ++ ++ /* ++ * cpu controller knobs stored for ABI/readback only. Writes are ++ * validated then ignored for scheduling — MuQSS has no group fairness ++ * or bandwidth enforcement. Keeps systemd/docker/podman happy. ++ */ ++ unsigned long weight; /* cgroup weight [CGROUP_WEIGHT_MIN, MAX] */ ++ s64 idle; ++ u64 period_us; ++ s64 quota_us; /* -1 == unlimited ("max") */ ++ u64 burst_us; ++}; ++ ++/* ++ * Default task group. ++ * Every task in system belongs to this group at bootup. ++ */ ++struct task_group root_task_group; ++LIST_HEAD(task_groups); ++ ++/* Cacheline aligned slab cache for task_group */ ++static struct kmem_cache *task_group_cache __read_mostly; ++ ++static void init_tg_cgroup_defaults(struct task_group *tg) ++{ ++ tg->weight = CGROUP_WEIGHT_DFL; ++ tg->idle = 0; ++ tg->period_us = MUQSS_CGROUP_PERIOD_DFL_US; ++ tg->quota_us = -1; ++ tg->burst_us = 0; ++} ++#endif /* CONFIG_CGROUP_SCHED */ ++ ++void __init sched_init(void) ++{ ++#ifdef CONFIG_SMP ++ int cpu_ids; ++#endif ++ int i; ++ struct rq *rq; ++ ++ wait_bit_init(); ++ ++ prio_ratios[0] = 128; ++ for (i = 1 ; i < NICE_WIDTH ; i++) ++ prio_ratios[i] = prio_ratios[i - 1] * 11 / 10; ++ ++ skiplist_node_init(&init_task.node); ++ ++#ifdef CONFIG_SMP ++ init_defrootdomain(); ++ cpumask_clear(&cpu_idle_map); ++#else ++ uprq = &per_cpu(runqueues, 0); ++#endif ++ ++#ifdef CONFIG_CGROUP_SCHED ++ task_group_cache = KMEM_CACHE(task_group, 0); ++ ++ list_add(&root_task_group.list, &task_groups); ++ INIT_LIST_HEAD(&root_task_group.children); ++ INIT_LIST_HEAD(&root_task_group.siblings); ++ init_tg_cgroup_defaults(&root_task_group); ++#endif /* CONFIG_CGROUP_SCHED */ ++ skiplist_cache_init(); ++ for_each_possible_cpu(i) { ++ rq = cpu_rq(i); ++ rq->node = kmalloc(sizeof(skiplist_node), GFP_ATOMIC); ++ skiplist_init(rq->node); ++ rq->sl = new_skiplist(rq->node); ++ rq->lock = kmalloc(sizeof(raw_spinlock_t), GFP_ATOMIC); ++ raw_spin_lock_init(rq->lock); ++ rq->nr_running = 0; ++ rq->nr_uninterruptible = 0; ++ rq->nr_switches = 0; ++ rq->clock = rq->niffies = rq->jiffy_niffies = 0; ++ rq->last_jiffy = jiffies; ++ rq->user_ns = rq->nice_ns = rq->softirq_ns = rq->system_ns = ++ rq->iowait_ns = rq->idle_ns = 0; ++ rq->dither = 0; ++ set_rq_task(rq, &init_task); ++ rq->iso_ticks = 0; ++ rq->iso_refractory = false; ++#ifdef CONFIG_SMP ++ rq->is_leader = true; ++ rq->smp_leader = NULL; ++#ifdef CONFIG_SCHED_MC ++ rq->mc_leader = NULL; ++#endif ++#ifdef CONFIG_SCHED_SMT ++ rq->smt_leader = NULL; ++#endif ++ rq->sd = NULL; ++ rq->rd = NULL; ++ rq->online = false; ++ rq->cpu = i; ++ rq_attach_root(rq, &def_root_domain); ++#endif /* CONFIG_SMP */ ++ init_rq_hrexpiry(rq); ++ atomic_set(&rq->nr_iowait, 0); ++ } ++ ++#ifdef CONFIG_SMP ++ cpu_ids = i; ++ /* ++ * Set the base locality for cpu cache distance calculation to ++ * "distant" (3). Make sure the distance from a CPU to itself is 0. ++ */ ++ for_each_possible_cpu(i) { ++ int j; ++ ++ rq = cpu_rq(i); ++#ifdef CONFIG_SCHED_MC ++ rq->cache_idle = sole_cpu_idle; ++#endif ++ rq->cpu_locality = kmalloc(cpu_ids * sizeof(int *), GFP_ATOMIC); ++ for_each_possible_cpu(j) { ++ if (i == j) ++ rq->cpu_locality[j] = LOCALITY_SAME; ++ else ++ rq->cpu_locality[j] = LOCALITY_DISTANT; ++ } ++ /* sl_order is O(possible_cpus²) pointers, same as rq_order. */ ++ rq->rq_order = kmalloc(cpu_ids * sizeof(struct rq *), GFP_ATOMIC); ++ rq->sl_order = kmalloc(cpu_ids * sizeof(skiplist *), GFP_ATOMIC); ++ rq->cpu_order = kmalloc(cpu_ids * sizeof(struct rq *), GFP_ATOMIC); ++ set_rq_order(rq, 0, rq); ++ rq->cpu_order[0] = rq; ++ for (j = 1; j < cpu_ids; j++) { ++ set_rq_order(rq, j, cpu_rq(j)); ++ rq->cpu_order[j] = cpu_rq(j); ++ } ++ } ++#endif ++ ++ /* ++ * The boot idle thread does lazy MMU switching as well: ++ */ ++ mmgrab_lazy_tlb(&init_mm); ++ enter_lazy_tlb(&init_mm, current); ++ ++ /* ++ * Make us the idle thread. Technically, schedule() should not be ++ * called from this thread, however somewhere below it might be, ++ * but because we are the idle thread, we just pick up running again ++ * when this runqueue becomes "idle". ++ */ ++ init_idle(current, smp_processor_id()); ++ ++#ifdef CONFIG_SMP ++ idle_thread_set_boot_cpu(); ++#endif /* SMP */ ++ ++ init_schedstats(); ++ ++ psi_init(); ++ ++ preempt_dynamic_init(); ++ ++ print_scheduler_version(); ++} ++ ++#ifdef CONFIG_DEBUG_ATOMIC_SLEEP ++static inline int preempt_count_equals(int preempt_offset) ++{ ++ int nested = preempt_count() + rcu_preempt_depth(); ++ ++ return (nested == preempt_offset); ++} ++ ++void __might_sleep(const char *file, int line) ++{ ++ unsigned int state = get_current_state(); ++ /* ++ * Blocking primitives will set (and therefore destroy) current->__state, ++ * since we will exit with TASK_RUNNING make sure we enter with it, ++ * otherwise we will destroy state. ++ */ ++ WARN_ONCE(state != TASK_RUNNING && current->task_state_change, ++ "do not call blocking ops when !TASK_RUNNING; " ++ "state=%x set at [<%p>] %pS\n", state, ++ (void *)current->task_state_change, ++ (void *)current->task_state_change); ++ ++ __might_resched(file, line, 0); ++} ++EXPORT_SYMBOL(__might_sleep); ++ ++void __cant_migrate(const char *file, int line) ++{ ++ static unsigned long prev_jiffy; ++ ++ if (irqs_disabled()) ++ return; ++ ++ if (is_migration_disabled(current)) ++ return; ++ ++ if (!IS_ENABLED(CONFIG_PREEMPT_COUNT)) ++ return; ++ ++ if (preempt_count() > 0) ++ return; ++ ++ if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) ++ return; ++ prev_jiffy = jiffies; ++ ++ pr_err("BUG: assuming non migratable context at %s:%d\n", file, line); ++ pr_err("in_atomic(): %d, irqs_disabled(): %d, migration_disabled() %u pid: %d, name: %s\n", ++ in_atomic(), irqs_disabled(), is_migration_disabled(current), ++ current->pid, current->comm); ++ ++ debug_show_held_locks(current); ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++EXPORT_SYMBOL_GPL(__cant_migrate); ++ ++void __cant_sleep(const char *file, int line, int preempt_offset) ++{ ++ static unsigned long prev_jiffy; ++ ++ if (irqs_disabled()) ++ return; ++ ++ if (!IS_ENABLED(CONFIG_PREEMPT_COUNT)) ++ return; ++ ++ if (preempt_count() > preempt_offset) ++ return; ++ ++ if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) ++ return; ++ prev_jiffy = jiffies; ++ ++ printk(KERN_ERR "BUG: assuming atomic context at %s:%d\n", file, line); ++ printk(KERN_ERR "in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n", ++ in_atomic(), irqs_disabled(), ++ current->pid, current->comm); ++ ++ debug_show_held_locks(current); ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++EXPORT_SYMBOL_GPL(__cant_sleep); ++ ++void __might_resched(const char *file, int line, unsigned int offsets) ++{ ++ /* Ratelimiting timestamp: */ ++ static unsigned long prev_jiffy; ++ ++ unsigned long preempt_disable_ip; ++ ++ /* WARN_ON_ONCE() by default, no rate limit required: */ ++ rcu_sleep_check(); ++ ++ if ((preempt_count_equals(offsets) && !irqs_disabled() && ++ !is_idle_task(current) && !current->non_block_count) || ++ system_state == SYSTEM_BOOTING || system_state > SYSTEM_RUNNING || ++ oops_in_progress) ++ return; ++ ++ if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) ++ return; ++ prev_jiffy = jiffies; ++ ++ /* Save this before calling printk(), since that will clobber it: */ ++ preempt_disable_ip = get_preempt_disable_ip(current); ++ ++ printk(KERN_ERR ++ "BUG: sleeping function called from invalid context at %s:%d\n", ++ file, line); ++ printk(KERN_ERR ++ "in_atomic(): %d, irqs_disabled(): %d, non_block: %d, pid: %d, name: %s\n", ++ in_atomic(), irqs_disabled(), current->non_block_count, ++ current->pid, current->comm); ++ ++ if (task_stack_end_corrupted(current)) ++ printk(KERN_EMERG "Thread overran stack, or stack corrupted\n"); ++ ++ debug_show_held_locks(current); ++ if (irqs_disabled()) ++ print_irqtrace_events(current); ++ if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) ++ && !preempt_count_equals(offsets)) { ++ pr_err("Preemption disabled at:"); ++ print_ip_sym(KERN_ERR, preempt_disable_ip); ++ } ++ dump_stack(); ++ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); ++} ++EXPORT_SYMBOL(__might_resched); ++#endif ++ ++#ifdef CONFIG_MAGIC_SYSRQ ++static inline void normalise_rt_tasks(void) ++{ ++ struct sched_attr attr = {}; ++ struct task_struct *g, *p; ++ struct rq_flags rf; ++ struct rq *rq; ++ ++ read_lock(&tasklist_lock); ++ for_each_process_thread(g, p) { ++ /* ++ * Only normalize user tasks: ++ */ ++ if (p->flags & PF_KTHREAD) ++ continue; ++ ++ if (!rt_task(p) && !iso_task(p)) ++ continue; ++ ++ rq = task_rq_lock(p, &rf); ++ __setscheduler(p, rq, SCHED_NORMAL, 0, &attr, false); ++ task_rq_unlock(rq, p, &rf); ++ } ++ read_unlock(&tasklist_lock); ++} ++ ++void normalize_rt_tasks(void) ++{ ++ normalise_rt_tasks(); ++} ++#endif /* CONFIG_MAGIC_SYSRQ */ ++ ++#if defined(CONFIG_IA64) || defined(CONFIG_KGDB_KDB) ++/* ++ * These functions are only useful for the IA64 MCA handling, or kdb. ++ * ++ * They can only be called when the whole system has been ++ * stopped - every CPU needs to be quiescent, and no scheduling ++ * activity can take place. Using them for anything else would ++ * be a serious bug, and as a result, they aren't even visible ++ * under any other configuration. ++ */ ++ ++/** ++ * curr_task - return the current task for a given CPU. ++ * @cpu: the processor in question. ++ * ++ * ONLY VALID WHEN THE WHOLE SYSTEM IS STOPPED! ++ * ++ * Return: The current task for @cpu. ++ */ ++struct task_struct *curr_task(int cpu) ++{ ++ return cpu_curr(cpu); ++} ++ ++#endif /* defined(CONFIG_IA64) || defined(CONFIG_KGDB_KDB) */ ++ ++#ifdef CONFIG_IA64 ++/** ++ * ia64_set_curr_task - set the current task for a given CPU. ++ * @cpu: the processor in question. ++ * @p: the task pointer to set. ++ * ++ * Description: This function must only be used when non-maskable interrupts ++ * are serviced on a separate stack. It allows the architecture to switch the ++ * notion of the current task on a CPU in a non-blocking manner. This function ++ * must be called with all CPU's synchronised, and interrupts disabled, the ++ * and caller must save the original value of the current task (see ++ * curr_task() above) and restore that value before reenabling interrupts and ++ * re-starting the system. ++ * ++ * ONLY VALID WHEN THE WHOLE SYSTEM IS STOPPED! ++ */ ++void ia64_set_curr_task(int cpu, struct task_struct *p) ++{ ++ cpu_curr(cpu) = p; ++} ++ ++#endif ++ ++#ifdef CONFIG_SCHED_DEBUG ++__read_mostly bool sched_debug_enabled; ++ ++void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, ++ struct seq_file *m) ++{ ++ seq_printf(m, "%s (%d, #threads: %d)\n", p->comm, task_pid_nr_ns(p, ns), ++ get_nr_threads(p)); ++} ++ ++void proc_sched_set_task(struct task_struct *p) ++{} ++#endif ++ ++#ifdef CONFIG_CGROUP_SCHED ++static void sched_free_group(struct task_group *tg) ++{ ++ kmem_cache_free(task_group_cache, tg); ++} ++ ++/* allocate runqueue etc for a new task group */ ++struct task_group *sched_create_group(struct task_group *parent) ++{ ++ struct task_group *tg; ++ ++ tg = kmem_cache_alloc(task_group_cache, GFP_KERNEL | __GFP_ZERO); ++ if (!tg) ++ return ERR_PTR(-ENOMEM); ++ ++ init_tg_cgroup_defaults(tg); ++ return tg; ++} ++ ++void sched_online_group(struct task_group *tg, struct task_group *parent) ++{ ++} ++ ++/* rcu callback to free various structures associated with a task group */ ++static void sched_free_group_rcu(struct rcu_head *rhp) ++{ ++ /* Now it should be safe to free those cfs_rqs */ ++ sched_free_group(container_of(rhp, struct task_group, rcu)); ++} ++ ++void sched_destroy_group(struct task_group *tg) ++{ ++ /* Wait for possible concurrent references to cfs_rqs complete */ ++ call_rcu(&tg->rcu, sched_free_group_rcu); ++} ++ ++void sched_release_group(struct task_group *tg) ++{ ++} ++ ++static inline struct task_group *css_tg(struct cgroup_subsys_state *css) ++{ ++ return css ? container_of(css, struct task_group, css) : NULL; ++} ++ ++static struct cgroup_subsys_state * ++cpu_cgroup_css_alloc(struct cgroup_subsys_state *parent_css) ++{ ++ struct task_group *parent = css_tg(parent_css); ++ struct task_group *tg; ++ ++ if (!parent) { ++ /* This is early initialization for the top cgroup */ ++ return &root_task_group.css; ++ } ++ ++ tg = sched_create_group(parent); ++ if (IS_ERR(tg)) ++ return ERR_PTR(-ENOMEM); ++ return &tg->css; ++} ++ ++/* Expose task group only after completing cgroup initialization */ ++static int cpu_cgroup_css_online(struct cgroup_subsys_state *css) ++{ ++ struct task_group *tg = css_tg(css); ++ struct task_group *parent = css_tg(css->parent); ++ ++ if (parent) ++ sched_online_group(tg, parent); ++ return 0; ++} ++ ++static void cpu_cgroup_css_released(struct cgroup_subsys_state *css) ++{ ++ struct task_group *tg = css_tg(css); ++ ++ sched_release_group(tg); ++} ++ ++static void cpu_cgroup_css_free(struct cgroup_subsys_state *css) ++{ ++ struct task_group *tg = css_tg(css); ++ ++ /* ++ * Relies on the RCU grace period between css_released() and this. ++ */ ++ sched_free_group(tg); ++} ++ ++static void cpu_cgroup_fork(struct task_struct *task) ++{ ++} ++ ++static int cpu_cgroup_can_attach(struct cgroup_taskset *tset) ++{ ++ return 0; ++} ++ ++static void cpu_cgroup_attach(struct cgroup_taskset *tset) ++{ ++} ++ ++/* ++ * Accept-and-ignore cpu controller files. ++ * ++ * Mainline wires these to CFS shares/bandwidth. MuQSS has neither, but ++ * container runtimes and systemd write CPUWeight=/CPUQuota=/--cpus and ++ * fail if the files are missing. Validate ranges, store for readback, ++ * and leave scheduling unaffected. ++ * ++ * nice↔weight table matches CFS (sched_prio_to_weight) so weight.nice ++ * round-trips to the same values userspace expects. ++ */ ++static const int muqss_prio_to_weight[40] = { ++ /* -20 */ 88761, 71755, 56483, 46273, 36291, ++ /* -15 */ 29154, 23254, 18705, 14949, 11916, ++ /* -10 */ 9548, 7620, 6100, 4904, 3906, ++ /* -5 */ 3121, 2501, 1991, 1586, 1277, ++ /* 0 */ 1024, 820, 655, 526, 423, ++ /* 5 */ 335, 272, 215, 172, 137, ++ /* 10 */ 110, 87, 70, 56, 45, ++ /* 15 */ 36, 29, 23, 18, 15, ++}; ++ ++static unsigned long muqss_weight_from_cgroup(unsigned long cgrp_weight) ++{ ++ return DIV_ROUND_CLOSEST_ULL(cgrp_weight * 1024, CGROUP_WEIGHT_DFL); ++} ++ ++static unsigned long muqss_weight_to_cgroup(unsigned long weight) ++{ ++ return clamp_t(unsigned long, ++ DIV_ROUND_CLOSEST_ULL(weight * CGROUP_WEIGHT_DFL, 1024), ++ CGROUP_WEIGHT_MIN, CGROUP_WEIGHT_MAX); ++} ++ ++static u64 cpu_weight_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->weight; ++} ++ ++static int cpu_weight_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 cgrp_weight) ++{ ++ if (cgrp_weight < CGROUP_WEIGHT_MIN || cgrp_weight > CGROUP_WEIGHT_MAX) ++ return -ERANGE; ++ ++ css_tg(css)->weight = cgrp_weight; ++ return 0; ++} ++ ++static s64 cpu_weight_nice_read_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ unsigned long weight = muqss_weight_from_cgroup(css_tg(css)->weight); ++ int last_delta = INT_MAX; ++ int prio, delta; ++ ++ for (prio = 0; prio < ARRAY_SIZE(muqss_prio_to_weight); prio++) { ++ delta = abs(muqss_prio_to_weight[prio] - (int)weight); ++ if (delta >= last_delta) ++ break; ++ last_delta = delta; ++ } ++ ++ return PRIO_TO_NICE(prio - 1 + MAX_RT_PRIO); ++} ++ ++static int cpu_weight_nice_write_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft, s64 nice) ++{ ++ int idx; ++ ++ if (nice < MIN_NICE || nice > MAX_NICE) ++ return -ERANGE; ++ ++ idx = NICE_TO_PRIO(nice) - MAX_RT_PRIO; ++ idx = array_index_nospec(idx, ARRAY_SIZE(muqss_prio_to_weight)); ++ css_tg(css)->weight = ++ muqss_weight_to_cgroup(muqss_prio_to_weight[idx]); ++ return 0; ++} ++ ++static s64 cpu_idle_read_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->idle; ++} ++ ++static int cpu_idle_write_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft, s64 idle) ++{ ++ if (idle != 0 && idle != 1) ++ return -EINVAL; ++ ++ css_tg(css)->idle = idle; ++ return 0; ++} ++ ++static void cpu_period_quota_print(struct seq_file *sf, long period, long quota) ++{ ++ if (quota < 0) ++ seq_puts(sf, "max"); ++ else ++ seq_printf(sf, "%ld", quota); ++ ++ seq_printf(sf, " %ld\n", period); ++} ++ ++static int cpu_period_quota_parse(char *buf, u64 *period_us_p, u64 *quota_us_p) ++{ ++ char tok[21]; /* U64_MAX */ ++ ++ if (sscanf(buf, "%20s %llu", tok, period_us_p) < 1) ++ return -EINVAL; ++ ++ if (sscanf(tok, "%llu", quota_us_p) < 1) { ++ if (!strcmp(tok, "max")) ++ *quota_us_p = U64_MAX; ++ else ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int cpu_max_show(struct seq_file *sf, void *v) ++{ ++ struct task_group *tg = css_tg(seq_css(sf)); ++ s64 quota = tg->quota_us; ++ ++ cpu_period_quota_print(sf, tg->period_us, quota); ++ return 0; ++} ++ ++static ssize_t cpu_max_write(struct kernfs_open_file *of, ++ char *buf, size_t nbytes, loff_t off) ++{ ++ struct task_group *tg = css_tg(of_css(of)); ++ u64 period_us = tg->period_us, quota_us; ++ int ret; ++ ++ ret = cpu_period_quota_parse(buf, &period_us, "a_us); ++ if (ret) ++ return ret; ++ ++ if (!period_us || period_us > USEC_PER_SEC) ++ return -EINVAL; ++ ++ tg->period_us = period_us; ++ tg->quota_us = (quota_us == U64_MAX) ? -1 : (s64)quota_us; ++ return nbytes; ++} ++ ++static u64 cpu_burst_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->burst_us; ++} ++ ++static int cpu_burst_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 burst_us) ++{ ++ struct task_group *tg = css_tg(css); ++ ++ /* Burst must not exceed a finite quota when one is set. */ ++ if (tg->quota_us >= 0 && burst_us > (u64)tg->quota_us) ++ return -EINVAL; ++ ++ tg->burst_us = burst_us; ++ return 0; ++} ++ ++/* Legacy v1 interfaces */ ++static u64 cpu_shares_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return muqss_weight_from_cgroup(css_tg(css)->weight); ++} ++ ++static int cpu_shares_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 share) ++{ ++ if (share < 2 || share > 262144) ++ return -ERANGE; ++ ++ css_tg(css)->weight = muqss_weight_to_cgroup(share); ++ return 0; ++} ++ ++static u64 cpu_period_read_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->period_us; ++} ++ ++static int cpu_period_write_u64(struct cgroup_subsys_state *css, ++ struct cftype *cft, u64 period_us) ++{ ++ if (!period_us || period_us > USEC_PER_SEC) ++ return -EINVAL; ++ ++ css_tg(css)->period_us = period_us; ++ return 0; ++} ++ ++static s64 cpu_quota_read_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft) ++{ ++ return css_tg(css)->quota_us; ++} ++ ++static int cpu_quota_write_s64(struct cgroup_subsys_state *css, ++ struct cftype *cft, s64 quota_us) ++{ ++ if (quota_us < -1 || quota_us > (s64)USEC_PER_SEC * 1024) ++ return -EINVAL; ++ ++ css_tg(css)->quota_us = quota_us; ++ return 0; ++} ++ ++static struct cftype cpu_legacy_files[] = { ++ { ++ .name = "shares", ++ .read_u64 = cpu_shares_read_u64, ++ .write_u64 = cpu_shares_write_u64, ++ }, ++ { ++ .name = "idle", ++ .read_s64 = cpu_idle_read_s64, ++ .write_s64 = cpu_idle_write_s64, ++ }, ++ { ++ .name = "cfs_period_us", ++ .read_u64 = cpu_period_read_u64, ++ .write_u64 = cpu_period_write_u64, ++ }, ++ { ++ .name = "cfs_quota_us", ++ .read_s64 = cpu_quota_read_s64, ++ .write_s64 = cpu_quota_write_s64, ++ }, ++ { ++ .name = "cfs_burst_us", ++ .read_u64 = cpu_burst_read_u64, ++ .write_u64 = cpu_burst_write_u64, ++ }, ++ { } /* Terminate */ ++}; ++ ++static struct cftype cpu_files[] = { ++ { ++ .name = "weight", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_u64 = cpu_weight_read_u64, ++ .write_u64 = cpu_weight_write_u64, ++ }, ++ { ++ .name = "weight.nice", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_s64 = cpu_weight_nice_read_s64, ++ .write_s64 = cpu_weight_nice_write_s64, ++ }, ++ { ++ .name = "idle", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_s64 = cpu_idle_read_s64, ++ .write_s64 = cpu_idle_write_s64, ++ }, ++ { ++ .name = "max", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .seq_show = cpu_max_show, ++ .write = cpu_max_write, ++ }, ++ { ++ .name = "max.burst", ++ .flags = CFTYPE_NOT_ON_ROOT, ++ .read_u64 = cpu_burst_read_u64, ++ .write_u64 = cpu_burst_write_u64, ++ }, ++ { } /* terminate */ ++}; ++ ++static int cpu_extra_stat_show(struct seq_file *sf, ++ struct cgroup_subsys_state *css) ++{ ++ return 0; ++} ++ ++struct cgroup_subsys cpu_cgrp_subsys = { ++ .css_alloc = cpu_cgroup_css_alloc, ++ .css_online = cpu_cgroup_css_online, ++ .css_released = cpu_cgroup_css_released, ++ .css_free = cpu_cgroup_css_free, ++ .css_extra_stat_show = cpu_extra_stat_show, ++ .fork = cpu_cgroup_fork, ++ .can_attach = cpu_cgroup_can_attach, ++ .attach = cpu_cgroup_attach, ++ .legacy_cftypes = cpu_legacy_files, ++ .dfl_cftypes = cpu_files, ++ .early_init = true, ++ .threaded = true, ++}; ++#endif /* CONFIG_CGROUP_SCHED */ ++ ++void call_trace_sched_update_nr_running(struct rq *rq, int count) ++{ ++ trace_sched_update_nr_running_tp(rq, count); ++} ++ ++/* CFS Compat */ ++#ifdef CONFIG_RCU_TORTURE_TEST ++int sysctl_sched_rt_runtime; ++#endif ++ ++/* ++ * Compatibility layer for core.c interfaces added after 5.12. ++ * ++ * These are all consumed by code outside the scheduler. Where the feature ++ * behind them does not exist under MuQSS (deadline bandwidth, sched_ext, ++ * mm_cid, the CFS runqueue debugfs) the implementation is deliberately inert ++ * rather than absent, so mainline callers need no #ifdef. ++ */ ++ ++/* ++ * Tracepoint helpers behind set_current_state()/set_need_resched(). Callers ++ * MUST guard these with a tracepoint_enabled() check, which is why they use ++ * the unguarded trace_call__() form. ++ */ ++void __trace_set_current_state(int state_value) ++{ ++ trace_call__sched_set_state_tp(current, state_value); ++} ++EXPORT_SYMBOL(__trace_set_current_state); ++ ++void __trace_set_need_resched(struct task_struct *curr, int tif) ++{ ++ trace_call__sched_set_need_resched_tp(curr, smp_processor_id(), tif); ++} ++EXPORT_SYMBOL_GPL(__trace_set_need_resched); ++ ++unsigned long long nr_context_switches_cpu(int cpu) ++{ ++ return cpu_rq(cpu)->nr_switches; ++} ++ ++/* ++ * External (non-sched/) callers cannot see task_on_rq_queued(); wrap it. ++ * Used by tick-sched nohz full path among others. ++ */ ++bool sched_task_on_rq(struct task_struct *p) ++{ ++ return task_on_rq_queued(p); ++} ++ ++unsigned long get_wchan(struct task_struct *p) ++{ ++ unsigned long ip = 0; ++ unsigned int state; ++ ++ if (!p || p == current) ++ return 0; ++ ++ /* Only get wchan if task is blocked and we can keep it that way. */ ++ raw_spin_lock_irq(&p->pi_lock); ++ state = READ_ONCE(p->__state); ++ smp_rmb(); /* see try_to_wake_up() */ ++ if (state != TASK_RUNNING && state != TASK_WAKING && !p->on_rq) ++ ip = __get_wchan(p); ++ raw_spin_unlock_irq(&p->pi_lock); ++ ++ return ip; ++} ++ ++/* ++ * Fork path. MuQSS does no cgroup bandwidth accounting and has no sched_ext ++ * to cancel, so these only need to exist. ++ */ ++int sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) ++{ ++ return 0; ++} ++ ++void sched_cancel_fork(struct task_struct *p) ++{ ++} ++ ++/* ++ * rt_mutex helpers. MuQSS has no proxy execution; pre/post still run the ++ * worker submit/update pair so blocking on an rt_mutex flushes plugged IO. ++ * rt_mutex_schedule() must not re-enter schedule() or submit_work runs twice. ++ */ ++#ifdef CONFIG_RT_MUTEXES ++#define fetch_and_set(x, v) ({ int _x = (x); (x) = (v); _x; }) ++#endif ++ ++void rt_mutex_pre_schedule(void) ++{ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1)); ++#endif ++ sched_submit_work(current); ++} ++ ++/* Futex callers cannot have worker state or plugged IO to submit. */ ++void rt_mutex_futex_pre_schedule(void) ++{ ++ lockdep_assert(!(current->flags & (PF_WQ_WORKER | PF_IO_WORKER))); ++ lockdep_assert(!current->plug); ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(!fetch_and_set(current->sched_rt_mutex, 1)); ++#endif ++} ++ ++void rt_mutex_schedule(void) ++{ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(current->sched_rt_mutex); ++#endif ++ __schedule_loop(SM_NONE); ++} ++ ++void rt_mutex_post_schedule(void) ++{ ++ sched_update_worker(current); ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0)); ++#endif ++} ++ ++void rt_mutex_futex_post_schedule(void) ++{ ++#ifdef CONFIG_RT_MUTEXES ++ lockdep_assert(fetch_and_set(current->sched_rt_mutex, 0)); ++#endif ++} ++ ++int dl_task_check_affinity(struct task_struct *p __always_unused, ++ const struct cpumask *mask __always_unused) ++{ ++ /* MuQSS has no deadline bandwidth admission. */ ++ return 0; ++} ++ ++#ifdef CONFIG_SMP ++/* Callers must hold p->pi_lock across the read and its use. */ ++static const struct cpumask *task_user_cpus(struct task_struct *p) ++{ ++ lockdep_assert_held(&p->pi_lock); ++ ++ if (!p->user_cpus_ptr) ++ return cpu_possible_mask; ++ return p->user_cpus_ptr; ++} ++ ++/* ++ * Copy out the intersection of the task's user-requested mask and @mask. ++ * pi_lock keeps user_cpus_ptr alive for the copy: everything that frees it ++ * clears the pointer under task_rq_lock(), which nests pi_lock, and only ++ * frees once that has been dropped. ++ */ ++static bool user_cpus_and(struct task_struct *p, struct cpumask *dst, ++ const struct cpumask *mask) ++{ ++ unsigned long flags; ++ bool ret; ++ ++ raw_spin_lock_irqsave(&p->pi_lock, flags); ++ ret = cpumask_and(dst, task_user_cpus(p), mask); ++ raw_spin_unlock_irqrestore(&p->pi_lock, flags); ++ ++ return ret; ++} ++ ++/* ++ * Intersect the task's user-requested mask with @subset_mask and apply it. ++ * Empty intersection leaves affinity unchanged. ++ */ ++static int restrict_cpus_allowed_ptr(struct task_struct *p, ++ struct cpumask *new_mask, ++ const struct cpumask *subset_mask) ++{ ++ if (!user_cpus_and(p, new_mask, subset_mask)) ++ return -EINVAL; ++ ++ return __set_cpus_allowed_ptr(p, new_mask, 0); ++} ++#endif ++ ++/* ++ * Restrict @p to CPUs it can actually run on (task_cpu_possible_mask()). ++ * ARM64 32-bit execve uses this when 64-bit-only CPUs exist. ++ */ ++void force_compatible_cpus_allowed_ptr(struct task_struct *p) ++{ ++ const struct cpumask *override_mask = task_cpu_possible_mask(p); ++#ifdef CONFIG_SMP ++ cpumask_var_t new_mask; ++ ++ alloc_cpumask_var(&new_mask, GFP_KERNEL); ++ ++ /* ++ * __migrate_task() can fail silently if the dest CPU is offlined ++ * concurrently, so hold the hotplug lock across the move. ++ */ ++ cpus_read_lock(); ++ if (!cpumask_available(new_mask)) ++ goto out_set_mask; ++ ++ if (!restrict_cpus_allowed_ptr(p, new_mask, override_mask)) ++ goto out_free_mask; ++ ++ cpuset_cpus_allowed(p, new_mask); ++ override_mask = new_mask; ++ ++out_set_mask: ++ if (printk_ratelimit()) { ++ printk_deferred("Overriding affinity for process %d (%s) to CPUs %*pbl\n", ++ task_pid_nr(p), p->comm, ++ cpumask_pr_args(override_mask)); ++ } ++ ++ WARN_ON(set_cpus_allowed_ptr(p, override_mask)); ++out_free_mask: ++ cpus_read_unlock(); ++ free_cpumask_var(new_mask); ++#else ++ WARN_ON(set_cpus_allowed_ptr(p, override_mask)); ++#endif ++} ++ ++/* ++ * Restore the affinity previously restricted by ++ * force_compatible_cpus_allowed_ptr(). Caller serialises the pair. ++ */ ++void relax_compatible_cpus_allowed_ptr(struct task_struct *p) ++{ ++#ifdef CONFIG_SMP ++ cpumask_var_t cpus_allowed, new_mask; ++ int ret = -ENOMEM; ++ ++ if (!alloc_cpumask_var(&cpus_allowed, GFP_KERNEL)) ++ goto warn; ++ if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) ++ goto out_free_cpus_allowed; ++ ++ cpuset_cpus_allowed(p, cpus_allowed); ++ user_cpus_and(p, new_mask, cpus_allowed); ++ ret = __set_cpus_allowed_ptr(p, new_mask, SCA_CHECK); ++ ++ free_cpumask_var(new_mask); ++out_free_cpus_allowed: ++ free_cpumask_var(cpus_allowed); ++warn: ++ WARN_ON_ONCE(ret); ++#else ++ WARN_ON(set_cpus_allowed_ptr(p, cpu_possible_mask)); ++#endif ++} ++ ++const char *preempt_model_str(void) ++{ ++ if (IS_ENABLED(CONFIG_PREEMPT_RT)) ++ return "PREEMPT_RT"; ++ if (preempt_model_full()) ++ return "PREEMPT"; ++ if (preempt_model_voluntary()) ++ return "VOLUNTARY"; ++ return "NONE"; ++} ++ ++#ifdef CONFIG_SMP ++bool cpus_equal_capacity(int this_cpu, int that_cpu) ++{ ++ if (!sched_asym_cpucap_active()) ++ return true; ++ ++ if (this_cpu == that_cpu) ++ return true; ++ ++ return arch_scale_cpu_capacity(this_cpu) == arch_scale_cpu_capacity(that_cpu); ++} ++ ++void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask) ++{ ++ struct affinity_context ac = { ++ .new_mask = new_mask, ++ .user_mask = NULL, ++ .flags = SCA_USER, ++ }; ++ ++ do_set_cpus_allowed(p, &ac); ++} ++ ++void ___migrate_enable(void) ++{ ++ __set_cpus_allowed_ptr(current, ¤t->cpus_mask, ++ SCA_MIGRATE_ENABLE); ++} ++#else /* !CONFIG_SMP */ ++/* ++ * Nothing ever repoints cpus_ptr away from cpus_mask on UP, so ++ * __migrate_enable() never reaches here - it just has to link. ++ */ ++void ___migrate_enable(void) ++{ ++} ++#endif /* CONFIG_SMP */ ++EXPORT_SYMBOL_GPL(___migrate_enable); ++ ++/* ++ * user_cpus_ptr records the affinity a task asked for so that it can be ++ * restored after a temporary restriction: sched_setaffinity() stores it, and ++ * bind_zero() stores the mask it displaces from a task that would otherwise ++ * have been stranded by a CPU going down. Either way the child inherits both ++ * the restriction (zerobound, copied by dup_task_struct()) and the mask that ++ * has to be put back for it. ++ */ ++int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, ++ int node) ++{ ++ cpumask_t *user_mask; ++ unsigned long flags; ++ ++ dst->user_cpus_ptr = NULL; ++ ++ /* ++ * Racing here is harmless: losing it just means the child forgoes a ++ * restore it would have got for free, and taking the pi_lock on every ++ * fork to close it is not worth that. ++ */ ++ if (data_race(!src->user_cpus_ptr)) ++ return 0; ++ ++ user_mask = kmalloc_node(cpumask_size(), GFP_KERNEL, node); ++ if (!user_mask) ++ return -ENOMEM; ++ ++ raw_spin_lock_irqsave(&src->pi_lock, flags); ++ if (src->user_cpus_ptr) { ++ swap(dst->user_cpus_ptr, user_mask); ++ cpumask_copy(dst->user_cpus_ptr, src->user_cpus_ptr); ++ } ++ raw_spin_unlock_irqrestore(&src->pi_lock, flags); ++ ++ kfree(user_mask); ++ ++ return 0; ++} ++ ++void release_user_cpus_ptr(struct task_struct *p) ++{ ++ kfree(p->user_cpus_ptr); ++ p->user_cpus_ptr = NULL; ++} ++ ++/* ++ * Modules cannot see the runqueues layout, so export out-of-line wrappers ++ * (INSTANTIATE_EXPORTED_MIGRATE_DISABLE makes the inlines become externs). ++ */ ++void migrate_disable(void) ++{ ++ __migrate_disable(); ++} ++EXPORT_SYMBOL_GPL(migrate_disable); ++ ++void migrate_enable(void) ++{ ++ __migrate_enable(); ++} ++EXPORT_SYMBOL_GPL(migrate_enable); ++ ++/* ++ * MuQSS picks the CPU for a task at schedule() time rather than at exec, so ++ * there is nothing useful to do here. ++ */ ++void sched_exec(void) ++{ ++} ++ ++/* ++ * Deadline bandwidth accounting. There is no deadline class, so no bandwidth ++ * is ever reserved and cpuset has nothing to move between root domains. ++ */ ++u64 dl_cookie; ++ ++int dl_bw_alloc(int cpu, u64 dl_bw) ++{ ++ return 0; ++} ++ ++void dl_bw_free(int cpu, u64 dl_bw) ++{ ++} ++ ++void sched_set_fifo_secondary(struct task_struct *p) ++{ ++ struct sched_param sp = { .sched_priority = MAX_RT_PRIO / 2 - 1 }; ++ ++ WARN_ON_ONCE(sched_setscheduler_nocheck(p, SCHED_FIFO, &sp) != 0); ++} ++EXPORT_SYMBOL_GPL(sched_set_fifo_secondary); ++ ++/* ++ * Scheduler debugfs. debug.c is CFS/EEVDF runqueue introspection and is not ++ * built under MuQSS; /proc//sched reports nothing. ++ */ ++bool sched_debug_verbose; ++ ++#ifdef CONFIG_SMP ++/* Only topology.c calls these, and it is not built on UP. */ ++void update_sched_domain_debugfs(void) ++{ ++} ++ ++void dirty_sched_domain_sysctl(int cpu) ++{ ++} ++#endif /* CONFIG_SMP */ ++ ++void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, ++ struct seq_file *m) ++{ ++} ++ ++void proc_sched_set_task(struct task_struct *p) ++{ ++} +diff --git a/kernel/sched/MuQSS.h b/kernel/sched/MuQSS.h +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/MuQSS.h +@@ -0,0 +1,1254 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef MUQSS_SCHED_H ++#define MUQSS_SCHED_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_PARAVIRT ++#include ++#endif ++ ++/* ++ * cpupri.h is deliberately not included: MuQSS has no RT class priority ++ * bitmap, root_domain keeps an opaque pointer instead, and pulling the header ++ * in would clash with the no-op cpupri_init()/cpupri_cleanup() below. ++ */ ++ ++#include ++ ++#ifdef CONFIG_SCHED_DEBUG ++# define SCHED_WARN_ON(x) WARN_ONCE(x, #x) ++#else ++# define SCHED_WARN_ON(x) ((void)(x)) ++#endif ++ ++/* Wake flags. The first three directly map to some SD flag value */ ++#define WF_EXEC 0x02 /* Wakeup after exec; maps to SD_BALANCE_EXEC */ ++#define WF_FORK 0x04 /* Wakeup after fork; maps to SD_BALANCE_FORK */ ++#define WF_TTWU 0x08 /* Wakeup; maps to SD_BALANCE_WAKE */ ++ ++#define WF_SYNC 0x10 /* Waker goes to sleep after wakeup */ ++#define WF_MIGRATED 0x20 /* Internal use, task got migrated */ ++#define WF_CURRENT_CPU 0x40 /* Prefer to move the wakee to the current CPU. */ ++#define WF_ON_CPU 0x80 /* Wakee is on_cpu */ ++ ++#ifdef CONFIG_SMP ++static_assert(WF_EXEC == SD_BALANCE_EXEC); ++static_assert(WF_FORK == SD_BALANCE_FORK); ++static_assert(WF_TTWU == SD_BALANCE_WAKE); ++#endif ++ ++/* task_struct::on_rq states: */ ++#define TASK_ON_RQ_QUEUED 1 ++#define TASK_ON_RQ_MIGRATING 2 ++ ++extern void call_trace_sched_update_nr_running(struct rq *rq, int count); ++ ++struct rq; ++ ++#ifdef CONFIG_SMP ++ ++static inline bool sched_asym_prefer(int a, int b) ++{ ++ return arch_asym_cpu_priority(a) > arch_asym_cpu_priority(b); ++} ++ ++struct perf_domain { ++ struct em_perf_domain *em_pd; ++ struct perf_domain *next; ++ struct rcu_head rcu; ++}; ++ ++/* Scheduling group status flags */ ++#define SG_OVERLOAD 0x1 /* More than one runnable task on a CPU. */ ++#define SG_OVERUTILIZED 0x2 /* One or more CPUs are over-utilized. */ ++ ++/* ++ * We add the notion of a root-domain which will be used to define per-domain ++ * variables. Each exclusive cpuset essentially defines an island domain by ++ * fully partitioning the member cpus from any other cpuset. Whenever a new ++ * exclusive cpuset is created, we also create and attach a new root-domain ++ * object. ++ * ++ */ ++struct root_domain { ++ atomic_t refcount; ++ atomic_t rto_count; ++ struct rcu_head rcu; ++ cpumask_var_t span; ++ cpumask_var_t online; ++ ++ /* ++ * Indicate pullable load on at least one CPU, e.g: ++ * - More than one runnable task ++ * - Running task is misfit ++ */ ++ int overload; ++ ++ /* Indicate one or more cpus over-utilized (tipping point) */ ++ int overutilized; ++ ++ /* ++ * The bit corresponding to a CPU gets set here if such CPU has more ++ * than one runnable -deadline task (as it is below for RT tasks). ++ */ ++ cpumask_var_t dlo_mask; ++ atomic_t dlo_count; ++ ++ /* Replace unused CFS structures with void */ ++ //struct dl_bw dl_bw; ++ //struct cpudl cpudl; ++ void *dl_bw; ++ void *cpudl; ++ u64 visit_cookie; ++ ++ /* ++ * The "RT overload" flag: it gets set if a CPU has more than ++ * one runnable RT task. ++ */ ++ cpumask_var_t rto_mask; ++ //struct cpupri cpupri; ++ void *cpupri; ++ ++ unsigned long max_cpu_capacity; ++ ++ /* ++ * NULL-terminated list of performance domains intersecting with the ++ * CPUs of the rd. Protected by RCU. ++ */ ++ struct perf_domain *pd; ++}; ++ ++extern void init_defrootdomain(void); ++extern int sched_init_domains(const struct cpumask *cpu_map); ++extern void rq_attach_root(struct rq *rq, struct root_domain *rd); ++ ++static inline void cpupri_cleanup(void __maybe_unused *cpupri) ++{ ++} ++ ++static inline void cpudl_cleanup(void __maybe_unused *cpudl) ++{ ++} ++ ++static inline void init_dl_bw(void __maybe_unused *dl_bw) ++{ ++} ++ ++static inline int cpudl_init(void __maybe_unused *dl_bw) ++{ ++ return 0; ++} ++ ++static inline int cpupri_init(void __maybe_unused *cpupri) ++{ ++ return 0; ++} ++#endif /* CONFIG_SMP */ ++ ++/* ++ * This is the main, per-CPU runqueue data structure. ++ * This data should only be modified by the local cpu. ++ */ ++struct rq { ++ raw_spinlock_t *lock; ++ raw_spinlock_t *orig_lock; ++ ++ struct task_struct __rcu *curr; ++ struct task_struct *idle; ++ struct task_struct *stop; ++ struct mm_struct *prev_mm; ++ ++ unsigned int nr_running; ++ /* ++ * This is part of a global counter where only the total sum ++ * over all CPUs matters. A task can increase this counter on ++ * one CPU and if it got migrated afterwards it may decrease ++ * it on another CPU. Always updated under the runqueue lock: ++ */ ++ unsigned long nr_uninterruptible; ++#ifdef CONFIG_SMP ++ unsigned int ttwu_pending; ++#endif ++ /* ++ * migrate_disable() nesting count. Mainline's migrate_disable() and ++ * __migrate_enable() are inline in and reach this via ++ * the RQ_nr_pinned offset generated from rq-offsets.c, so the member ++ * is required whether or not MuQSS itself consults it. ++ */ ++ unsigned int nr_pinned; ++ u64 nr_switches; ++ ++ /* ++ * Isolated curr snapshot for lockless peeks. Own cacheline so ++ * last_jiffy / niffies (and remote synchronise_niffies()) do not ++ * invalidate it. Under SMT_NICE this is four fields; without it, ++ * deadline + prio only. Do not pull nr_running, online, or sl in. ++ */ ++ u64 rq_deadline ____cacheline_aligned; ++ int rq_prio; ++#ifdef CONFIG_SMT_NICE ++ struct mm_struct *rq_mm; ++ int rq_smt_bias; /* Policy/nice level bias across smt siblings */ ++#endif ++ u8 __rq_snap_pad[0] ____cacheline_aligned; ++ ++ unsigned long last_scheduler_tick; /* Last jiffy this RQ ticked */ ++ unsigned long last_jiffy; /* Last jiffy this RQ updated rq clock */ ++ u64 niffies; /* Last time this RQ updated rq clock */ ++ u64 jiffy_niffies; /* Niffies as counted by the tick alone */ ++ ++ u64 load_update; /* When we last updated load */ ++ unsigned long load_avg; /* Rolling load average */ ++#ifdef CONFIG_HAVE_SCHED_AVG_IRQ ++ u64 irq_load_update; /* When we last updated IRQ load */ ++ unsigned long irq_load_avg; /* Rolling IRQ load average */ ++#endif ++ /* Accurate timekeeping data */ ++ unsigned long user_ns, nice_ns, irq_ns, softirq_ns, system_ns, ++ iowait_ns, idle_ns; ++ atomic_t nr_iowait; ++ ++#ifdef CONFIG_MEMBARRIER ++ int membarrier_state; ++#endif ++ ++ /* ++ * MuQSS runs no deadline server. topology.c unconditionally tests ++ * rq->fair_server.dl_server when attaching a root domain, so carry a ++ * one-byte flag that is never set rather than #ifdef that code out. ++ * __dl_server_attach_root() below is a no-op taking void *, so the ++ * member's type does not have to match sched_dl_entity. ++ */ ++ struct { ++ bool dl_server; ++ } fair_server; ++ ++ skiplist_node *node; ++ skiplist *sl; ++#ifdef CONFIG_SMP ++ struct task_struct *preempt; /* Preempt triggered on this task */ ++ struct task_struct *preempting; /* Hint only, what task is preempting */ ++ ++ int cpu; /* cpu of this runqueue */ ++ bool online; ++ ++ struct root_domain *rd; ++ struct sched_domain *sd; ++ ++ unsigned long cpu_capacity_orig; ++ ++ int *cpu_locality; /* CPU relative cache distance */ ++ struct rq **rq_order; /* Shared RQs ordered by relative cache distance */ ++ skiplist **sl_order; /* Leaders' skiplists, parallel to rq_order */ ++ struct rq **cpu_order; /* RQs of discrete CPUs ordered by distance */ ++ ++ bool is_leader; ++ struct rq *smp_leader; /* First physical CPU per node */ ++#ifdef CONFIG_SCHED_THERMAL_PRESSURE ++ struct sched_avg avg_thermal; ++#endif /* CONFIG_SCHED_THERMAL_PRESSURE */ ++#ifdef CONFIG_SCHED_SMT ++ struct rq *smt_leader; /* First logical CPU in SMT siblings */ ++ cpumask_t thread_mask; ++ bool has_smt_sibling; /* This CPU has SMT siblings at all */ ++#endif /* CONFIG_SCHED_SMT */ ++#ifdef CONFIG_SCHED_MC ++ struct rq *mc_leader; /* First logical CPU in MC siblings */ ++ cpumask_t core_mask; ++ bool (*cache_idle)(struct rq *rq); ++ /* See if all cache siblings are idle */ ++#endif /* CONFIG_SCHED_MC */ ++ /* last busy→idle, wakeup idle pick */ ++ unsigned long idle_jiffy; ++#endif /* CONFIG_SMP */ ++ ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++ u64 prev_irq_time; ++ u64 psi_irq_time; ++#endif /* CONFIG_IRQ_TIME_ACCOUNTING */ ++#ifdef CONFIG_PARAVIRT ++ u64 prev_steal_time; ++#endif /* CONFIG_PARAVIRT */ ++#ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING ++ u64 prev_steal_time_rq; ++#endif /* CONFIG_PARAVIRT_TIME_ACCOUNTING */ ++ ++ u64 clock, last_tick; ++ /* Ensure that all clocks are in the same cache line */ ++ u64 clock_task ____cacheline_aligned; ++ int dither; ++ ++ int iso_ticks; ++ bool iso_refractory; ++ ++#ifdef CONFIG_HIGH_RES_TIMERS ++ struct hrtimer hrexpiry_timer; ++ /* ++ * Deferred start/cancel around __schedule() — same scheme as mainline ++ * hrtick. Reprogramming the oneshot clockevent from set_rq_task() on ++ * every context switch under the rq lock starves TIMER_SOFTIRQ on SMP. ++ */ ++ call_single_data_t hrexpiry_csd; ++ unsigned int hrexpiry_sched; ++ s64 hrexpiry_delay; ++ ktime_t hrexpiry_time; ++#endif ++ ++ int rt_nr_running; /* Number real time tasks running */ ++#ifdef CONFIG_SCHEDSTATS ++ ++ /* latency stats */ ++ struct sched_info rq_sched_info; ++ unsigned long long rq_cpu_time; ++ /* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */ ++ ++ /* sys_sched_yield() stats */ ++ unsigned int yld_count; ++ ++ /* schedule() stats */ ++ unsigned int sched_switch; ++ unsigned int sched_count; ++ unsigned int sched_goidle; ++ ++ /* try_to_wake_up() stats */ ++ unsigned int ttwu_count; ++ unsigned int ttwu_local; ++#endif /* CONFIG_SCHEDSTATS */ ++ ++#ifdef CONFIG_CPU_IDLE ++ /* Must be inspected within a rcu lock section */ ++ struct cpuidle_state *idle_state; ++#endif ++}; ++ ++static inline u64 __rq_clock_broken(struct rq *rq) ++{ ++ return READ_ONCE(rq->clock); ++} ++ ++static inline u64 rq_clock(struct rq *rq) ++{ ++ lockdep_assert_held(rq->lock); ++ ++ return rq->clock; ++} ++ ++static inline u64 rq_clock_task(struct rq *rq) ++{ ++ lockdep_assert_held(rq->lock); ++ ++ return rq->clock_task; ++} ++ ++/** ++ * By default the decay is the default pelt decay period. ++ * The decay shift can change the decay period in ++ * multiples of 32. ++ * Decay shift Decay period(ms) ++ * 0 32 ++ * 1 64 ++ * 2 128 ++ * 3 256 ++ * 4 512 ++ */ ++extern int sched_thermal_decay_shift; ++ ++static inline u64 rq_clock_thermal(struct rq *rq) ++{ ++ return rq_clock_task(rq) >> sched_thermal_decay_shift; ++} ++ ++struct rq_flags { ++ unsigned long flags; ++}; ++ ++#ifdef CONFIG_SMP ++struct rq *cpu_rq(int cpu); ++#endif ++ ++#ifndef CONFIG_SMP ++extern struct rq *uprq; ++#define cpu_rq(cpu) (uprq) ++#define this_rq() (uprq) ++#define raw_rq() (uprq) ++#define task_rq(p) (uprq) ++#define cpu_curr(cpu) ((uprq)->curr) ++#else /* CONFIG_SMP */ ++DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); ++#define cpu_rq(cpu) (&per_cpu(runqueues, (cpu))) ++#define this_rq() this_cpu_ptr(&runqueues) ++#define task_rq(p) cpu_rq(task_cpu(p)) ++#define cpu_curr(cpu) (cpu_rq(cpu)->curr) ++#define raw_rq() raw_cpu_ptr(&runqueues) ++#endif /* CONFIG_SMP */ ++ ++static inline int task_current(struct rq *rq, struct task_struct *p) ++{ ++ return rq->curr == p; ++} ++ ++static inline int task_running(struct rq *rq, struct task_struct *p) ++{ ++#ifdef CONFIG_SMP ++ return p->on_cpu; ++#else ++ return task_current(rq, p); ++#endif ++} ++ ++/* ++ * Mainline name used by stats.h / psi.c / RT helpers. Equivalent to ++ * task_running() under SMP; always uses p->on_cpu so the @rq argument is ++ * unused (kept for API compatibility with mainline call sites). ++ */ ++static inline int task_on_cpu(struct rq *rq, struct task_struct *p) ++{ ++#ifdef CONFIG_SMP ++ return p->on_cpu; ++#else ++ return task_current(rq, p); ++#endif ++} ++ ++static inline int task_on_rq_queued(struct task_struct *p) ++{ ++ return p->on_rq == TASK_ON_RQ_QUEUED; ++} ++ ++static inline int task_on_rq_migrating(struct task_struct *p) ++{ ++ return READ_ONCE(p->on_rq) == TASK_ON_RQ_MIGRATING; ++} ++ ++static inline void lockdep_assert_rq_held(struct rq *rq) ++{ ++ lockdep_assert_held(rq->lock); ++} ++ ++static inline void rq_lock(struct rq *rq) ++ __acquires(rq->lock) ++{ ++ raw_spin_lock(rq->lock); ++} ++ ++static inline void rq_unlock(struct rq *rq) ++ __releases(rq->lock) ++{ ++ raw_spin_unlock(rq->lock); ++} ++ ++static inline void rq_lock_irq(struct rq *rq) ++ __acquires(rq->lock) ++{ ++ raw_spin_lock_irq(rq->lock); ++} ++ ++static inline void rq_unlock_irq(struct rq *rq, struct rq_flags __always_unused *rf) ++ __releases(rq->lock) ++{ ++ raw_spin_unlock_irq(rq->lock); ++} ++ ++static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf) ++ __acquires(rq->lock) ++{ ++ raw_spin_lock_irqsave(rq->lock, rf->flags); ++} ++ ++static inline void rq_unlock_irqrestore(struct rq *rq, struct rq_flags *rf) ++ __releases(rq->lock) ++{ ++ raw_spin_unlock_irqrestore(rq->lock, rf->flags); ++} ++ ++static inline struct rq *task_rq_lock(struct task_struct *p, struct rq_flags *rf) ++ __acquires(p->pi_lock) ++ __acquires(rq->lock) ++{ ++ struct rq *rq; ++ ++ while (42) { ++ raw_spin_lock_irqsave(&p->pi_lock, rf->flags); ++ rq = task_rq(p); ++ raw_spin_lock(rq->lock); ++ if (likely(rq == task_rq(p))) ++ break; ++ raw_spin_unlock(rq->lock); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags); ++ } ++ return rq; ++} ++ ++static inline void task_rq_unlock(struct rq *rq, struct task_struct *p, struct rq_flags *rf) ++ __releases(rq->lock) ++ __releases(p->pi_lock) ++{ ++ rq_unlock(rq); ++ raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags); ++} ++ ++static inline struct rq *__task_rq_lock(struct task_struct *p, struct rq_flags __always_unused *rf) ++ __acquires(rq->lock) ++{ ++ struct rq *rq; ++ ++ lockdep_assert_held(&p->pi_lock); ++ ++ while (42) { ++ rq = task_rq(p); ++ raw_spin_lock(rq->lock); ++ if (likely(rq == task_rq(p))) ++ break; ++ raw_spin_unlock(rq->lock); ++ } ++ return rq; ++} ++ ++/* ++ * Match mainline's 3-arg signature (stats.h / psi paths pass @p). MuQSS does ++ * not pin the lock cookie, so @p and @rf are unused beyond API compatibility. ++ */ ++static inline void __task_rq_unlock(struct rq *rq, ++ struct task_struct __always_unused *p, ++ struct rq_flags __always_unused *rf) ++{ ++ rq_unlock(rq); ++} ++ ++/* ++ * scoped_guard / guard(rq_lock_irq) used by psi_cgroup_restart() and similar. ++ * MuQSS rq_lock_irq takes only the rq (no pin cookie). ++ */ ++DEFINE_LOCK_GUARD_1(rq_lock_irq, struct rq, ++ rq_lock_irq(_T->lock), ++ rq_unlock_irq(_T->lock, &_T->rf), ++ struct rq_flags rf) ++ ++DEFINE_LOCK_GUARD_1(rq_lock_irqsave, struct rq, ++ rq_lock_irqsave(_T->lock, &_T->rf), ++ rq_unlock_irqrestore(_T->lock, &_T->rf), ++ struct rq_flags rf) ++ ++static inline struct rq * ++this_rq_lock_irq(struct rq_flags *rf) ++ __acquires(rq->lock) ++{ ++ struct rq *rq; ++ ++ local_irq_disable(); ++ rq = this_rq(); ++ rq_lock(rq); ++ return rq; ++} ++ ++/* ++ * {de,en}queue flags: Most not used on MuQSS. ++ * ++ * DEQUEUE_SLEEP - task is no longer runnable ++ * ENQUEUE_WAKEUP - task just became runnable ++ * ++ * SAVE/RESTORE - an otherwise spurious dequeue/enqueue, done to ensure tasks ++ * are in a known state which allows modification. Such pairs ++ * should preserve as much state as possible. ++ * ++ * MOVE - paired with SAVE/RESTORE, explicitly does not preserve the location ++ * in the runqueue. ++ * ++ * ENQUEUE_HEAD - place at front of runqueue (tail if not specified) ++ * ENQUEUE_REPLENISH - CBS (replenish runtime and postpone deadline) ++ * ENQUEUE_MIGRATED - the task was migrated during wakeup ++ * ++ */ ++ ++#define DEQUEUE_SLEEP 0x01 ++#define DEQUEUE_SAVE 0x02 /* matches ENQUEUE_RESTORE */ ++ ++#define ENQUEUE_WAKEUP 0x01 ++#define ENQUEUE_RESTORE 0x02 ++ ++#ifdef CONFIG_SMP ++#define ENQUEUE_MIGRATED 0x40 ++#else ++#define ENQUEUE_MIGRATED 0x00 ++#endif ++ ++#ifdef CONFIG_NUMA ++enum numa_topology_type { ++ NUMA_DIRECT, ++ NUMA_GLUELESS_MESH, ++ NUMA_BACKPLANE, ++}; ++extern enum numa_topology_type sched_numa_topology_type; ++extern int sched_max_numa_distance; ++extern bool find_numa_distance(int distance); ++extern void sched_init_numa(int offline_node); ++extern void sched_domains_numa_masks_set(unsigned int cpu); ++extern void sched_domains_numa_masks_clear(unsigned int cpu); ++extern int sched_numa_find_closest(const struct cpumask *cpus, int cpu); ++#else ++static inline void sched_init_numa(int offline_node) { } ++static inline void sched_domains_numa_masks_set(unsigned int cpu) { } ++static inline void sched_domains_numa_masks_clear(unsigned int cpu) { } ++static inline int sched_numa_find_closest(const struct cpumask *cpus, int cpu) ++{ ++ return nr_cpu_ids; ++} ++#endif ++ ++extern struct mutex sched_domains_mutex; ++extern struct static_key_false sched_schedstats; ++ ++#define rcu_dereference_sched_domain(p) \ ++ rcu_dereference_all_check((p), lockdep_is_held(&sched_domains_mutex)) ++ ++struct affinity_context { ++ const struct cpumask *new_mask; ++ struct cpumask *user_mask; ++ unsigned int flags; ++}; ++ ++#define MDF_PUSH 0x01 ++ ++static inline bool is_migration_disabled(struct task_struct *p) ++{ ++ return p->migration_disabled; ++} ++ ++#define SCA_CHECK 0x01 ++#define SCA_MIGRATE_DISABLE 0x02 ++#define SCA_MIGRATE_ENABLE 0x04 ++#define SCA_USER 0x08 ++ ++#ifdef CONFIG_SMP ++ ++/* ++ * The domain tree (rq->sd) is protected by RCU's quiescent state transition. ++ * See destroy_sched_domains: call_rcu for details. ++ * ++ * The domain tree of any CPU may only be accessed from within ++ * preempt-disabled sections. ++ */ ++#define for_each_domain(cpu, __sd) \ ++ for (__sd = rcu_dereference_sched_domain(cpu_rq(cpu)->sd); \ ++ __sd; __sd = __sd->parent) ++ ++/** ++ * highest_flag_domain - Return highest sched_domain containing flag. ++ * @cpu: The cpu whose highest level of sched domain is to ++ * be returned. ++ * @flag: The flag to check for the highest sched_domain ++ * for the given cpu. ++ * ++ * Returns the highest sched_domain of a cpu which contains the given flag. ++ */ ++static inline struct sched_domain *highest_flag_domain(int cpu, int flag) ++{ ++ struct sched_domain *sd, *hsd = NULL; ++ ++ for_each_domain(cpu, sd) { ++ if (!(sd->flags & flag)) ++ break; ++ hsd = sd; ++ } ++ ++ return hsd; ++} ++ ++static inline struct sched_domain *lowest_flag_domain(int cpu, int flag) ++{ ++ struct sched_domain *sd; ++ ++ for_each_domain(cpu, sd) { ++ if (sd->flags & flag) ++ break; ++ } ++ ++ return sd; ++} ++ ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_llc); ++DECLARE_PER_CPU(int, sd_llc_size); ++DECLARE_PER_CPU(int, sd_llc_id); ++DECLARE_PER_CPU(int, sd_share_id); ++DECLARE_PER_CPU(struct sched_domain_shared __rcu *, sd_llc_shared); ++DECLARE_PER_CPU(struct sched_domain_shared __rcu *, sd_balance_shared); ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_numa); ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_asym_packing); ++DECLARE_PER_CPU(struct sched_domain __rcu *, sd_asym_cpucapacity); ++ ++void sched_domains_free_llc_id(int cpu); ++ ++extern struct static_key_false sched_asym_cpucapacity; ++extern struct static_key_false sched_cluster_active; ++ ++static __always_inline bool sched_asym_cpucap_active(void) ++{ ++ return static_branch_unlikely(&sched_asym_cpucapacity); ++} ++ ++struct sched_group_capacity { ++ atomic_t ref; ++ /* ++ * CPU capacity of this group, SCHED_CAPACITY_SCALE being max capacity ++ * for a single CPU. ++ */ ++ unsigned long capacity; ++ unsigned long min_capacity; /* Min per-CPU capacity in group */ ++ unsigned long max_capacity; /* Max per-CPU capacity in group */ ++ unsigned long next_update; ++ int imbalance; /* XXX unrelated to capacity but shared group state */ ++ ++ int id; ++ ++ unsigned long cpumask[]; /* Balance mask */ ++}; ++ ++struct sched_group { ++ struct sched_group *next; /* Must be a circular list */ ++ atomic_t ref; ++ ++ unsigned int group_weight; ++ unsigned int cores; ++ struct sched_group_capacity *sgc; ++ int asym_prefer_cpu; /* CPU of highest priority in group */ ++ int flags; ++ ++ /* ++ * The CPUs this group covers. ++ * ++ * NOTE: this field is variable length. (Allocated dynamically ++ * by attaching extra space to the end of the structure, ++ * depending on how many CPUs the kernel has booted up with) ++ */ ++ unsigned long cpumask[]; ++}; ++ ++static inline struct cpumask *sched_group_span(struct sched_group *sg) ++{ ++ return to_cpumask(sg->cpumask); ++} ++ ++/* ++ * See build_balance_mask(). ++ */ ++static inline struct cpumask *group_balance_mask(struct sched_group *sg) ++{ ++ return to_cpumask(sg->sgc->cpumask); ++} ++ ++extern int group_balance_cpu(struct sched_group *sg); ++extern void update_sched_domain_debugfs(void); ++extern void dirty_sched_domain_sysctl(int cpu); ++extern void sched_update_numa(int cpu, bool online); ++extern void sched_get_rd(struct root_domain *rd); ++extern void sched_put_rd(struct root_domain *rd); ++extern bool sched_debug_verbose; ++ ++/** ++ * group_first_cpu - Returns the first cpu in the cpumask of a sched_group. ++ * @group: The group whose first cpu is to be returned. ++ */ ++static inline unsigned int group_first_cpu(struct sched_group *group) ++{ ++ return cpumask_first(sched_group_span(group)); ++} ++ ++ ++/* ++ * register_sched_domain_sysctl()/unregister_sched_domain_sysctl() were ++ * replaced upstream by update_sched_domain_debugfs(), declared above with ++ * dirty_sched_domain_sysctl(). flush_smp_call_function_from_idle() likewise ++ * became flush_smp_call_function_queue(), declared in smp.h. ++ */ ++ ++extern void set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx); ++ ++extern void set_rq_online (struct rq *rq); ++extern void set_rq_offline(struct rq *rq); ++extern bool sched_smp_initialized; ++ ++static inline void update_group_capacity(struct sched_domain *sd, int cpu) ++{ ++} ++ ++static inline void trigger_load_balance(struct rq *rq) ++{ ++} ++ ++#else /* CONFIG_SMP */ ++ ++static inline void flush_smp_call_function_from_idle(void) { } ++ ++/* ++ * UP has no sched domains and MuQSS's rq has no ->sd, but stats.c walks the ++ * domain list unconditionally. Hand it an empty list. ++ */ ++#define for_each_domain(cpu, __sd) \ ++ for (__sd = NULL; __sd; __sd = __sd->parent) ++ ++#endif /* CONFIG_SMP */ ++ ++#ifdef CONFIG_CPU_IDLE ++static inline void idle_set_state(struct rq *rq, ++ struct cpuidle_state *idle_state) ++{ ++ rq->idle_state = idle_state; ++} ++ ++static inline struct cpuidle_state *idle_get_state(struct rq *rq) ++{ ++ SCHED_WARN_ON(!rcu_read_lock_held()); ++ return rq->idle_state; ++} ++#else ++static inline void idle_set_state(struct rq *rq, ++ struct cpuidle_state *idle_state) ++{ ++} ++ ++static inline struct cpuidle_state *idle_get_state(struct rq *rq) ++{ ++ return NULL; ++} ++#endif ++ ++#ifdef CONFIG_SCHED_DEBUG ++extern bool sched_debug_enabled; ++#endif ++ ++extern void schedule_idle(void); ++ ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++struct irqtime { ++ u64 total; ++ u64 tick_delta; ++ u64 irq_start_time; ++ struct u64_stats_sync sync; ++}; ++ ++DECLARE_PER_CPU(struct irqtime, cpu_irqtime); ++ ++/* ++ * Returns the irqtime minus the softirq time computed by ksoftirqd. ++ * Otherwise ksoftirqd's sum_exec_runtime is substracted its own runtime ++ * and never move forward. ++ */ ++static inline u64 irq_time_read(int cpu) ++{ ++ struct irqtime *irqtime = &per_cpu(cpu_irqtime, cpu); ++ unsigned int seq; ++ u64 total; ++ ++ do { ++ seq = __u64_stats_fetch_begin(&irqtime->sync); ++ total = irqtime->total; ++ } while (__u64_stats_fetch_retry(&irqtime->sync, seq)); ++ ++ return total; ++} ++#endif /* CONFIG_IRQ_TIME_ACCOUNTING */ ++ ++static inline bool sched_stop_runnable(struct rq *rq) ++{ ++ return rq->stop && task_on_rq_queued(rq->stop); ++} ++ ++#ifdef CONFIG_SMP ++static inline int cpu_of(struct rq *rq) ++{ ++ return rq->cpu; ++} ++#else /* CONFIG_SMP */ ++static inline int cpu_of(struct rq *rq) ++{ ++ return 0; ++} ++#endif ++ ++#ifdef CONFIG_CPU_FREQ ++DECLARE_PER_CPU(struct update_util_data *, cpufreq_update_util_data); ++ ++static inline void cpufreq_trigger(struct rq *rq, unsigned int flags) ++{ ++ struct update_util_data *data; ++ ++ data = rcu_dereference_sched(*per_cpu_ptr(&cpufreq_update_util_data, ++ cpu_of(rq))); ++ ++ if (data) ++ data->func(data, rq->niffies, flags); ++} ++#else ++static inline void cpufreq_trigger(struct rq *rq, unsigned int flag) ++{ ++} ++#endif /* CONFIG_CPU_FREQ */ ++ ++static __always_inline ++unsigned int uclamp_rq_util_with(struct rq __maybe_unused *rq, unsigned int util, ++ struct task_struct __maybe_unused *p) ++{ ++ return util; ++} ++ ++static inline bool uclamp_is_used(void) ++{ ++ return false; ++} ++ ++#ifndef arch_scale_freq_tick ++static __always_inline ++void arch_scale_freq_tick(void) ++{ ++} ++#endif ++ ++#ifdef arch_scale_freq_capacity ++#ifndef arch_scale_freq_invariant ++#define arch_scale_freq_invariant() (true) ++#endif ++#else /* arch_scale_freq_capacity */ ++#define arch_scale_freq_invariant() (false) ++#endif ++ ++/* ++ * read_sum_exec_runtime() lives in cputime.c and now goes through ++ * tsk_seruntime(), so MuQSS needs no copy of it here. ++ */ ++ ++#ifndef arch_scale_freq_capacity ++/** ++ * arch_scale_freq_capacity - get the frequency scale factor of a given CPU. ++ * @cpu: the CPU in question. ++ * ++ * Return: the frequency scale factor normalized against SCHED_CAPACITY_SCALE, i.e. ++ * ++ * f_curr ++ * ------ * SCHED_CAPACITY_SCALE ++ * f_max ++ */ ++static __always_inline ++unsigned long arch_scale_freq_capacity(int cpu) ++{ ++ return SCHED_CAPACITY_SCALE; ++} ++#endif ++ ++#ifdef CONFIG_NO_HZ_FULL ++extern bool sched_can_stop_tick(struct rq *rq); ++extern int __init sched_tick_offload_init(void); ++ ++/* ++ * Tick may be needed by tasks in the runqueue depending on their policy and ++ * requirements. If tick is needed, lets send the target an IPI to kick it out of ++ * nohz mode if necessary. ++ */ ++static inline void sched_update_tick_dependency(struct rq *rq) ++{ ++ int cpu = cpu_of(rq); ++ ++ if (!tick_nohz_full_cpu(cpu)) ++ return; ++ ++ if (sched_can_stop_tick(rq)) ++ tick_nohz_dep_clear_cpu(cpu, TICK_DEP_BIT_SCHED); ++ else ++ tick_nohz_dep_set_cpu(cpu, TICK_DEP_BIT_SCHED); ++} ++#else ++static inline int sched_tick_offload_init(void) { return 0; } ++static inline void sched_update_tick_dependency(struct rq *rq) { } ++#endif ++ ++#define SCHED_FLAG_SUGOV 0x10000000 ++ ++#ifdef CONFIG_SMP ++/* ++ * enum cpu_util_type and its FREQUENCY_UTIL/ENERGY_UTIL members were removed ++ * upstream; effective_cpu_util() now reports a utilisation figure plus the ++ * usable capacity range. MuQSS supplies it from MuQSS.c. ++ */ ++unsigned long effective_cpu_util(int cpu, unsigned long util_cfs, ++ unsigned long *min, unsigned long *max); ++ ++static inline unsigned long cpu_bw_dl(struct rq *rq) ++{ ++ return 0; ++} ++ ++static inline unsigned long cpu_util_dl(struct rq *rq) ++{ ++ return 0; ++} ++ ++static inline unsigned long cpu_util_cfs(struct rq *rq) ++{ ++ unsigned long ret = READ_ONCE(rq->load_avg); ++ ++ if (ret > SCHED_CAPACITY_SCALE) ++ ret = SCHED_CAPACITY_SCALE; ++ return ret; ++} ++ ++static inline unsigned long cpu_util_rt(struct rq *rq) ++{ ++ unsigned long ret = READ_ONCE(rq->rt_nr_running); ++ ++ if (ret > SCHED_CAPACITY_SCALE) ++ ret = SCHED_CAPACITY_SCALE; ++ return ret; ++} ++ ++#ifdef CONFIG_HAVE_SCHED_AVG_IRQ ++static inline unsigned long cpu_util_irq(struct rq *rq) ++{ ++ unsigned long ret = READ_ONCE(rq->irq_load_avg); ++ ++ if (ret > SCHED_CAPACITY_SCALE) ++ ret = SCHED_CAPACITY_SCALE; ++ return ret; ++} ++ ++static inline ++unsigned long scale_irq_capacity(unsigned long util, unsigned long irq, unsigned long max) ++{ ++ util *= (max - irq); ++ util /= max; ++ ++ return util; ++ ++} ++#else /* CONFIG_HAVE_SCHED_AVG_IRQ */ ++static inline unsigned long cpu_util_irq(struct rq *rq) ++{ ++ return 0; ++} ++ ++static inline ++unsigned long scale_irq_capacity(unsigned long util, unsigned long irq, unsigned long max) ++{ ++ return util; ++} ++#endif /* CONFIG_HAVE_SCHED_AVG_IRQ */ ++#endif /* CONFIG_SMP */ ++ ++#if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_CPU_FREQ_GOV_SCHEDUTIL) ++#define perf_domain_span(pd) (to_cpumask(((pd)->em_pd->cpus))) ++ ++DECLARE_STATIC_KEY_FALSE(sched_energy_present); ++ ++static inline bool sched_energy_enabled(void) ++{ ++ return static_branch_unlikely(&sched_energy_present); ++} ++ ++#else /* ! (CONFIG_ENERGY_MODEL && CONFIG_CPU_FREQ_GOV_SCHEDUTIL) */ ++ ++#define perf_domain_span(pd) NULL ++static inline bool sched_energy_enabled(void) { return false; } ++ ++#endif /* CONFIG_ENERGY_MODEL && CONFIG_CPU_FREQ_GOV_SCHEDUTIL */ ++ ++#ifdef CONFIG_MEMBARRIER ++/* ++ * The scheduler provides memory barriers required by membarrier between: ++ * - prior user-space memory accesses and store to rq->membarrier_state, ++ * - store to rq->membarrier_state and following user-space memory accesses. ++ * In the same way it provides those guarantees around store to rq->curr. ++ */ ++static inline void membarrier_switch_mm(struct rq *rq, ++ struct mm_struct *prev_mm, ++ struct mm_struct *next_mm) ++{ ++ int membarrier_state; ++ ++ if (prev_mm == next_mm) ++ return; ++ ++ membarrier_state = atomic_read(&next_mm->membarrier_state); ++ if (READ_ONCE(rq->membarrier_state) == membarrier_state) ++ return; ++ ++ WRITE_ONCE(rq->membarrier_state, membarrier_state); ++} ++#else ++static inline void membarrier_switch_mm(struct rq *rq, ++ struct mm_struct *prev_mm, ++ struct mm_struct *next_mm) ++{ ++} ++#endif ++ ++#ifdef CONFIG_SMP ++static inline bool is_per_cpu_kthread(struct task_struct *p) ++{ ++ if (!(p->flags & PF_KTHREAD)) ++ return false; ++ ++ if (p->nr_cpus_allowed != 1) ++ return false; ++ ++ return true; ++} ++#endif ++ ++void swake_up_all_locked(struct swait_queue_head *q); ++void __prepare_to_swait(struct swait_queue_head *q, struct swait_queue *wait); ++ ++/* pelt.h compat CONFIG_SCHED_THERMAL_PRESSURE impossible with MUQSS */ ++static inline int ++update_thermal_load_avg(u64 now, struct rq *rq, u64 capacity) ++{ ++ return 0; ++} ++ ++static inline u64 thermal_load_avg(struct rq *rq) ++{ ++ return 0; ++} ++ ++#ifdef CONFIG_RCU_TORTURE_TEST ++extern int sysctl_sched_rt_runtime; ++#endif ++ ++/* ++ * Asymmetric CPU capacity bits, used by topology.c. MuQSS does not do ++ * capacity-aware placement, but the domain build code still maintains the ++ * list. ++ */ ++struct asym_cap_data { ++ struct list_head link; ++ struct rcu_head rcu; ++ unsigned long capacity; ++ unsigned long cpus[]; ++}; ++ ++extern struct list_head asym_cap_list; ++ ++#define cpu_capacity_span(asym_data) to_cpumask((asym_data)->cpus) ++ ++/* ++ * Stubs for mainline scheduler machinery MuQSS does not implement. These are ++ * referenced by the support files retained in build_muqss.c. ++ */ ++ ++/* No deadline server: topology.c attaches none. See rq->fair_server. */ ++static inline void __dl_server_attach_root(void *dl_se, struct rq *rq) ++{ ++} ++ ++/* sched_ext is mutually exclusive with MuQSS; schedutil still asks. */ ++static inline u32 scx_cpuperf_target(s32 cpu) ++{ ++ return 0; ++} ++ ++static inline bool scx_switched_all(void) ++{ ++ return false; ++} ++ ++/* No nohz idle balancing: MuQSS balances implicitly at task selection. */ ++static inline void nohz_run_idle_balance(int cpu) ++{ ++} ++ ++/* No utilisation clamping, and no CFS utilisation to boost or clamp. */ ++static inline bool uclamp_rq_is_capped(struct rq *rq) ++{ ++ return false; ++} ++ ++static inline unsigned long cpu_util_cfs_boost(int cpu) ++{ ++ return 0; ++} ++ ++#ifdef CONFIG_IRQ_TIME_ACCOUNTING ++DECLARE_STATIC_KEY_FALSE(sched_clock_irqtime); ++ ++static inline int irqtime_enabled(void) ++{ ++ return static_branch_likely(&sched_clock_irqtime); ++} ++#else ++static inline int irqtime_enabled(void) ++{ ++ return 0; ++} ++#endif ++ ++extern unsigned long sugov_effective_cpu_perf(int cpu, unsigned long actual, ++ unsigned long min, ++ unsigned long max); ++ ++extern int try_to_wake_up(struct task_struct *p, unsigned int state, ++ int wake_flags); ++ ++asmlinkage void schedule_user(void); ++extern void resched_cpu(int cpu); ++extern bool available_idle_cpu(int cpu); ++extern void do_set_cpus_allowed(struct task_struct *p, ++ struct affinity_context *ctx); ++extern void __do_set_cpus_allowed(struct task_struct *p, ++ struct affinity_context *ctx); ++ ++#ifdef CONFIG_PREEMPT_DYNAMIC ++extern int sched_dynamic_mode(const char *str); ++extern void sched_dynamic_update(int mode); ++#endif ++ ++#ifdef CONFIG_CGROUP_SCHED ++extern struct task_group *sched_create_group(struct task_group *parent); ++extern void sched_online_group(struct task_group *tg, ++ struct task_group *parent); ++extern void sched_destroy_group(struct task_group *tg); ++extern void sched_release_group(struct task_group *tg); ++#endif ++ ++#endif /* MUQSS_SCHED_H */ +diff --git a/kernel/sched/build_muqss.c b/kernel/sched/build_muqss.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/build_muqss.c +@@ -0,0 +1,131 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * The scheduler support files MuQSS retains from mainline, built in a single ++ * compilation unit for the same build-efficiency reasons as build_policy.c ++ * and build_utility.c. ++ * ++ * MuQSS replaces core.c, fair.c, rt.c, deadline.c, syscalls.c and stop_task.c ++ * outright, so neither of the mainline aggregates can be used as-is. The ++ * files gathered here are the subset of build_utility.c and build_policy.c ++ * that survives, and they are #included rather than built individually ++ * because several of them (clock.c, completion.c, psi.c) carry no includes of ++ * their own and only compile as part of an aggregate. ++ * ++ * Deliberately absent, and why: ++ * ++ * fair.c rt.c deadline.c ext.c stop_task.c policy replaced by MuQSS ++ * cpudeadline.c cpupri.c pelt.c supporting data for those classes ++ * syscalls.c MuQSS implements the syscalls ++ * core_sched.c SCHED_CORE gated off ++ * debug.c CFS/EEVDF runqueue introspection ++ * loadavg.c MuQSS keeps its own load average ++ * autogroup.c cpuacct.c gated off in Kconfig.MuQSS ++ */ ++ ++/* Headers: */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "sched.h" ++#include "stats.h" ++ ++/* psi.c needs wq_worker_last_func(); mainline gets this via sched.h. */ ++#include "../workqueue_internal.h" ++ ++/* Source code modules: */ ++ ++#include "clock.c" ++ ++#ifdef CONFIG_CPU_FREQ ++# include "cpufreq.c" ++#endif ++ ++#ifdef CONFIG_CPU_FREQ_GOV_SCHEDUTIL ++# include "cpufreq_schedutil.c" ++#endif ++ ++#ifdef CONFIG_SCHEDSTATS ++# include "stats.c" ++#endif ++ ++#include "completion.c" ++#include "swait.c" ++#include "wait_bit.c" ++#include "wait.c" ++ ++#include "cputime.c" ++#include "idle.c" ++ ++#ifdef CONFIG_SMP ++# include "topology.c" ++#endif ++ ++#ifdef CONFIG_PSI ++# include "psi.c" ++#endif ++ ++#ifdef CONFIG_MEMBARRIER ++# include "membarrier.c" ++#endif ++ ++#ifdef CONFIG_CPU_ISOLATION ++# include "isolation.c" ++#endif ++ ++#include "skip_list.c" +diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c +--- a/kernel/sched/cputime.c ++++ b/kernel/sched/cputime.c +@@ -305,7 +305,7 @@ static inline u64 account_other_time(u64 max) + #ifdef CONFIG_64BIT + static inline u64 read_sum_exec_runtime(struct task_struct *t) + { +- return t->se.sum_exec_runtime; ++ return tsk_seruntime(t); + } + #else /* !CONFIG_64BIT: */ + static u64 read_sum_exec_runtime(struct task_struct *t) +@@ -315,7 +315,7 @@ static u64 read_sum_exec_runtime(struct task_struct *t) + struct rq *rq; + + rq = task_rq_lock(t, &rf); +- ns = t->se.sum_exec_runtime; ++ ns = tsk_seruntime(t); + task_rq_unlock(rq, t, &rf); + + return ns; +@@ -689,6 +689,19 @@ void account_process_tick(struct task_struct *p, int user_tick) + { + u64 cputime, steal; + ++ /* ++ * MuQSS accounts this tick itself, from sched_tick() -> ++ * update_cpu_clock_tick(), which update_process_times() calls a few ++ * lines after calling us. Its accounting is in real nanoseconds rather ++ * than whole ticks, and it is the same p->utime/p->stime and the same ++ * kcpustat[] fields we would be adding to here, so running both counts ++ * every busy tick twice. That is invisible in per-task figures, since ++ * cputime_adjust() rescales utime/stime to sched_time, but it doubles ++ * the busy buckets of /proc/stat. Leave it all to MuQSS. ++ */ ++ if (IS_ENABLED(CONFIG_SCHED_MUQSS)) ++ return; ++ + if (vtime_accounting_enabled_this_cpu()) + return; + +@@ -811,7 +824,7 @@ void cputime_adjust(struct task_cputime *curr, struct prev_cputime *prev, + void task_cputime_adjusted(struct task_struct *p, u64 *ut, u64 *st) + { + struct task_cputime cputime = { +- .sum_exec_runtime = p->se.sum_exec_runtime, ++ .sum_exec_runtime = tsk_seruntime(p), + }; + + if (task_cputime(p, &cputime.utime, &cputime.stime)) +diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c +--- a/kernel/sched/idle.c ++++ b/kernel/sched/idle.c +@@ -454,6 +454,7 @@ void cpu_startup_entry(enum cpuhp_state state) + do_idle(); + } + ++#ifndef CONFIG_SCHED_MUQSS + /* + * idle-task scheduling class. + */ +@@ -591,3 +592,4 @@ DEFINE_SCHED_CLASS(idle) = { + .switching_to = switching_to_idle, + .update_curr = update_curr_idle, + }; ++#endif /* CONFIG_SCHED_MUQSS */ +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -5,6 +5,10 @@ + #ifndef _KERNEL_SCHED_SCHED_H + #define _KERNEL_SCHED_SCHED_H + ++#ifdef CONFIG_SCHED_MUQSS ++#include "MuQSS.h" ++#else /* CONFIG_SCHED_MUQSS */ ++ + #include + #include + #include +@@ -4216,4 +4220,6 @@ DEFINE_CLASS_IS_UNCONDITIONAL(sched_change) + + #include "ext/ext.h" + ++#endif /* CONFIG_SCHED_MUQSS */ ++ + #endif /* _KERNEL_SCHED_SCHED_H */ +diff --git a/kernel/sched/skip_list.c b/kernel/sched/skip_list.c +new file mode 100644 +--- /dev/null ++++ b/kernel/sched/skip_list.c +@@ -0,0 +1,187 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ Copyright (C) 2011,2016 Con Kolivas. ++ ++ Code based on example originally by William Pugh. ++ ++Skip Lists are a probabilistic alternative to balanced trees, as ++described in the June 1990 issue of CACM and were invented by ++William Pugh in 1987. ++ ++A couple of comments about this implementation: ++The routine randomLevel has been hard-coded to generate random ++levels using p=0.25. It can be easily changed. ++ ++The insertion routine has been implemented so as to use the ++dirty hack described in the CACM paper: if a random level is ++generated that is more than the current maximum level, the ++current maximum level plus one is used instead. ++ ++Levels start at zero and go up to MaxLevel (which is equal to ++MaxNumberOfLevels-1). ++ ++The routines defined in this file are: ++ ++init: defines slnode ++ ++new_skiplist: returns a new, empty list ++ ++randomLevel: Returns a random level based on a u64 random seed passed to it. ++In MuQSS, the "niffy" time is used for this purpose. ++ ++insert(l, node, key): inserts node into l ordered by key. This operation ++occurs in O(log n) time. ++ ++delnode(slnode, l, node): deletes the node from l. This operation occurs ++in O(k) time where k is the number of levels of the node in question ++(max 4). The original delete function occurred in O(log n) time and ++involved a search. ++ ++MuQSS Notes: In this implementation of skiplists, there are bidirectional ++next/prev pointers. The node is embedded in task_struct; the task is ++recovered with container_of(). The key here is chosen by the scheduler ++so as to sort tasks according to the priority list requirements. The ++scheduler lookup occurs in O(1) time because it is always the first ++item in the level 0 linked list. Since the task struct embeds the node, ++it can also remove it much faster than the original implementation with ++the aid of prev<->next pointer manipulation and no searching. ++ ++*/ ++ ++#include ++#include ++#include ++#include ++ ++static struct kmem_cache *skiplist_cache __read_mostly; ++ ++#define MaxNumberOfLevels SKIPLIST_MAXLEVEL ++#define MaxLevel (MaxNumberOfLevels - 1) ++ ++void skiplist_init(skiplist_node *slnode) ++{ ++ int i; ++ ++ slnode->key = 0xFFFFFFFFFFFFFFFF; ++ for (i = 0; i < MaxNumberOfLevels; i++) ++ slnode->next[i] = slnode->prev[i] = slnode; ++} ++ ++void __init skiplist_cache_init(void) ++{ ++ skiplist_cache = kmem_cache_create("skiplist", sizeof(skiplist), ++ 0, SLAB_HWCACHE_ALIGN, NULL); ++ BUG_ON(!skiplist_cache); ++} ++ ++skiplist *new_skiplist(skiplist_node *slnode) ++{ ++ skiplist *l = kmem_cache_zalloc(skiplist_cache, GFP_ATOMIC); ++ ++ BUG_ON(!l); ++ l->header = slnode; ++ l->best_key = ~0ULL; ++ return l; ++} ++ ++void skiplist_free(skiplist *l) ++{ ++ kmem_cache_free(skiplist_cache, l); ++} ++ ++void free_skiplist(skiplist *l) ++{ ++ skiplist_node *p, *q; ++ ++ p = l->header; ++ do { ++ q = p->next[0]; ++ p->next[0]->prev[0] = q->prev[0]; ++ skiplist_node_init(p); ++ p = q; ++ } while (p != l->header); ++ skiplist_free(l); ++} ++ ++void skiplist_node_init(skiplist_node *node) ++{ ++ memset(node, 0, sizeof(skiplist_node)); ++} ++ ++static inline unsigned int randomLevel(const long unsigned int randseed) ++{ ++ /* ++ * Two bits per extra level (p = 0.25). Search 2*MaxLevel+1 bits ++ * so the result stays in 0..MaxLevel; this matches the old ++ * 8-slot arrays whose random height never exceeded 3. ++ */ ++ return find_first_bit(&randseed, MaxLevel * 2 + 1) / 2; ++} ++ ++void skiplist_insert(skiplist *l, skiplist_node *node, keyType key, unsigned int randseed) ++{ ++ skiplist_node *update[MaxNumberOfLevels]; ++ skiplist_node *p, *q; ++ int k = l->level; ++ ++ p = l->header; ++ do { ++ while (q = p->next[k], q->key <= key) ++ p = q; ++ update[k] = p; ++ } while (--k >= 0); ++ ++ WRITE_ONCE(l->entries, l->entries + 1); ++ k = randomLevel(randseed); ++ if (k > l->level) { ++ k = ++l->level; ++ update[k] = l->header; ++ } ++ ++ node->key = key; ++ do { ++ p = update[k]; ++ node->next[k] = p->next[k]; ++ p->next[k] = node; ++ node->prev[k] = p; ++ node->next[k]->prev[k] = node; ++ } while (--k >= 0); ++} ++ ++/* ++ * Unlink @node from @l. Returns true if it was linked and has been removed, ++ * false if it was not on any list and nothing was changed - the caller's own ++ * accounting has to be skipped along with the removal in that case. ++ */ ++bool skiplist_delete(skiplist *l, skiplist_node *node) ++{ ++ int k, m; ++ ++ /* ++ * Insert fills levels contiguously from 0 up and removal zeroes the ++ * whole node, so a NULL next[] marks the top of this node and stands ++ * in for the level count the node used to carry. ++ */ ++ for (k = 0; k < MaxNumberOfLevels && node->next[k]; k++) { ++ node->prev[k]->next[k] = node->next[k]; ++ node->next[k]->prev[k] = node->prev[k]; ++ } ++ m = k - 1; ++ /* ++ * A node that was never queued has no levels linked at all. Leave ++ * before decrementing entries, which would otherwise go negative and ++ * make the emptiness tests in earliest_deadline_task() pass on an ++ * empty list, handing the caller the header as if it were a task. ++ */ ++ if (WARN_ON_ONCE(m < 0)) ++ return false; ++ skiplist_node_init(node); ++ if (m == l->level) { ++ while (l->header->next[m] == l->header && l->header->prev[m] == l->header && m > 0) ++ m--; ++ l->level = m; ++ } ++ WRITE_ONCE(l->entries, l->entries - 1); ++ ++ return true; ++} +diff --git a/kernel/sched/stats.h b/kernel/sched/stats.h +--- a/kernel/sched/stats.h ++++ b/kernel/sched/stats.h +@@ -89,6 +89,8 @@ static inline void rq_sched_info_depart (struct rq *rq, unsigned long long delt + + #endif /* CONFIG_SCHEDSTATS */ + ++#ifndef CONFIG_SCHED_MUQSS ++/* Only reachable from fair.c, debug.c and core.c, none of which MuQSS builds. */ + static inline struct sched_statistics * + __schedstats_from_se(struct sched_entity *se) + { +@@ -98,6 +100,7 @@ __schedstats_from_se(struct sched_entity *se) + #endif + return &task_of(se)->stats; + } ++#endif /* CONFIG_SCHED_MUQSS */ + + #ifdef CONFIG_PSI + void psi_task_change(struct task_struct *task, int clear, int set); +@@ -135,6 +138,23 @@ static inline void psi_enqueue(struct task_struct *p, int flags) + if (task_on_cpu(task_rq(p), p)) + return; + ++#ifdef CONFIG_SCHED_MUQSS ++ /* ++ * MuQSS has no delayed-dequeue (no task_struct::se), so a migration ++ * always moves a runnable task and a non-migration is a wakeup. ++ */ ++ if (flags & ENQUEUE_MIGRATED) { ++ set = TSK_RUNNING; ++ if (p->in_memstall) ++ set |= TSK_MEMSTALL | TSK_MEMSTALL_RUNNING; ++ } else { ++ if (p->in_iowait) ++ clear |= TSK_IOWAIT; ++ set = TSK_RUNNING; ++ if (p->in_memstall) ++ set |= TSK_MEMSTALL_RUNNING; ++ } ++#else + if (p->se.sched_delayed) { + /* CPU migration of "sleeping" task */ + WARN_ON_ONCE(!(flags & ENQUEUE_MIGRATED)); +@@ -155,6 +175,7 @@ static inline void psi_enqueue(struct task_struct *p, int flags) + if (p->in_memstall) + set |= TSK_MEMSTALL_RUNNING; + } ++#endif /* CONFIG_SCHED_MUQSS */ + + psi_task_change(p, clear, set); + } +diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig +--- a/kernel/time/Kconfig ++++ b/kernel/time/Kconfig +@@ -103,18 +103,36 @@ config NO_HZ_COMMON + bool + select TICK_ONESHOT + ++# ++# Silent symbols keep CONFIG_NO_HZ_* / CONFIG_HZ_PERIODIC available to the ++# rest of the tree. The promptable *_NODEF names are what olddefconfig can ++# stick on; renaming every choice member (including idle) means a saved ++# "# CONFIG_NO_HZ_IDLE is not set" cannot pin the choice onto full dynticks. ++# ++config HZ_PERIODIC ++ bool ++ ++config NO_HZ_IDLE ++ bool ++ ++config NO_HZ_FULL ++ bool ++ + choice + prompt "Timer tick handling" +- default NO_HZ_IDLE if NO_HZ ++ default NO_HZ_IDLE_NODEF if NO_HZ ++ default NO_HZ_IDLE_NODEF + +-config HZ_PERIODIC ++config HZ_PERIODIC_NODEF + bool "Periodic timer ticks (constant rate, no dynticks)" ++ select HZ_PERIODIC + help + This option keeps the tick running periodically at a constant + rate, even when the CPU doesn't need it. + +-config NO_HZ_IDLE ++config NO_HZ_IDLE_NODEF + bool "Idle dynticks system (tickless idle)" ++ select NO_HZ_IDLE + select NO_HZ_COMMON + help + This option enables a tickless idle system: timer interrupts +@@ -123,8 +141,9 @@ config NO_HZ_IDLE + + Most of the time you want to say Y here. + +-config NO_HZ_FULL ++config NO_HZ_FULL_NODEF + bool "Full dynticks system (tickless)" ++ select NO_HZ_FULL + # NO_HZ_COMMON dependency + # We need at least one periodic CPU for timekeeping + depends on SMP +@@ -150,6 +169,8 @@ config NO_HZ_FULL + By default, without passing the nohz_full parameter, this behaves just + like NO_HZ_IDLE. + ++ Not recommended for desktops, laptops, or mobile devices. ++ + If you're a distro say Y. + + endchoice +diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c +--- a/kernel/time/clockevents.c ++++ b/kernel/time/clockevents.c +@@ -194,8 +194,8 @@ int clockevents_tick_resume(struct clock_event_device *dev) + + #ifdef CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST + +-/* Limit min_delta to a jiffy */ +-#define MIN_DELTA_LIMIT (NSEC_PER_SEC / HZ) ++/* Limit min_delta to the configurable hrtimer granularity */ ++#define MIN_DELTA_LIMIT (hrtimer_granularity_us * NSEC_PER_USEC) + + /** + * clockevents_increase_min_delta - raise minimum delta of a clock event device +diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -223,7 +223,7 @@ static void task_sample_cputime(struct task_struct *p, u64 *samples) + u64 stime, utime; + + task_cputime(p, &utime, &stime); +- store_samples(samples, stime, utime, p->se.sum_exec_runtime); ++ store_samples(samples, stime, utime, tsk_seruntime(p)); + } + + static void proc_sample_cputime_atomic(struct task_cputime_atomic *at, +@@ -905,10 +905,12 @@ static void collect_posix_cputimers(struct posix_cputimers *pct, u64 *samples, + + static inline void check_dl_overrun(struct task_struct *tsk) + { ++#ifndef CONFIG_SCHED_MUQSS + if (tsk->dl.dl_overrun) { + tsk->dl.dl_overrun = 0; + send_signal_locked(SIGXCPU, SEND_SIG_PRIV, tsk, PIDTYPE_TGID); + } ++#endif + } + + static bool check_rlimit(u64 time, u64 limit, int signo, bool rt, bool hard) +@@ -952,7 +954,7 @@ static void check_thread_timers(struct task_struct *tsk, + soft = task_rlimit(tsk, RLIMIT_RTTIME); + if (soft != RLIM_INFINITY) { + /* Task RT timeout is accounted in jiffies. RTTIME is usec */ +- unsigned long rttime = tsk->rt.timeout * (USEC_PER_SEC / HZ); ++ unsigned long rttime = tsk_rttimeout(tsk) * (USEC_PER_SEC / HZ); + unsigned long hard = task_rlimit_max(tsk, RLIMIT_RTTIME); + + /* At the hard limit, send SIGKILL. No further action. */ +@@ -1183,7 +1185,7 @@ static inline bool fastpath_timer_check(struct task_struct *tsk) + return true; + } + +- if (dl_task(tsk) && tsk->dl.dl_overrun) ++ if (dl_task(tsk) && tsk_dl_overrun(tsk)) + return true; + + return false; +diff --git a/kernel/time/sleep_timeout.c b/kernel/time/sleep_timeout.c +--- a/kernel/time/sleep_timeout.c ++++ b/kernel/time/sleep_timeout.c +@@ -4,13 +4,57 @@ + */ + + #include ++#include + #include + #include + #include + #include ++#include + + #include "tick-internal.h" + ++/* ++ * Granularity, in microseconds, below which a high resolution timeout is not ++ * worth arming. Also caps how far clockevents_increase_min_delta() is allowed ++ * to raise a clock event device's minimum delta. ++ */ ++int __read_mostly hrtimer_granularity_us = 100; ++ ++/* The timeout, in microseconds, used by schedule_min_hrtimeout(). */ ++int __read_mostly hrtimeout_min_us = 500; ++ ++#ifdef CONFIG_SYSCTL ++static const int hrtimeout_us_max = 10000; ++ ++static const struct ctl_table hrtimeout_sysctl[] = { ++ { ++ .procname = "hrtimer_granularity_us", ++ .data = &hrtimer_granularity_us, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ONE, ++ .extra2 = (void *)&hrtimeout_us_max, ++ }, ++ { ++ .procname = "hrtimeout_min_us", ++ .data = &hrtimeout_min_us, ++ .maxlen = sizeof(int), ++ .mode = 0644, ++ .proc_handler = proc_dointvec_minmax, ++ .extra1 = SYSCTL_ONE, ++ .extra2 = (void *)&hrtimeout_us_max, ++ }, ++}; ++ ++static int __init hrtimeout_sysctl_init(void) ++{ ++ register_sysctl("kernel", hrtimeout_sysctl); ++ return 0; ++} ++device_initcall(hrtimeout_sysctl_init); ++#endif /* CONFIG_SYSCTL */ ++ + /* + * Since schedule_timeout()'s timer is defined on the stack, it must store + * the target task on the stack as well. +@@ -279,10 +323,164 @@ int __sched schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode) + } + EXPORT_SYMBOL_GPL(schedule_hrtimeout); + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* ++ * As per schedule_hrtimeout but takes a millisecond value and returns how ++ * many milliseconds are left. ++ */ ++long __sched schedule_msec_hrtimeout(long timeout) ++{ ++ struct hrtimer_sleeper t; ++ int delta, jiffs; ++ ktime_t expires; ++ ++ if (!timeout) { ++ __set_current_state(TASK_RUNNING); ++ return 0; ++ } ++ ++ jiffs = msecs_to_jiffies(timeout); ++ /* ++ * If regular timer resolution is adequate or hrtimer resolution is not ++ * (yet) better than Hz, as would occur during startup, use regular ++ * timers. Freezing gets the same treatment as some drivers still do not ++ * correctly use freezable timeouts. ++ */ ++ if (jiffs > 4 || hrtimer_resolution >= NSEC_PER_SEC / HZ || pm_freezing) ++ return schedule_timeout(jiffs); ++ ++ /* The jiffs > 4 test above bounds timeout well under one second. */ ++ delta = (timeout % 1000) * NSEC_PER_MSEC; ++ expires = ktime_set(0, delta); ++ ++ hrtimer_setup_sleeper_on_stack(&t, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ /* ++ * No slack is given here. The whole point of taking the high resolution ++ * path is that the timeout is too short for the tick to resolve, and a ++ * slack of delta would push the expiry out to twice the request. ++ */ ++ hrtimer_set_expires(&t.timer, expires); ++ ++ hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_REL); ++ ++ if (likely(t.task)) ++ schedule(); ++ ++ hrtimer_cancel(&t.timer); ++ destroy_hrtimer_on_stack(&t.timer); ++ ++ __set_current_state(TASK_RUNNING); ++ ++ expires = hrtimer_expires_remaining(&t.timer); ++ timeout = ktime_to_ms(expires); ++ return timeout < 0 ? 0 : timeout; ++} ++ ++EXPORT_SYMBOL(schedule_msec_hrtimeout); ++ ++/* ++ * As per schedule_msec_hrtimeout but takes a microsecond value and returns ++ * how many microseconds are left. ++ */ ++static long __sched schedule_usec_hrtimeout(long timeout) ++{ ++ struct hrtimer_sleeper t; ++ ktime_t expires; ++ int delta; ++ ++ if (!timeout) { ++ __set_current_state(TASK_RUNNING); ++ return 0; ++ } ++ ++ if (hrtimer_resolution >= NSEC_PER_SEC / HZ) ++ return schedule_timeout(usecs_to_jiffies(timeout)); ++ ++ if (timeout < hrtimer_granularity_us) ++ timeout = hrtimer_granularity_us; ++ delta = (timeout % USEC_PER_SEC) * NSEC_PER_USEC; ++ expires = ktime_set(0, delta); ++ ++ hrtimer_setup_sleeper_on_stack(&t, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ /* No slack is given here, as per schedule_msec_hrtimeout(). */ ++ hrtimer_set_expires(&t.timer, expires); ++ ++ hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_REL); ++ ++ if (likely(t.task)) ++ schedule(); ++ ++ hrtimer_cancel(&t.timer); ++ destroy_hrtimer_on_stack(&t.timer); ++ ++ __set_current_state(TASK_RUNNING); ++ ++ expires = hrtimer_expires_remaining(&t.timer); ++ timeout = ktime_to_us(expires); ++ return timeout < 0 ? 0 : timeout; ++} ++ ++long __sched schedule_min_hrtimeout(void) ++{ ++ return usecs_to_jiffies(schedule_usec_hrtimeout(hrtimeout_min_us)); ++} ++ ++EXPORT_SYMBOL(schedule_min_hrtimeout); ++ ++long __sched schedule_msec_hrtimeout_interruptible(long timeout) ++{ ++ __set_current_state(TASK_INTERRUPTIBLE); ++ return schedule_msec_hrtimeout(timeout); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_interruptible); ++ ++long __sched schedule_msec_hrtimeout_uninterruptible(long timeout) ++{ ++ __set_current_state(TASK_UNINTERRUPTIBLE); ++ return schedule_msec_hrtimeout(timeout); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_uninterruptible); ++ ++#else /* !CONFIG_HIGH_RES_TIMERS */ ++ ++/* ++ * Without high resolution timers there is nothing better than the regular ++ * jiffy based timeouts to fall back on. ++ */ ++long __sched schedule_msec_hrtimeout(long timeout) ++{ ++ return schedule_timeout(msecs_to_jiffies(timeout)); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout); ++ ++long __sched schedule_min_hrtimeout(void) ++{ ++ return schedule_timeout(1); ++} ++EXPORT_SYMBOL(schedule_min_hrtimeout); ++ ++long __sched schedule_msec_hrtimeout_interruptible(long timeout) ++{ ++ return schedule_timeout_interruptible(msecs_to_jiffies(timeout)); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_interruptible); ++ ++long __sched schedule_msec_hrtimeout_uninterruptible(long timeout) ++{ ++ return schedule_timeout_uninterruptible(msecs_to_jiffies(timeout)); ++} ++EXPORT_SYMBOL(schedule_msec_hrtimeout_uninterruptible); ++ ++#endif /* CONFIG_HIGH_RES_TIMERS */ ++ + /** + * msleep - sleep safely even with waitqueue interruptions + * @msecs: Requested sleep duration in milliseconds + * ++ * Sleeps shorter than five ticks are handed to a high resolution timer instead, ++ * where that has better resolution than the tick, so the slack described below ++ * only applies to the jiffy based path. ++ * + * msleep() uses jiffy based timeouts for the sleep duration. Because of the + * design of the timer wheel, the maximum additional percentage delay (slack) is + * 12.5%. This is only valid for timers which will end up in level 1 or a higher +@@ -312,7 +510,19 @@ EXPORT_SYMBOL_GPL(schedule_hrtimeout); + */ + void msleep(unsigned int msecs) + { +- unsigned long timeout = msecs_to_jiffies(msecs); ++ int jiffs = msecs_to_jiffies(msecs); ++ unsigned long timeout; ++ ++ /* ++ * Use high resolution timers where the resolution of tick based ++ * timers is inadequate. ++ */ ++ if (jiffs < 5 && hrtimer_resolution < NSEC_PER_SEC / HZ && !pm_freezing) { ++ while (msecs) ++ msecs = schedule_msec_hrtimeout_uninterruptible(msecs); ++ return; ++ } ++ timeout = jiffs; + + while (timeout) + timeout = schedule_timeout_uninterruptible(timeout); +@@ -333,7 +543,15 @@ EXPORT_SYMBOL(msleep); + */ + unsigned long msleep_interruptible(unsigned int msecs) + { +- unsigned long timeout = msecs_to_jiffies(msecs); ++ int jiffs = msecs_to_jiffies(msecs); ++ unsigned long timeout; ++ ++ if (jiffs < 5 && hrtimer_resolution < NSEC_PER_SEC / HZ && !pm_freezing) { ++ while (msecs && !signal_pending(current)) ++ msecs = schedule_msec_hrtimeout_interruptible(msecs); ++ return msecs; ++ } ++ timeout = jiffs; + + while (timeout && !signal_pending(current)) + timeout = schedule_timeout_interruptible(timeout); +diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h +--- a/kernel/time/tick-internal.h ++++ b/kernel/time/tick-internal.h +@@ -13,6 +13,9 @@ struct timer_events { + u64 global; + }; + ++/* Defined in sleep_timeout.c, tunable via sysctl */ ++extern int hrtimer_granularity_us; ++ + #ifdef CONFIG_GENERIC_CLOCKEVENTS + + # define TICK_DO_TIMER_NONE -1 +diff --git a/kernel/trace/rv/monitors/deadline/Kconfig b/kernel/trace/rv/monitors/deadline/Kconfig +--- a/kernel/trace/rv/monitors/deadline/Kconfig ++++ b/kernel/trace/rv/monitors/deadline/Kconfig +@@ -1,5 +1,5 @@ + config RV_MON_DEADLINE +- depends on RV ++ depends on RV && !SCHED_MUQSS + bool "deadline monitor" + help + Collection of monitors to check the deadline scheduler and server +diff --git a/kernel/workqueue.c b/kernel/workqueue.c +--- a/kernel/workqueue.c ++++ b/kernel/workqueue.c +@@ -56,6 +56,7 @@ + #include + #include + #include ++#include + + #include "workqueue_internal.h" + +@@ -1438,7 +1439,7 @@ void wq_worker_running(struct task_struct *task) + * CPU intensive auto-detection cares about how long a work item hogged + * CPU without sleeping. Reset the starting timestamp on wakeup. + */ +- worker->current_at = worker->task->se.sum_exec_runtime; ++ worker->current_at = tsk_seruntime(worker->task); + + WRITE_ONCE(worker->sleeping, 0); + } +@@ -1523,7 +1524,7 @@ void wq_worker_tick(struct task_struct *task) + * We probably want to make this prettier in the future. + */ + if ((worker->flags & WORKER_NOT_RUNNING) || READ_ONCE(worker->sleeping) || +- worker->task->se.sum_exec_runtime - worker->current_at < ++ tsk_seruntime(worker->task) - worker->current_at < + wq_cpu_intensive_thresh_us * NSEC_PER_USEC) + return; + +@@ -2449,6 +2450,7 @@ bool queue_work_on(int cpu, struct workqueue_struct *wq, + + if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) && + !clear_pending_if_disabled(work)) { ++ muqss_work_set_owner(work); + __queue_work(cpu, wq, work); + ret = true; + } +@@ -2530,6 +2532,7 @@ bool queue_work_node(int node, struct workqueue_struct *wq, + !clear_pending_if_disabled(work)) { + int cpu = select_numa_node_cpu(node); + ++ muqss_work_set_owner(work); + __queue_work(cpu, wq, work); + ret = true; + } +@@ -2617,6 +2620,7 @@ bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, + + if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) && + !clear_pending_if_disabled(work)) { ++ muqss_work_set_owner(work); + __queue_delayed_work(cpu, wq, dwork, delay); + ret = true; + } +@@ -2652,8 +2656,10 @@ bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq, + + ret = work_grab_pending(&dwork->work, WORK_CANCEL_DELAYED, &irq_flags); + +- if (!clear_pending_if_disabled(&dwork->work)) ++ if (!clear_pending_if_disabled(&dwork->work)) { ++ muqss_work_set_owner(&dwork->work); + __queue_delayed_work(cpu, wq, dwork, delay); ++ } + + local_irq_restore(irq_flags); + return ret; +@@ -2690,6 +2696,7 @@ bool queue_rcu_work(struct workqueue_struct *wq, struct rcu_work *rwork) + */ + if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) && + !WARN_ON_ONCE(clear_pending_if_disabled(work))) { ++ muqss_work_set_owner(work); + rwork->wq = wq; + call_rcu_hurry(&rwork->rcu, rcu_work_rcufn); + return true; +@@ -3223,7 +3230,9 @@ __acquires(&pool->lock) + { + struct pool_workqueue *pwq = get_work_pwq(work); + struct worker_pool *pool = worker->pool; ++ struct muqss_kern_window kern_window; + unsigned long work_data; ++ unsigned int muqss_owner; + int lockdep_start_depth, rcu_start_depth; + bool bh_draining = pool->flags & POOL_BH_DRAINING; + #ifdef CONFIG_LOCKDEP +@@ -3249,11 +3258,23 @@ __acquires(&pool->lock) + worker->current_func = work->func; + worker->current_pwq = pwq; + if (worker->task) +- worker->current_at = worker->task->se.sum_exec_runtime; ++ worker->current_at = tsk_seruntime(worker->task); + worker->current_start = jiffies; + work_data = *work_data_bits(work); + worker->current_color = get_work_color(work_data); + ++ /* ++ * Read while PENDING still holds this work item for us. Once it is ++ * cleared below another CPU may requeue the same item and restamp it ++ * with a different owner, and the one we are about to run belongs to ++ * whoever queued it, not to them. ++ * ++ * A BH pool runs work in softirq context on top of whichever task was ++ * interrupted, so worker->task is NULL and there is no worker runtime ++ * to measure. Charge nobody there. ++ */ ++ muqss_owner = worker->task ? muqss_work_owner(work) : 0; ++ + /* + * Record wq name for cmdline and debug reporting, may get + * overridden through set_worker_desc(). +@@ -3318,6 +3339,13 @@ __acquires(&pool->lock) + * workqueues), so hiding them isn't a problem. + */ + lockdep_invariant_state(true); ++ /* ++ * Everything from here to muqss_kerntime_end() is done on the queueing ++ * task's account: the CPU time it costs this worker is charged back to ++ * it, and so is any I/O the work item submits or any page it dirties, ++ * which would otherwise be lost to a kernel thread. ++ */ ++ muqss_kerntime_begin(&kern_window, muqss_owner); + trace_workqueue_execute_start(work); + worker->current_func(work); + /* +@@ -3325,6 +3353,7 @@ __acquires(&pool->lock) + * point will only record its address. + */ + trace_workqueue_execute_end(work, worker->current_func); ++ muqss_kerntime_end(&kern_window); + + lock_map_release(&lockdep_map); + if (!bh_draining) +diff --git a/mm/khugepaged.c b/mm/khugepaged.c +--- a/mm/khugepaged.c ++++ b/mm/khugepaged.c +@@ -969,7 +969,7 @@ static void khugepaged_alloc_sleep(void) + + add_wait_queue(&khugepaged_wait, &wait); + __set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE); +- schedule_timeout(msecs_to_jiffies(khugepaged_alloc_sleep_millisecs)); ++ schedule_msec_hrtimeout(khugepaged_alloc_sleep_millisecs); + remove_wait_queue(&khugepaged_wait, &wait); + } + +diff --git a/mm/page-writeback.c b/mm/page-writeback.c +--- a/mm/page-writeback.c ++++ b/mm/page-writeback.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -2636,6 +2637,12 @@ static void folio_account_dirtied(struct folio *folio, + current->nr_dirtied += nr; + __this_cpu_add(bdp_ratelimits, nr); + ++ /* ++ * Record who dirtied this so the writeback issued for it later ++ * by a flusher thread can still be charged to them. ++ */ ++ muqss_iotime_dirty_folio(folio); ++ + mem_cgroup_track_foreign_dirty(folio, wb); + } + } +diff --git a/mm/page_alloc.c b/mm/page_alloc.c +--- a/mm/page_alloc.c ++++ b/mm/page_alloc.c +@@ -1401,6 +1401,7 @@ static __always_inline bool __free_pages_prepare(struct page *page, + } + + page_cpupid_reset_last(page); ++ page_io_owner_reset(page); + page->flags.f &= ~PAGE_FLAGS_CHECK_AT_PREP; + page->private = 0; + reset_page_owner(page, order); +diff --git a/net/core/pktgen.c b/net/core/pktgen.c +--- a/net/core/pktgen.c ++++ b/net/core/pktgen.c +@@ -1992,7 +1992,7 @@ static ssize_t pktgen_thread_write(struct file *file, + mutex_lock(&pktgen_thread_lock); + t->control |= T_REMDEVALL; + mutex_unlock(&pktgen_thread_lock); +- schedule_timeout_interruptible(msecs_to_jiffies(125)); /* Propagate thread->control */ ++ schedule_msec_hrtimeout_interruptible(125); /* Propagate thread->control */ + ret = count; + sprintf(pg_result, "OK: rem_device_all"); + goto out; +@@ -2064,7 +2064,7 @@ static void pktgen_mark_device(const struct pktgen_net *pn, const char *ifname) + mutex_unlock(&pktgen_thread_lock); + pr_debug("%s: waiting for %s to disappear....\n", + __func__, ifname); +- schedule_timeout_interruptible(msecs_to_jiffies(msec_per_try)); ++ schedule_msec_hrtimeout_interruptible(msec_per_try); + mutex_lock(&pktgen_thread_lock); + + if (++i >= max_tries) { +@@ -3321,7 +3321,7 @@ static void pktgen_run_all_threads(struct pktgen_net *pn) + pktgen_handle_all_threads(pn, T_RUN); + + /* Propagate thread->control */ +- schedule_timeout_interruptible(msecs_to_jiffies(125)); ++ schedule_msec_hrtimeout_interruptible(125); + + pktgen_wait_all_threads_run(pn); + } +@@ -3333,7 +3333,7 @@ static void pktgen_reset_all_threads(struct pktgen_net *pn) + pktgen_handle_all_threads(pn, T_REMDEVALL); + + /* Propagate thread->control */ +- schedule_timeout_interruptible(msecs_to_jiffies(125)); ++ schedule_msec_hrtimeout_interruptible(125); + + pktgen_wait_all_threads_run(pn); + } +diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c +--- a/sound/hda/core/controller.c ++++ b/sound/hda/core/controller.c +@@ -340,7 +340,7 @@ static int snd_hdac_bus_get_response_rirb(struct hdac_bus *bus, + break; + #define LOOP_COUNT_MAX 3000 + if (!bus->polling_mode) { +- schedule_timeout(msecs_to_jiffies(2)); ++ schedule_msec_hrtimeout(2); + } else if (bus->needs_damn_long_delay || + loopcounter > LOOP_COUNT_MAX) { + if (loopcounter > LOOP_COUNT_MAX && !warned) { +diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c +--- a/sound/isa/msnd/msnd_pinnacle.c ++++ b/sound/isa/msnd/msnd_pinnacle.c +@@ -490,7 +490,7 @@ static int snd_msnd_calibrate_adc(struct snd_msnd *chip, u16 srate) + & ~0x0001, chip->SMA + SMA_wCurrHostStatusFlags); + if (snd_msnd_send_word(chip, 0, 0, HDEXAR_CAL_A_TO_D) == 0 && + snd_msnd_send_dsp_cmd_chk(chip, HDEX_AUX_REQ) == 0) { +- schedule_timeout_interruptible(msecs_to_jiffies(333)); ++ schedule_msec_hrtimeout_interruptible(333); + return 0; + } + dev_warn(chip->card->dev, LOGNAME ": ADC calibration failed\n"); +diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c +--- a/sound/pci/maestro3.c ++++ b/sound/pci/maestro3.c +@@ -1999,7 +1999,7 @@ static void snd_m3_ac97_reset(struct snd_m3 *chip) + outw(0, io + GPIO_DATA); + outw(dir | GPO_PRIMARY_AC97, io + GPIO_DIRECTION); + +- schedule_timeout_uninterruptible(msecs_to_jiffies(delay1)); ++ schedule_msec_hrtimeout_uninterruptible(delay1); + + outw(GPO_PRIMARY_AC97, io + GPIO_DATA); + udelay(5); +@@ -2007,7 +2007,7 @@ static void snd_m3_ac97_reset(struct snd_m3 *chip) + outw(IO_SRAM_ENABLE | SERIAL_AC_LINK_ENABLE, io + RING_BUS_CTRL_A); + outw(~0, io + GPIO_MASK); + +- schedule_timeout_uninterruptible(msecs_to_jiffies(delay2)); ++ schedule_msec_hrtimeout_uninterruptible(delay2); + + if (! snd_m3_try_read_vendor(chip)) + break; +@@ -2054,7 +2054,7 @@ static int snd_m3_mixer(struct snd_m3 *chip) + + /* seems ac97 PCM needs initialization.. hack hack.. */ + snd_ac97_write(chip->ac97, AC97_PCM, 0x8000 | (15 << 8) | 15); +- schedule_timeout_uninterruptible(msecs_to_jiffies(100)); ++ schedule_msec_hrtimeout_uninterruptible(100); + snd_ac97_write(chip->ac97, AC97_PCM, 0); + + #ifndef CONFIG_SND_MAESTRO3_INPUT +diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c +--- a/sound/pci/ymfpci/ymfpci_main.c ++++ b/sound/pci/ymfpci/ymfpci_main.c +@@ -742,7 +742,7 @@ static void snd_ymfpci_irq_wait(struct snd_ymfpci *chip) + init_waitqueue_entry(&wait, current); + add_wait_queue(&chip->interrupt_sleep, &wait); + atomic_inc(&chip->interrupt_sleep_count); +- schedule_timeout_uninterruptible(msecs_to_jiffies(50)); ++ schedule_msec_hrtimeout_uninterruptible(50); + remove_wait_queue(&chip->interrupt_sleep, &wait); + } + } +diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c +--- a/sound/soc/codecs/rt5631.c ++++ b/sound/soc/codecs/rt5631.c +@@ -417,7 +417,7 @@ static void onebit_depop_mute_stage(struct snd_soc_component *component, int ena + hp_zc = snd_soc_component_read(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { +- schedule_timeout_uninterruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_uninterruptible(10); + /* config one-bit depop parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x307f); + snd_soc_component_update_bits(component, RT5631_HP_OUT_VOL, +@@ -529,7 +529,7 @@ static void depop_seq_mute_stage(struct snd_soc_component *component, int enable + hp_zc = snd_soc_component_read(component, RT5631_INT_ST_IRQ_CTRL_2); + snd_soc_component_write(component, RT5631_INT_ST_IRQ_CTRL_2, hp_zc & 0xf7ff); + if (enable) { +- schedule_timeout_uninterruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_uninterruptible(10); + + /* config depop sequence parameter */ + rt5631_write_index(component, RT5631_SPK_INTL_CTRL, 0x302f); +@@ -1496,8 +1496,7 @@ static int rt5631_codec_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + "change PLL in master mode\n"); + snd_soc_component_write(component, RT5631_PLL_CTRL, + codec_master_pll_div[i].reg_val); +- schedule_timeout_uninterruptible( +- msecs_to_jiffies(20)); ++ schedule_msec_hrtimeout_uninterruptible(20); + snd_soc_component_update_bits(component, + RT5631_GLOBAL_CLK_CTRL, + RT5631_SYSCLK_SOUR_SEL_MASK | +@@ -1515,8 +1514,7 @@ static int rt5631_codec_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + "change PLL in slave mode\n"); + snd_soc_component_write(component, RT5631_PLL_CTRL, + codec_slave_pll_div[i].reg_val); +- schedule_timeout_uninterruptible( +- msecs_to_jiffies(20)); ++ schedule_msec_hrtimeout_uninterruptible(20); + snd_soc_component_update_bits(component, + RT5631_GLOBAL_CLK_CTRL, + RT5631_SYSCLK_SOUR_SEL_MASK | +diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c +--- a/sound/soc/codecs/wm8350.c ++++ b/sound/soc/codecs/wm8350.c +@@ -234,11 +234,9 @@ static void wm8350_pga_work(struct work_struct *work) + out2->ramp == WM8350_RAMP_UP) { + /* delay is longer over 0dB as increases are larger */ + if (i >= WM8350_OUTn_0dB) +- schedule_timeout_interruptible(msecs_to_jiffies +- (2)); ++ schedule_msec_hrtimeout_interruptible(2); + else +- schedule_timeout_interruptible(msecs_to_jiffies +- (1)); ++ schedule_msec_hrtimeout_interruptible(1); + } else + udelay(50); /* doesn't matter if we delay longer */ + } +@@ -1122,9 +1120,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + (platform->dis_out4 << 6)); + + /* wait for discharge */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform-> +- cap_discharge_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->cap_discharge_msecs); + + /* enable antipop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, +@@ -1138,9 +1134,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + WM8350_VBUFEN); + + /* wait for vmid */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform-> +- vmid_charge_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->vmid_charge_msecs); + + /* turn on vmid 300k */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & +@@ -1189,9 +1183,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* wait */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform-> +- vmid_discharge_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->vmid_discharge_msecs); + + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8) | +@@ -1207,8 +1199,7 @@ static int wm8350_set_bias_level(struct snd_soc_component *component, + pm1 | WM8350_OUTPUT_DRAIN_EN); + + /* wait */ +- schedule_timeout_interruptible(msecs_to_jiffies +- (platform->drain_msecs)); ++ schedule_msec_hrtimeout_interruptible(platform->drain_msecs); + + pm1 &= ~WM8350_BIASEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); +diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c +--- a/sound/soc/codecs/wm8900.c ++++ b/sound/soc/codecs/wm8900.c +@@ -1057,7 +1057,7 @@ static int wm8900_set_bias_level(struct snd_soc_component *component, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1); + + /* Allow capacitors to charge */ +- schedule_timeout_interruptible(msecs_to_jiffies(400)); ++ schedule_msec_hrtimeout_interruptible(400); + + /* Enable bias */ + snd_soc_component_write(component, WM8900_REG_POWER1, +@@ -1091,7 +1091,7 @@ static int wm8900_set_bias_level(struct snd_soc_component *component, + /* Discharge caps */ + snd_soc_component_write(component, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); +- schedule_timeout_interruptible(msecs_to_jiffies(500)); ++ schedule_msec_hrtimeout_interruptible(500); + + /* Remove clamp */ + snd_soc_component_write(component, WM8900_REG_HPCTL1, 0); +@@ -1105,7 +1105,7 @@ static int wm8900_set_bias_level(struct snd_soc_component *component, + /* Need to let things settle before stopping the clock + * to ensure that restart works, see "Stopping the + * master clock" in the datasheet. */ +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + snd_soc_component_write(component, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + break; +diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c +--- a/sound/soc/codecs/wm9713.c ++++ b/sound/soc/codecs/wm9713.c +@@ -199,7 +199,7 @@ static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w, + + /* Gracefully shut down the voice interface. */ + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0f00, 0x0200); +- schedule_timeout_interruptible(msecs_to_jiffies(1)); ++ schedule_msec_hrtimeout_interruptible(1); + snd_soc_component_update_bits(component, AC97_HANDSET_RATE, 0x0f00, 0x0f00); + snd_soc_component_update_bits(component, AC97_EXTENDED_MID, 0x1000, 0x1000); + +@@ -864,7 +864,7 @@ static int wm9713_set_pll(struct snd_soc_component *component, + wm9713->pll_in = freq_in; + + /* wait 10ms AC97 link frames for the link to stabilise */ +- schedule_timeout_interruptible(msecs_to_jiffies(10)); ++ schedule_msec_hrtimeout_interruptible(10); + return 0; + } + +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -166,7 +166,7 @@ static void dapm_assert_locked(struct snd_soc_dapm_context *dapm) + static void dapm_pop_wait(void) + { + if (pop_time) +- schedule_timeout_uninterruptible(msecs_to_jiffies(pop_time)); ++ schedule_msec_hrtimeout_uninterruptible(pop_time); + } + + __printf(2, 3) +diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c +--- a/sound/usb/line6/pcm.c ++++ b/sound/usb/line6/pcm.c +@@ -127,7 +127,7 @@ static void line6_wait_clear_audio_urbs(struct snd_line6_pcm *line6pcm, + if (!alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); +- schedule_timeout(1); ++ schedule_min_hrtimeout(); + } while (--timeout > 0); + if (alive) + dev_err(line6pcm->line6->ifcdev, +diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile +--- a/tools/objtool/Makefile ++++ b/tools/objtool/Makefile +@@ -101,9 +101,19 @@ LIBOPCODES_LIBS := $(shell \ + HAVE_DISASM_STYLED := $(shell echo '$(pound)include ' | \ + $(HOSTCC) -E -xc - 2>/dev/null | grep -q disassembler_style && echo y) + ++# ++# Linking -lopcodes can succeed when the shared library is present without ++# development headers. That is common on Gentoo without sys-libs/binutils-libs ++# (bfd.h lives under /usr/lib64/binutils///include, not ++# /usr/include). Do not enable DISAS unless the headers actually compile. ++# ++HAVE_DISAS_HEADERS := $(shell printf "$(pound)include \n$(pound)include \nint main(void) { return 0; }\n" | \ ++ $(HOSTCC) $(HOSTCFLAGS) -DPACKAGE='"objtool"' -xc - -c -o /dev/null 2>/dev/null && echo y) ++ + BUILD_DISAS := n + + ifneq ($(LIBOPCODES_LIBS),) ++ifeq ($(HAVE_DISAS_HEADERS),y) + BUILD_DISAS := y + OBJTOOL_CFLAGS += -DDISAS -DPACKAGE='"objtool"' + OBJTOOL_LDFLAGS += $(LIBOPCODES_LIBS) +@@ -111,6 +121,7 @@ ifeq ($(HAVE_DISASM_STYLED),y) + OBJTOOL_CFLAGS += -DDISASM_INIT_STYLED + endif + endif ++endif + + export BUILD_DISAS + diff --git a/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch.sig b/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch.sig new file mode 100644 index 0000000..8af6db4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0110-muqss-0.31.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch b/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch new file mode 100644 index 0000000..14e2d46 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch @@ -0,0 +1,582 @@ +diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h +--- a/arch/x86/include/asm/pgtable.h ++++ b/arch/x86/include/asm/pgtable.h +@@ -50,7 +50,7 @@ void ptdump_walk_user_pgd_level_checkwx(void); + extern spinlock_t pgd_lock; + extern struct list_head pgd_list; + +-extern struct mm_struct *pgd_page_get_mm(struct page *page); ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt); + + extern pmdval_t early_pmd_flags; + +diff --git a/arch/x86/include/asm/pgtable_types.h b/arch/x86/include/asm/pgtable_types.h +--- a/arch/x86/include/asm/pgtable_types.h ++++ b/arch/x86/include/asm/pgtable_types.h +@@ -512,7 +512,7 @@ static inline pgprot_t pgprot_large_2_4k(pgprot_t pgprot) + return __pgprot(protval_large_2_4k(pgprot_val(pgprot))); + } + +- ++struct ptdesc; + typedef struct page *pgtable_t; + + extern pteval_t __supported_pte_mask; +diff --git a/arch/x86/include/asm/tlbflush.h b/arch/x86/include/asm/tlbflush.h +--- a/arch/x86/include/asm/tlbflush.h ++++ b/arch/x86/include/asm/tlbflush.h +@@ -4,6 +4,7 @@ + + #include + #include ++#include + #include + + #include +@@ -211,6 +212,12 @@ extern u16 invlpgb_count_max; + + extern void initialize_tlbstate_and_flush(void); + ++/* ++ * Keep stack-allocated flush_tlb_info cacheline aligned, but cap the ++ * alignment to avoid excessive stack usage on large-cacheline systems. ++ */ ++#define FLUSH_TLB_INFO_ALIGN MIN(SMP_CACHE_BYTES, 64) ++ + /* + * TLB flushing: + * +@@ -249,7 +256,7 @@ struct flush_tlb_info { + u8 stride_shift; + u8 freed_tables; + u8 trim_cpumask; +-}; ++} __aligned(FLUSH_TLB_INFO_ALIGN); + + void flush_tlb_local(void); + void flush_tlb_one_user(unsigned long addr); +diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c +--- a/arch/x86/kernel/kvm.c ++++ b/arch/x86/kernel/kvm.c +@@ -663,8 +663,10 @@ static void kvm_flush_tlb_multi(const struct cpumask *cpumask, + u8 state; + int cpu; + struct kvm_steal_time *src; +- struct cpumask *flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); ++ struct cpumask *flushmask; + ++ guard(preempt)(); ++ flushmask = this_cpu_cpumask_var_ptr(__pv_cpu_mask); + cpumask_copy(flushmask, cpumask); + /* + * We have to call flush only on online vCPUs. And +diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c +--- a/arch/x86/mm/fault.c ++++ b/arch/x86/mm/fault.c +@@ -275,17 +275,17 @@ void arch_sync_kernel_mappings(unsigned long start, unsigned long end) + for (addr = start & PMD_MASK; + addr >= TASK_SIZE_MAX && addr < VMALLOC_END; + addr += PMD_SIZE) { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + spinlock_t *pgt_lock; + + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + + spin_lock(pgt_lock); +- vmalloc_sync_one(page_address(page), addr); ++ vmalloc_sync_one(ptdesc_address(ptdesc), addr); + spin_unlock(pgt_lock); + } + spin_unlock(&pgd_lock); +diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c +--- a/arch/x86/mm/init_64.c ++++ b/arch/x86/mm/init_64.c +@@ -136,7 +136,7 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + const pgd_t *pgd_ref = pgd_offset_k(addr); +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Check for overflow */ + if (addr < start) +@@ -146,13 +146,13 @@ static void sync_global_pgds_l5(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!pgd_none(*pgd_ref) && !pgd_none(*pgd)) +@@ -174,7 +174,7 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + for (addr = start; addr <= end; addr = ALIGN(addr + 1, PGDIR_SIZE)) { + pgd_t *pgd_ref = pgd_offset_k(addr); + const p4d_t *p4d_ref; +- struct page *page; ++ struct ptdesc *ptdesc; + + /* + * With folded p4d, pgd_none() is always false, we need to +@@ -187,15 +187,15 @@ static void sync_global_pgds_l4(unsigned long start, unsigned long end) + continue; + + spin_lock(&pgd_lock); +- list_for_each_entry(page, &pgd_list, lru) { ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + spinlock_t *pgt_lock; + +- pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d = p4d_offset(pgd, addr); + /* the pgt_lock only for Xen */ +- pgt_lock = &pgd_page_get_mm(page)->page_table_lock; ++ pgt_lock = &pgd_page_get_mm(ptdesc)->page_table_lock; + spin_lock(pgt_lock); + + if (!p4d_none(*p4d_ref) && !p4d_none(*p4d)) +diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c +--- a/arch/x86/mm/pat/set_memory.c ++++ b/arch/x86/mm/pat/set_memory.c +@@ -88,9 +88,8 @@ static unsigned long direct_pages_count[PG_LEVEL_NUM]; + void update_page_count(int level, unsigned long pages) + { + /* Protect against CPA */ +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + direct_pages_count[level] += pages; +- spin_unlock(&pgd_lock); + } + + static void split_page_count(int level) +@@ -420,7 +419,7 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + int collapsed = 0; + int i; + +- spin_lock(&cpa_lock); ++ guard(spinlock)(&cpa_lock); + + if (cpa->flags & (CPA_PAGES_ARRAY | CPA_ARRAY)) { + for (i = 0; i < cpa->numpages; i++) +@@ -435,10 +434,8 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + collapsed += collapse_large_pages(addr, &pgtables); + } + +- if (!collapsed) { +- spin_unlock(&cpa_lock); ++ if (!collapsed) + return; +- } + + flush_tlb_all(); + +@@ -454,8 +451,6 @@ static void __cpa_collapse_large_pages(struct cpa_data *cpa) + else + pagetable_free(ptdesc); + } +- +- spin_unlock(&cpa_lock); + } + + static void cpa_collapse_large_pages(struct cpa_data *cpa) +@@ -916,24 +911,23 @@ static void __set_pmd_pte(pte_t *kpte, unsigned long address, pte_t pte) + { + /* change init_mm */ + set_pte_atomic(kpte, pte); +-#ifdef CONFIG_X86_32 +- { +- struct page *page; + +- list_for_each_entry(page, &pgd_list, lru) { ++ if (IS_ENABLED(CONFIG_X86_32)) { ++ struct ptdesc *ptdesc; ++ ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; + pmd_t *pmd; + +- pgd = (pgd_t *)page_address(page) + pgd_index(address); ++ pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(address); + p4d = p4d_offset(pgd, address); + pud = pud_offset(p4d, address); + pmd = pmd_offset(pud, address); + set_pte_atomic((pte_t *)pmd, pte); + } + } +-#endif + } + + static pgprot_t pgprot_clear_protnone_bits(pgprot_t prot) +@@ -1103,16 +1097,11 @@ static int __should_split_large_page(pte_t *kpte, unsigned long address, + static int should_split_large_page(pte_t *kpte, unsigned long address, + struct cpa_data *cpa) + { +- int do_split; +- + if (cpa->force_split) + return 1; + +- spin_lock(&pgd_lock); +- do_split = __should_split_large_page(kpte, address, cpa); +- spin_unlock(&pgd_lock); +- +- return do_split; ++ guard(spinlock)(&pgd_lock); ++ return __should_split_large_page(kpte, address, cpa); + } + + static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn, +@@ -1162,16 +1151,14 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + bool nx, rw; + pte_t *tmp; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + /* + * Check for races, another CPU might have split this page + * up for us already: + */ + tmp = _lookup_address_cpa(cpa, address, &level, &nx, &rw); +- if (tmp != kpte) { +- spin_unlock(&pgd_lock); ++ if (tmp != kpte) + return 1; +- } + + paravirt_alloc_pte(&init_mm, page_to_pfn(base)); + +@@ -1204,7 +1191,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + break; + + default: +- spin_unlock(&pgd_lock); + return 1; + } + +@@ -1252,7 +1238,6 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address, + * just split large page entry. + */ + flush_tlb_all(); +- spin_unlock(&pgd_lock); + + return 0; + } +@@ -1327,11 +1312,11 @@ static int collapse_pmd_page(pmd_t *pmd, unsigned long addr, + list_add(&page_ptdesc(pmd_page(old_pmd))->pt_list, pgtables); + + if (IS_ENABLED(CONFIG_X86_32)) { +- struct page *page; ++ struct ptdesc *ptdesc; + + /* Update all PGD tables to use the same large page */ +- list_for_each_entry(page, &pgd_list, lru) { +- pgd_t *pgd = (pgd_t *)page_address(page) + pgd_index(addr); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ pgd_t *pgd = (pgd_t *)ptdesc_address(ptdesc) + pgd_index(addr); + p4d_t *p4d = p4d_offset(pgd, addr); + pud_t *pud = pud_offset(p4d, addr); + pmd_t *pmd = pmd_offset(pud, addr); +@@ -1405,7 +1390,7 @@ static int collapse_pud_page(pud_t *pud, unsigned long addr, + */ + static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + { +- int collapsed = 0; ++ int collapsed; + pgd_t *pgd; + p4d_t *p4d; + pud_t *pud; +@@ -1413,26 +1398,24 @@ static int collapse_large_pages(unsigned long addr, struct list_head *pgtables) + + addr &= PMD_MASK; + +- spin_lock(&pgd_lock); ++ guard(spinlock)(&pgd_lock); + pgd = pgd_offset_k(addr); + if (pgd_none(*pgd)) +- goto out; ++ return 0; + p4d = p4d_offset(pgd, addr); + if (p4d_none(*p4d)) +- goto out; ++ return 0; + pud = pud_offset(p4d, addr); + if (!pud_present(*pud) || pud_leaf(*pud)) +- goto out; ++ return 0; + pmd = pmd_offset(pud, addr); + if (!pmd_present(*pmd) || pmd_leaf(*pmd)) +- goto out; ++ return 0; + + collapsed = collapse_pmd_page(pmd, addr, pgtables); + if (collapsed) + collapsed += collapse_pud_page(pud, addr, pgtables); + +-out: +- spin_unlock(&pgd_lock); + return collapsed; + } + +diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c +--- a/arch/x86/mm/pgtable.c ++++ b/arch/x86/mm/pgtable.c +@@ -74,9 +74,9 @@ static void pgd_set_mm(pgd_t *pgd, struct mm_struct *mm) + virt_to_ptdesc(pgd)->pt_mm = mm; + } + +-struct mm_struct *pgd_page_get_mm(struct page *page) ++struct mm_struct *pgd_page_get_mm(struct ptdesc *pt) + { +- return page_ptdesc(page)->pt_mm; ++ return pt->pt_mm; + } + + static void pgd_ctor(struct mm_struct *mm, pgd_t *pgd) +diff --git a/arch/x86/mm/tlb.c b/arch/x86/mm/tlb.c +--- a/arch/x86/mm/tlb.c ++++ b/arch/x86/mm/tlb.c +@@ -1373,35 +1373,19 @@ void flush_tlb_multi(const struct cpumask *cpumask, + */ + unsigned long tlb_single_page_flush_ceiling __read_mostly = 33; + +-static DEFINE_PER_CPU_SHARED_ALIGNED(struct flush_tlb_info, flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +-static DEFINE_PER_CPU(unsigned int, flush_tlb_info_idx); +-#endif +- +-static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, +- unsigned long start, unsigned long end, +- unsigned int stride_shift, bool freed_tables, +- u64 new_tlb_gen) ++static void init_flush_tlb_info(struct flush_tlb_info *info, ++ struct mm_struct *mm, ++ unsigned long start, unsigned long end, ++ unsigned int stride_shift, bool freed_tables, ++ u64 new_tlb_gen) + { +- struct flush_tlb_info *info = this_cpu_ptr(&flush_tlb_info); +- +-#ifdef CONFIG_DEBUG_VM +- /* +- * Ensure that the following code is non-reentrant and flush_tlb_info +- * is not overwritten. This means no TLB flushing is initiated by +- * interrupt handlers and machine-check exception handlers. +- */ +- BUG_ON(this_cpu_inc_return(flush_tlb_info_idx) != 1); +-#endif +- + /* + * If the number of flushes is so large that a full flush + * would be faster, do a full flush. + */ + if ((end - start) >> stride_shift > tlb_single_page_flush_ceiling) { +- start = 0; +- end = TLB_FLUSH_ALL; ++ start = 0; ++ end = TLB_FLUSH_ALL; + } + + info->start = start; +@@ -1412,32 +1396,21 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, + info->new_tlb_gen = new_tlb_gen; + info->initiating_cpu = smp_processor_id(); + info->trim_cpumask = 0; +- +- return info; +-} +- +-static void put_flush_tlb_info(void) +-{ +-#ifdef CONFIG_DEBUG_VM +- /* Complete reentrancy prevention checks */ +- barrier(); +- this_cpu_dec(flush_tlb_info_idx); +-#endif + } + + void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + unsigned long end, unsigned int stride_shift, + bool freed_tables) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + u64 new_tlb_gen; + + /* This is also a barrier that synchronizes with switch_mm(). */ + new_tlb_gen = inc_mm_tlb_gen(mm); + +- info = get_flush_tlb_info(mm, start, end, stride_shift, freed_tables, +- new_tlb_gen); ++ init_flush_tlb_info(&info, mm, start, end, stride_shift, freed_tables, new_tlb_gen); + + /* + * flush_tlb_multi() is not optimized for the common case in which only +@@ -1445,20 +1418,24 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, + * flush_tlb_func_local() directly in this case. + */ + if (mm_global_asid(mm)) { +- broadcast_tlb_flush(info); ++ broadcast_tlb_flush(&info); + } else if (cpumask_any_but(mm_cpumask(mm), cpu) < nr_cpu_ids) { +- info->trim_cpumask = should_trim_cpumask(mm); +- flush_tlb_multi(mm_cpumask(mm), info); +- consider_global_asid(mm); ++ remote_flush = true; + } else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) { ++ info.trim_cpumask = should_trim_cpumask(mm); ++ flush_tlb_multi(mm_cpumask(mm), &info); ++ consider_global_asid(mm); ++ } ++ + mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, end); + } + +@@ -1527,19 +1504,16 @@ static void kernel_tlb_flush_range(struct flush_tlb_info *info) + + void flush_tlb_kernel_range(unsigned long start, unsigned long end) + { +- struct flush_tlb_info *info; ++ struct flush_tlb_info info; + + guard(preempt)(); ++ init_flush_tlb_info(&info, NULL, start, end, PAGE_SHIFT, false, ++ TLB_GENERATION_INVALID); + +- info = get_flush_tlb_info(NULL, start, end, PAGE_SHIFT, false, +- TLB_GENERATION_INVALID); +- +- if (info->end == TLB_FLUSH_ALL) +- kernel_tlb_flush_all(info); ++ if (info.end == TLB_FLUSH_ALL) ++ kernel_tlb_flush_all(&info); + else +- kernel_tlb_flush_range(info); +- +- put_flush_tlb_info(); ++ kernel_tlb_flush_range(&info); + } + + /* +@@ -1707,12 +1681,12 @@ EXPORT_SYMBOL_FOR_KVM(__flush_tlb_all); + + void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + { +- struct flush_tlb_info *info; +- ++ struct flush_tlb_info info; ++ bool remote_flush = false; + int cpu = get_cpu(); + +- info = get_flush_tlb_info(NULL, 0, TLB_FLUSH_ALL, 0, false, +- TLB_GENERATION_INVALID); ++ init_flush_tlb_info(&info, NULL, 0, TLB_FLUSH_ALL, 0, false, ++ TLB_GENERATION_INVALID); + /* + * flush_tlb_multi() is not optimized for the common case in which only + * a local TLB flush is needed. Optimize this use-case by calling +@@ -1722,18 +1696,20 @@ void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch) + invlpgb_flush_all_nonglobals(); + batch->unmapped_pages = false; + } else if (cpumask_any_but(&batch->cpumask, cpu) < nr_cpu_ids) { +- flush_tlb_multi(&batch->cpumask, info); ++ remote_flush = true; + } else if (cpumask_test_cpu(cpu, &batch->cpumask)) { + lockdep_assert_irqs_enabled(); + local_irq_disable(); +- flush_tlb_func(info); ++ flush_tlb_func(&info); + local_irq_enable(); + } + +- cpumask_clear(&batch->cpumask); +- +- put_flush_tlb_info(); + put_cpu(); ++ ++ if (remote_flush) ++ flush_tlb_multi(&batch->cpumask, &info); ++ ++ cpumask_clear(&batch->cpumask); + } + + /* +diff --git a/arch/x86/xen/mmu_pv.c b/arch/x86/xen/mmu_pv.c +--- a/arch/x86/xen/mmu_pv.c ++++ b/arch/x86/xen/mmu_pv.c +@@ -836,15 +836,15 @@ static void xen_pgd_pin(struct mm_struct *mm) + */ + void xen_mm_pin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (!PagePinned(page)) { +- __xen_pgd_pin(&init_mm, (pgd_t *)page_address(page)); +- SetPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (!PagePinned(ptdesc_page(ptdesc))) { ++ __xen_pgd_pin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ SetPageSavePinned(ptdesc_page(ptdesc)); + } + } + +@@ -947,16 +947,16 @@ static void xen_pgd_unpin(struct mm_struct *mm) + */ + void xen_mm_unpin_all(void) + { +- struct page *page; ++ struct ptdesc *ptdesc; + + spin_lock(&init_mm.page_table_lock); + spin_lock(&pgd_lock); + +- list_for_each_entry(page, &pgd_list, lru) { +- if (PageSavePinned(page)) { +- BUG_ON(!PagePinned(page)); +- __xen_pgd_unpin(&init_mm, (pgd_t *)page_address(page)); +- ClearPageSavePinned(page); ++ list_for_each_entry(ptdesc, &pgd_list, pt_list) { ++ if (PageSavePinned(ptdesc_page(ptdesc))) { ++ BUG_ON(!PagePinned(ptdesc_page(ptdesc))); ++ __xen_pgd_unpin(&init_mm, (pgd_t *)ptdesc_address(ptdesc)); ++ ClearPageSavePinned(ptdesc_page(ptdesc)); + } + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch.sig b/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch.sig new file mode 100644 index 0000000..3f44a63 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0120-tlbpull.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch b/pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch new file mode 100644 index 0000000..02cfa25 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -832,6 +832,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 + /* +@@ -1442,6 +1453,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-muqss/0121-smp-preempt.patch.sig b/pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch.sig new file mode 100644 index 0000000..f04310e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0121-smp-preempt.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch b/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch new file mode 100644 index 0000000..245f6e1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch @@ -0,0 +1,21 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -13350,12 +13350,15 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, + * still unbalanced. ld_moved simply stays zero, so it is + * correctly treated as an imbalance. + */ +- env.loop_max = min(sysctl_sched_nr_migrate, busiest->nr_running); +- + more_balance: + rq_lock_irqsave(busiest, &rf); + update_rq_clock(busiest); + ++ if (!env.loop_max) ++ env.loop_max = min(sysctl_sched_nr_migrate, busiest->cfs.h_nr_queued); ++ else ++ env.loop_max = min(env.loop_max, busiest->cfs.h_nr_queued); ++ + /* + * cur_ld_moved - load moved in current iteration + * ld_moved - cumulative load moved across iterations diff --git a/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch.sig b/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch.sig new file mode 100644 index 0000000..b0ccd26 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0130-sched-detach-tasks.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch b/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch new file mode 100644 index 0000000..2d03b2d --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch @@ -0,0 +1,367 @@ +diff --git a/arch/arm/include/asm/mmu_context.h b/arch/arm/include/asm/mmu_context.h +--- a/arch/arm/include/asm/mmu_context.h ++++ b/arch/arm/include/asm/mmu_context.h +@@ -80,7 +80,7 @@ static inline void check_and_switch_context(struct mm_struct *mm, + #ifndef MODULE + #define finish_arch_post_lock_switch \ + finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct mm_struct *mm = current->mm; + +diff --git a/arch/riscv/include/asm/sync_core.h b/arch/riscv/include/asm/sync_core.h +--- a/arch/riscv/include/asm/sync_core.h ++++ b/arch/riscv/include/asm/sync_core.h +@@ -6,7 +6,7 @@ + * RISC-V implements return to user-space through an xRET instruction, + * which is not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + asm volatile ("fence.i" ::: "memory"); + } +diff --git a/arch/s390/include/asm/mmu_context.h b/arch/s390/include/asm/mmu_context.h +--- a/arch/s390/include/asm/mmu_context.h ++++ b/arch/s390/include/asm/mmu_context.h +@@ -93,7 +93,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + struct task_struct *tsk = current; + struct mm_struct *mm = tsk->mm; +diff --git a/arch/sparc/include/asm/mmu_context_64.h b/arch/sparc/include/asm/mmu_context_64.h +--- a/arch/sparc/include/asm/mmu_context_64.h ++++ b/arch/sparc/include/asm/mmu_context_64.h +@@ -160,7 +160,7 @@ static inline void arch_start_context_switch(struct task_struct *prev) + } + + #define finish_arch_post_lock_switch finish_arch_post_lock_switch +-static inline void finish_arch_post_lock_switch(void) ++static __always_inline void finish_arch_post_lock_switch(void) + { + /* Restore the state of MCDPER register for the new process + * just switched to. +diff --git a/arch/x86/include/asm/sync_core.h b/arch/x86/include/asm/sync_core.h +--- a/arch/x86/include/asm/sync_core.h ++++ b/arch/x86/include/asm/sync_core.h +@@ -93,7 +93,7 @@ static __always_inline void sync_core(void) + * to user-mode. x86 implements return to user-space through sysexit, + * sysrel, and sysretq, which are not core serializing. + */ +-static inline void sync_core_before_usermode(void) ++static __always_inline void sync_core_before_usermode(void) + { + /* With PTI, we unconditionally serialize before running user code. */ + if (static_cpu_has(X86_FEATURE_PTI)) +diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h +--- a/include/linux/perf_event.h ++++ b/include/linux/perf_event.h +@@ -1632,7 +1632,7 @@ static inline void perf_event_task_migrate(struct task_struct *task) + task->sched_migrated = 1; + } + +-static inline void perf_event_task_sched_in(struct task_struct *prev, ++static __always_inline void perf_event_task_sched_in(struct task_struct *prev, + struct task_struct *task) + { + if (static_branch_unlikely(&perf_sched_events)) +diff --git a/include/linux/sched/mm.h b/include/linux/sched/mm.h +--- a/include/linux/sched/mm.h ++++ b/include/linux/sched/mm.h +@@ -32,7 +32,7 @@ extern struct mm_struct *mm_alloc(void); + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmgrab(struct mm_struct *mm) ++static __always_inline void mmgrab(struct mm_struct *mm) + { + atomic_inc(&mm->mm_count); + } +@@ -44,7 +44,7 @@ static inline void smp_mb__after_mmgrab(void) + + extern void __mmdrop(struct mm_struct *mm); + +-static inline void mmdrop(struct mm_struct *mm) ++static __always_inline void mmdrop(struct mm_struct *mm) + { + /* + * The implicit full barrier implied by atomic_dec_and_test() is +@@ -71,27 +71,27 @@ static inline void __mmdrop_delayed(struct rcu_head *rhp) + * Invoked from finish_task_switch(). Delegates the heavy lifting on RT + * kernels via RCU. + */ +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + /* Provides a full memory barrier. See mmdrop() */ + if (atomic_dec_and_test(&mm->mm_count)) + call_rcu(&mm->delayed_drop, __mmdrop_delayed); + } + #else +-static inline void mmdrop_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_sched(struct mm_struct *mm) + { + mmdrop(mm); + } + #endif + + /* Helpers for lazy TLB mm refcounting */ +-static inline void mmgrab_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmgrab_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmgrab(mm); + } + +-static inline void mmdrop_lazy_tlb(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) { + mmdrop(mm); +@@ -104,7 +104,7 @@ static inline void mmdrop_lazy_tlb(struct mm_struct *mm) + } + } + +-static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) ++static __always_inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + { + if (IS_ENABLED(CONFIG_MMU_LAZY_TLB_REFCOUNT)) + mmdrop_sched(mm); +@@ -128,12 +128,12 @@ static inline void mmdrop_lazy_tlb_sched(struct mm_struct *mm) + * See also for an in-depth explanation + * of &mm_struct.mm_count vs &mm_struct.mm_users. + */ +-static inline void mmget(struct mm_struct *mm) ++static __always_inline void mmget(struct mm_struct *mm) + { + atomic_inc(&mm->mm_users); + } + +-static inline bool mmget_not_zero(struct mm_struct *mm) ++static __always_inline bool mmget_not_zero(struct mm_struct *mm) + { + return atomic_inc_not_zero(&mm->mm_users); + } +@@ -532,7 +532,7 @@ enum { + #include + #endif + +-static inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) ++static __always_inline void membarrier_mm_sync_core_before_usermode(struct mm_struct *mm) + { + /* + * The atomic_read() below prevents CSE. The following should +diff --git a/include/linux/tick.h b/include/linux/tick.h +--- a/include/linux/tick.h ++++ b/include/linux/tick.h +@@ -175,7 +175,7 @@ extern cpumask_var_t tick_nohz_full_mask; + #ifdef CONFIG_NO_HZ_FULL + extern bool tick_nohz_full_running; + +-static inline bool tick_nohz_full_enabled(void) ++static __always_inline bool tick_nohz_full_enabled(void) + { + if (!context_tracking_enabled()) + return false; +@@ -299,7 +299,7 @@ static inline void __tick_nohz_task_switch(void) { } + static inline void tick_nohz_full_setup(cpumask_var_t cpumask) { } + #endif + +-static inline void tick_nohz_task_switch(void) ++static __always_inline void tick_nohz_task_switch(void) + { + if (tick_nohz_full_enabled()) + __tick_nohz_task_switch(); +diff --git a/include/linux/vtime.h b/include/linux/vtime.h +--- a/include/linux/vtime.h ++++ b/include/linux/vtime.h +@@ -89,24 +89,24 @@ static __always_inline void vtime_account_guest_exit(void) + * For now vtime state is tied to context tracking. We might want to decouple + * those later if necessary. + */ +-static inline bool vtime_accounting_enabled(void) ++static __always_inline bool vtime_accounting_enabled(void) + { + return context_tracking_enabled(); + } + +-static inline bool vtime_accounting_enabled_cpu(int cpu) ++static __always_inline bool vtime_accounting_enabled_cpu(int cpu) + { + return vtime_generic_enabled_cpu(cpu); + } + +-static inline bool vtime_accounting_enabled_this_cpu(void) ++static __always_inline bool vtime_accounting_enabled_this_cpu(void) + { + return vtime_generic_enabled_this_cpu(); + } + + extern void vtime_task_switch_generic(struct task_struct *prev); + +-static inline void vtime_task_switch(struct task_struct *prev) ++static __always_inline void vtime_task_switch(struct task_struct *prev) + { + if (vtime_accounting_enabled_this_cpu()) + vtime_task_switch_generic(prev); +diff --git a/kernel/sched/core.c b/kernel/sched/core.c +--- a/kernel/sched/core.c ++++ b/kernel/sched/core.c +@@ -5098,7 +5098,7 @@ static inline void prepare_task(struct task_struct *next) + WRITE_ONCE(next->on_cpu, 1); + } + +-static inline void finish_task(struct task_struct *prev) ++static __always_inline void finish_task(struct task_struct *prev) + { + /* + * This must be the very last reference to @prev from this CPU. After +@@ -5142,7 +5142,7 @@ static void zap_balance_callbacks(struct rq *rq) + rq->balance_callback = found ? &balance_push_callback : NULL; + } + +-static void do_balance_callbacks(struct rq *rq, struct balance_callback *head) ++static __always_inline void do_balance_callbacks(struct rq *rq, struct balance_callback *head) + { + void (*func)(struct rq *rq); + struct balance_callback *next; +@@ -5177,7 +5177,7 @@ struct balance_callback balance_push_callback = { + .func = balance_push, + }; + +-static inline struct balance_callback * ++static __always_inline struct balance_callback * + __splice_balance_callbacks(struct rq *rq, bool split) + { + struct balance_callback *head = rq->balance_callback; +@@ -5251,7 +5251,7 @@ prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf + __acquire(__rq_lockp(this_rq())); + } + +-static inline void finish_lock_switch(struct rq *rq) ++static __always_inline void finish_lock_switch(struct rq *rq) + __releases(__rq_lockp(rq)) + { + /* +@@ -5285,7 +5285,7 @@ static inline void kmap_local_sched_out(void) + #endif + } + +-static inline void kmap_local_sched_in(void) ++static __always_inline void kmap_local_sched_in(void) + { + #ifdef CONFIG_KMAP_LOCAL + if (unlikely(current->kmap_ctrl.idx)) +@@ -5339,7 +5339,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, + * past. 'prev == current' is still correct but we need to recalculate this_rq + * because prev may have moved to another CPU. + */ +-static struct rq *finish_task_switch(struct task_struct *prev) ++static __always_inline struct rq *finish_task_switch(struct task_struct *prev) + __releases(__rq_lockp(this_rq())) + { + struct rq *rq = this_rq(); +diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h +--- a/kernel/sched/sched.h ++++ b/kernel/sched/sched.h +@@ -1464,12 +1464,12 @@ static inline struct cpumask *sched_group_span(struct sched_group *sg); + + DECLARE_STATIC_KEY_FALSE(__sched_core_enabled); + +-static inline bool sched_core_enabled(struct rq *rq) ++static __always_inline bool sched_core_enabled(struct rq *rq) + { + return static_branch_unlikely(&__sched_core_enabled) && rq->core_enabled; + } + +-static inline bool sched_core_disabled(void) ++static __always_inline bool sched_core_disabled(void) + { + return !static_branch_unlikely(&__sched_core_enabled); + } +@@ -1478,7 +1478,7 @@ static inline bool sched_core_disabled(void) + * Be careful with this function; not for general use. The return value isn't + * stable unless you actually hold a relevant rq->__lock. + */ +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + if (sched_core_enabled(rq)) + return &rq->core->__lock; +@@ -1486,7 +1486,7 @@ static inline raw_spinlock_t *rq_lockp(struct rq *rq) + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + if (rq->core_enabled) +@@ -1589,12 +1589,12 @@ static inline bool sched_core_disabled(void) + return true; + } + +-static inline raw_spinlock_t *rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *rq_lockp(struct rq *rq) + { + return &rq->__lock; + } + +-static inline raw_spinlock_t *__rq_lockp(struct rq *rq) ++static __always_inline raw_spinlock_t *__rq_lockp(struct rq *rq) + __returns_ctx_lock(rq_lockp(rq)) /* alias them */ + { + return &rq->__lock; +@@ -1654,33 +1654,33 @@ extern void raw_spin_rq_lock_nested(struct rq *rq, int subclass) + extern bool raw_spin_rq_trylock(struct rq *rq) + __cond_acquires(true, __rq_lockp(rq)); + +-static inline void raw_spin_rq_lock(struct rq *rq) ++static __always_inline void raw_spin_rq_lock(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + raw_spin_rq_lock_nested(rq, 0); + } + +-static inline void raw_spin_rq_unlock(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_unlock(rq_lockp(rq)); + } + +-static inline void raw_spin_rq_lock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_lock_irq(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + local_irq_disable(); + raw_spin_rq_lock(rq); + } + +-static inline void raw_spin_rq_unlock_irq(struct rq *rq) ++static __always_inline void raw_spin_rq_unlock_irq(struct rq *rq) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); + local_irq_enable(); + } + +-static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) ++static __always_inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + __acquires(__rq_lockp(rq)) + { + unsigned long flags; +@@ -1691,7 +1691,7 @@ static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq) + return flags; + } + +-static inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) ++static __always_inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags) + __releases(__rq_lockp(rq)) + { + raw_spin_rq_unlock(rq); diff --git a/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch.sig b/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch.sig new file mode 100644 index 0000000..878f946 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0140-sched-always-inline.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch new file mode 100644 index 0000000..f26a59b --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch @@ -0,0 +1,97 @@ +diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c +--- a/kernel/sched/fair.c ++++ b/kernel/sched/fair.c +@@ -10575,17 +10575,40 @@ static enum llc_mig can_migrate_llc(int src_cpu, int dst_cpu, + return mig_llc; + } + ++static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct *p) ++{ ++ /* ++ * On asymmetric CPU capacity domains, do not let cache-aware ++ * balancing pull the task onto a destination CPU that cannot ++ * accommodate it. Doing so would turn the task into a misfit on ++ * the destination, trading a cache-locality gain for a capacity ++ * loss. If the task already does not fit its source CPU, the move ++ * cannot make things worse, so let the LLC preference decide. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && p && ++ !task_fits_cpu(p, env->dst_cpu) && ++ task_fits_cpu(p, env->src_cpu)) ++ return true; ++ ++ return false; ++} ++ + /* + * Check if task p can migrate from source LLC to + * destination LLC in terms of cache aware load balance. + */ +-static enum llc_mig can_migrate_llc_task(int src_cpu, int dst_cpu, ++static enum llc_mig can_migrate_llc_task(struct lb_env *env, + struct task_struct *p) + { + struct mm_struct *mm; + bool to_pref; +- int cpu; ++ int cpu, src_cpu, dst_cpu; + ++ if (task_misfits_asym_cpu(env, p)) ++ return mig_forbid; ++ ++ src_cpu = env->src_cpu; ++ dst_cpu = env->dst_cpu; + mm = p->mm; + if (!mm) + return mig_unrestricted; +@@ -10642,6 +10665,14 @@ alb_break_llc(struct lb_env *env) + unsigned long util = 0; + struct task_struct *cur; + ++ /* ++ * Migrating misfit tasks from current CPU ++ * to CPU with a better fit. ++ * Prioritize that over LLC preference. ++ */ ++ if (env->migration_type == migrate_misfit) ++ return false; ++ + if (env->src_rq->nr_running <= 1) + return true; + +@@ -10649,7 +10680,8 @@ alb_break_llc(struct lb_env *env) + if (cur && cur->sched_class == &fair_sched_class) + util = task_util(cur); + +- if (can_migrate_llc(env->src_cpu, env->dst_cpu, ++ if (task_misfits_asym_cpu(env, cur) || ++ can_migrate_llc(env->src_cpu, env->dst_cpu, + util, false) == mig_forbid) + return true; + } +@@ -10689,8 +10721,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) + READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) + return true; + +- if (can_migrate_llc_task(env->src_cpu, +- env->dst_cpu, p) != mig_forbid) ++ if (can_migrate_llc_task(env, p) != mig_forbid) + return false; + + return true; +@@ -11753,6 +11784,15 @@ static inline bool llc_balance(struct lb_env *env, struct sg_lb_stats *sgs, + if (env->sd->flags & SD_SHARE_LLC) + return false; + ++ /* ++ * On asymmetric domains, group_misfit_task_load ++ * should be prioritized to move tasks to CPU that fit them ++ * over aggregating tasks to their preferred LLC. ++ */ ++ if ((env->sd->flags & SD_ASYM_CPUCAPACITY) && ++ sgs->group_misfit_task_load) ++ return false; ++ + /* + * Skip cache aware tagging if nr_balanced_failed is sufficiently high. + * Threshold of cache_nice_tries is set to 1 higher than nr_balance_failed diff --git a/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch.sig new file mode 100644 index 0000000..0af85fe Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0141-sched-urgent-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch b/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch new file mode 100644 index 0000000..137ec28 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch @@ -0,0 +1,88 @@ +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 +@@ -11912,12 +11912,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; +- ++ if (rq->misfit_task_load) { ++ /* ++ * Always mark the root domain overloaded so big ++ * CPUs can pick up misfit tasks via newly idle ++ * balance. ++ */ + if (balancing_at_rd) + *sg_overloaded = 1; ++ ++ /* ++ * Only account misfit load if @dst_cpu can ++ * help; otherwise, the group may be classified ++ * as misfit_task and update_sd_pick_busiest() ++ * will skip it. ++ */ ++ if (capacity_greater(capacity_of(env->dst_cpu), ++ group->sgc->max_capacity) && ++ (sgs->group_misfit_task_load < rq->misfit_task_load)) ++ sgs->group_misfit_task_load = rq->misfit_task_load; + } + } else if (env->idle && sched_reduced_capacity(rq, env->sd)) { + /* Check for a task running on a CPU with reduced capacity */ +@@ -13030,9 +13043,24 @@ static struct rq *sched_balance_find_src_rq(struct lb_env *env, + * average load. + */ + if (env->sd->flags & SD_ASYM_CPUCAPACITY && +- !capacity_greater(capacity_of(env->dst_cpu), capacity) && +- nr_running == 1) +- continue; ++ nr_running == 1) { ++ bool cluster_equal_cap = static_branch_unlikely(&sched_cluster_active) && ++ (get_actual_cpu_capacity(env->dst_cpu) == ++ get_actual_cpu_capacity(i)); ++ bool smt_degraded_cap = sched_smt_active() && !is_core_idle(i); ++ ++ /* ++ * Busy SMT siblings reduce the capacity of CPU @i. Do ++ * not skip it in this case. ++ * ++ * CONFIG_SCHED_CLUSTER requires balancing load across ++ * clusters of identical capacity, accounting for ++ * hardware and cpufreq pressure. ++ */ ++ if (!smt_degraded_cap && !cluster_equal_cap && ++ !capacity_greater(capacity_of(env->dst_cpu), capacity)) ++ continue; ++ } + + /* + * Make sure we only pull tasks from a CPU of lower priority +diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c +--- a/kernel/sched/topology.c ++++ b/kernel/sched/topology.c +@@ -1995,10 +1995,6 @@ sd_init(struct sched_domain_topology_level *tl, + /* + * Convert topological properties into behaviour. + */ +- /* Don't attempt to spread across CPUs of different capacities. */ +- if ((sd->flags & SD_ASYM_CPUCAPACITY) && sd->child) +- sd->child->flags &= ~SD_PREFER_SIBLING; +- + if (sd->flags & SD_SHARE_CPUCAPACITY) { + sd->imbalance_pct = 110; + diff --git a/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch.sig b/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch.sig new file mode 100644 index 0000000..f125b5b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0143-sched-hybrid-cluster-balancing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch b/pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch new file mode 100644 index 0000000..5af4cbd --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -13934,29 +13934,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-muqss/0144-sched-nohz-idle-core.patch.sig b/pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch.sig new file mode 100644 index 0000000..9ac0c5f Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0144-sched-nohz-idle-core.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch b/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch new file mode 100644 index 0000000..5110d06 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch @@ -0,0 +1,2365 @@ +diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c +--- a/drivers/acpi/acpi_processor.c ++++ b/drivers/acpi/acpi_processor.c +@@ -994,3 +994,357 @@ end: + } + EXPORT_SYMBOL_NS_GPL(acpi_processor_evaluate_cst, "ACPI_PROCESSOR_IDLE"); + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ ++ ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++struct acpi_lpi_states_array { ++ unsigned int size; ++ unsigned int composite_states_size; ++ struct acpi_lpi_state *entries; ++ struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; ++}; ++ ++static int obj_get_integer(union acpi_object *obj, u32 *value) ++{ ++ if (obj->type != ACPI_TYPE_INTEGER) ++ return -EINVAL; ++ ++ *value = obj->integer.value; ++ return 0; ++} ++ ++#define lpi_state_debug(handle, message, state_idx) \ ++ acpi_handle_debug(handle, message " for _LPI state %u\n", state_idx) ++ ++static void process_lpi_state_package(union acpi_object *lpi_pkg, ++ struct acpi_lpi_state *lpi_state, ++ acpi_handle handle, ++ unsigned int state_idx, bool strict) ++{ ++ union acpi_object *lpi_pkg_elem, *obj; ++ ++ if (lpi_pkg->type != ACPI_TYPE_PACKAGE || lpi_pkg->package.count < 7) ++ return; ++ ++ lpi_pkg_elem = lpi_pkg->package.elements; ++ ++ /* Get the entry method first and skip the state if that fails. */ ++ obj = &lpi_pkg_elem[6]; ++ if (obj->type == ACPI_TYPE_BUFFER) { ++ struct acpi_power_register *reg; ++ ++ if (obj->buffer.length < sizeof(*reg)) { ++ lpi_state_debug(handle, "Invalid register data", state_idx); ++ return; ++ } ++ ++ reg = (struct acpi_power_register *)obj->buffer.pointer; ++ if (reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) { ++ lpi_state_debug(handle, "Unsupported entry method", state_idx); ++ return; ++ } ++ ++ lpi_state->entry_method = ACPI_CSTATE_FFH; ++ lpi_state->address = reg->address; ++ } else if (obj->type == ACPI_TYPE_INTEGER) { ++ lpi_state->entry_method = ACPI_CSTATE_INTEGER; ++ lpi_state->address = obj->integer.value; ++ } else { ++ lpi_state_debug(handle, "Invalid entry method", state_idx); ++ return; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[0], &lpi_state->min_residency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No min. residency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us min. residency", state_idx); ++ lpi_state->min_residency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[1], &lpi_state->wake_latency)) { ++ if (strict) { ++ lpi_state_debug(handle, "No wake latency", state_idx); ++ return; ++ } ++ ++ lpi_state_debug(handle, "Assuming 10 us wake latency", state_idx); ++ lpi_state->wake_latency = 10; ++ } ++ ++ if (obj_get_integer(&lpi_pkg_elem[2], &lpi_state->flags)) ++ lpi_state->flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[3], &lpi_state->arch_flags)) ++ lpi_state->arch_flags = 0; ++ ++ if (obj_get_integer(&lpi_pkg_elem[4], &lpi_state->res_cnt_freq)) ++ lpi_state->res_cnt_freq = 1; ++ ++ if (obj_get_integer(&lpi_pkg_elem[5], &lpi_state->enable_parent_state)) ++ lpi_state->enable_parent_state = 0; ++ ++ /* Skip elements [7-8] i.e. Residency/Usage counters. */ ++ ++ /* ++ * Avoid out-of-bounds access if the size of the package is less than ++ * expected. ++ */ ++ if (lpi_pkg->package.count < 10) ++ return; ++ ++ obj = &lpi_pkg_elem[9]; ++ if (obj->type == ACPI_TYPE_STRING) ++ strscpy(lpi_state->desc, obj->string.pointer, ACPI_CX_DESC_LEN); ++} ++ ++static int acpi_processor_evaluate_lpi(acpi_handle handle, ++ struct acpi_lpi_states_array *info, ++ bool strict) ++{ ++ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object *lpi_data, *lpi_pkg; ++ unsigned int lpi_pkg_count, state_idx; ++ struct acpi_lpi_state *lpi_state; ++ acpi_status status; ++ int ret = 0; ++ ++ status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); ++ if (ACPI_FAILURE(status)) { ++ acpi_handle_debug(handle, "No _LPI, giving up\n"); ++ return -ENODEV; ++ } ++ ++ lpi_data = buffer.pointer; ++ ++ /* There must be at least 4 elements = 3 elements + 1 package */ ++ if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || ++ lpi_data->package.count < 4) { ++ acpi_handle_debug(handle, "Not enough elements in _LPI\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_pkg_count = lpi_data->package.elements[2].integer.value; ++ ++ /* Validate number of power states. */ ++ if (!lpi_pkg_count || lpi_pkg_count != lpi_data->package.count - 3) { ++ acpi_handle_debug(handle, "Invalid _LPI state count\n"); ++ ret = -ENODATA; ++ goto end; ++ } ++ ++ lpi_state = kzalloc_objs(*lpi_state, lpi_pkg_count); ++ if (!lpi_state) { ++ ret = -ENOMEM; ++ goto end; ++ } ++ ++ info->size = lpi_pkg_count; ++ info->entries = lpi_state; ++ ++ /* _LPI State packages start at index 3. */ ++ lpi_pkg = &lpi_data->package.elements[3]; ++ ++ for (state_idx = 1; state_idx <= lpi_pkg_count; state_idx++) { ++ lpi_state->index = state_idx; ++ process_lpi_state_package(lpi_pkg++, lpi_state++, handle, ++ state_idx, strict); ++ } ++ ++ acpi_handle_debug(handle, "Found %u power states\n", lpi_pkg_count); ++end: ++ kfree(buffer.pointer); ++ return ret; ++} ++ ++/** ++ * combine_lpi_states - combine local and parent LPI states to form a composite LPI state ++ * ++ * @local: local LPI state ++ * @parent: parent LPI state ++ * @result: composite LPI state ++ */ ++static bool combine_lpi_states(struct acpi_lpi_state *local, ++ struct acpi_lpi_state *parent, ++ struct acpi_lpi_state *result) ++{ ++ if (parent->entry_method == ACPI_CSTATE_INTEGER) { ++ if (!parent->address) /* 0 means autopromotable */ ++ return false; ++ result->address = local->address + parent->address; ++ } else { ++ result->address = parent->address; ++ } ++ ++ result->min_residency = max(local->min_residency, parent->min_residency); ++ result->wake_latency = local->wake_latency + parent->wake_latency; ++ result->enable_parent_state = parent->enable_parent_state; ++ result->entry_method = local->entry_method; ++ ++ result->flags = parent->flags; ++ result->arch_flags = parent->arch_flags; ++ result->index = parent->index; ++ ++ scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); ++ return true; ++} ++ ++#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) ++ ++static void stash_composite_state(struct acpi_lpi_states_array *curr_level, ++ struct acpi_lpi_state *t) ++{ ++ curr_level->composite_states[curr_level->composite_states_size++] = t; ++} ++ ++static bool too_many_states(acpi_handle handle, unsigned int state_count) ++{ ++ if (state_count < ACPI_PROCESSOR_MAX_POWER) ++ return false; ++ ++ acpi_handle_info(handle, "No space for more _LPI states than %d\n", ++ ACPI_PROCESSOR_MAX_POWER); ++ return true; ++} ++ ++static unsigned int flatten_lpi_states(acpi_handle handle, ++ struct acpi_lpi_state *lpi_states, ++ unsigned int state_count, ++ struct acpi_lpi_states_array *curr, ++ struct acpi_lpi_states_array *prev) ++{ ++ struct acpi_lpi_state *parent_lpi = curr->entries; ++ unsigned int j; ++ ++ /* ++ * Combine each of the "raw" _LPI states from the current (processor ++ * container) level with all of the composite _LPI states from the ++ * previous (processor or processor container) level. ++ */ ++ for (j = 0; j < curr->size; j++, parent_lpi++) { ++ struct acpi_lpi_state *flpi; ++ int i; ++ ++ if (!(parent_lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) ++ continue; ++ ++ if (too_many_states(handle, state_count)) ++ break; ++ ++ flpi = &lpi_states[state_count]; ++ ++ for (i = 0; i < prev->composite_states_size; i++) { ++ struct acpi_lpi_state *local_lpi = prev->composite_states[i]; ++ ++ if (parent_lpi->index > local_lpi->enable_parent_state) ++ continue; ++ ++ if (!combine_lpi_states(local_lpi, parent_lpi, flpi)) ++ continue; ++ ++ stash_composite_state(curr, flpi); ++ state_count++; ++ flpi++; ++ ++ if (state_count >= ACPI_PROCESSOR_MAX_POWER) ++ break; ++ } ++ } ++ ++ return state_count; ++} ++ ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ struct acpi_lpi_states_array info[2], *prev, *curr; ++ acpi_handle handle = pr_handle; ++ unsigned int state_count = 0; ++ unsigned int i; ++ int ret; ++ ++ if (!osc_pc_lpi_support_confirmed) ++ return -EOPNOTSUPP; ++ ++ curr = &info[0]; ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ return ret; ++ ++ /* Copy all of the usable first-level states to power.lpi_states[]. */ ++ for (i = 0; i < curr->size; i++) { ++ struct acpi_lpi_state *lpi = &curr->entries[i]; ++ struct acpi_lpi_state *flpi; ++ ++ /* ++ * Skip states that are not enabled or have an inadequate entry ++ * method for this level. ++ */ ++ if (!(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) || ++ lpi->entry_method == ACPI_CSTATE_INTEGER) ++ continue; ++ ++ if (too_many_states(pr_handle, state_count)) ++ break; ++ ++ flpi = &pr_power->lpi_states[state_count++]; ++ memcpy(flpi, lpi, sizeof(*lpi)); ++ stash_composite_state(curr, flpi); ++ } ++ ++ kfree(curr->entries); ++ ++ /* ++ * If there are no _LPI states at the first level, there are no _LPI ++ * states at all. ++ */ ++ if (!state_count) ++ return -ENODATA; ++ ++ prev = curr; ++ curr = &info[1]; ++ ++ for (;;) { ++ struct acpi_lpi_states_array *tmp; ++ struct acpi_device *d; ++ ++ if (ACPI_FAILURE(acpi_get_parent(handle, &handle))) ++ break; ++ ++ d = acpi_fetch_acpi_dev(handle); ++ if (!d) ++ break; ++ ++ if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) ++ break; ++ ++ curr->composite_states_size = 0; ++ ++ ret = acpi_processor_evaluate_lpi(handle, curr, strict); ++ if (ret) ++ break; ++ ++ /* flatten all the LPI states in this level of hierarchy */ ++ state_count = flatten_lpi_states(pr_handle, pr_power->lpi_states, ++ state_count, curr, prev); ++ ++ kfree(curr->entries); ++ ++ tmp = prev, prev = curr, curr = tmp; ++ } ++ ++ /* reset the index after flattening */ ++ for (i = 0; i < state_count; i++) ++ pr_power->lpi_states[i].index = i; ++ ++ pr_power->count = state_count; ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_processor_extract_lpi_info, "ACPI_PROCESSOR_IDLE"); ++#endif /* CONFIG_ACPI_PROCESSOR_IDLE */ +diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c +--- a/drivers/acpi/processor_idle.c ++++ b/drivers/acpi/processor_idle.c +@@ -853,237 +853,6 @@ static int acpi_processor_setup_cstates(struct acpi_processor *pr) + + #endif /* CONFIG_ACPI_PROCESSOR_CSTATE */ + +-struct acpi_lpi_states_array { +- unsigned int size; +- unsigned int composite_states_size; +- struct acpi_lpi_state *entries; +- struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER]; +-}; +- +-static int obj_get_integer(union acpi_object *obj, u32 *value) +-{ +- if (obj->type != ACPI_TYPE_INTEGER) +- return -EINVAL; +- +- *value = obj->integer.value; +- return 0; +-} +- +-static int acpi_processor_evaluate_lpi(acpi_handle handle, +- struct acpi_lpi_states_array *info) +-{ +- acpi_status status; +- int ret = 0; +- int pkg_count, state_idx = 1, loop; +- struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; +- union acpi_object *lpi_data; +- struct acpi_lpi_state *lpi_state; +- +- status = acpi_evaluate_object(handle, "_LPI", NULL, &buffer); +- if (ACPI_FAILURE(status)) { +- acpi_handle_debug(handle, "No _LPI, giving up\n"); +- return -ENODEV; +- } +- +- lpi_data = buffer.pointer; +- +- /* There must be at least 4 elements = 3 elements + 1 package */ +- if (!lpi_data || lpi_data->type != ACPI_TYPE_PACKAGE || +- lpi_data->package.count < 4) { +- pr_debug("not enough elements in _LPI\n"); +- ret = -ENODATA; +- goto end; +- } +- +- pkg_count = lpi_data->package.elements[2].integer.value; +- +- /* Validate number of power states. */ +- if (pkg_count < 1 || pkg_count != lpi_data->package.count - 3) { +- pr_debug("count given by _LPI is not valid\n"); +- ret = -ENODATA; +- goto end; +- } +- +- lpi_state = kzalloc_objs(*lpi_state, pkg_count); +- if (!lpi_state) { +- ret = -ENOMEM; +- goto end; +- } +- +- info->size = pkg_count; +- info->entries = lpi_state; +- +- /* LPI States start at index 3 */ +- for (loop = 3; state_idx <= pkg_count; loop++, state_idx++, lpi_state++) { +- union acpi_object *element, *pkg_elem, *obj; +- +- element = &lpi_data->package.elements[loop]; +- if (element->type != ACPI_TYPE_PACKAGE || element->package.count < 7) +- continue; +- +- pkg_elem = element->package.elements; +- +- obj = pkg_elem + 6; +- if (obj->type == ACPI_TYPE_BUFFER) { +- struct acpi_power_register *reg; +- +- if (obj->buffer.length < sizeof(*reg)) { +- acpi_handle_debug(handle, +- "Invalid register data for _LPI state %d\n", +- state_idx); +- continue; +- } +- +- reg = (struct acpi_power_register *)obj->buffer.pointer; +- if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_IO && +- reg->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE) +- continue; +- +- lpi_state->address = reg->address; +- lpi_state->entry_method = +- reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE ? +- ACPI_CSTATE_FFH : ACPI_CSTATE_SYSTEMIO; +- } else if (obj->type == ACPI_TYPE_INTEGER) { +- lpi_state->entry_method = ACPI_CSTATE_INTEGER; +- lpi_state->address = obj->integer.value; +- } else { +- pr_debug("Entry method of state-%d is invalid, disable it.\n", +- state_idx); +- continue; +- } +- +- lpi_state->index = state_idx; +- if (obj_get_integer(pkg_elem + 0, &lpi_state->min_residency)) { +- pr_debug("No min. residency found, assuming 10 us\n"); +- lpi_state->min_residency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 1, &lpi_state->wake_latency)) { +- pr_debug("No wakeup residency found, assuming 10 us\n"); +- lpi_state->wake_latency = 10; +- } +- +- if (obj_get_integer(pkg_elem + 2, &lpi_state->flags)) +- lpi_state->flags = 0; +- +- if (obj_get_integer(pkg_elem + 3, &lpi_state->arch_flags)) +- lpi_state->arch_flags = 0; +- +- if (obj_get_integer(pkg_elem + 4, &lpi_state->res_cnt_freq)) +- lpi_state->res_cnt_freq = 1; +- +- if (obj_get_integer(pkg_elem + 5, &lpi_state->enable_parent_state)) +- lpi_state->enable_parent_state = 0; +- +- /* Skip elements [7-8] i.e. Residency/Usage counters. */ +- +- /* +- * Avoid out-of-bounds access if the size of the package is less +- * than expected. +- */ +- if (element->package.count < 10) +- continue; +- +- obj = pkg_elem + 9; +- if (obj->type == ACPI_TYPE_STRING) +- strscpy(lpi_state->desc, obj->string.pointer, +- ACPI_CX_DESC_LEN); +- } +- +- acpi_handle_debug(handle, "Found %d power states\n", state_idx); +-end: +- kfree(buffer.pointer); +- return ret; +-} +- +-/** +- * combine_lpi_states - combine local and parent LPI states to form a composite LPI state +- * +- * @local: local LPI state +- * @parent: parent LPI state +- * @result: composite LPI state +- */ +-static bool combine_lpi_states(struct acpi_lpi_state *local, +- struct acpi_lpi_state *parent, +- struct acpi_lpi_state *result) +-{ +- if (parent->entry_method == ACPI_CSTATE_INTEGER) { +- if (!parent->address) /* 0 means autopromotable */ +- return false; +- result->address = local->address + parent->address; +- } else { +- result->address = parent->address; +- } +- +- result->min_residency = max(local->min_residency, parent->min_residency); +- result->wake_latency = local->wake_latency + parent->wake_latency; +- result->enable_parent_state = parent->enable_parent_state; +- result->entry_method = local->entry_method; +- +- result->flags = parent->flags; +- result->arch_flags = parent->arch_flags; +- result->index = parent->index; +- +- scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); +- return true; +-} +- +-#define ACPI_LPI_STATE_FLAGS_ENABLED BIT(0) +- +-static void stash_composite_state(struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_state *t) +-{ +- curr_level->composite_states[curr_level->composite_states_size++] = t; +-} +- +-static unsigned int flatten_lpi_states(struct acpi_processor *pr, +- unsigned int flat_state_cnt, +- struct acpi_lpi_states_array *curr_level, +- struct acpi_lpi_states_array *prev_level) +-{ +- int i, j, state_count = curr_level->size; +- struct acpi_lpi_state *p, *t = curr_level->entries; +- +- curr_level->composite_states_size = 0; +- for (j = 0; j < state_count; j++, t++) { +- struct acpi_lpi_state *flpi; +- +- if (!(t->flags & ACPI_LPI_STATE_FLAGS_ENABLED)) +- continue; +- +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) { +- pr_warn("Limiting number of LPI states to max (%d)\n", +- ACPI_PROCESSOR_MAX_POWER); +- pr_warn("Please increase ACPI_PROCESSOR_MAX_POWER if needed.\n"); +- break; +- } +- +- flpi = &pr->power.lpi_states[flat_state_cnt]; +- +- if (!prev_level) { /* leaf/processor node */ +- memcpy(flpi, t, sizeof(*t)); +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- continue; +- } +- +- for (i = 0; i < prev_level->composite_states_size; i++) { +- p = prev_level->composite_states[i]; +- if (t->index <= p->enable_parent_state && +- combine_lpi_states(p, t, flpi)) { +- stash_composite_state(curr_level, flpi); +- flat_state_cnt++; +- flpi++; +- if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) +- break; +- } +- } +- } +- +- kfree(curr_level->entries); +- return flat_state_cnt; +-} +- + int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + { + return -EOPNOTSUPP; +@@ -1091,64 +860,16 @@ int __weak acpi_processor_ffh_lpi_probe(unsigned int cpu) + + static int acpi_processor_get_lpi_info(struct acpi_processor *pr) + { +- int ret, i; +- acpi_status status; +- acpi_handle handle = pr->handle, pr_ahandle; +- struct acpi_device *d = NULL; +- struct acpi_lpi_states_array info[2], *tmp, *prev, *curr; +- unsigned int state_count; ++ int ret; + + /* make sure our architecture has support */ + ret = acpi_processor_ffh_lpi_probe(pr->id); + if (ret == -EOPNOTSUPP) + return ret; + +- if (!osc_pc_lpi_support_confirmed) +- return -EOPNOTSUPP; +- +- if (!acpi_has_method(handle, "_LPI")) +- return -EINVAL; +- +- prev = &info[0]; +- curr = &info[1]; +- handle = pr->handle; +- ret = acpi_processor_evaluate_lpi(handle, prev); ++ ret = acpi_processor_extract_lpi_info(pr->handle, &pr->power, false); + if (ret) + return ret; +- state_count = flatten_lpi_states(pr, 0, prev, NULL); +- +- status = acpi_get_parent(handle, &pr_ahandle); +- while (ACPI_SUCCESS(status)) { +- d = acpi_fetch_acpi_dev(pr_ahandle); +- if (!d) +- break; +- +- handle = pr_ahandle; +- +- if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID)) +- break; +- +- /* can be optional ? */ +- if (!acpi_has_method(handle, "_LPI")) +- break; +- +- ret = acpi_processor_evaluate_lpi(handle, curr); +- if (ret) +- break; +- +- /* flatten all the LPI states in this level of hierarchy */ +- state_count = flatten_lpi_states(pr, state_count, curr, prev); +- +- tmp = prev, prev = curr, curr = tmp; +- +- status = acpi_get_parent(handle, &pr_ahandle); +- } +- +- /* reset the index after flattening */ +- for (i = 0; i < state_count; i++) +- pr->power.lpi_states[i].index = i; +- +- pr->power.count = state_count; + + /* Tell driver that _LPI is supported. */ + pr->flags.has_lpi = 1; +diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c +--- a/drivers/base/power/main.c ++++ b/drivers/base/power/main.c +@@ -41,6 +41,9 @@ + #include "../base.h" + #include "power.h" + ++#undef MODULE_PARAM_PREFIX ++#define MODULE_PARAM_PREFIX "pm." ++ + typedef int (*pm_callback_t)(struct device *); + + /* +@@ -532,6 +535,11 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644); + MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace, + "Backtrace all CPUs on DPM watchdog timeout"); + ++static bool __read_mostly dpm_watchdog_enabled = ++ IS_ENABLED(CONFIG_DPM_WATCHDOG_ENABLED); ++module_param(dpm_watchdog_enabled, bool, 0644); ++MODULE_PARM_DESC(dpm_watchdog_enabled, "Enable DPM watchdog"); ++ + static unsigned int __read_mostly dpm_watchdog_timeout = CONFIG_DPM_WATCHDOG_TIMEOUT; + static unsigned int __read_mostly dpm_watchdog_warning_timeout = + CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT; +@@ -627,6 +635,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + wd->dev = dev; + wd->tsk = current; + wd->fatal = dpm_watchdog_timeout == dpm_watchdog_warning_timeout; +@@ -645,6 +656,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd) + { + struct timer_list *timer = &wd->timer; + ++ if (!dpm_watchdog_enabled) ++ return; ++ + timer_delete_sync(timer); + timer_destroy_on_stack(timer); + } +diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c +--- a/drivers/base/power/runtime.c ++++ b/drivers/base/power/runtime.c +@@ -469,9 +469,6 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) + if (retval == -EACCES) + retval = -EAGAIN; + +- if (retval != -EAGAIN && retval != -EBUSY) +- dev->power.runtime_error = retval; +- + return retval; + } + +@@ -751,6 +748,9 @@ static int rpm_suspend(struct device *dev, int rpmflags) + dev->power.deferred_resume = false; + wake_up_all(&dev->power.wait_queue); + ++ if (retval != -EAGAIN && retval != -EBUSY) ++ dev->power.runtime_error = retval; ++ + /* + * On transient errors, if the callback routine failed an autosuspend, + * and if the last_busy time has been updated so that there is a new +diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c +--- a/drivers/cpufreq/amd-pstate-ut.c ++++ b/drivers/cpufreq/amd-pstate-ut.c +@@ -59,6 +59,7 @@ static int amd_pstate_ut_check_freq(u32 index); + static int amd_pstate_ut_epp(u32 index); + static int amd_pstate_ut_check_driver(u32 index); + static int amd_pstate_ut_check_freq_attrs(u32 index); ++static int amd_pstate_ut_check_floor_freq(u32 index); + + static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid }, +@@ -68,6 +69,7 @@ static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = { + {"amd_pstate_ut_epp", amd_pstate_ut_epp }, + {"amd_pstate_ut_check_driver", amd_pstate_ut_check_driver }, + {"amd_pstate_ut_check_freq_attrs", amd_pstate_ut_check_freq_attrs }, ++ {"amd_pstate_ut_check_floor_freq", amd_pstate_ut_check_floor_freq }, + }; + + static bool test_in_list(const char *list, const char *name) +@@ -275,6 +277,7 @@ static int amd_pstate_set_mode(enum amd_pstate_mode mode) + static int amd_pstate_ut_epp(u32 index) + { + static const char * const epp_strings[] = { ++ "dynamic", + "power", + "balance_power", + "balance_performance", +@@ -282,10 +285,10 @@ static int amd_pstate_ut_epp(u32 index) + }; + char *buf __free(cleanup_page) = NULL; + struct cpufreq_policy *policy = NULL; ++ unsigned long orig_dynamic_epp = 0; + enum amd_pstate_mode orig_mode; + struct amd_cpudata *cpudata; + unsigned long orig_policy; +- bool orig_dynamic_epp; + int ret, cpu = 0; + u16 epp; + int i; +@@ -294,9 +297,11 @@ static int amd_pstate_ut_epp(u32 index) + if (!policy) + return -ENODEV; + +- cpudata = policy->driver_data; + orig_mode = amd_pstate_get_status(); +- orig_dynamic_epp = cpudata->dynamic_epp; ++ if (policy->driver_data) { ++ cpudata = policy->driver_data; ++ orig_dynamic_epp = cpudata->dynamic_epp; ++ } + + /* Drop reference before potential driver change. */ + cpufreq_cpu_put(policy); +@@ -321,16 +326,6 @@ static int amd_pstate_ut_epp(u32 index) + orig_policy = cpudata->policy; + cpudata->policy = CPUFREQ_POLICY_POWERSAVE; + +- /* +- * Disable dynamic EPP before running test. If "orig_dynamic_epp" is +- * true, the driver will do a redundant switch at the end and there +- * is no need for enabling it again at the end of the test. +- */ +- if (cpudata->dynamic_epp) { +- pr_debug("Dynamic EPP is enabled, disabling it\n"); +- amd_pstate_clear_dynamic_epp(policy); +- } +- + for (epp = 0; epp <= U8_MAX; epp++) { + u8 val; + +@@ -367,6 +362,11 @@ static int amd_pstate_ut_epp(u32 index) + if (ret < 0) + goto out; + strreplace(buf, '\n', '\0'); ++ /* ++ * "dynamic" mode reports the EPP as "dynamic(profile:X)" ++ * Trim at "(" and just compare tie the epp string. ++ */ ++ strreplace(buf, '(', '\0'); + + if (strcmp(buf, epp_strings[i])) { + pr_err("String EPP value mismatch: %s != %s\n", buf, epp_strings[i]); +@@ -380,18 +380,23 @@ static int amd_pstate_ut_epp(u32 index) + out: + if (policy) { + cpudata->policy = orig_policy; ++ /* ++ * If the driver had enabled dynamic_epp to begin with, ++ * restore it here before dropping policy reference. ++ */ ++ if (orig_dynamic_epp) { ++ int ret2; ++ ++ ret2 = store_energy_performance_preference(policy, ++ epp_strings[0], ++ strlen(epp_strings[0])); ++ if (!ret && (ret2 < 0)) ++ ret = ret2; ++ } + up_write(&policy->rwsem); + cpufreq_cpu_put(policy); + } + +- if (orig_dynamic_epp) { +- int ret2; +- +- ret2 = amd_pstate_set_mode(AMD_PSTATE_DISABLE); +- if (!ret && ret2) +- ret = ret2; +- } +- + if (orig_mode != amd_pstate_get_status()) { + int ret2; + +@@ -557,6 +562,80 @@ out: + return ret; + } + ++static int amd_pstate_ut_check_floor_freq(u32 index) ++{ ++ struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL; ++ char *buf __free(cleanup_page) = NULL; ++ unsigned int orig_floor_freq; ++ unsigned int floor_freq; ++ int ret, cpu = 0; ++ ++ if (!cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO)) ++ return -EOPNOTSUPP; ++ ++ policy = cpufreq_cpu_get(cpu); ++ if (!policy) ++ return -ENODEV; ++ ++ buf = (char *)__get_free_page(GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ guard(rwsem_write)(&policy->rwsem); ++ ++ if (!policy->driver_data) ++ return -ENODEV; ++ ++ /* Retrieve original floor frequency */ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ ret = kstrtou32(buf, 0, &orig_floor_freq); ++ if (ret) ++ return ret; ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", policy->cpuinfo.min_freq); ++ ++ /* Set floor frequency to cpuinfo.min_freq */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to set floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ ret = show_amd_pstate_floor_freq(policy, buf); ++ if (ret < 0) ++ return ret; ++ ++ strreplace(buf, '\n', '\0'); ++ ret = kstrtou32(buf, 0, &floor_freq); ++ if (ret) ++ return ret; ++ ++ /* Confirm sysfs reflects the change correctly. */ ++ if (floor_freq != policy->cpuinfo.min_freq) { ++ pr_err("Floor frequency value mismatch: %u != %u\n", ++ floor_freq, policy->cpuinfo.min_freq); ++ return -EINVAL; ++ } ++ ++ memset(buf, 0, PAGE_SIZE); ++ snprintf(buf, PAGE_SIZE, "%u", orig_floor_freq); ++ ++ /* Restore the original value. */ ++ ret = store_amd_pstate_floor_freq(policy, buf, strlen(buf)); ++ if (ret < 0) { ++ pr_err("Failed to restore floor frequency to %s\n", buf); ++ return ret; ++ } ++ ++ return 0; ++} ++ + static int __init amd_pstate_ut_init(void) + { + u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases); +@@ -575,10 +654,16 @@ static int __init amd_pstate_ut_init(void) + + ret = amd_pstate_ut_cases[i].func(i); + +- if (ret) ++ if (ret) { ++ /* Platform does not support the feature being tested. */ ++ if (ret == -EOPNOTSUPP) { ++ pr_err("%-4d %-20s\t skipped!\n", i+1, amd_pstate_ut_cases[i].name); ++ continue; ++ } + pr_err("%-4d %-20s\t fail: %d!\n", i+1, amd_pstate_ut_cases[i].name, ret); +- else ++ } else { + pr_info("%-4d %-20s\t success!\n", i+1, amd_pstate_ut_cases[i].name); ++ } + } + + return 0; +diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c +--- a/drivers/cpufreq/amd-pstate.c ++++ b/drivers/cpufreq/amd-pstate.c +@@ -75,7 +75,7 @@ const char *amd_pstate_get_mode_string(enum amd_pstate_mode mode) + mode = AMD_PSTATE_UNDEFINED; + return amd_pstate_mode_string[mode]; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_mode_string); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_mode_string); + + struct quirk_entry { + u32 nominal_freq; +@@ -87,7 +87,6 @@ static struct cpufreq_driver amd_pstate_driver; + static struct cpufreq_driver amd_pstate_epp_driver; + static int cppc_state = AMD_PSTATE_UNDEFINED; + static bool amd_pstate_prefcore = true; +-static bool dynamic_epp; + static struct quirk_entry *quirks; + + /* +@@ -106,6 +105,7 @@ static struct quirk_entry *quirks; + * 3 balance_power + * 4 power + * 5 custom (for raw EPP values) ++ * 6 dynamic (platform profile driven selection) + */ + enum energy_perf_value_index { + EPP_INDEX_DEFAULT = 0, +@@ -114,6 +114,7 @@ enum energy_perf_value_index { + EPP_INDEX_BALANCE_POWERSAVE, + EPP_INDEX_POWERSAVE, + EPP_INDEX_CUSTOM, ++ EPP_INDEX_DYNAMIC, + EPP_INDEX_MAX, + }; + +@@ -124,6 +125,7 @@ static const char * const energy_perf_strings[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = "balance_power", + [EPP_INDEX_POWERSAVE] = "power", + [EPP_INDEX_CUSTOM] = "custom", ++ [EPP_INDEX_DYNAMIC] = "dynamic", + }; + static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); + +@@ -134,7 +136,7 @@ static unsigned int epp_values[] = { + [EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_BALANCE_POWERSAVE, + [EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_POWERSAVE, + }; +-static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 1); ++static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 2); + + typedef int (*cppc_mode_transition_fn)(int); + +@@ -462,7 +464,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + { + union perf_cached perf = READ_ONCE(cpudata->perf); + u64 cap1, numerator, cppc_req; +- u8 min_perf; + + int ret = rdmsrq_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, + &cap1); +@@ -478,16 +479,6 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + return ret; + + WRITE_ONCE(cpudata->cppc_req_cached, cppc_req); +- min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); +- +- /* +- * Clear out the min_perf part to check if the rest of the MSR is 0, if yes, this is an +- * indication that the min_perf value is the one specified through the BIOS option +- */ +- cppc_req &= ~(AMD_CPPC_MIN_PERF_MASK); +- +- if (!cppc_req) +- perf.bios_min_perf = min_perf; + + perf.highest_perf = numerator; + perf.max_limit_perf = numerator; +@@ -495,6 +486,7 @@ static int msr_init_perf(struct amd_cpudata *cpudata) + perf.nominal_perf = FIELD_GET(AMD_CPPC_NOMINAL_PERF_MASK, cap1); + perf.lowest_nonlinear_perf = FIELD_GET(AMD_CPPC_LOWNONLIN_PERF_MASK, cap1); + perf.lowest_perf = FIELD_GET(AMD_CPPC_LOWEST_PERF_MASK, cap1); ++ perf.bios_min_perf = FIELD_GET(AMD_CPPC_MIN_PERF_MASK, cppc_req); + WRITE_ONCE(cpudata->perf, perf); + WRITE_ONCE(cpudata->prefcore_ranking, FIELD_GET(AMD_CPPC_HIGHEST_PERF_MASK, cap1)); + WRITE_ONCE(cpudata->floor_perf_cnt, FIELD_GET(AMD_CPPC_FLOOR_PERF_CNT_MASK, cap1)); +@@ -789,6 +781,7 @@ static unsigned int amd_pstate_fast_switch(struct cpufreq_policy *policy, + static void amd_pstate_adjust_perf(struct cpufreq_policy *policy, + unsigned long _min_perf, + unsigned long target_perf, ++ unsigned long _max_perf, + unsigned long capacity) + { + u8 max_perf, min_perf, des_perf, cap_perf; +@@ -1044,6 +1037,13 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) + return -EINVAL; + } + ++ if (perf.bios_min_perf) { ++ u32 bios_min_freq = perf_to_freq(perf, cpudata->nominal_freq, perf.bios_min_perf); ++ ++ pr_debug("Found Requested CPU Min Frequency of %uKHz on CPU%d\n", ++ bios_min_freq, cpudata->cpu); ++ } ++ + return 0; + } + +@@ -1192,6 +1192,24 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, + return NOTIFY_OK; + } + ++static int amd_pstate_get_epp_from_platform_profile(struct cpufreq_policy *policy, ++ enum platform_profile_option profile) ++{ ++ switch (profile) { ++ case PLATFORM_PROFILE_PERFORMANCE: ++ return AMD_CPPC_EPP_PERFORMANCE; ++ case PLATFORM_PROFILE_BALANCED: ++ return amd_pstate_get_balanced_epp(policy); ++ case PLATFORM_PROFILE_LOW_POWER: ++ return AMD_CPPC_EPP_POWERSAVE; ++ default: ++ break; ++ } ++ ++ pr_err("Unknown Platform Profile %d\n", profile); ++ return -EOPNOTSUPP; ++} ++ + static int amd_pstate_profile_probe(void *drvdata, unsigned long *choices) + { + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); +@@ -1217,31 +1235,19 @@ static int amd_pstate_profile_set(struct device *dev, + struct amd_cpudata *cpudata = dev_get_drvdata(dev); + struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpudata->cpu); + int ret; ++ u8 epp; + + if (!policy) + return -ENODEV; + +- switch (profile) { +- case PLATFORM_PROFILE_LOW_POWER: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_POWERSAVE); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_BALANCED: +- ret = amd_pstate_set_epp(policy, +- amd_pstate_get_balanced_epp(policy)); +- if (ret) +- return ret; +- break; +- case PLATFORM_PROFILE_PERFORMANCE: +- ret = amd_pstate_set_epp(policy, AMD_CPPC_EPP_PERFORMANCE); +- if (ret) +- return ret; +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; ++ ret = amd_pstate_set_epp(policy, epp); ++ if (ret) ++ return ret; + + cpudata->current_profile = profile; + +@@ -1267,28 +1273,20 @@ void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy) + kfree(cpudata->profile_name); + cpudata->dynamic_epp = false; + } +-EXPORT_SYMBOL_GPL(amd_pstate_clear_dynamic_epp); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_clear_dynamic_epp); + + static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + { + struct amd_cpudata *cpudata = policy->driver_data; ++ u64 prev = READ_ONCE(cpudata->cppc_req_cached); + int ret; + u8 epp; + +- switch (cpudata->current_profile) { +- case PLATFORM_PROFILE_PERFORMANCE: +- epp = AMD_CPPC_EPP_PERFORMANCE; +- break; +- case PLATFORM_PROFILE_LOW_POWER: +- epp = AMD_CPPC_EPP_POWERSAVE; +- break; +- case PLATFORM_PROFILE_BALANCED: +- epp = amd_pstate_get_balanced_epp(policy); +- break; +- default: +- pr_err("Unknown Platform Profile %d\n", cpudata->current_profile); +- return -EOPNOTSUPP; +- } ++ ret = amd_pstate_get_epp_from_platform_profile(policy, cpudata->current_profile); ++ if (ret < 0) ++ return ret; ++ ++ epp = (u8)ret; + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1321,6 +1319,9 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) + cleanup: + amd_pstate_clear_dynamic_epp(policy); + ++ epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, prev); ++ /* Restore previous EPP if toggling Dynamic EPP failed. */ ++ amd_pstate_set_epp(policy, epp); + return ret; + } + +@@ -1391,7 +1392,7 @@ static ssize_t show_amd_pstate_hw_prefcore(struct cpufreq_policy *policy, + static ssize_t show_energy_performance_available_preferences( + struct cpufreq_policy *policy, char *buf) + { +- int offset = 0, i; ++ int i, offset = 0; + struct amd_cpudata *cpudata = policy->driver_data; + + if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) +@@ -1414,11 +1415,6 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + bool raw_epp = false; + u8 epp; + +- if (cpudata->dynamic_epp) { +- pr_debug("EPP cannot be set when dynamic EPP is enabled\n"); +- return -EBUSY; +- } +- + /* + * if the value matches a number, use that, otherwise see if + * matches an index in the energy_perf_strings array +@@ -1429,6 +1425,25 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + ret = sysfs_match_string(energy_perf_strings, buf); + if (ret < 0 || ret == EPP_INDEX_CUSTOM) + return -EINVAL; ++ ++ if (ret == EPP_INDEX_DYNAMIC) { ++ if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) ++ return -EBUSY; ++ /* ++ * Dynamic EPP was already enabled for this CPU. ++ * Nothing to do. ++ */ ++ if (cpudata->dynamic_epp) ++ return count; ++ ++ cpudata->current_profile = PLATFORM_PROFILE_BALANCED; ++ ret = amd_pstate_set_dynamic_epp(policy); ++ if (ret) ++ return ret; ++ ++ return count; ++ } ++ + if (ret) + epp = epp_values[ret]; + else +@@ -1440,6 +1455,13 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + return -EBUSY; + } + ++ /* ++ * Dynamic EPP was enabled previously! ++ * Switch back to the static EPP mode. ++ */ ++ if (cpudata->dynamic_epp) ++ amd_pstate_clear_dynamic_epp(policy); ++ + ret = amd_pstate_set_epp(policy, epp); + if (ret) + return ret; +@@ -1448,7 +1470,7 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + + return count; + } +-EXPORT_SYMBOL_GPL(store_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_energy_performance_preference); + + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf) + { +@@ -1457,7 +1479,7 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + + epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached); + +- if (cpudata->raw_epp) ++ if (!cpudata->dynamic_epp && cpudata->raw_epp) + return sysfs_emit(buf, "%u\n", epp); + + switch (epp) { +@@ -1477,12 +1499,14 @@ ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char * + return -EINVAL; + } + ++ if (cpudata->dynamic_epp) ++ return sysfs_emit(buf, "dynamic(profile:%s)\n", energy_perf_strings[preference]); ++ + return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]); + } +-EXPORT_SYMBOL_GPL(show_energy_performance_preference); ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_energy_performance_preference); + +-static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, +- const char *buf, size_t count) ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count) + { + struct amd_cpudata *cpudata = policy->driver_data; + union perf_cached perf = READ_ONCE(cpudata->perf); +@@ -1505,13 +1529,15 @@ static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, + + return ret ?: count; + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(store_amd_pstate_floor_freq); + +-static ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf) + { + struct amd_cpudata *cpudata = policy->driver_data; + + return sysfs_emit(buf, "%u\n", cpudata->floor_freq); + } ++EXPORT_SYMBOL_FOR_PSTATE_UT(show_amd_pstate_floor_freq); + + static ssize_t show_amd_pstate_floor_count(struct cpufreq_policy *policy, char *buf) + { +@@ -1579,7 +1605,7 @@ struct freq_attr **amd_pstate_get_current_attrs(void) + return NULL; + return current_pstate_driver->attr; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_current_attrs); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_current_attrs); + + static struct freq_attr **get_freq_attrs(void) + { +@@ -1764,7 +1790,7 @@ int amd_pstate_get_status(void) + { + return cppc_state; + } +-EXPORT_SYMBOL_GPL(amd_pstate_get_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status); + + int amd_pstate_update_status(const char *buf, size_t size) + { +@@ -1784,7 +1810,7 @@ int amd_pstate_update_status(const char *buf, size_t size) + + return 0; + } +-EXPORT_SYMBOL_GPL(amd_pstate_update_status); ++EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_update_status); + + static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +@@ -1812,50 +1838,12 @@ static ssize_t prefcore_show(struct device *dev, + return sysfs_emit(buf, "%s\n", str_enabled_disabled(amd_pstate_prefcore)); + } + +-static ssize_t dynamic_epp_show(struct device *dev, +- struct device_attribute *attr, char *buf) +-{ +- return sysfs_emit(buf, "%s\n", str_enabled_disabled(dynamic_epp)); +-} +- +-static ssize_t dynamic_epp_store(struct device *a, struct device_attribute *b, +- const char *buf, size_t count) +-{ +- bool enabled; +- int ret; +- +- ret = kstrtobool(buf, &enabled); +- if (ret) +- return ret; +- +- guard(mutex)(&amd_pstate_driver_lock); +- +- if (cppc_state != AMD_PSTATE_ACTIVE) { +- pr_debug("dynamic_epp can only be toggled in active mode\n"); +- return -EINVAL; +- } +- +- /* Nothing to do */ +- if (dynamic_epp == enabled) +- return count; +- +- /* reinitialize with desired dynamic EPP value */ +- dynamic_epp = enabled; +- ret = amd_pstate_change_driver_mode(cppc_state); +- if (ret) +- dynamic_epp = false; +- +- return ret ? ret : count; +-} +- + static DEVICE_ATTR_RW(status); + static DEVICE_ATTR_RO(prefcore); +-static DEVICE_ATTR_RW(dynamic_epp); + + static struct attribute *pstate_global_attributes[] = { + &dev_attr_status.attr, + &dev_attr_prefcore.attr, +- &dev_attr_dynamic_epp.attr, + NULL + }; + +@@ -1938,12 +1926,13 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + + policy->boost_supported = READ_ONCE(cpudata->boost_supported); + +- /* Fetch the firmware programmed default EPP value */ ++ /* Cache the firmware programmed EPP */ + default_epp = amd_pstate_get_epp(cpudata); + if (default_epp < 0) { + ret = default_epp; + goto free_cpudata1; + } ++ FIELD_MODIFY(AMD_CPPC_EPP_PERF_MASK, &cpudata->cppc_req_cached, default_epp); + + /* + * Set the policy to provide a valid fallback value in case +@@ -1961,10 +1950,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) + cpudata->current_profile = PLATFORM_PROFILE_BALANCED; + } + +- if (dynamic_epp) +- ret = amd_pstate_set_dynamic_epp(policy); +- else +- ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); ++ ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); + if (ret) + goto free_cpudata1; + +@@ -2036,6 +2022,18 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy) + if (!policy->cpuinfo.max_freq) + return -ENODEV; + ++ /* Must be a switch between PERFORMANCE and POWERSAVE */ ++ if (cpudata->policy != policy->policy) { ++ /* ++ * Disable dynamic_epp when switching ++ * out of CPUFREQ_POLICY_POWERSAVE. ++ */ ++ if (cpudata->dynamic_epp) { ++ WARN_ON_ONCE(cpudata->policy != CPUFREQ_POLICY_POWERSAVE); ++ amd_pstate_clear_dynamic_epp(policy); ++ } ++ } ++ + cpudata->policy = policy->policy; + + ret = amd_pstate_epp_update_limit(policy, true); +@@ -2363,19 +2361,8 @@ static int __init amd_prefcore_param(char *str) + return 0; + } + +-static int __init amd_dynamic_epp_param(char *str) +-{ +- if (!strcmp(str, "disable")) +- dynamic_epp = false; +- if (!strcmp(str, "enable")) +- dynamic_epp = true; +- +- return 0; +-} +- + early_param("amd_pstate", amd_pstate_param); + early_param("amd_prefcore", amd_prefcore_param); +-early_param("amd_dynamic_epp", amd_dynamic_epp_param); + + MODULE_AUTHOR("Huang Rui "); + MODULE_DESCRIPTION("AMD Processor P-state Frequency Driver"); +diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h +--- a/drivers/cpufreq/amd-pstate.h ++++ b/drivers/cpufreq/amd-pstate.h +@@ -11,6 +11,13 @@ + #include + #include + ++#if IS_MODULE(CONFIG_X86_AMD_PSTATE_UT) ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) \ ++ EXPORT_SYMBOL_FOR_MODULES(symbol, "amd-pstate-ut") ++#else ++#define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) ++#endif ++ + /********************************************************************* + * AMD P-state INTERFACE * + *********************************************************************/ +@@ -153,6 +160,8 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, + const char *buf, size_t count); + ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf); + void amd_pstate_clear_dynamic_epp(struct cpufreq_policy *policy); ++ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy, const char *buf, size_t count); ++ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf); + + struct freq_attr; + +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2225,14 +2225,17 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + * @policy: cpufreq policy object of the target CPU. + * @min_perf: Minimum (required) performance level (units of @capacity). + * @target_perf: Target (desired) performance level (units of @capacity). ++ * @max_perf: Maximum (allowed) performance level (units of @capacity). + * @capacity: Capacity of the target CPU. + * +- * Carry out a fast performance level switch of @cpu without sleeping. ++ * Carry out a fast performance level adjustment for the CPU represented by ++ * @policy without sleeping. + * + * The driver's ->adjust_perf() callback invoked by this function must be +- * suitable for being called from within RCU-sched read-side critical sections +- * and it is expected to select a suitable performance level equal to or above +- * @min_perf and preferably equal to or below @target_perf. ++ * suitable for calling from within RCU-sched read-side critical sections and ++ * it is expected to program the processor to select suitable performance ++ * levels between @min_perf and @max_perf inclusive and preferably close to ++ * @target_perf going forward for the CPU represented by @policy. + * + * This function must not be called if policy->fast_switch_enabled is unset. + * +@@ -2244,9 +2247,10 @@ EXPORT_SYMBOL_GPL(cpufreq_driver_fast_switch); + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { +- cpufreq_driver->adjust_perf(policy, min_perf, target_perf, capacity); ++ cpufreq_driver->adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /** +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -299,7 +299,6 @@ struct pstate_funcs { + static struct pstate_funcs pstate_funcs __read_mostly; + + static bool hwp_active __ro_after_init; +-static int hwp_mode_bdw __ro_after_init; + static bool per_cpu_limits __ro_after_init; + static bool hwp_forced __ro_after_init; + static bool hwp_boost __read_mostly; +@@ -587,21 +586,14 @@ static void intel_pstate_hybrid_hwp_adjust(struct cpudata *cpu) + + hwp_is_hybrid = true; + +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_pstate * scaling, +- perf_ctl_scaling); +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_pstate * scaling, +- perf_ctl_scaling); +- + freq = perf_ctl_max_phys * perf_ctl_scaling; + cpu->pstate.max_pstate_physical = intel_pstate_freq_to_hwp(cpu, freq); + +- freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.min_freq = freq; + /* + * Cast the min P-state value retrieved via pstate_funcs.get_min() to + * the effective range of HWP performance levels. + */ +- cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, freq); ++ cpu->pstate.min_pstate = intel_pstate_freq_to_hwp(cpu, cpu->pstate.min_freq); + } + + static bool turbo_is_disabled(void) +@@ -979,12 +971,10 @@ static int hybrid_get_cost(struct device *dev, unsigned long freq, + * capacity. Similarly, P-cores start to be populated when E-cores are + * utilized above 60% of the capacity. + */ +- if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_ATOM) { +- if (hybrid_has_l3(dev->id)) /* E-core */ +- *cost += 1; +- } else { /* P-core */ ++ if (hybrid_get_cpu_type(dev->id) == INTEL_CPU_TYPE_CORE) /* P-core */ + *cost += 2; +- } ++ else if (hybrid_has_l3(dev->id)) /* E-core */ ++ *cost += 1; + + return 0; + } +@@ -1185,6 +1175,22 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static void intel_pstate_update_freq_limits(struct cpudata *cpu) ++{ ++ int scaling = cpu->pstate.scaling; ++ unsigned int turbo_freq = cpu->pstate.turbo_pstate * scaling; ++ unsigned int max_freq = cpu->pstate.max_pstate * scaling; ++ int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; ++ ++ if (scaling != perf_ctl_scaling) { ++ turbo_freq = rounddown(turbo_freq, perf_ctl_scaling); ++ max_freq = rounddown(max_freq, perf_ctl_scaling); ++ } ++ ++ cpu->pstate.turbo_freq = turbo_freq; ++ cpu->pstate.max_freq = max_freq; ++} ++ + static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + { + u64 cap; +@@ -1197,20 +1203,8 @@ static void __intel_pstate_get_hwp_cap(struct cpudata *cpu) + + static void intel_pstate_get_hwp_cap(struct cpudata *cpu) + { +- int scaling = cpu->pstate.scaling; +- + __intel_pstate_get_hwp_cap(cpu); +- +- cpu->pstate.max_freq = cpu->pstate.max_pstate * scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * scaling; +- if (scaling != cpu->pstate.perf_ctl_scaling) { +- int perf_ctl_scaling = cpu->pstate.perf_ctl_scaling; +- +- cpu->pstate.max_freq = rounddown(cpu->pstate.max_freq, +- perf_ctl_scaling); +- cpu->pstate.turbo_freq = rounddown(cpu->pstate.turbo_freq, +- perf_ctl_scaling); +- } ++ intel_pstate_update_freq_limits(cpu); + } + + static void hybrid_update_capacity(struct cpudata *cpu) +@@ -2299,33 +2293,16 @@ static int hwp_get_cpu_scaling(int cpu) + return intel_pstate_cppc_get_scaling(cpu); + } + +-static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) +-{ +- trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); +- cpu->pstate.current_pstate = pstate; +- /* +- * Generally, there is no guarantee that this code will always run on +- * the CPU being updated, so force the register update to run on the +- * right CPU. +- */ +- wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, +- pstate_funcs.get_val(cpu, pstate)); +-} +- +-static void intel_pstate_set_min_pstate(struct cpudata *cpu) +-{ +- intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); +-} +- + static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + { + int perf_ctl_scaling = pstate_funcs.get_scaling(); + + cpu->pstate.max_pstate_physical = pstate_funcs.get_max_physical(cpu->cpu); + cpu->pstate.min_pstate = pstate_funcs.get_min(cpu->cpu); ++ cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; + cpu->pstate.perf_ctl_scaling = perf_ctl_scaling; + +- if (hwp_active && !hwp_mode_bdw) { ++ if (hwp_active) { + __intel_pstate_get_hwp_cap(cpu); + + if (pstate_funcs.get_cpu_scaling) { +@@ -2345,11 +2322,7 @@ static void intel_pstate_get_cpu_pstates(struct cpudata *cpu) + cpu->pstate.turbo_pstate = pstate_funcs.get_turbo(cpu->cpu); + } + +- if (cpu->pstate.scaling == perf_ctl_scaling) { +- cpu->pstate.min_freq = cpu->pstate.min_pstate * perf_ctl_scaling; +- cpu->pstate.max_freq = cpu->pstate.max_pstate * perf_ctl_scaling; +- cpu->pstate.turbo_freq = cpu->pstate.turbo_pstate * perf_ctl_scaling; +- } ++ intel_pstate_update_freq_limits(cpu); + + if (pstate_funcs.get_aperf_mperf_shift) + cpu->aperf_mperf_shift = pstate_funcs.get_aperf_mperf_shift(); +@@ -2882,8 +2855,22 @@ static void intel_pstate_update_perf_limits(struct cpudata *cpu, + cpu->min_perf_ratio); + } + ++static void intel_pstate_set_pstate(struct cpudata *cpu, int pstate) ++{ ++ trace_cpu_frequency(pstate * cpu->pstate.scaling, cpu->cpu); ++ cpu->pstate.current_pstate = pstate; ++ /* ++ * Generally, there is no guarantee that this code will always run on ++ * the CPU being updated, so force the register update to run on the ++ * right CPU. ++ */ ++ wrmsrq_on_cpu(cpu->cpu, MSR_IA32_PERF_CTL, ++ pstate_funcs.get_val(cpu, pstate)); ++} ++ + static int intel_pstate_set_policy(struct cpufreq_policy *policy) + { ++ unsigned int freq = policy->min; + struct cpudata *cpu; + + if (!policy->cpuinfo.max_freq) +@@ -2899,7 +2886,23 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + + intel_pstate_update_perf_limits(cpu, policy->min, policy->max); + +- if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ if (hwp_active) { ++ /* ++ * The active mode only requires an update util hook if HWP ++ * boost is used and the policy is not "performance". ++ */ ++ if (hwp_boost && cpu->policy != CPUFREQ_POLICY_PERFORMANCE) { ++ intel_pstate_set_update_util_hook(policy->cpu); ++ } else { ++ intel_pstate_clear_update_util_hook(policy->cpu); ++ if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { ++ freq = cpu->max_perf_ratio * cpu->pstate.scaling; ++ if (cpu->pstate.scaling != cpu->pstate.perf_ctl_scaling) ++ freq = rounddown(freq, cpu->pstate.perf_ctl_scaling); ++ } ++ } ++ intel_pstate_hwp_set(policy->cpu); ++ } else if (cpu->policy == CPUFREQ_POLICY_PERFORMANCE) { + int pstate = max(cpu->pstate.min_pstate, cpu->max_perf_ratio); + + /* +@@ -2908,25 +2911,17 @@ static int intel_pstate_set_policy(struct cpufreq_policy *policy) + */ + intel_pstate_clear_update_util_hook(policy->cpu); + intel_pstate_set_pstate(cpu, pstate); ++ freq = pstate * cpu->pstate.scaling; + } else { + intel_pstate_set_update_util_hook(policy->cpu); + } +- +- if (hwp_active) { +- /* +- * When hwp_boost was active before and dynamically it +- * was turned off, in that case we need to clear the +- * update util hook. +- */ +- if (!hwp_boost) +- intel_pstate_clear_update_util_hook(policy->cpu); +- intel_pstate_hwp_set(policy->cpu); +- } + /* +- * policy->cur is never updated with the intel_pstate driver, but it +- * is used as a stale frequency value. So, keep it within limits. ++ * policy->cur is never updated in the intel_pstate driver, but it is ++ * used as a stale frequency value, so set it to reflect the actual ++ * requested P-state in the "performance" policy case and to the min ++ * otherwise. + */ +- policy->cur = policy->min; ++ policy->cur = freq; + + mutex_unlock(&intel_pstate_limits_lock); + +@@ -2969,6 +2964,11 @@ static int intel_pstate_verify_policy(struct cpufreq_policy_data *policy) + return 0; + } + ++static void intel_pstate_set_min_pstate(struct cpudata *cpu) ++{ ++ intel_pstate_set_pstate(cpu, cpu->pstate.min_pstate); ++} ++ + static int intel_cpufreq_cpu_offline(struct cpufreq_policy *policy) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3242,6 +3242,7 @@ static unsigned int intel_cpufreq_fast_switch(struct cpufreq_policy *policy, + static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity) + { + struct cpudata *cpu = all_cpu_data[policy->cpu]; +@@ -3272,7 +3273,13 @@ static void intel_cpufreq_adjust_perf(struct cpufreq_policy *policy, + if (min_pstate > cpu->max_perf_ratio) + min_pstate = cpu->max_perf_ratio; + +- max_pstate = min(cap_pstate, cpu->max_perf_ratio); ++ max_pstate = cap_pstate; ++ if (max_perf < capacity) ++ max_pstate = DIV_ROUND_UP(cap_pstate * max_perf, capacity); ++ ++ if (max_pstate > cpu->max_perf_ratio) ++ max_pstate = cpu->max_perf_ratio; ++ + if (max_pstate < min_pstate) + max_pstate = min_pstate; + +@@ -3679,14 +3686,14 @@ static inline bool intel_pstate_has_acpi_ppc(void) { return false; } + static inline void intel_pstate_request_control_from_smm(void) {} + #endif /* CONFIG_ACPI */ + +-#define INTEL_PSTATE_HWP_BROADWELL 0x01 ++#define INTEL_PSTATE_HWP_NOT_HYBRID 0x01 + + #define X86_MATCH_HWP(vfm, hwp_mode) \ + X86_MATCH_VFM_FEATURE(vfm, X86_FEATURE_HWP, hwp_mode) + + static const struct x86_cpu_id hwp_support_ids[] __initconst = { +- X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_BROADWELL), +- X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_BROADWELL), ++ X86_MATCH_HWP(INTEL_BROADWELL_X, INTEL_PSTATE_HWP_NOT_HYBRID), ++ X86_MATCH_HWP(INTEL_BROADWELL_D, INTEL_PSTATE_HWP_NOT_HYBRID), + X86_MATCH_HWP(INTEL_ANY, 0), + {} + }; +@@ -3811,7 +3818,6 @@ static int __init intel_pstate_init(void) + + if (!no_hwp) { + hwp_active = true; +- hwp_mode_bdw = id->driver_data; + intel_pstate.attr = hwp_cpufreq_attrs; + intel_cpufreq.attr = hwp_cpufreq_attrs; + intel_cpufreq.flags |= CPUFREQ_NEED_UPDATE_LIMITS; +@@ -3819,7 +3825,8 @@ static int __init intel_pstate_init(void) + if (!default_driver) + default_driver = &intel_pstate; + +- pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; ++ if (!id->driver_data) ++ pstate_funcs.get_cpu_scaling = hwp_get_cpu_scaling; + + goto hwp_cpu_matched; + } +diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c +--- a/drivers/idle/intel_idle.c ++++ b/drivers/idle/intel_idle.c +@@ -1780,6 +1780,7 @@ module_param_named(no_native, no_native, bool, 0444); + MODULE_PARM_DESC(no_native, "Ignore cpu specific (native) idle states in lieu of ACPI idle states"); + + static struct acpi_processor_power acpi_state_table __initdata; ++static bool acpi_lpi_available __initdata; + + /** + * intel_idle_cst_usable - Check if the _CST information can be used. +@@ -1804,18 +1805,37 @@ static bool __init intel_idle_cst_usable(void) + return true; + } + +-static bool __init intel_idle_acpi_cst_extract(void) ++static bool __init intel_idle_acpi_extract_lpi_cstates(void) + { + unsigned int cpu; + +- if (no_acpi) { +- pr_debug("Not allowed to use ACPI _CST\n"); +- return false; ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); ++ if (!pr) ++ continue; ++ ++ if (acpi_processor_extract_lpi_info(pr->handle, ++ &acpi_state_table, true)) ++ continue; ++ ++ acpi_lpi_available = true; ++ return true; + } + +- for_each_possible_cpu(cpu) { +- struct acpi_processor *pr = per_cpu(processors, cpu); ++ pr_debug("No ACPI _LPI idle states\n"); ++ return false; ++} + ++static bool __init intel_idle_acpi_extract_cst_cstates(void) ++{ ++ unsigned int cpu; ++ ++ for_each_possible_cpu(cpu) { ++ struct acpi_processor *pr; ++ ++ pr = per_cpu(processors, cpu); + if (!pr) + continue; + +@@ -1827,18 +1847,96 @@ static bool __init intel_idle_acpi_cst_extract(void) + if (!intel_idle_cst_usable()) + continue; + +- if (!acpi_processor_claim_cst_control()) +- break; +- + return true; + } + +- acpi_state_table.count = 0; + pr_debug("ACPI _CST not found or not usable\n"); + return false; + } + +-static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) ++static bool __init intel_idle_acpi_extract_cstates(void) ++{ ++ if (intel_idle_acpi_extract_lpi_cstates()) ++ return true; ++ ++ if (intel_idle_acpi_extract_cst_cstates()) ++ return true; ++ ++ return false; ++} ++ ++static bool __init intel_idle_acpi_probe(void) ++{ ++ if (no_acpi) { ++ pr_debug("Not allowed to use ACPI for C-states extraction\n"); ++ return false; ++ } ++ ++ if (intel_idle_acpi_extract_cstates() && ++ acpi_processor_claim_cst_control()) ++ return true; ++ ++ acpi_state_table.count = 0; ++ return false; ++} ++ ++static void __init intel_idle_complete_state_init(struct cpuidle_state *state) ++{ ++ if (intel_idle_state_needs_timer_stop(state)) ++ state->flags |= CPUIDLE_FLAG_TIMER_STOP; ++ ++ state->enter = intel_idle; ++ state->enter_dead = intel_idle_enter_dead; ++ state->enter_s2idle = intel_idle_s2idle; ++} ++ ++static void __init intel_idle_init_cstates_acpi_lpi(struct cpuidle_driver *drv) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ struct acpi_lpi_state *lpi_state; ++ struct cpuidle_state *state; ++ ++ if (intel_idle_max_cstate_reached(index)) ++ break; ++ ++ lpi_state = &acpi_state_table.lpi_states[index]; ++ ++ state = &drv->states[drv->state_count++]; ++ ++ scnprintf(state->name, CPUIDLE_NAME_LEN, "C%d_LPI", index + 1); ++ strscpy(state->desc, lpi_state->desc, CPUIDLE_DESC_LEN); ++ state->exit_latency = lpi_state->wake_latency; ++ state->target_residency = lpi_state->min_residency; ++ state->flags = MWAIT2flg(lpi_state->address); ++ /* ++ * Assume that entering any of the idle states extracted from ++ * _LPI except for the first two will cause the TLB to be ++ * flushed and let the core call leave_mm() for them upfront ++ * to avoid unnecessary wakeups due to TLB shootdowns. ++ */ ++ if (index > 1) ++ state->flags |= CPUIDLE_FLAG_TLB_FLUSHED; ++ ++ if (disabled_states_mask & BIT(index + 1)) ++ state->flags |= CPUIDLE_FLAG_OFF; ++ ++ intel_idle_complete_state_init(state); ++ ++ pr_info("%s: MWAIT hint 0x%x\n", state->name, flg2MWAIT(state->flags)); ++ } ++ ++ /* ++ * Assume the first idle state in the table to be C1 and if any deeper ++ * idle states are exposed while X86_FEATURE_NONSTOP_TSC is unset, mark ++ * the TSC as unstable. ++ */ ++ if (index > 1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) ++ mark_tsc_unstable("TSC halts in idle"); ++} ++ ++static void __init intel_idle_init_cstates_acpi_cst(struct cpuidle_driver *drv) + { + int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +@@ -1880,49 +1978,76 @@ static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + if (disabled_states_mask & BIT(cstate)) + state->flags |= CPUIDLE_FLAG_OFF; + +- if (intel_idle_state_needs_timer_stop(state)) +- state->flags |= CPUIDLE_FLAG_TIMER_STOP; +- + if (cx->type > ACPI_STATE_C1 && !boot_cpu_has(X86_FEATURE_NONSTOP_TSC)) + mark_tsc_unstable("TSC halts in idle"); + +- state->enter = intel_idle; +- state->enter_dead = intel_idle_enter_dead; +- state->enter_s2idle = intel_idle_s2idle; ++ intel_idle_complete_state_init(state); + } + } + +-static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++static void __init intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) + { +- int cstate, limit; ++ if (acpi_lpi_available) ++ intel_idle_init_cstates_acpi_lpi(drv); ++ else ++ intel_idle_init_cstates_acpi_cst(drv); ++} + +- /* +- * If there are no _CST C-states, do not disable any C-states by +- * default. +- */ +- if (!acpi_state_table.count) +- return false; ++static bool __init intel_idle_acpi_hint_match(unsigned int flags, u32 acpi_hint, ++ u32 table_hint) ++{ ++ if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { ++ acpi_hint &= ~MWAIT_SUBSTATE_MASK; ++ table_hint &= ~MWAIT_SUBSTATE_MASK; ++ } ++ return acpi_hint == table_hint; ++} ++ ++static bool __init intel_idle_off_by_default_lpi(unsigned int flags, u32 mwait_hint) ++{ ++ int index; ++ ++ for (index = 0; index < acpi_state_table.count; index++) { ++ u32 acpi_hint = acpi_state_table.lpi_states[index].address; ++ ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) ++ return false; ++ } ++ return true; ++} ++ ++static bool __init intel_idle_off_by_default_cst(unsigned int flags, u32 mwait_hint) ++{ ++ int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + +- limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + /* + * If limit > 0, intel_idle_cst_usable() has returned 'true', so all of + * the interesting states are ACPI_CSTATE_FFH. + */ + for (cstate = 1; cstate < limit; cstate++) { + u32 acpi_hint = acpi_state_table.states[cstate].address; +- u32 table_hint = mwait_hint; + +- if (flags & CPUIDLE_FLAG_PARTIAL_HINT_MATCH) { +- acpi_hint &= ~MWAIT_SUBSTATE_MASK; +- table_hint &= ~MWAIT_SUBSTATE_MASK; +- } +- +- if (acpi_hint == table_hint) ++ if (intel_idle_acpi_hint_match(flags, acpi_hint, mwait_hint)) + return false; + } + return true; + } + ++static bool __init intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) ++{ ++ /* ++ * If there is no C-states information in the ACPI tables, do not ++ * disable any C-states by default. ++ */ ++ if (!acpi_state_table.count) ++ return false; ++ ++ if (acpi_lpi_available) ++ return intel_idle_off_by_default_lpi(flags, mwait_hint); ++ ++ return intel_idle_off_by_default_cst(flags, mwait_hint); ++} ++ + static inline bool ignore_native(void) + { + return no_native && !no_acpi; +@@ -1930,7 +2055,7 @@ static inline bool ignore_native(void) + #else /* !CONFIG_ACPI_PROCESSOR_CSTATE */ + #define force_use_acpi (false) + +-static inline bool intel_idle_acpi_cst_extract(void) { return false; } ++static inline bool intel_idle_acpi_probe(void) { return false; } + static inline void intel_idle_init_cstates_acpi(struct cpuidle_driver *drv) { } + static inline bool intel_idle_off_by_default(unsigned int flags, u32 mwait_hint) + { +@@ -2745,7 +2870,7 @@ static int __init intel_idle_init(void) + if (icpu) { + if (icpu->state_table) + cpuidle_state_table = icpu->state_table; +- else if (!intel_idle_acpi_cst_extract()) ++ else if (!intel_idle_acpi_probe()) + return -ENODEV; + + auto_demotion_disable_flags = icpu->auto_demotion_disable_flags; +@@ -2754,8 +2879,8 @@ static int __init intel_idle_init(void) + if (icpu->c1_demotion_supported) + c1_demotion_supported = true; + if (icpu->use_acpi || force_use_acpi) +- intel_idle_acpi_cst_extract(); +- } else if (!intel_idle_acpi_cst_extract()) { ++ intel_idle_acpi_probe(); ++ } else if (!intel_idle_acpi_probe()) { + return -ENODEV; + } + +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -1581,8 +1581,6 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { +- int ret; +- + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1591,39 +1589,35 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clks) + return opp_table; + +- /* Find clk for the device */ +- opp_table->clk = clk_get(dev, NULL); ++ /* ++ * There are few platforms which don't want the OPP core to manage ++ * device's clock settings. In such cases neither the platform ++ * provides the clks explicitly to us, nor the DT contains a valid ++ * clk entry. The OPP nodes in DT may still contain "opp-hz" property ++ * though, which we need to parse and allow the platform to find an ++ * OPP based on freq later on. ++ * ++ * This is a simple solution to take care of such corner cases, i.e. ++ * make the clk_count 1, which lets us allocate space for frequency ++ * in opp->rates and also parse the entries in DT. Use ++ * clk_get_optional() instead of clk_get() so opp_table->clk stays ++ * NULL for such devices, instead of holding an ERR_PTR(-ENOENT) that ++ * consumers must remember to special-case. ++ */ ++ opp_table->clk = clk_get_optional(dev, NULL); + +- ret = PTR_ERR_OR_ZERO(opp_table->clk); +- if (!ret) { ++ if (IS_ERR(opp_table->clk)) { ++ dev_pm_opp_put_opp_table(opp_table); ++ dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); ++ return ERR_CAST(opp_table->clk); ++ } ++ ++ if (opp_table->clk) + opp_table->config_clks = _opp_config_clk_single; +- opp_table->clk_count = 1; +- return opp_table; +- } + +- if (ret == -ENOENT) { +- /* +- * There are few platforms which don't want the OPP core to +- * manage device's clock settings. In such cases neither the +- * platform provides the clks explicitly to us, nor the DT +- * contains a valid clk entry. The OPP nodes in DT may still +- * contain "opp-hz" property though, which we need to parse and +- * allow the platform to find an OPP based on freq later on. +- * +- * This is a simple solution to take care of such corner cases, +- * i.e. make the clk_count 1, which lets us allocate space for +- * frequency in opp->rates and also parse the entries in DT. +- */ +- opp_table->clk_count = 1; ++ opp_table->clk_count = 1; + +- dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); +- return opp_table; +- } +- +- dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, ret, "Couldn't find clock\n"); +- +- return ERR_PTR(ret); ++ return opp_table; + } + + /* +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -315,6 +315,19 @@ static inline int acpi_processor_evaluate_cst(acpi_handle handle, u32 cpu, + } + #endif + ++#ifdef CONFIG_ACPI_PROCESSOR_IDLE ++int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict); ++#else ++static inline int acpi_processor_extract_lpi_info(acpi_handle pr_handle, ++ struct acpi_processor_power *pr_power, ++ bool strict) ++{ ++ return -ENODEV; ++} ++#endif ++ + #ifdef CONFIG_ACPI_HOTPLUG_CPU + /* Arch dependent functions for cpu hotplug support */ + int acpi_map_cpu(acpi_handle handle, phys_cpuid_t physid, u32 acpi_id, +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -379,6 +379,7 @@ struct cpufreq_driver { + void (*adjust_perf)(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + + /* +@@ -624,6 +625,7 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy, + void cpufreq_driver_adjust_perf(struct cpufreq_policy *policy, + unsigned long min_perf, + unsigned long target_perf, ++ unsigned long max_perf, + unsigned long capacity); + bool cpufreq_driver_has_adjust_perf(void); + int cpufreq_driver_target(struct cpufreq_policy *policy, +diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig +--- a/kernel/power/Kconfig ++++ b/kernel/power/Kconfig +@@ -268,6 +268,16 @@ config DPM_WATCHDOG + captured in pstore device for inspection in subsequent + boot session. + ++config DPM_WATCHDOG_ENABLED ++ bool "Enable DPM watchdog by default" ++ depends on DPM_WATCHDOG ++ default y ++ help ++ If you say Y here, the DPM watchdog will be enabled by default. ++ If you say N, it will be compiled in but disabled. It can be ++ enabled at boot time via the "pm.dpm_watchdog_enabled" kernel ++ parameter or at runtime via sysfs. ++ + config DPM_WATCHDOG_TIMEOUT + int "Watchdog timeout to panic in seconds" + range 1 120 +diff --git a/kernel/power/swap.c b/kernel/power/swap.c +--- a/kernel/power/swap.c ++++ b/kernel/power/swap.c +@@ -430,19 +430,22 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + + if (!handle->cur) + return -EINVAL; +- offset = alloc_swapdev_block(root_swap); +- error = write_page(buf, offset, hb); +- if (error) +- return error; +- handle->cur->entries[handle->k++] = offset; ++ ++ /* ++ * If the current map page is full, allocate and link next one first. ++ * Delaying this until here avoids writing an empty swap map page when ++ * the image size is an exact MAP_PAGE_ENTRIES multiple. ++ */ + if (handle->k >= MAP_PAGE_ENTRIES) { + offset = alloc_swapdev_block(root_swap); + if (!offset) + return -ENOSPC; ++ + handle->cur->next_swap = offset; + error = write_page(handle->cur, handle->cur_swap, hb); + if (error) +- goto out; ++ return error; ++ + clear_page(handle->cur); + handle->cur_swap = offset; + handle->k = 0; +@@ -450,7 +453,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + if (hb && low_free_pages() <= handle->reqd_free_pages) { + error = hib_wait_io(hb); + if (error) +- goto out; ++ return error; + /* + * Recalculate the number of required free pages, to + * make sure we never take more than half. +@@ -458,14 +461,21 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf, + handle->reqd_free_pages = reqd_free_pages(); + } + } +- out: +- return error; ++ ++ offset = alloc_swapdev_block(root_swap); ++ error = write_page(buf, offset, hb); ++ if (error) ++ return error; ++ handle->cur->entries[handle->k++] = offset; ++ return 0; + } + + static int flush_swap_writer(struct swap_map_handle *handle) + { +- if (handle->cur && handle->cur_swap) ++ if (handle->cur && handle->cur_swap && handle->k) + return write_page(handle->cur, handle->cur_swap, NULL); ++ else if (handle->cur && handle->cur_swap) ++ return 0; + else + return -EINVAL; + } +@@ -739,7 +749,7 @@ static int save_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +@@ -1243,7 +1253,7 @@ static int load_compressed_image(struct swap_map_handle *handle, + + data[thr].cc = crypto_alloc_acomp(hib_comp_algo, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR_OR_NULL(data[thr].cc)) { +- pr_err("Could not allocate comp stream %ld\n", PTR_ERR(data[thr].cc)); ++ pr_err("Could not allocate comp stream %pe\n", data[thr].cc); + ret = -EFAULT; + goto out_clean; + } +diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c +--- a/kernel/sched/cpufreq_schedutil.c ++++ b/kernel/sched/cpufreq_schedutil.c +@@ -50,6 +50,7 @@ struct sugov_cpu { + + unsigned long util; + unsigned long bw_min; ++ unsigned long bw_max; + + /* The field below is for single-CPU policies only: */ + #ifdef CONFIG_NO_HZ_COMMON +@@ -243,6 +244,7 @@ static void sugov_get_util(struct sugov_cpu *sg_cpu, unsigned long boost) + util = effective_cpu_util(sg_cpu->cpu, util, &min, &max); + util = max(util, boost); + sg_cpu->bw_min = min; ++ sg_cpu->bw_max = max; + sg_cpu->util = sugov_effective_cpu_perf(sg_cpu->cpu, util, min, max); + } + +@@ -495,7 +497,7 @@ static void sugov_update_single_perf(struct update_util_data *hook, u64 time, + sg_cpu->util = prev_util; + + cpufreq_driver_adjust_perf(sg_policy->policy, sg_cpu->bw_min, +- sg_cpu->util, max_cap); ++ sg_cpu->util, sg_cpu->bw_max, max_cap); + + sg_policy->need_freq_update = false; + sg_policy->last_freq_update_time = time; +@@ -601,7 +603,7 @@ static ssize_t rate_limit_us_show(struct gov_attr_set *attr_set, char *buf) + { + struct sugov_tunables *tunables = to_sugov_tunables(attr_set); + +- return sprintf(buf, "%u\n", tunables->rate_limit_us); ++ return sysfs_emit(buf, "%u\n", tunables->rate_limit_us); + } + + static ssize_t +diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs +--- a/rust/kernel/cpufreq.rs ++++ b/rust/kernel/cpufreq.rs +@@ -797,7 +797,13 @@ pub trait Driver { + } + + /// Driver's `adjust_perf` callback. +- fn adjust_perf(_policy: &mut Policy, _min_perf: usize, _target_perf: usize, _capacity: usize) { ++ fn adjust_perf( ++ _policy: &mut Policy, ++ _min_perf: usize, ++ _target_perf: usize, ++ _max_perf: usize, ++ _capacity: usize, ++ ) { + build_error!(VTABLE_DEFAULT_ERROR) + } + +@@ -1270,12 +1276,13 @@ impl Registration { + ptr: *mut bindings::cpufreq_policy, + min_perf: c_ulong, + target_perf: c_ulong, ++ max_perf: c_ulong, + capacity: c_ulong, + ) { + // SAFETY: The `ptr` is guaranteed to be valid by the contract with the C code for the + // lifetime of `policy`. + let policy = unsafe { Policy::from_raw_mut(ptr) }; +- T::adjust_perf(policy, min_perf, target_perf, capacity); ++ T::adjust_perf(policy, min_perf, target_perf, max_perf, capacity); + } + + /// Driver's `get_intermediate` callback. diff --git a/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch.sig new file mode 100644 index 0000000..8ffea46 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0210-pm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch new file mode 100644 index 0000000..ed2d0f5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch @@ -0,0 +1,104 @@ +diff --git a/drivers/opp/core.c b/drivers/opp/core.c +--- a/drivers/opp/core.c ++++ b/drivers/opp/core.c +@@ -453,8 +453,8 @@ int dev_pm_opp_get_opp_count(struct device *dev) + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_dbg(dev, "%s: OPP table not found (%ld)\n", +- __func__, PTR_ERR(opp_table)); ++ dev_dbg(dev, "%s: OPP table not found (%pe)\n", ++ __func__, opp_table); + return PTR_ERR(opp_table); + } + +@@ -611,8 +611,8 @@ _find_key(struct device *dev, unsigned long *key, int index, bool available, + _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -722,8 +722,8 @@ struct dev_pm_opp *dev_pm_opp_find_key_exact(struct device *dev, + struct opp_table *opp_table __free(put_opp_table) = _find_opp_table(dev); + + if (IS_ERR(opp_table)) { +- dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_err(dev, "%s: OPP table not found (%pe)\n", __func__, ++ opp_table); + return ERR_CAST(opp_table); + } + +@@ -1036,8 +1036,8 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, + + /* Regulator not available for device */ + if (IS_ERR(reg)) { +- dev_dbg(dev, "%s: regulator not available: %ld\n", __func__, +- PTR_ERR(reg)); ++ dev_dbg(dev, "%s: regulator not available: %pe\n", __func__, ++ reg); + return 0; + } + +@@ -1448,8 +1448,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) + temp_freq = freq; + opp = _find_freq_ceil(opp_table, &temp_freq); + if (IS_ERR(opp)) { +- dev_err(dev, "%s: failed to find OPP for freq %lu (%ld)\n", +- __func__, freq, PTR_ERR(opp)); ++ dev_err(dev, "%s: failed to find OPP for freq %lu (%pe)\n", ++ __func__, freq, opp); + return PTR_ERR(opp); + } + +@@ -1581,6 +1581,8 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) + { ++ int ret; ++ + /* + * Return early if we don't need to get clk or we have already done it + * earlier. +@@ -1607,9 +1609,9 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, + opp_table->clk = clk_get_optional(dev, NULL); + + if (IS_ERR(opp_table->clk)) { ++ ret = dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); + dev_pm_opp_put_opp_table(opp_table); +- dev_err_probe(dev, PTR_ERR(opp_table->clk), "Couldn't find clock\n"); +- return ERR_CAST(opp_table->clk); ++ return ERR_PTR(ret); + } + + if (opp_table->clk) +@@ -2869,8 +2871,8 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, + struct dev_pm_opp *opp __free(put_opp) = ERR_PTR(-ENODEV), *tmp_opp; + + if (IS_ERR(opp_table)) { +- dev_warn(dev, "%s: Device OPP not found (%ld)\n", __func__, +- PTR_ERR(opp_table)); ++ dev_warn(dev, "%s: Device OPP not found (%pe)\n", __func__, ++ opp_table); + return PTR_ERR(opp_table); + } + +diff --git a/drivers/opp/of.c b/drivers/opp/of.c +--- a/drivers/opp/of.c ++++ b/drivers/opp/of.c +@@ -1345,8 +1345,8 @@ int of_get_required_opp_performance_state(struct device_node *np, int index) + _find_table_of_opp_np(required_np); + + if (IS_ERR(opp_table)) { +- pr_err("%s: Failed to find required OPP table %pOF: %ld\n", +- __func__, np, PTR_ERR(opp_table)); ++ pr_err("%s: Failed to find required OPP table %pOF: %pe\n", ++ __func__, np, opp_table); + return PTR_ERR(opp_table); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch.sig new file mode 100644 index 0000000..b6284eb Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0211-pm-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch new file mode 100644 index 0000000..0e9c073 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch @@ -0,0 +1,54 @@ +diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c +--- a/kernel/power/hibernate.c ++++ b/kernel/power/hibernate.c +@@ -408,9 +408,18 @@ int hibernation_snapshot(int platform_mode) + if (error) + goto Close; + ++ error = dpm_prepare(PMSG_FREEZE); ++ if (error) ++ goto Complete; ++ ++ /* Preallocate image memory before freezing kernel threads and shutting down devices. */ ++ error = hibernate_preallocate_memory(); ++ if (error) ++ goto Complete; ++ + error = freeze_kernel_threads(); + if (error) +- goto Close; ++ goto Cleanup; + + if (hibernation_test(TEST_FREEZER)) { + +@@ -422,15 +431,6 @@ int hibernation_snapshot(int platform_mode) + goto Thaw; + } + +- error = dpm_prepare(PMSG_FREEZE); +- if (error) +- goto Complete; +- +- /* Preallocate image memory before shutting down devices. */ +- error = hibernate_preallocate_memory(); +- if (error) +- goto Complete; +- + console_suspend_all(); + pm_restrict_gfp_mask(); + +@@ -464,10 +464,12 @@ int hibernation_snapshot(int platform_mode) + platform_end(platform_mode); + return error; + +- Complete: +- dpm_complete(PMSG_RECOVER); + Thaw: + thaw_kernel_threads(); ++ Cleanup: ++ swsusp_free(); ++ Complete: ++ dpm_complete(PMSG_RECOVER); + goto Close; + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch.sig new file mode 100644 index 0000000..17b44d0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0212-pm-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch b/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch new file mode 100644 index 0000000..e8c1523 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch @@ -0,0 +1,61 @@ +diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c +--- a/drivers/cpufreq/cpufreq.c ++++ b/drivers/cpufreq/cpufreq.c +@@ -2590,8 +2590,8 @@ static void cpufreq_update_pressure(struct cpufreq_policy *policy) + + cpu = cpumask_first(policy->related_cpus); + max_freq = arch_scale_freq_ref(cpu); +- if (!max_freq) +- max_freq = policy->cpuinfo.max_freq; ++ if (!max_freq && cpufreq_driver->scale_freq_ref) ++ max_freq = cpufreq_driver->scale_freq_ref(policy); + + capped_freq = policy->max; + +diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c +--- a/drivers/cpufreq/intel_pstate.c ++++ b/drivers/cpufreq/intel_pstate.c +@@ -1175,6 +1175,14 @@ static bool hybrid_clear_max_perf_cpu(void) + return ret; + } + ++static unsigned int intel_pstate_scale_freq_ref(struct cpufreq_policy *policy) ++{ ++ if (READ_ONCE(all_cpu_data[policy->cpu]->capacity_perf)) ++ return policy->cpuinfo.max_freq; ++ ++ return 0; ++} ++ + static void intel_pstate_update_freq_limits(struct cpudata *cpu) + { + int scaling = cpu->pstate.scaling; +@@ -3092,6 +3100,7 @@ static struct cpufreq_driver intel_pstate = { + .offline = intel_pstate_cpu_offline, + .online = intel_pstate_cpu_online, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_pstate", + }; + +@@ -3415,6 +3424,7 @@ static struct cpufreq_driver intel_cpufreq = { + .suspend = intel_cpufreq_suspend, + .resume = intel_pstate_resume, + .update_limits = intel_pstate_update_limits, ++ .scale_freq_ref = intel_pstate_scale_freq_ref, + .name = "intel_cpufreq", + }; + +diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h +--- a/include/linux/cpufreq.h ++++ b/include/linux/cpufreq.h +@@ -420,6 +420,9 @@ struct cpufreq_driver { + /* Will be called after the driver is fully initialized */ + void (*ready)(struct cpufreq_policy *policy); + ++ /* Return the capacity reference frequency for policy. */ ++ unsigned int (*scale_freq_ref)(struct cpufreq_policy *policy); ++ + struct freq_attr **attr; + + /* platform specific boost support code */ diff --git a/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch.sig new file mode 100644 index 0000000..8400915 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0213-pm-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch b/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch new file mode 100644 index 0000000..1c5c5b3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch @@ -0,0 +1,95 @@ +diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h +--- a/arch/x86/include/asm/cpufeatures.h ++++ b/arch/x86/include/asm/cpufeatures.h +@@ -471,6 +471,8 @@ + #define X86_FEATURE_AUTOIBRS (20*32+ 8) /* Automatic IBRS */ + #define X86_FEATURE_NO_SMM_CTL_MSR (20*32+ 9) /* SMM_CTL MSR is not present */ + ++#define X86_FEATURE_L2_TLB_SIZE_X32 (20*32+14) /* L2 TLB sizes are encoded as multiples of 32 */ ++ + #define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */ + + #define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */ +diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c +--- a/arch/x86/kernel/cpu/amd.c ++++ b/arch/x86/kernel/cpu/amd.c +@@ -1190,7 +1190,7 @@ static unsigned int amd_size_cache(struct cpuinfo_x86 *c, unsigned int size) + + static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + { +- u32 ebx, eax, ecx, edx; ++ u32 ebx, eax, ecx, edx, shift, tmp; + u16 mask = 0xfff; + + if (c->x86 < 0xf) +@@ -1199,10 +1199,12 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + if (c->extended_cpuid_level < 0x80000006) + return; + ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; ++ + cpuid(0x80000006, &eax, &ebx, &ecx, &edx); + +- tlb_lld_4k = (ebx >> 16) & mask; +- tlb_lli_4k = ebx & mask; ++ tlb_lld_4k = ((ebx >> 16) & mask) << shift; ++ tlb_lli_4k = (ebx & mask) << shift; + + /* + * K8 doesn't have 2M/4M entries in the L2 TLB so read out the L1 TLB +@@ -1214,16 +1216,18 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + } + + /* Handle DTLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!((eax >> 16) & mask)) ++ tmp = ((eax >> 16) & mask) << shift; ++ if (!tmp) + tlb_lld_2m = (cpuid_eax(0x80000005) >> 16) & 0xff; + else +- tlb_lld_2m = (eax >> 16) & mask; ++ tlb_lld_2m = tmp; + + /* a 4M entry uses two 2M entries */ + tlb_lld_4m = tlb_lld_2m >> 1; + + /* Handle ITLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ +- if (!(eax & mask)) { ++ tmp = (eax & mask) << shift; ++ if (!tmp) { + /* Erratum 658 */ + if (c->x86 == 0x15 && c->x86_model <= 0x1f) { + tlb_lli_2m = 1024; +@@ -1231,8 +1235,9 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) + cpuid(0x80000005, &eax, &ebx, &ecx, &edx); + tlb_lli_2m = eax & 0xff; + } +- } else +- tlb_lli_2m = eax & mask; ++ } else { ++ tlb_lli_2m = tmp; ++ } + + tlb_lli_4m = tlb_lli_2m >> 1; + +diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c +--- a/arch/x86/kernel/cpu/common.c ++++ b/arch/x86/kernel/cpu/common.c +@@ -857,7 +857,7 @@ static void get_model_name(struct cpuinfo_x86 *c) + + void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + { +- unsigned int n, dummy, ebx, ecx, edx, l2size; ++ unsigned int n, dummy, ebx, ecx, edx, l2size, shift __maybe_unused; + + n = c->extended_cpuid_level; + +@@ -877,7 +877,9 @@ void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) + l2size = ecx >> 16; + + #ifdef CONFIG_X86_64 ++ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; + c->x86_tlbsize += ((ebx >> 16) & 0xfff) + (ebx & 0xfff); ++ c->x86_tlbsize <<= shift; + #else + /* do processor-specific cache resizing */ + if (this_cpu->legacy_cache_size) diff --git a/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch.sig new file mode 100644 index 0000000..aa440e0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0220-x86-amd-zen5-tlb-sizes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch b/pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch new file mode 100644 index 0000000..43793e5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0250-zsmalloc.patch.sig b/pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch.sig new file mode 100644 index 0000000..d6828f5 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0250-zsmalloc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch b/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch new file mode 100644 index 0000000..f3b0e56 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch @@ -0,0 +1,313 @@ +diff --git a/include/linux/rmap.h b/include/linux/rmap.h +--- a/include/linux/rmap.h ++++ b/include/linux/rmap.h +@@ -843,7 +843,7 @@ static inline int folio_try_share_anon_rmap_pmd(struct folio *folio, + * Called from mm/vmscan.c to handle paging out + */ + int folio_referenced(struct folio *, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags); ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags); + + void try_to_migrate(struct folio *folio, enum ttu_flags flags); + void try_to_unmap(struct folio *, enum ttu_flags flags); +@@ -975,10 +975,9 @@ struct anon_vma *folio_lock_anon_vma_read(const struct folio *folio, + #define anon_vma_prepare(vma) (0) + + static inline int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, +- vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + return 0; + } + +diff --git a/mm/rmap.c b/mm/rmap.c +--- a/mm/rmap.c ++++ b/mm/rmap.c +@@ -907,7 +907,7 @@ pmd_t *mm_find_pmd(struct mm_struct *mm, unsigned long address) + struct folio_referenced_arg { + int mapcount; + int referenced; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + struct mem_cgroup *memcg; + }; + +@@ -926,7 +926,7 @@ static bool folio_referenced_one(struct folio *folio, + address = pvmw.address; + nr = 1; + +- if (vma->vm_flags & VM_LOCKED) { ++ if (vma_test(vma, VMA_LOCKED_BIT)) { + ptes++; + pra->mapcount--; + +@@ -947,7 +947,7 @@ static bool folio_referenced_one(struct folio *folio, + /* Restore the mlock which got missed */ + mlock_vma_folio(folio, vma); + page_vma_mapped_walk_done(&pvmw); +- pra->vm_flags |= VM_LOCKED; ++ vma_flags_set(&pra->vma_flags, VMA_LOCKED_BIT); + return false; /* To break the loop */ + } + +@@ -1015,8 +1015,11 @@ static bool folio_referenced_one(struct folio *folio, + referenced++; + + if (referenced) { ++ vma_flags_t vma_flags = vma->flags; ++ + pra->referenced++; +- pra->vm_flags |= vma->vm_flags & ~VM_LOCKED; ++ vma_flags_clear(&vma_flags, VMA_LOCKED_BIT); ++ vma_flags_set_mask(&pra->vma_flags, vma_flags); + } + + if (!pra->mapcount) +@@ -1054,7 +1057,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * @folio: The folio to test. + * @is_locked: Caller holds lock on the folio. + * @memcg: target memory cgroup +- * @vm_flags: A combination of all the vma->vm_flags which referenced the folio. ++ * @vma_flags: A combination of all the vma->flags which referenced the folio. + * + * Quick test_and_clear_referenced for all mappings of a folio, + * +@@ -1062,7 +1065,7 @@ static bool invalid_folio_referenced_vma(struct vm_area_struct *vma, void *arg) + * the function bailed out due to rmap lock contention. + */ + int folio_referenced(struct folio *folio, int is_locked, +- struct mem_cgroup *memcg, vm_flags_t *vm_flags) ++ struct mem_cgroup *memcg, vma_flags_t *vma_flags) + { + bool we_locked = false; + struct folio_referenced_arg pra = { +@@ -1078,7 +1081,7 @@ int folio_referenced(struct folio *folio, int is_locked, + }; + + VM_WARN_ON_ONCE_FOLIO(folio_is_zone_device(folio), folio); +- *vm_flags = 0; ++ vma_flags_clear_all(vma_flags); + if (!pra.mapcount) + return 0; + +@@ -1092,7 +1095,7 @@ int folio_referenced(struct folio *folio, int is_locked, + } + + rmap_walk(folio, &rwc); +- *vm_flags = pra.vm_flags; ++ vma_flags_set_mask(vma_flags, pra.vma_flags); + + if (we_locked) + folio_unlock(folio); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -262,6 +262,12 @@ static bool writeback_throttling_sane(struct scan_control *sc) + } + #endif + ++static inline bool is_exec_file_folio(const struct folio *folio, ++ const vma_flags_t *vma_flags) ++{ ++ return vma_flags_test(vma_flags, VMA_EXEC_BIT) && folio_is_file_lru(folio); ++} ++ + static void set_task_reclaim_state(struct task_struct *task, + struct reclaim_state *rs) + { +@@ -830,10 +836,16 @@ enum folio_references { + * with PG_active set. In contrast, the aging (page table walk) path uses + * folio_update_gen(). + */ +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + /* see the comment on LRU_REFS_FLAGS */ + if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* Activate file-backed executable folios after first usage. */ ++ if (is_exec_file_folio(folio, vma_flags)) { ++ set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset)); ++ return true; ++ } ++ + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return false; + } +@@ -846,7 +858,7 @@ static bool lru_gen_set_refs(struct folio *folio) + return true; + } + #else +-static bool lru_gen_set_refs(struct folio *folio) ++static bool lru_gen_set_refs(struct folio *folio, const vma_flags_t *vma_flags) + { + return false; + } +@@ -856,16 +868,16 @@ static enum folio_references folio_check_references(struct folio *folio, + struct scan_control *sc) + { + int referenced_ptes, referenced_folio; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + + referenced_ptes = folio_referenced(folio, 1, sc->target_mem_cgroup, +- &vm_flags); ++ &vma_flags); + + /* + * The supposedly reclaimable folio was found to be in a VM_LOCKED vma. + * Let the folio, now marked Mlocked, be moved to the unevictable list. + */ +- if (vm_flags & VM_LOCKED) ++ if (vma_flags_test(&vma_flags, VMA_LOCKED_BIT)) + return FOLIOREF_ACTIVATE; + + /* +@@ -881,7 +893,7 @@ static enum folio_references folio_check_references(struct folio *folio, + if (!referenced_ptes) + return FOLIOREF_RECLAIM; + +- return lru_gen_set_refs(folio) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; ++ return lru_gen_set_refs(folio, &vma_flags) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP; + } + + referenced_folio = folio_test_clear_referenced(folio); +@@ -909,7 +921,7 @@ static enum folio_references folio_check_references(struct folio *folio, + /* + * Activate file-backed executable folios after first usage. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) ++ if (is_exec_file_folio(folio, &vma_flags)) + return FOLIOREF_ACTIVATE; + + return FOLIOREF_KEEP; +@@ -2067,7 +2079,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + { + unsigned long nr_taken; + unsigned long nr_scanned; +- vm_flags_t vm_flags; ++ vma_flags_t vma_flags; + LIST_HEAD(l_hold); /* The folios which were snipped off */ + LIST_HEAD(l_active); + LIST_HEAD(l_inactive); +@@ -2111,7 +2123,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + + /* Referenced or rmap lock contention: rotate */ + if (folio_referenced(folio, 0, sc->target_mem_cgroup, +- &vm_flags) != 0) { ++ &vma_flags) != 0) { + /* + * Identify referenced, file-backed active folios and + * give them one more trip around the active list. So +@@ -2121,7 +2133,7 @@ static void shrink_active_list(unsigned long nr_to_scan, + * IO, plus JVM can create lots of anon VM_EXEC folios, + * so we ignore them here. + */ +- if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio)) { ++ if (is_exec_file_folio(folio, &vma_flags)) { + nr_rotated += folio_nr_pages(folio); + list_add(&folio->lru, &l_active); + continue; +@@ -3190,14 +3202,19 @@ static bool positive_ctrl_err(struct ctrl_pos *sp, struct ctrl_pos *pv) + ******************************************************************************/ + + /* promote pages accessed through page tables */ +-static int folio_update_gen(struct folio *folio, int gen) ++static int folio_update_gen(struct folio *folio, int gen, const vma_flags_t *vma_flags) + { + unsigned long new_flags, old_flags = READ_ONCE(folio->flags.f); + + VM_WARN_ON_ONCE(gen >= MAX_NR_GENS); + +- /* see the comment on LRU_REFS_FLAGS */ +- if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) { ++ /* ++ * See the comment on LRU_REFS_FLAGS, and activate file-backed ++ * executable folios after first usage to avoid typical IO ++ * thrashing from reclaiming. ++ */ ++ if (!folio_test_referenced(folio) && !folio_test_workingset(folio) && ++ !is_exec_file_folio(folio, vma_flags)) { + set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced)); + return -1; + } +@@ -3430,8 +3447,8 @@ static bool suitable_to_scan(int total, int young) + return young * n >= total; + } + +-static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, +- int new_gen, bool dirty) ++static void walk_update_folio(struct lru_gen_mm_walk *walk, struct vm_area_struct *vma, ++ struct folio *folio, int new_gen, bool dirty) + { + int old_gen; + +@@ -3444,10 +3461,10 @@ static void walk_update_folio(struct lru_gen_mm_walk *walk, struct folio *folio, + folio_mark_dirty(folio); + + if (walk) { +- old_gen = folio_update_gen(folio, new_gen); ++ old_gen = folio_update_gen(folio, new_gen, &vma->flags); + if (old_gen >= 0 && old_gen != new_gen) + update_batch_size(walk, folio, old_gen, new_gen); +- } else if (lru_gen_set_refs(folio)) { ++ } else if (lru_gen_set_refs(folio, &vma->flags)) { + old_gen = folio_lru_gen(folio); + if (old_gen >= 0 && old_gen != new_gen) + folio_activate(folio); +@@ -3520,7 +3537,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3533,7 +3550,7 @@ static bool walk_pte_range(pmd_t *pmd, unsigned long start, unsigned long end, + walk->mm_stats[MM_LEAF_YOUNG] += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, args->vma, last, gen, dirty); + last = NULL; + + if (i < PTRS_PER_PTE && get_next_vma(PMD_MASK, PAGE_SIZE, args, &start, &end)) +@@ -3611,7 +3628,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + goto next; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -3625,7 +3642,7 @@ static void walk_pmd_range_locked(pud_t *pud, unsigned long addr, struct vm_area + i = i > MIN_LRU_BATCH ? 0 : find_next_bit(bitmap, MIN_LRU_BATCH, i) + 1; + } while (i <= MIN_LRU_BATCH); + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + spin_unlock(ptl); +@@ -4260,7 +4277,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + continue; + + if (last != folio) { +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + last = folio; + dirty = false; +@@ -4272,7 +4289,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + young += nr; + } + +- walk_update_folio(walk, last, gen, dirty); ++ walk_update_folio(walk, vma, last, gen, dirty); + + lazy_mmu_mode_disable(); + diff --git a/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch.sig b/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch.sig new file mode 100644 index 0000000..7023e21 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0260-mglru-exec-protect.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch b/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch new file mode 100644 index 0000000..afe8241 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch @@ -0,0 +1,161 @@ +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -195,22 +195,28 @@ struct ksm_stable_node { + * @node: rb node of this rmap_item in the unstable tree + * @head: pointer to stable_node heading this list in the stable tree + * @hlist: link into hlist of rmap_items hanging off that stable_node +- * @age: number of scan iterations since creation +- * @remaining_skips: how many scans to skip ++ * @age: number of scan iterations since creation (unstable node) ++ * @remaining_skips: how many scans to skip (unstable node) ++ * @linear_page_index: the original page's index before merged by KSM (stable node) + */ + struct ksm_rmap_item { + struct ksm_rmap_item *rmap_list; + union { +- struct anon_vma *anon_vma; /* when stable */ ++ struct anon_vma *anon_vma; /* for reverse mapping, when stable */ + #ifdef CONFIG_NUMA + int nid; /* when node of unstable tree */ + #endif + }; + struct mm_struct *mm; + unsigned long address; /* + low bits used for flags below */ +- unsigned int oldchecksum; /* when unstable */ +- rmap_age_t age; +- rmap_age_t remaining_skips; ++ union { ++ struct { ++ unsigned int oldchecksum; ++ rmap_age_t age; ++ rmap_age_t remaining_skips; ++ }; /* when unstable */ ++ unsigned long linear_page_index; /* for reverse mapping, when stable */ ++ }; + union { + struct rb_node node; /* when node of unstable tree */ + struct { /* when listed from stable tree */ +@@ -776,6 +782,11 @@ static struct vm_area_struct *find_mergeable_vma(struct mm_struct *mm, + return vma; + } + ++/* ++ * break_cow: actively break COW, replacing the KSM page by a fresh anonymous ++ * page. This is called when rmap_item has not yet become stable, but page ++ * has been merged. ++ */ + static void break_cow(struct ksm_rmap_item *rmap_item) + { + struct mm_struct *mm = rmap_item->mm; +@@ -787,6 +798,11 @@ static void break_cow(struct ksm_rmap_item *rmap_item) + * to undo, we also need to drop a reference to the anon_vma. + */ + put_anon_vma(rmap_item->anon_vma); ++ /* ++ * Reset linear_page_index that might overlay age-related ++ * information. (it's still unstable node) ++ */ ++ rmap_item->linear_page_index = 0; + + mmap_read_lock(mm); + vma = find_mergeable_vma(mm, addr); +@@ -899,6 +915,8 @@ static void remove_node_from_stable_tree(struct ksm_stable_node *stable_node) + VM_BUG_ON(stable_node->rmap_hlist_len <= 0); + stable_node->rmap_hlist_len--; + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->address &= PAGE_MASK; + cond_resched(); + } +@@ -1052,6 +1070,8 @@ static void remove_rmap_item_from_tree(struct ksm_rmap_item *rmap_item) + stable_node->rmap_hlist_len--; + + put_anon_vma(rmap_item->anon_vma); ++ /* Reset linear_page_index that might overlay age-related information. */ ++ rmap_item->linear_page_index = 0; + rmap_item->head = NULL; + rmap_item->address &= PAGE_MASK; + +@@ -1598,8 +1618,15 @@ static int try_to_merge_with_ksm_page(struct ksm_rmap_item *rmap_item, + /* Unstable nid is in union with stable anon_vma: remove first */ + remove_rmap_item_from_tree(rmap_item); + +- /* Must get reference to anon_vma while still holding mmap_lock */ ++ /* ++ * We can consider the VMA only while still holding the mmap lock, ++ * so lock, so reference the anon_vma and calculate the linear ++ * page index early, before stable_tree_append(). If anything goes ++ * wrong that prevents the rmap_item from being added to the ++ * stable_tree, break_cow() will clean it up. ++ */ + rmap_item->anon_vma = vma->anon_vma; ++ rmap_item->linear_page_index = linear_page_index(vma, rmap_item->address); + get_anon_vma(vma->anon_vma); + out: + mmap_read_unlock(mm); +@@ -2458,6 +2485,13 @@ static bool should_skip_rmap_item(struct folio *folio, + if (folio_test_ksm(folio)) + return false; + ++ /* ++ * There is no age information in stable-tree nodes. We might end up ++ * here without a KSM page for example after COW. ++ */ ++ if (rmap_item->address & STABLE_FLAG) ++ return false; ++ + age = rmap_item->age; + if (age != U8_MAX) + rmap_item->age++; +@@ -3173,6 +3207,7 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + hlist_for_each_entry(rmap_item, &stable_node->hlist, hlist) { + /* Ignore the stable/unstable/sqnr flags */ + const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct anon_vma *anon_vma = rmap_item->anon_vma; + struct anon_vma_chain *vmac; + struct vm_area_struct *vma; +@@ -3186,8 +3221,18 @@ void rmap_walk_ksm(struct folio *folio, struct rmap_walk_control *rwc) + anon_vma_lock_read(anon_vma); + } + ++ /* ++ * Currently, KSM folios are always small folios, so it's ++ * sufficient to search for a single page. We can simply use ++ * the linear_page_index of the original de-duplicate ++ * anonymous page that we remembered in the rmap_item while ++ * de-duplicating. Note that mremap() always de-duplicates KSM ++ * folios: so if there was mremap() in our parent or our child, ++ * we wouldn't have the KSM folio mapped in these processes ++ * anymore. ++ */ + anon_vma_interval_tree_foreach(vmac, &anon_vma->rb_root, +- 0, ULONG_MAX) { ++ index, index) { + + cond_resched(); + vma = vmac->vma; +@@ -3243,17 +3288,17 @@ void collect_procs_ksm(const struct folio *folio, const struct page *page, + rcu_read_lock(); + for_each_process(tsk) { + struct anon_vma_chain *vmac; +- unsigned long addr; ++ const unsigned long addr = rmap_item->address & PAGE_MASK; ++ const unsigned long index = rmap_item->linear_page_index; + struct task_struct *t = + task_early_kill(tsk, force_early); + if (!t) + continue; +- anon_vma_interval_tree_foreach(vmac, &av->rb_root, 0, +- ULONG_MAX) ++ anon_vma_interval_tree_foreach(vmac, &av->rb_root, index, ++ index) + { + vma = vmac->vma; + if (vma->vm_mm == t->mm) { +- addr = rmap_item->address & PAGE_MASK; + add_to_kill_ksm(t, page, vma, to_kill, + addr); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch.sig new file mode 100644 index 0000000..3262347 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0270-ksm-rmap-walk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch b/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch new file mode 100644 index 0000000..e0bf148 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch @@ -0,0 +1,1162 @@ +diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c +--- a/drivers/block/zram/backend_842.c ++++ b/drivers/block/zram/backend_842.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "842: " fmt ++ + #include + #include + #include +@@ -13,6 +15,14 @@ static void release_params_842(struct zcomp_params *params) + + static int setup_params_842(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c +--- a/drivers/block/zram/backend_deflate.c ++++ b/drivers/block/zram/backend_deflate.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "deflate: " fmt ++ + #include + #include + #include +@@ -22,15 +24,26 @@ static void deflate_release_params(struct zcomp_params *params) + + static int deflate_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = Z_DEFAULT_COMPRESSION; ++ } else if (params->level < Z_DEFAULT_COMPRESSION || ++ params->level > Z_BEST_COMPRESSION) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } ++ + if (params->deflate.winbits == ZCOMP_PARAM_NOT_SET) { + params->deflate.winbits = DEFLATE_DEF_WINBITS; + } else { + s32 wb = params->deflate.winbits; + + if ((wb < -15 || wb > -9) && (wb < 9 || wb > 15)) { +- pr_err("invalid deflate winbits: %d\n", wb); ++ pr_err("invalid winbits %d\n", wb); + return -EINVAL; + } + } +diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c +--- a/drivers/block/zram/backend_lz4.c ++++ b/drivers/block/zram/backend_lz4.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4: " fmt ++ + #include + #include + #include +@@ -28,8 +32,12 @@ static int lz4_setup_params(struct zcomp_params *params) + LZ4_stream_t *dict_stream; + int ret; + +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4_ACCELERATION_DEFAULT; ++ } else if (params->level < LZ4_ACCELERATION_DEFAULT) { ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + if (!params->dict || !params->dict_sz) + return 0; +diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c +--- a/drivers/block/zram/backend_lz4hc.c ++++ b/drivers/block/zram/backend_lz4hc.c +@@ -1,3 +1,7 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#define pr_fmt(fmt) "lz4hc: " fmt ++ + #include + #include + #include +@@ -18,8 +22,18 @@ static void lz4hc_release_params(struct zcomp_params *params) + + static int lz4hc_setup_params(struct zcomp_params *params) + { +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = LZ4HC_DEFAULT_CLEVEL; ++ } else if (params->level < 1 || params->level > LZ4HC_MAX_CLEVEL) { ++ /* ++ * Use < 1 rather than < LZ4HC_MIN_CLEVEL here because ++ * LZ4HC_compress_generic() only clamps levels below 1 ++ * (levels 1 and 2 are valid). LZ4HC_MIN_CLEVEL (3) is ++ * advisory and not enforced by the library. ++ */ ++ pr_err("invalid compression level %d\n", params->level); ++ return -EINVAL; ++ } + + return 0; + } +diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c +--- a/drivers/block/zram/backend_lzo.c ++++ b/drivers/block/zram/backend_lzo.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzo_release_params(struct zcomp_params *params) + + static int lzo_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c +--- a/drivers/block/zram/backend_lzorle.c ++++ b/drivers/block/zram/backend_lzorle.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "lzo-rle: " fmt ++ + #include + #include + #include +@@ -12,6 +14,14 @@ static void lzorle_release_params(struct zcomp_params *params) + + static int lzorle_setup_params(struct zcomp_params *params) + { ++ if (params->dict_sz) { ++ pr_err("dictionary is not supported\n"); ++ return -EOPNOTSUPP; ++ } ++ if (params->level != ZCOMP_PARAM_NOT_SET) { ++ pr_err("compression level is not supported\n"); ++ return -EOPNOTSUPP; ++ } + return 0; + } + +diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c +--- a/drivers/block/zram/backend_zstd.c ++++ b/drivers/block/zram/backend_zstd.c +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + ++#define pr_fmt(fmt) "zstd: " fmt ++ + #include + #include + #include +@@ -58,8 +60,13 @@ static int zstd_setup_params(struct zcomp_params *params) + return -ENOMEM; + + params->drv_data = zp; +- if (params->level == ZCOMP_PARAM_NOT_SET) ++ if (params->level == ZCOMP_PARAM_NOT_SET) { + params->level = zstd_default_clevel(); ++ } else if (params->level < zstd_min_clevel() || ++ params->level > zstd_max_clevel()) { ++ pr_err("invalid compression level %d\n", params->level); ++ goto error; ++ } + + zp->cprm = zstd_get_params(params->level, PAGE_SIZE); + +@@ -85,7 +92,6 @@ static int zstd_setup_params(struct zcomp_params *params) + return 0; + + error: +- zstd_release_params(params); + return -EINVAL; + } + +@@ -161,7 +167,6 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx) + return 0; + + error: +- zstd_release_params(params); + zstd_destroy(ctx); + return -EINVAL; + } +diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c +--- a/drivers/block/zram/zram_drv.c ++++ b/drivers/block/zram/zram_drv.c +@@ -1664,6 +1664,17 @@ static void comp_algorithm_set(struct zram *zram, u32 prio, const char *alg) + zram->comp_algs[prio] = alg; + } + ++static void comp_params_reset(struct zram *zram, u32 prio) ++{ ++ struct zcomp_params *params = &zram->params[prio]; ++ ++ vfree(params->dict); ++ params->level = ZCOMP_PARAM_NOT_SET; ++ params->deflate.winbits = ZCOMP_PARAM_NOT_SET; ++ params->dict_sz = 0; ++ params->dict = NULL; ++} ++ + static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + { + const char *alg; +@@ -1684,20 +1695,10 @@ static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) + } + + comp_algorithm_set(zram, prio, alg); ++ comp_params_reset(zram, prio); + return 0; + } + +-static void comp_params_reset(struct zram *zram, u32 prio) +-{ +- struct zcomp_params *params = &zram->params[prio]; +- +- vfree(params->dict); +- params->level = ZCOMP_PARAM_NOT_SET; +- params->deflate.winbits = ZCOMP_PARAM_NOT_SET; +- params->dict_sz = 0; +- params->dict = NULL; +-} +- + static int comp_params_store(struct zram *zram, u32 prio, s32 level, + const char *dict_path, + struct deflate_params *deflate_params) +@@ -1712,8 +1713,16 @@ static int comp_params_store(struct zram *zram, u32 prio, s32 level, + INT_MAX, + NULL, + READING_POLICY); +- if (sz < 0) ++ if (sz < 0) { ++ pr_err("failed to load dictionary %s (err=%zd)\n", ++ dict_path, sz); ++ return sz; ++ } ++ if (sz == 0) { ++ pr_err("failed to load dictionary %s (empty file)\n", ++ dict_path); + return -EINVAL; ++ } + } + + zram->params[prio].dict_sz = sz; +diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h +--- a/include/linux/memcontrol.h ++++ b/include/linux/memcontrol.h +@@ -952,6 +952,8 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item); + bool memcg_stat_item_valid(int idx); + bool memcg_vm_event_item_valid(enum vm_event_item idx); + unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx); ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx); + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx); + +@@ -1404,6 +1406,12 @@ static inline unsigned long lruvec_page_state(struct lruvec *lruvec, + return node_page_state(lruvec_pgdat(lruvec), idx); + } + ++static inline unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++} ++ + static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -323,6 +323,8 @@ enum node_stat_item { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -755,6 +757,12 @@ void lru_gen_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, + + #endif /* CONFIG_LRU_GEN */ + ++struct lru_cost { ++ unsigned long count; ++ unsigned long last_rotated; ++ unsigned long last_io; ++}; ++ + struct lruvec { + struct list_head lists[NR_LRU_LISTS]; + /* per lruvec lru_lock for memcg */ +@@ -763,9 +771,12 @@ struct lruvec { + * These track the cost of reclaiming one LRU - file or anon - + * over the other. As the observed cost of reclaiming one LRU + * increases, the reclaim scan balance tips toward the other. ++ * Updated and decayed at prepare_scan_control() time; cost_lock ++ * serialises that update. + */ +- unsigned long anon_cost; +- unsigned long file_cost; ++ struct lru_cost cost[ANON_AND_FILE]; ++ /* Protects cost[]. */ ++ spinlock_t cost_lock; + /* Non-resident age, driven by LRU movement */ + atomic_long_t nonresident_age; + /* Refaults at the time of last reclaim cycle */ +diff --git a/include/linux/swap.h b/include/linux/swap.h +--- a/include/linux/swap.h ++++ b/include/linux/swap.h +@@ -309,9 +309,6 @@ extern unsigned long totalreserve_pages; + + + /* linux/mm/swap.c */ +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated); +-void lru_note_cost_refault(struct folio *); + void folio_add_lru(struct folio *); + void folio_add_lru_vma(struct folio *, struct vm_area_struct *); + void mark_page_accessed(struct page *); +diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h +--- a/include/linux/vmstat.h ++++ b/include/linux/vmstat.h +@@ -20,7 +20,6 @@ struct reclaim_stat { + unsigned nr_congested; + unsigned nr_writeback; + unsigned nr_immediate; +- unsigned nr_pageout; + unsigned nr_activate[ANON_AND_FILE]; + unsigned nr_ref_keep; + unsigned nr_unmap_fail; +@@ -194,6 +193,19 @@ unsigned long global_node_page_state_pages(enum node_stat_item item) + return x; + } + ++/* ++ * Non-clamping variant of global_node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * Returns the raw wrapping value so that unsigned modular subtraction stays ++ * correct across a signed-long overflow (a real hazard on 32-bit) that the ++ * clamp in global_node_page_state() would otherwise turn into a huge spurious ++ * delta. Do NOT use for non-monotonic page-count reads. ++ */ ++static inline unsigned long global_node_page_state_monotonic(enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&vm_node_stat[item]); ++} ++ + static inline unsigned long global_node_page_state(enum node_stat_item item) + { + VM_WARN_ON_ONCE(vmstat_item_in_bytes(item)); +@@ -259,11 +271,14 @@ extern unsigned long node_page_state(struct pglist_data *pgdat, + enum node_stat_item item); + extern unsigned long node_page_state_pages(struct pglist_data *pgdat, + enum node_stat_item item); ++extern unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item); + extern void fold_vm_numa_events(void); + #else + #define sum_zone_node_page_state(node, item) global_zone_page_state(item) + #define node_page_state(node, item) global_node_page_state(item) + #define node_page_state_pages(node, item) global_node_page_state_pages(item) ++#define node_page_state_monotonic(node, item) global_node_page_state_monotonic(item) + static inline void fold_vm_numa_events(void) + { + } +diff --git a/lib/maple_tree.c b/lib/maple_tree.c +--- a/lib/maple_tree.c ++++ b/lib/maple_tree.c +@@ -1501,14 +1501,26 @@ static inline void mas_parent_gap(struct ma_state *mas, unsigned char offset, + goto ascend; + } + ++static __always_inline void mas_update_gap_known(struct ma_state *mas, ++ unsigned long gap) ++{ ++ unsigned char pslot; ++ unsigned long p_gap; ++ ++ pslot = mte_parent_slot(mas->node); ++ p_gap = ma_gaps(mte_parent(mas->node), ++ mas_parent_type(mas, mas->node))[pslot]; ++ ++ if (p_gap != gap) ++ mas_parent_gap(mas, pslot, gap); ++} ++ + /* + * mas_update_gap() - Update a nodes gaps and propagate up if necessary. + * @mas: the maple state. + */ + static inline void mas_update_gap(struct ma_state *mas) + { +- unsigned char pslot; +- unsigned long p_gap; + unsigned long max_gap; + + if (!mt_is_alloc(mas->tree)) +@@ -1518,13 +1530,7 @@ static inline void mas_update_gap(struct ma_state *mas) + return; + + max_gap = mas_max_gap(mas); +- +- pslot = mte_parent_slot(mas->node); +- p_gap = ma_gaps(mte_parent(mas->node), +- mas_parent_type(mas, mas->node))[pslot]; +- +- if (p_gap != max_gap) +- mas_parent_gap(mas, pslot, max_gap); ++ mas_update_gap_known(mas, max_gap); + } + + /* +@@ -2081,8 +2087,8 @@ static inline void mas_wmb_replace(struct ma_state *mas, struct maple_copy *cp) + mas->node = mt_slot_locked(mas->tree, cp->slot, 0); + /* Insert the new data in the tree */ + mas_topiary_replace(mas, old_enode, cp->height); +- if (!mte_is_leaf(mas->node)) +- mas_update_gap(mas); ++ if (mt_is_alloc(mas->tree) && !mte_is_root(mas->node)) ++ mas_update_gap_known(mas, cp->gap[0]); + + mtree_range_walk(mas); + } +@@ -3125,7 +3131,7 @@ static void mas_wr_spanning_store(struct ma_wr_state *wr_mas) + static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + { + unsigned char dst_offset, offset_end; +- unsigned char copy_size, node_pivots; ++ unsigned char copy_size, node_pivots, node_slots; + struct maple_node reuse, *newnode; + unsigned long *dst_pivots; + void __rcu **dst_slots; +@@ -3138,6 +3144,7 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + in_rcu = mt_in_rcu(mas->tree); + offset_end = wr_mas->offset_end; + node_pivots = mt_pivots[wr_mas->type]; ++ node_slots = mt_slots[wr_mas->type]; + /* Assume last adds an entry */ + new_end = mas->end + 1 - offset_end + mas->offset; + if (mas->last == wr_mas->end_piv) { +@@ -3149,7 +3156,6 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + if (in_rcu) { + newnode = mas_pop_node(mas); + } else { +- memset(&reuse, 0, sizeof(struct maple_node)); + newnode = &reuse; + } + +@@ -3193,7 +3199,21 @@ static inline void mas_wr_node_store(struct ma_wr_state *wr_mas) + dst_pivots[new_end] = mas->max; + + done: +- mas_leaf_set_meta(newnode, maple_leaf_64, new_end); ++ if (!in_rcu && new_end + 2 < node_slots) { ++ unsigned char clear_from = new_end + 1; ++ ++ /* ++ * Note that the last slot is never cleared, since the metadata ++ * will be stored there or it has a value. ++ */ ++ memset(dst_slots + clear_from, 0, ++ sizeof(void __rcu *) * (node_slots - clear_from)); ++ if (clear_from < node_pivots) ++ memset(dst_pivots + clear_from, 0, ++ sizeof(unsigned long) * (node_pivots - clear_from)); ++ } ++ ++ mas_leaf_set_meta(newnode, wr_mas->type, new_end); + if (in_rcu) { + struct maple_enode *old_enode = mas->node; + +@@ -3218,7 +3238,7 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas) + void __rcu **slots = wr_mas->slots; + bool gap = false; + +- gap |= !mt_slot_locked(mas->tree, slots, offset); ++ gap |= !wr_mas->content; + gap |= !mt_slot_locked(mas->tree, slots, offset + 1); + + if (wr_mas->offset_end - offset == 1) { +@@ -3661,6 +3681,9 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + { + struct ma_state *mas = wr_mas->mas; + unsigned char new_end; ++ bool appending; ++ bool one_slot; ++ bool in_rcu; + + if (unlikely(mas_is_none(mas) || mas_is_ptr(mas))) + return wr_store_root; +@@ -3680,21 +3703,30 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas) + return wr_new_root; + + new_end = mas_wr_new_end(wr_mas); ++ in_rcu = mt_in_rcu(mas->tree); ++ appending = mas->offset == mas->end; ++ one_slot = wr_mas->offset_end - mas->offset == 1; ++ + /* Potential spanning rebalance collapsing a node */ + if (new_end < mt_min_slots[wr_mas->type]) { + if (!mte_is_root(mas->node)) + return wr_rebalance; ++ if (!in_rcu) { ++ if (appending) ++ return wr_append; ++ else if (mas->end == new_end && one_slot) ++ return wr_slot_store; ++ } + return wr_node_store; + } + + if (new_end >= mt_slots[wr_mas->type]) + return wr_split_store; + +- if (!mt_in_rcu(mas->tree) && (mas->offset == mas->end)) ++ if (!in_rcu && appending) + return wr_append; + +- if ((new_end == mas->end) && (!mt_in_rcu(mas->tree) || +- (wr_mas->offset_end - mas->offset == 1))) ++ if (new_end == mas->end && (!in_rcu || one_slot)) + return wr_slot_store; + + return wr_node_store; +diff --git a/mm/khugepaged.c b/mm/khugepaged.c +--- a/mm/khugepaged.c ++++ b/mm/khugepaged.c +@@ -1442,10 +1442,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s + + result = SCAN_SUCCEED; + out_up_write: +- if (anon_vma_locked) +- anon_vma_unlock_write(vma->anon_vma); + if (pte) + pte_unmap(pte); ++ if (anon_vma_locked) ++ anon_vma_unlock_write(vma->anon_vma); + mmap_write_unlock(mm); + out_nolock: + if (folio) +diff --git a/mm/ksm.c b/mm/ksm.c +--- a/mm/ksm.c ++++ b/mm/ksm.c +@@ -3061,10 +3061,9 @@ int __ksm_enter(struct mm_struct *mm) + + slot = &mm_slot->slot; + ++ spin_lock(&ksm_mmlist_lock); + /* Check ksm_run too? Would need tighter locking */ + needs_wakeup = list_empty(&ksm_mm_head.slot.mm_node); +- +- spin_lock(&ksm_mmlist_lock); + mm_slot_insert(mm_slots_hash, mm, slot); + /* + * When KSM_RUN_MERGE (or KSM_RUN_STOP), +diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c +--- a/mm/memcontrol-v1.c ++++ b/mm/memcontrol-v1.c +@@ -1998,8 +1998,8 @@ void memcg1_stat_format(struct mem_cgroup *memcg, struct seq_buf *s) + for_each_online_pgdat(pgdat) { + mz = memcg->nodeinfo[pgdat->node_id]; + +- anon_cost += mz->lruvec.anon_cost; +- file_cost += mz->lruvec.file_cost; ++ anon_cost += mz->lruvec.cost[WORKINGSET_ANON].count; ++ file_cost += mz->lruvec.cost[WORKINGSET_FILE].count; + } + seq_buf_printf(s, "anon_cost %lu\n", anon_cost); + seq_buf_printf(s, "file_cost %lu\n", file_cost); +diff --git a/mm/memcontrol.c b/mm/memcontrol.c +--- a/mm/memcontrol.c ++++ b/mm/memcontrol.c +@@ -393,6 +393,7 @@ static const unsigned int memcg_node_stat_items[] = { + NR_SHMEM_THPS, + NR_FILE_THPS, + NR_ANON_THPS, ++ NR_VMSCAN_WRITE, + NR_VMALLOC, + NR_KERNEL_STACK_KB, + NR_PAGETABLE, +@@ -419,6 +420,8 @@ static const unsigned int memcg_node_stat_items[] = { + PGSCAN_PROACTIVE, + PGSCAN_ANON, + PGSCAN_FILE, ++ PGROTATE_ANON, ++ PGROTATE_FILE, + PGREFILL, + #ifdef CONFIG_HUGETLB_PAGE + NR_HUGETLB, +@@ -502,6 +505,42 @@ unsigned long lruvec_page_state(struct lruvec *lruvec, enum node_stat_item idx) + return x; + } + ++/** ++ * lruvec_page_state_monotonic - non-clamping lruvec stat read for delta sampling ++ * @lruvec: the LRU vector to read from ++ * @idx: the node_stat_item to read ++ * ++ * Returns the raw state[idx] value cast to unsigned long, skipping the ++ * clamp-negative-to-zero step in lruvec_page_state(). Intended for callers ++ * that snapshot a monotonically-incremented counter and subtract two ++ * samples: unsigned modular arithmetic then yields the correct delta across ++ * a signed-long wraparound (a real hazard on 32-bit) that the clamp would ++ * otherwise turn into a huge spurious delta. ++ * ++ * Do NOT use for non-monotonic page-count reads where a transient negative ++ * reading from per-CPU delta skew must present as zero. ++ * ++ * XXX: This helper (and its node/global peers) exists because some ++ * monotonically-incremented event counters are stored in ++ * enum node_stat_item. ++ */ ++unsigned long lruvec_page_state_monotonic(struct lruvec *lruvec, ++ enum node_stat_item idx) ++{ ++ struct mem_cgroup_per_node *pn; ++ int i; ++ ++ if (mem_cgroup_disabled()) ++ return node_page_state_monotonic(lruvec_pgdat(lruvec), idx); ++ ++ i = memcg_stats_index(idx); ++ if (WARN_ONCE(BAD_STAT_IDX(i), "%s: missing stat item %d\n", __func__, idx)) ++ return 0; ++ ++ pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec); ++ return (unsigned long)READ_ONCE(pn->lruvec_stats->state[i]); ++} ++ + unsigned long lruvec_page_state_local(struct lruvec *lruvec, + enum node_stat_item idx) + { +@@ -2095,7 +2134,12 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages) + + stock_pages = READ_ONCE(stock->nr_pages[i]); + if (stock_pages >= nr_pages) { +- WRITE_ONCE(stock->nr_pages[i], stock_pages - nr_pages); ++ stock_pages -= nr_pages; ++ WRITE_ONCE(stock->nr_pages[i], stock_pages); ++ if (!stock_pages) { ++ css_put(&memcg->css); ++ WRITE_ONCE(stock->cached[i], NULL); ++ } + ret = true; + } + break; +diff --git a/mm/migrate_device.c b/mm/migrate_device.c +--- a/mm/migrate_device.c ++++ b/mm/migrate_device.c +@@ -872,7 +872,7 @@ static int migrate_vma_insert_huge_pmd_page(struct migrate_vma *migrate, + + if (flush) { + pte_free(vma->vm_mm, pgtable); +- flush_cache_page(vma, addr, addr + HPAGE_PMD_SIZE); ++ flush_cache_range(vma, addr, addr + HPAGE_PMD_SIZE); + pmdp_invalidate(vma, addr, pmdp); + } else { + pgtable_trans_huge_deposit(vma->vm_mm, pmdp, pgtable); +diff --git a/mm/mmzone.c b/mm/mmzone.c +--- a/mm/mmzone.c ++++ b/mm/mmzone.c +@@ -78,6 +78,7 @@ void lruvec_init(struct lruvec *lruvec) + + memset(lruvec, 0, sizeof(struct lruvec)); + spin_lock_init(&lruvec->lru_lock); ++ spin_lock_init(&lruvec->cost_lock); + zswap_lruvec_state_init(lruvec); + + for_each_lru(lru) +diff --git a/mm/mremap.c b/mm/mremap.c +--- a/mm/mremap.c ++++ b/mm/mremap.c +@@ -264,7 +264,7 @@ static int move_ptes(struct pagetable_move_control *pmc, + + for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes * PAGE_SIZE, + new_ptep += nr_ptes, new_addr += nr_ptes * PAGE_SIZE) { +- VM_WARN_ON_ONCE(!pte_none(*new_ptep)); ++ VM_WARN_ON_ONCE(!pte_none(ptep_get(new_ptep))); + + nr_ptes = 1; + max_nr_ptes = (old_end - old_addr) >> PAGE_SHIFT; +diff --git a/mm/shmem.c b/mm/shmem.c +--- a/mm/shmem.c ++++ b/mm/shmem.c +@@ -3612,6 +3612,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + struct shmem_inode_info *info = SHMEM_I(inode); + struct shmem_falloc shmem_falloc; + pgoff_t start, index, end, undo_fallocend; ++ loff_t aligned_end; + int error; + + if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE)) +@@ -3668,8 +3669,15 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset, + goto out; + } + ++ /* Check for wraparound */ ++ if (check_add_overflow(offset + len, (loff_t)PAGE_SIZE - 1, ++ &aligned_end)) { ++ error = -EFBIG; ++ goto out; ++ } ++ + start = offset >> PAGE_SHIFT; +- end = (offset + len + PAGE_SIZE - 1) >> PAGE_SHIFT; ++ end = aligned_end >> PAGE_SHIFT; + /* Try to avoid a swapstorm if len is impossible to satisfy */ + if (sbinfo->max_blocks && end - start > sbinfo->max_blocks) { + error = -ENOSPC; +diff --git a/mm/swap.c b/mm/swap.c +--- a/mm/swap.c ++++ b/mm/swap.c +@@ -272,73 +272,6 @@ void folio_rotate_reclaimable(struct folio *folio) + folio_batch_add_and_move(folio, lru_move_tail); + } + +-void lru_note_cost_unlock_irq(struct lruvec *lruvec, bool file, +- unsigned int nr_io, unsigned int nr_rotated) +- __releases(lruvec->lru_lock) +- __releases(rcu) +-{ +- unsigned long cost; +- +- /* +- * Reflect the relative cost of incurring IO and spending CPU +- * time on rotations. This doesn't attempt to make a precise +- * comparison, it just says: if reloads are about comparable +- * between the LRU lists, or rotations are overwhelmingly +- * different between them, adjust scan balance for CPU work. +- */ +- cost = nr_io * SWAP_CLUSTER_MAX + nr_rotated; +- if (!cost) { +- spin_unlock_irq(&lruvec->lru_lock); +- rcu_read_unlock(); +- return; +- } +- +- for (;;) { +- unsigned long lrusize; +- +- /* Record cost event */ +- if (file) +- lruvec->file_cost += cost; +- else +- lruvec->anon_cost += cost; +- +- /* +- * Decay previous events +- * +- * Because workloads change over time (and to avoid +- * overflow) we keep these statistics as a floating +- * average, which ends up weighing recent refaults +- * more than old ones. +- */ +- lrusize = lruvec_page_state(lruvec, NR_INACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_ACTIVE_ANON) + +- lruvec_page_state(lruvec, NR_INACTIVE_FILE) + +- lruvec_page_state(lruvec, NR_ACTIVE_FILE); +- +- if (lruvec->file_cost + lruvec->anon_cost > lrusize / 4) { +- lruvec->file_cost /= 2; +- lruvec->anon_cost /= 2; +- } +- +- spin_unlock_irq(&lruvec->lru_lock); +- lruvec = parent_lruvec(lruvec); +- if (!lruvec) { +- rcu_read_unlock(); +- break; +- } +- spin_lock_irq(&lruvec->lru_lock); +- } +-} +- +-void lru_note_cost_refault(struct folio *folio) +-{ +- struct lruvec *lruvec; +- +- lruvec = folio_lruvec_lock_irq(folio); +- lru_note_cost_unlock_irq(lruvec, folio_is_file_lru(folio), +- folio_nr_pages(folio), 0); +-} +- + static void lru_activate(struct lruvec *lruvec, struct folio *folio) + { + long nr_pages = folio_nr_pages(folio); +@@ -1173,8 +1106,6 @@ void lru_reparent_memcg(struct mem_cgroup *memcg, struct mem_cgroup *parent, int + + child_lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); + parent_lruvec = mem_cgroup_lruvec(parent, NODE_DATA(nid)); +- parent_lruvec->anon_cost += child_lruvec->anon_cost; +- parent_lruvec->file_cost += child_lruvec->file_cost; + + for_each_lru(lru) + lruvec_reparent_lru(child_lruvec, parent_lruvec, lru, nid); +diff --git a/mm/vma.c b/mm/vma.c +--- a/mm/vma.c ++++ b/mm/vma.c +@@ -1866,10 +1866,10 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + bool *need_rmap_locks) + { + struct vm_area_struct *vma = *vmap; +- unsigned long vma_start = vma->vm_start; ++ unsigned long old_vma_start = vma->vm_start; + struct mm_struct *mm = vma->vm_mm; + struct vm_area_struct *new_vma; +- bool faulted_in_anon_vma = true; ++ bool can_self_merge = false; + VMA_ITERATOR(vmi, mm, addr); + VMG_VMA_STATE(vmg, &vmi, NULL, vma, addr, addr + len); + +@@ -1879,7 +1879,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + */ + if (unlikely(vma_is_anonymous(vma) && !vma->anon_vma)) { + pgoff = addr >> PAGE_SHIFT; +- faulted_in_anon_vma = false; ++ can_self_merge = true; + } + + /* +@@ -1899,24 +1899,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, + new_vma = vma_merge_copied_range(&vmg); + + if (new_vma) { +- /* +- * Source vma may have been merged into new_vma +- */ +- if (unlikely(vma_start >= new_vma->vm_start && +- vma_start < new_vma->vm_end)) { ++ /* Self-merged and VMA replaced. */ ++ if (unlikely(new_vma->vm_start < old_vma_start && ++ new_vma->vm_end > old_vma_start)) { + /* +- * The only way we can get a vma_merge with +- * self during an mremap is if the vma hasn't +- * been faulted in yet and we were allowed to +- * reset the dst vma->vm_pgoff to the +- * destination address of the mremap to allow +- * the merge to happen. mremap must change the +- * vm_pgoff linearity between src and dst vmas +- * (in turn preventing a vma_merge) to be +- * safe. It is only safe to keep the vm_pgoff +- * linear if there are no pages mapped yet. ++ * The only way a VMA can both self-merge and be ++ * replaced is if the remap places the new VMA ++ * immediately prior to its old self ('next') and ++ * immediately after another VMA ('prev') causing the ++ * next to be removed and prev to be expanded to cover ++ * the entire range. ++ * ++ * This should only be possible if the page offset was ++ * updated, i.e. the VMA is unfaulted. + */ +- VM_BUG_ON_VMA(faulted_in_anon_vma, new_vma); ++ VM_WARN_ON_ONCE_VMA(!can_self_merge, new_vma); + *vmap = vma = new_vma; + } + *need_rmap_locks = (new_vma->vm_pgoff <= vma->vm_pgoff); +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -669,7 +669,7 @@ static pageout_t pageout(struct folio *folio, struct address_space *mapping, + folio_clear_reclaim(folio); + + trace_mm_vmscan_write_folio(folio); +- node_stat_add_folio(folio, NR_VMSCAN_WRITE); ++ lruvec_stat_mod_folio(folio, NR_VMSCAN_WRITE, folio_nr_pages(folio)); + return PAGE_SUCCESS; + } + +@@ -1420,8 +1420,6 @@ static unsigned int shrink_folio_list(struct list_head *folio_list, + sc->nr_scanned -= (nr_pages - 1); + nr_pages = 1; + } +- stat->nr_pageout += nr_pages; +- + if (folio_test_writeback(folio)) + goto keep; + if (folio_test_dirty(folio)) +@@ -2045,10 +2043,10 @@ static unsigned long shrink_inactive_list(unsigned long nr_to_scan, + item = PGSTEAL_KSWAPD + reclaimer_offset(sc); + mod_lruvec_state(lruvec, item, nr_reclaimed); + mod_lruvec_state(lruvec, PGSTEAL_ANON + file, nr_reclaimed); ++ if (nr_scanned > nr_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, ++ nr_scanned - nr_reclaimed); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, stat.nr_pageout, +- nr_scanned - nr_reclaimed); + handle_reclaim_writeback(nr_taken, pgdat, sc, &stat); + trace_mm_vmscan_lru_shrink_inactive(pgdat->node_id, + nr_scanned, nr_reclaimed, &stat, sc->priority, file); +@@ -2154,9 +2152,9 @@ static void shrink_active_list(unsigned long nr_to_scan, + count_vm_events(PGDEACTIVATE, nr_deactivate); + count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE, nr_deactivate); + mod_node_page_state(pgdat, NR_ISOLATED_ANON + file, -nr_taken); ++ if (nr_rotated) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + file, nr_rotated); + +- lruvec_lock_irq(lruvec); +- lru_note_cost_unlock_irq(lruvec, file, 0, nr_rotated); + trace_mm_vmscan_lru_shrink_active(pgdat->node_id, nr_taken, nr_activate, + nr_deactivate, nr_rotated, sc->priority, file); + } +@@ -2289,8 +2287,10 @@ enum scan_balance { + + static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + { +- unsigned long file; ++ struct lru_cost *anon_cost, *file_cost; + struct lruvec *target_lruvec; ++ unsigned long lrusize; ++ unsigned long file; + + if (lru_gen_enabled() && !lru_gen_switching()) + return; +@@ -2306,11 +2306,69 @@ static void prepare_scan_control(pg_data_t *pgdat, struct scan_control *sc) + + /* + * Determine the scan balance between anon and file LRUs. ++ * ++ * The cost model is based on rotations, refaults and ++ * reclaim-driven writes (anon only) on each side. ++ * ++ * These event counters are monotonic, so each reclaim cycle ++ * the delta since the last scan is extracted and incorporated ++ * into a decaying average. This ensures currency, as workloads ++ * change over time, and avoids overflow in the calculations. ++ * ++ * Use lruvec_page_state_monotonic() so unsigned subtraction ++ * yields the correct delta across a signed-long wraparound of ++ * the underlying counter (a real hazard on 32-bit that the ++ * clamp in lruvec_page_state() would otherwise turn into a huge ++ * spurious delta). + */ +- spin_lock_irq(&target_lruvec->lru_lock); +- sc->anon_cost = target_lruvec->anon_cost; +- sc->file_cost = target_lruvec->file_cost; +- spin_unlock_irq(&target_lruvec->lru_lock); ++ spin_lock(&target_lruvec->cost_lock); ++ ++ for (int f = 0; f <= 1; f++) { ++ struct lru_cost *cost = &target_lruvec->cost[f]; ++ unsigned long rotated, io, nr_rotated, nr_io; ++ ++ rotated = lruvec_page_state_monotonic(target_lruvec, ++ PGROTATE_ANON + f); ++ io = lruvec_page_state_monotonic(target_lruvec, ++ WORKINGSET_RESTORE_BASE + f); ++ if (f == WORKINGSET_ANON) ++ io += lruvec_page_state_monotonic(target_lruvec, ++ NR_VMSCAN_WRITE); ++ ++ nr_rotated = rotated - cost->last_rotated; ++ nr_io = io - cost->last_io; ++ ++ /* ++ * Reflect the relative cost of incurring IO and spending ++ * CPU time on rotations. This doesn't attempt to make a ++ * precise comparison, it just says: if reloads are about ++ * comparable between the LRU lists, or rotations are ++ * overwhelmingly different between them, adjust scan ++ * balance for CPU work. ++ */ ++ cost->count += nr_io * SWAP_CLUSTER_MAX + nr_rotated; ++ ++ cost->last_rotated = rotated; ++ cost->last_io = io; ++ } ++ ++ anon_cost = &target_lruvec->cost[WORKINGSET_ANON]; ++ file_cost = &target_lruvec->cost[WORKINGSET_FILE]; ++ ++ lrusize = lruvec_page_state(target_lruvec, NR_INACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_ANON) + ++ lruvec_page_state(target_lruvec, NR_INACTIVE_FILE) + ++ lruvec_page_state(target_lruvec, NR_ACTIVE_FILE); ++ ++ while (anon_cost->count + file_cost->count > lrusize / 4) { ++ anon_cost->count /= 2; ++ file_cost->count /= 2; ++ } ++ ++ sc->anon_cost = anon_cost->count; ++ sc->file_cost = file_cost->count; ++ ++ spin_unlock(&target_lruvec->cost_lock); + + /* + * Target desirable inactive:active list ratios for the anon +@@ -4197,7 +4255,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw, unsigned int nr) + unsigned long end; + struct lru_gen_mm_walk *walk; + struct folio *last = NULL; +- int young = 1; ++ int young = nr; + pte_t *pte = pvmw->pte; + unsigned long addr = pvmw->address; + struct vm_area_struct *vma = pvmw->vma; +@@ -4819,7 +4877,8 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + struct reclaim_stat stat; + struct lru_gen_mm_walk *walk; + int scanned, reclaimed; +- int isolated = 0, type, type_scanned; ++ int isolated = 0, nr_isolated = 0, type, type_scanned; ++ unsigned long total_reclaimed = 0; + bool skip_retry = false; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); +@@ -4831,6 +4890,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + + scanned = isolate_folios(nr_to_scan, lruvec, sc, swappiness, + &list, &isolated, &type, &type_scanned); ++ nr_isolated = isolated; + + /* Scanning may have emptied the oldest gen, flush it */ + if (scanned) +@@ -4843,6 +4903,7 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + retry: + reclaimed = shrink_folio_list(&list, pgdat, sc, &stat, false, memcg); + sc->nr_reclaimed += reclaimed; ++ total_reclaimed += reclaimed; + /* Retry pass is only meant for clean folios without new isolation */ + if (isolated) + handle_reclaim_writeback(isolated, pgdat, sc, &stat); +@@ -4892,6 +4953,10 @@ static int evict_folios(unsigned long nr_to_scan, struct lruvec *lruvec, + goto retry; + } + ++ if (nr_isolated > total_reclaimed) ++ mod_lruvec_state(lruvec, PGROTATE_ANON + type, ++ nr_isolated - total_reclaimed); ++ + return scanned; + } + +diff --git a/mm/vmstat.c b/mm/vmstat.c +--- a/mm/vmstat.c ++++ b/mm/vmstat.c +@@ -1024,6 +1024,17 @@ unsigned long node_page_state(struct pglist_data *pgdat, + + return node_page_state_pages(pgdat, item); + } ++ ++/* ++ * Non-clamping variant of node_page_state() intended for callers that ++ * snapshot a monotonically-incremented counter and subtract two samples. ++ * See global_node_page_state_monotonic() for the rationale. ++ */ ++unsigned long node_page_state_monotonic(struct pglist_data *pgdat, ++ enum node_stat_item item) ++{ ++ return (unsigned long)atomic_long_read(&pgdat->vm_stat[item]); ++} + #endif + + /* +@@ -1289,6 +1300,8 @@ const char * const vmstat_text[] = { + [I(PGSCAN_PROACTIVE)] = "pgscan_proactive", + [I(PGSCAN_ANON)] = "pgscan_anon", + [I(PGSCAN_FILE)] = "pgscan_file", ++ [I(PGROTATE_ANON)] = "pgrotate_anon", ++ [I(PGROTATE_FILE)] = "pgrotate_file", + [I(PGREFILL)] = "pgrefill", + #ifdef CONFIG_HUGETLB_PAGE + [I(NR_HUGETLB)] = "nr_hugetlb", +diff --git a/mm/workingset.c b/mm/workingset.c +--- a/mm/workingset.c ++++ b/mm/workingset.c +@@ -584,11 +584,6 @@ void workingset_refault(struct folio *folio, void *shadow) + /* Folio was active prior to eviction */ + if (workingset) { + folio_set_workingset(folio); +- /* +- * XXX: Move to folio_add_lru() when it supports new vs +- * putback +- */ +- lru_note_cost_refault(folio); + mod_lruvec_state(lruvec, WORKINGSET_RESTORE_BASE + file, nr); + } + out: +diff --git a/mm/zswap.c b/mm/zswap.c +--- a/mm/zswap.c ++++ b/mm/zswap.c +@@ -1217,7 +1217,7 @@ static unsigned long zswap_shrinker_count(struct shrinker *shrinker, + * Without memcg, use the zswap pool-wide metrics. + */ + if (!mem_cgroup_disabled()) { +- mem_cgroup_flush_stats(memcg); ++ mem_cgroup_flush_stats_ratelimited(memcg); + nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT; + nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED); + } else { +@@ -1275,6 +1275,14 @@ static struct shrinker *zswap_alloc_shrinker(void) + return shrinker; + } + ++/* ++ * Scan up to SWAP_CLUSTER_MAX pages on each per-node zswap LRU of @memcg ++ * and write back the reclaimable ones. ++ * ++ * Return: 0 if at least one entry was written back, -EAGAIN if entries ++ * were scanned but none could be written back, or -ENOENT if @memcg has ++ * writeback disabled, is a zombie cgroup, or has empty zswap LRUs. ++ */ + static int shrink_memcg(struct mem_cgroup *memcg) + { + int nid, shrunk = 0, scanned = 0; +@@ -1290,13 +1298,14 @@ static int shrink_memcg(struct mem_cgroup *memcg) + return -ENOENT; + + for_each_node_state(nid, N_NORMAL_MEMORY) { +- unsigned long nr_to_walk = 1; ++ unsigned long nr_to_walk = SWAP_CLUSTER_MAX; + + shrunk += list_lru_walk_one(&zswap_list_lru, nid, memcg, + &shrink_memcg_cb, NULL, &nr_to_walk); +- scanned += 1 - nr_to_walk; ++ scanned += SWAP_CLUSTER_MAX - nr_to_walk; + } + ++ /* Nothing was scanned: every LRU under @memcg was empty. */ + if (!scanned) + return -ENOENT; + diff --git a/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch.sig new file mode 100644 index 0000000..8a29396 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0280-mm-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch b/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch new file mode 100644 index 0000000..e8e9809 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch @@ -0,0 +1,35 @@ +diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c +--- a/mm/pgtable-generic.c ++++ b/mm/pgtable-generic.c +@@ -438,12 +438,30 @@ static void kernel_pgtable_work_func(struct work_struct *work) + __pagetable_free(pt); + } + ++static void schedule_kernel_pgtable_free(void) ++{ ++ schedule_work(&kernel_pgtable_work.work); ++} ++ + void pagetable_free_kernel(struct ptdesc *pt) + { + spin_lock(&kernel_pgtable_work.lock); + list_add(&pt->pt_list, &kernel_pgtable_work.list); + spin_unlock(&kernel_pgtable_work.lock); + +- schedule_work(&kernel_pgtable_work.work); ++ /* ++ * The workqueue may not exist yet while the system is booting. ++ * kernel_pgtable_drain_early() schedules the work once it does. ++ */ ++ if (system_state != SYSTEM_BOOTING) ++ schedule_kernel_pgtable_free(); + } ++ ++static int __init kernel_pgtable_drain_early(void) ++{ ++ /* Free the kernel page tables queued while booting. */ ++ schedule_kernel_pgtable_free(); ++ return 0; ++} ++core_initcall(kernel_pgtable_drain_early); + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig b/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig new file mode 100644 index 0000000..cf29026 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0281-mm-no-deferred-kernel-page-table-free-at-boot.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0290-zstd-bmi2-fallback-aliases.patch b/pkgbuilds/linux-omarchy-muqss/0290-zstd-bmi2-fallback-aliases.patch new file mode 100644 index 0000000..a14613c --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0290-zstd-bmi2-fallback-aliases.patch.sig b/pkgbuilds/linux-omarchy-muqss/0290-zstd-bmi2-fallback-aliases.patch.sig new file mode 100644 index 0000000..6707301 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0290-zstd-bmi2-fallback-aliases.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch b/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch new file mode 100644 index 0000000..9f0b872 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch @@ -0,0 +1,413 @@ +diff --git a/lib/zstd/common/compiler.h b/lib/zstd/common/compiler.h +--- a/lib/zstd/common/compiler.h ++++ b/lib/zstd/common/compiler.h +@@ -14,6 +14,7 @@ + + #include + ++#include "zstd_deps.h" + #include "portability_macros.h" + + /*-******************************************************* +@@ -96,6 +97,17 @@ + */ + #define BMI2_TARGET_ATTRIBUTE TARGET_ATTRIBUTE("lzcnt,bmi,bmi2") + ++#if !DYNAMIC_BMI2 ++# define ZSTD_USE_BMI2(bmi2) 0 ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#elif defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++# define ZSTD_USE_BMI2(bmi2) cpu_feature_enabled(X86_FEATURE_BMI2) ++# define ZSTD_SET_BMI2(state, value) do { } while (0) ++#else ++# define ZSTD_USE_BMI2(bmi2) (bmi2) ++# define ZSTD_SET_BMI2(state, value) do { (state) = (value); } while (0) ++#endif ++ + /* prefetch + * can be disabled, by declaring NO_PREFETCH build macro */ + #if ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) +diff --git a/lib/zstd/common/entropy_common.c b/lib/zstd/common/entropy_common.c +--- a/lib/zstd/common/entropy_common.c ++++ b/lib/zstd/common/entropy_common.c +@@ -16,6 +16,10 @@ + /* ************************************* + * Dependencies + ***************************************/ ++#include "zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "mem.h" + #include "error_private.h" /* ERR_*, ERROR */ + #define FSE_STATIC_LINKING_ONLY /* FSE_MIN_TABLELOG */ +@@ -210,11 +214,9 @@ size_t FSE_readNCount_bmi2( + short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr, + const void* headerBuffer, size_t hbSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_readNCount_body_bmi2(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +-#endif + (void)bmi2; + return FSE_readNCount_body_default(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize); + } +@@ -335,11 +337,9 @@ size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats, + void* workSpace, size_t wkspSize, + int flags) + { +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_readStats_body_bmi2(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +-#endif + (void)flags; + return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/fse_decompress.c b/lib/zstd/common/fse_decompress.c +--- a/lib/zstd/common/fse_decompress.c ++++ b/lib/zstd/common/fse_decompress.c +@@ -24,6 +24,9 @@ + #include "fse.h" + #include "error_private.h" + #include "zstd_deps.h" /* ZSTD_memcpy */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "bits.h" /* ZSTD_highbit32 */ + + +@@ -306,11 +309,9 @@ BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, siz + + size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return FSE_decompress_wksp_body_bmi2(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +-#endif + (void)bmi2; + return FSE_decompress_wksp_body_default(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize); + } +diff --git a/lib/zstd/common/zstd_deps.h b/lib/zstd/common/zstd_deps.h +--- a/lib/zstd/common/zstd_deps.h ++++ b/lib/zstd/common/zstd_deps.h +@@ -26,6 +26,11 @@ + #ifndef ZSTD_DEPS_COMMON + #define ZSTD_DEPS_COMMON + ++#if defined(__KERNEL__) && defined(CONFIG_X86) && \ ++ !defined(__DISABLE_EXPORTS) ++#define ZSTD_USE_KERNEL_CPU_FEATURES ++#endif ++ + #include + #include + +diff --git a/lib/zstd/compress/huf_compress.c b/lib/zstd/compress/huf_compress.c +--- a/lib/zstd/compress/huf_compress.c ++++ b/lib/zstd/compress/huf_compress.c +@@ -22,6 +22,9 @@ + * Includes + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" + #include "hist.h" +@@ -1138,9 +1141,10 @@ HUF_compress1X_usingCTable_internal(void* dst, size_t dstSize, + const void* src, size_t srcSize, + const HUF_CElt* CTable, const int flags) + { +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + return HUF_compress1X_usingCTable_internal_bmi2(dst, dstSize, src, srcSize, CTable); + } ++ (void)flags; + return HUF_compress1X_usingCTable_internal_default(dst, dstSize, src, srcSize, CTable); + } + +diff --git a/lib/zstd/compress/zstd_compress.c b/lib/zstd/compress/zstd_compress.c +--- a/lib/zstd/compress/zstd_compress.c ++++ b/lib/zstd/compress/zstd_compress.c +@@ -102,7 +102,7 @@ static void ZSTD_initCCtx(ZSTD_CCtx* cctx, ZSTD_customMem memManager) + assert(cctx != NULL); + ZSTD_memset(cctx, 0, sizeof(*cctx)); + cctx->customMem = memManager; +- cctx->bmi2 = ZSTD_cpuSupportsBmi2(); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuSupportsBmi2()); + { size_t const err = ZSTD_CCtx_reset(cctx, ZSTD_reset_parameters); + assert(!ZSTD_isError(err)); + (void)err; +@@ -142,7 +142,7 @@ ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize) + cctx->blockState.nextCBlock = (ZSTD_compressedBlockState_t*)ZSTD_cwksp_reserve_object(&cctx->workspace, sizeof(ZSTD_compressedBlockState_t)); + cctx->tmpWorkspace = ZSTD_cwksp_reserve_object(&cctx->workspace, TMP_WORKSPACE_SIZE); + cctx->tmpWkspSize = TMP_WORKSPACE_SIZE; +- cctx->bmi2 = ZSTD_cpuid_bmi2(ZSTD_cpuid()); ++ ZSTD_SET_BMI2(cctx->bmi2, ZSTD_cpuid_bmi2(ZSTD_cpuid())); + return cctx; + } + +@@ -4042,7 +4042,7 @@ ZSTD_compressSeqStore_singleBlock(ZSTD_CCtx* zc, + op + ZSTD_blockHeaderSize, dstCapacity - ZSTD_blockHeaderSize, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + FORWARD_IF_ERROR(cSeqsSize, "ZSTD_entropyCompressSeqStore failed!"); + + if (!zc->isFirstBlock && +@@ -4332,7 +4332,7 @@ ZSTD_compressBlock_internal(ZSTD_CCtx* zc, + dst, dstCapacity, + srcSize, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */, +- zc->bmi2); ++ ZSTD_CCtx_get_bmi2(zc)); + + if (frame && + /* We don't want to emit our first block as a RLE even if it qualifies because +@@ -6796,7 +6796,7 @@ ZSTD_compressSequences_internal(ZSTD_CCtx* cctx, + op + ZSTD_blockHeaderSize /* Leave space for block header */, dstCapacity - ZSTD_blockHeaderSize, + blockSize, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + DEBUGLOG(5, "Compressed sequences size: %zu", compressedSeqsSize); + +@@ -7321,7 +7321,7 @@ ZSTD_compressSequencesAndLiterals_internal(ZSTD_CCtx* cctx, + &cctx->blockState.prevCBlock->entropy, &cctx->blockState.nextCBlock->entropy, + &cctx->appliedParams, + cctx->tmpWorkspace, cctx->tmpWkspSize /* statically allocated in resetCCtx */, +- cctx->bmi2); ++ ZSTD_CCtx_get_bmi2(cctx)); + FORWARD_IF_ERROR(compressedSeqsSize, "Compressing sequences of block failed"); + /* note: the spec forbids for any compressed block to be larger than maximum block size */ + if (compressedSeqsSize > cctx->blockSizeMax) compressedSeqsSize = 0; +diff --git a/lib/zstd/compress/zstd_compress_internal.h b/lib/zstd/compress/zstd_compress_internal.h +--- a/lib/zstd/compress/zstd_compress_internal.h ++++ b/lib/zstd/compress/zstd_compress_internal.h +@@ -537,6 +537,15 @@ struct ZSTD_CCtx_s { + size_t extSeqBufCapacity; + }; + ++MEM_STATIC int ZSTD_CCtx_get_bmi2(const struct ZSTD_CCtx_s *cctx) { ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++ return cctx->bmi2; ++#else ++ (void)cctx; ++ return 0; ++#endif ++} ++ + typedef enum { ZSTD_dtlm_fast, ZSTD_dtlm_full } ZSTD_dictTableLoadMethod_e; + typedef enum { ZSTD_tfp_forCCtx, ZSTD_tfp_forCDict } ZSTD_tableFillPurpose_e; + +diff --git a/lib/zstd/compress/zstd_compress_sequences.c b/lib/zstd/compress/zstd_compress_sequences.c +--- a/lib/zstd/compress/zstd_compress_sequences.c ++++ b/lib/zstd/compress/zstd_compress_sequences.c +@@ -12,6 +12,10 @@ + /*-************************************* + * Dependencies + ***************************************/ ++#include "../common/zstd_deps.h" ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "zstd_compress_sequences.h" + + /* +@@ -429,15 +433,13 @@ size_t ZSTD_encodeSequences( + SeqDef const* sequences, size_t nbSeq, int longOffsets, int bmi2) + { + DEBUGLOG(5, "ZSTD_encodeSequences: dstCapacity = %u", (unsigned)dstCapacity); +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + return ZSTD_encodeSequences_bmi2(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, + CTable_OffsetBits, ofCodeTable, + CTable_LitLength, llCodeTable, + sequences, nbSeq, longOffsets); + } +-#endif + (void)bmi2; + return ZSTD_encodeSequences_default(dst, dstCapacity, + CTable_MatchLength, mlCodeTable, +diff --git a/lib/zstd/compress/zstd_compress_superblock.c b/lib/zstd/compress/zstd_compress_superblock.c +--- a/lib/zstd/compress/zstd_compress_superblock.c ++++ b/lib/zstd/compress/zstd_compress_superblock.c +@@ -684,6 +684,6 @@ size_t ZSTD_compressSuperBlock(ZSTD_CCtx* zc, + &zc->appliedParams, + dst, dstCapacity, + src, srcSize, +- zc->bmi2, lastBlock, ++ ZSTD_CCtx_get_bmi2(zc), lastBlock, + zc->tmpWorkspace, zc->tmpWkspSize /* statically allocated in resetCCtx */); + } +diff --git a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c +--- a/lib/zstd/decompress/huf_decompress.c ++++ b/lib/zstd/decompress/huf_decompress.c +@@ -17,6 +17,9 @@ + * Dependencies + ****************************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" + #include "../common/bitstream.h" /* BIT_* */ + #include "../common/fse.h" /* to compress headers */ +@@ -113,9 +116,10 @@ typedef size_t (*HUF_DecompressUsingDTableFn)(void *dst, size_t dstSize, + static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ + size_t cSrcSize, HUF_DTable const* DTable, int flags) \ + { \ +- if (flags & HUF_flags_bmi2) { \ ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { \ + return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \ + } \ ++ (void)flags; \ + return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \ + } + +@@ -899,18 +903,16 @@ static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X1_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X1_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X1_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +@@ -1723,18 +1725,16 @@ static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, + HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X2_usingDTable_internal_default; + HUF_DecompressFastLoopFn loopFn = HUF_decompress4X2_usingDTable_internal_fast_c_loop; + +-#if DYNAMIC_BMI2 +- if (flags & HUF_flags_bmi2) { ++ if (ZSTD_USE_BMI2(flags & HUF_flags_bmi2)) { + fallbackFn = HUF_decompress4X2_usingDTable_internal_bmi2; + # if ZSTD_ENABLE_ASM_X86_64_BMI2 + if (!(flags & HUF_flags_disableAsm)) { + loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop; + } + # endif +- } else { ++ } else if (DYNAMIC_BMI2) { + return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable); + } +-#endif + + #if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__) + if (!(flags & HUF_flags_disableAsm)) { +diff --git a/lib/zstd/decompress/zstd_decompress.c b/lib/zstd/decompress/zstd_decompress.c +--- a/lib/zstd/decompress/zstd_decompress.c ++++ b/lib/zstd/decompress/zstd_decompress.c +@@ -259,9 +259,7 @@ static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx) + dctx->noForwardProgress = 0; + dctx->oversizedDuration = 0; + dctx->isFrameDecompression = 1; +-#if DYNAMIC_BMI2 +- dctx->bmi2 = ZSTD_cpuSupportsBmi2(); +-#endif ++ ZSTD_SET_BMI2(dctx->bmi2, ZSTD_cpuSupportsBmi2()); + dctx->ddictSet = NULL; + ZSTD_DCtx_resetParameters(dctx); + #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +diff --git a/lib/zstd/decompress/zstd_decompress_block.c b/lib/zstd/decompress/zstd_decompress_block.c +--- a/lib/zstd/decompress/zstd_decompress_block.c ++++ b/lib/zstd/decompress/zstd_decompress_block.c +@@ -16,6 +16,9 @@ + * Dependencies + *********************************************************/ + #include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */ ++#if defined(ZSTD_USE_KERNEL_CPU_FEATURES) ++#include ++#endif + #include "../common/compiler.h" /* prefetch */ + #include "../common/cpu.h" /* bmi2 */ + #include "../common/mem.h" /* low level memory routines */ +@@ -631,13 +634,11 @@ void ZSTD_buildFSETable(ZSTD_seqSymbol* dt, + const U32* baseValue, const U8* nbAdditionalBits, + unsigned tableLog, void* wksp, size_t wkspSize, int bmi2) + { +-#if DYNAMIC_BMI2 +- if (bmi2) { ++ if (ZSTD_USE_BMI2(bmi2)) { + ZSTD_buildFSETable_body_bmi2(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); + return; + } +-#endif + (void)bmi2; + ZSTD_buildFSETable_body_default(dt, normalizedCounter, maxSymbolValue, + baseValue, nbAdditionalBits, tableLog, wksp, wkspSize); +@@ -1955,11 +1956,9 @@ ZSTD_decompressSequences(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequences"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + static size_t +@@ -1968,11 +1967,9 @@ ZSTD_decompressSequencesSplitLitBuffer(ZSTD_DCtx* dctx, void* dst, size_t maxDst + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesSplitLitBuffer"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */ +@@ -1991,11 +1988,9 @@ ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx, + const ZSTD_longOffset_e isLongOffset) + { + DEBUGLOG(5, "ZSTD_decompressSequencesLong"); +-#if DYNAMIC_BMI2 +- if (ZSTD_DCtx_get_bmi2(dctx)) { ++ if (ZSTD_USE_BMI2(ZSTD_DCtx_get_bmi2(dctx))) { + return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } +-#endif + return ZSTD_decompressSequencesLong_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset); + } + #endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */ +diff --git a/lib/zstd/decompress/zstd_decompress_internal.h b/lib/zstd/decompress/zstd_decompress_internal.h +--- a/lib/zstd/decompress/zstd_decompress_internal.h ++++ b/lib/zstd/decompress/zstd_decompress_internal.h +@@ -204,7 +204,7 @@ struct ZSTD_DCtx_s + }; /* typedef'd to ZSTD_DCtx within "zstd.h" */ + + MEM_STATIC int ZSTD_DCtx_get_bmi2(const struct ZSTD_DCtx_s *dctx) { +-#if DYNAMIC_BMI2 ++#if DYNAMIC_BMI2 && !defined(ZSTD_USE_KERNEL_CPU_FEATURES) + return dctx->bmi2; + #else + (void)dctx; diff --git a/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig b/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig new file mode 100644 index 0000000..e39aa6c Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0291-zstd-bmi2-cpu-feature-dispatch.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch b/pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch new file mode 100644 index 0000000..4007e61 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0292-crypto-zstd-defer-cstream-init.patch.sig b/pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch.sig new file mode 100644 index 0000000..eb7a182 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0292-crypto-zstd-defer-cstream-init.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch b/pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch new file mode 100644 index 0000000..224f1ad --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0293-crypto-zstd-defer-dstream-init.patch.sig b/pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch.sig new file mode 100644 index 0000000..697fca0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0293-crypto-zstd-defer-dstream-init.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch b/pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch new file mode 100644 index 0000000..2700fc9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0295-af-alg-restrict.patch.sig b/pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch.sig new file mode 100644 index 0000000..0a98371 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0295-af-alg-restrict.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch b/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch new file mode 100644 index 0000000..837f7bc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch @@ -0,0 +1,4042 @@ +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 +@@ -2630,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); + /* +@@ -3922,7 +3921,7 @@ int btrfs_update_block_group(struct btrfs_trans_handle *trans, + old_val += num_bytes; + cache->used = old_val; + cache->reserved -= num_bytes; +- cache->reclaim_mark = 0; ++ cache->reclaim_mark = false; + space_info->bytes_reserved -= num_bytes; + space_info->bytes_used += num_bytes; + space_info->disk_used += num_bytes * factor; +diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h +--- a/fs/btrfs/block-group.h ++++ b/fs/btrfs/block-group.h +@@ -263,6 +263,9 @@ struct btrfs_block_group { + + enum btrfs_block_group_size_class size_class:8; + ++ /* If set, this blockgroup is not used for allocation between two reclaim sweeps. */ ++ bool reclaim_mark; ++ + /* + * Number of extents in this block group used for swap files. + * All accesses protected by the spinlock 'lock'. +@@ -281,7 +284,6 @@ struct btrfs_block_group { + struct list_head active_bg_list; + struct work_struct zone_finish_work; + struct extent_buffer *last_eb; +- u64 reclaim_mark; + }; + + static inline u64 btrfs_block_group_end(const struct btrfs_block_group *block_group) +diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c +--- a/fs/btrfs/compression.c ++++ b/fs/btrfs/compression.c +@@ -651,9 +651,9 @@ struct heuristic_ws { + u8 *sample; + u32 sample_size; + /* Buckets store counters for each byte value */ +- struct bucket_item *bucket; ++ struct bucket_item bucket[BUCKET_SIZE]; + /* Sorting buffer */ +- struct bucket_item *bucket_b; ++ struct bucket_item bucket_b[BUCKET_SIZE]; + struct list_head list; + }; + +@@ -664,8 +664,6 @@ static void free_heuristic_ws(struct list_head *ws) + workspace = list_entry(ws, struct heuristic_ws, list); + + kvfree(workspace->sample); +- kfree(workspace->bucket); +- kfree(workspace->bucket_b); + kfree(workspace); + } + +@@ -681,14 +679,6 @@ static struct list_head *alloc_heuristic_ws(struct btrfs_fs_info *fs_info) + if (!ws->sample) + goto fail; + +- ws->bucket = kzalloc_objs(*ws->bucket, BUCKET_SIZE); +- if (!ws->bucket) +- goto fail; +- +- ws->bucket_b = kzalloc_objs(*ws->bucket_b, BUCKET_SIZE); +- if (!ws->bucket_b) +- goto fail; +- + INIT_LIST_HEAD(&ws->list); + return &ws->list; + fail: +diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c +--- a/fs/btrfs/ctree.c ++++ b/fs/btrfs/ctree.c +@@ -1460,6 +1460,7 @@ static noinline void unlock_up(struct btrfs_path *path, int level, + */ + static int + read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **eb_ret, int slot, + const struct btrfs_key *key) + { +@@ -1546,7 +1547,8 @@ read_block_for_search(struct btrfs_root *root, struct btrfs_path *p, + if (p->reada != READA_NONE) + reada_for_search(fs_info, p, parent_level, slot, key->objectid); + +- tmp = btrfs_find_create_tree_block(fs_info, blocknr, check.owner_root, check.level); ++ tmp = btrfs_find_create_tree_block(fs_info, pa, blocknr, ++ check.owner_root, check.level); + if (IS_ERR(tmp)) { + ret = PTR_ERR(tmp); + tmp = NULL; +@@ -2004,6 +2006,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + u8 lowest_level = 0; + int min_write_lock_level; + int prev_cmp; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + if (!root) + return -EINVAL; +@@ -2058,6 +2061,11 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + prev_cmp = -1; + b = btrfs_search_slot_get_root(root, p, write_lock_level); + if (IS_ERR(b)) { +@@ -2187,7 +2195,7 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) { + trace_btrfs_search_slot_restart(root, level, "read_block"); + goto again; +@@ -2234,6 +2242,8 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + ALLOW_ERROR_INJECTION(btrfs_search_slot, ERRNO); +@@ -2259,6 +2269,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + int level; + int lowest_unlock = 1; + u8 lowest_level = 0; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + + lowest_level = p->lowest_level; + WARN_ON(p->nodes[0] != NULL); +@@ -2270,6 +2281,11 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + } + + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + b = btrfs_get_old_root(root, time_seq); + if (unlikely(!b)) { + ret = -EIO; +@@ -2316,7 +2332,7 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + goto done; + } + +- ret2 = read_block_for_search(root, p, &b, slot, key); ++ ret2 = read_block_for_search(root, p, &pa, &b, slot, key); + if (ret2 == -EAGAIN && !p->nowait) + goto again; + if (ret2) { +@@ -2339,6 +2355,8 @@ int btrfs_search_old_slot(struct btrfs_root *root, const struct btrfs_key *key, + if (ret < 0) + btrfs_release_path(p); + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +@@ -4780,6 +4798,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + struct extent_buffer *next; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_key key; ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + bool need_commit_sem = false; + u32 nritems; + int ret; +@@ -4798,6 +4817,11 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + + btrfs_item_key_to_cpu(path->nodes[0], &key, nritems - 1); + again: ++ if (pa.needs_prealloc) { ++ ret = btrfs_init_eb_prealloc(fs_info, &pa, false); ++ if (ret) ++ goto done; ++ } + level = 1; + next = NULL; + btrfs_release_path(path); +@@ -4880,7 +4904,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + } + + next = c; +- ret = read_block_for_search(root, path, &next, slot, &key); ++ ret = read_block_for_search(root, path, &pa, &next, slot, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4923,7 +4947,7 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + if (!level) + break; + +- ret = read_block_for_search(root, path, &next, 0, &key); ++ ret = read_block_for_search(root, path, &pa, &next, 0, &key); + if (ret == -EAGAIN && !path->nowait) + goto again; + +@@ -4956,6 +4980,8 @@ int btrfs_next_old_leaf(struct btrfs_root *root, struct btrfs_path *path, + ret = ret2; + } + ++ btrfs_free_eb_prealloc(&pa); ++ + return ret; + } + +diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h +--- a/fs/btrfs/ctree.h ++++ b/fs/btrfs/ctree.h +@@ -131,7 +131,6 @@ enum { + BTRFS_ROOT_ORPHAN_ITEM_INSERTED, + BTRFS_ROOT_DEFRAG_RUNNING, + BTRFS_ROOT_FORCE_COW, +- BTRFS_ROOT_MULTI_LOG_TASKS, + BTRFS_ROOT_DIRTY, + BTRFS_ROOT_DELETING, + +@@ -196,9 +195,7 @@ struct btrfs_root { + struct list_head log_ctxs[2]; + /* Used only for log trees of subvolumes, not for the log root tree */ + atomic_t log_writers; +- atomic_t log_commit[2]; +- /* Used only for log trees of subvolumes, not for the log root tree */ +- atomic_t log_batch; ++ bool log_commit[2]; + /* + * Protected by the 'log_mutex' lock but can be read without holding + * that lock to avoid unnecessary lock contention, in which case it +@@ -216,7 +213,6 @@ struct btrfs_root { + * to access this field. + */ + int last_log_commit; +- pid_t log_start_pid; + + u64 last_trans; + +diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c +--- a/fs/btrfs/defrag.c ++++ b/fs/btrfs/defrag.c +@@ -1093,7 +1093,7 @@ static int defrag_collect_targets(struct btrfs_inode *inode, + struct defrag_target_range *tmp; + + list_for_each_entry_safe(entry, tmp, target_list, list) { +- list_del_init(&entry->list); ++ list_del(&entry->list); + kfree(entry); + } + } +@@ -1169,7 +1169,7 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + struct defrag_target_range *entry; + struct defrag_target_range *tmp; + LIST_HEAD(target_list); +- struct folio **folios; ++ struct folio AUTO_KFREE(*folios); + const u32 sectorsize = inode->root->fs_info->sectorsize; + u64 cur = start; + const unsigned int nr_pages = ((start + len - 1) >> PAGE_SHIFT) - +@@ -1196,10 +1196,8 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + * range or the extent lock. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len); +- if (ret < 0) { +- kfree(folios); ++ if (ret < 0) + return ret; +- } + + /* Prepare all pages */ + for (int i = 0; cur < start + len && i < nr_pages; i++) { +@@ -1234,16 +1232,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + if (ret < 0) + goto unlock_extent; + +- list_for_each_entry(entry, &target_list, list) { ++ list_for_each_entry_safe(entry, tmp, &target_list, list) { + defrag_one_locked_target(inode, entry, folios, nr_pages, &cached_state); + if (entry->start > last_defrag_end) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, + entry->start - last_defrag_end, true); + last_defrag_end = entry->start + entry->len; +- } +- +- list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); + kfree(entry); + } + unlock_extent: +@@ -1255,7 +1249,6 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len, + folio_unlock(folios[i]); + folio_put(folios[i]); + } +- kfree(folios); + btrfs_delalloc_release_extents(inode, len); + if (last_defrag_end < start + len) + btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end, +@@ -1326,10 +1319,8 @@ static int defrag_one_cluster(struct btrfs_inode *inode, + inode->root->fs_info->sectorsize_bits; + } + out: +- list_for_each_entry_safe(entry, tmp, &target_list, list) { +- list_del_init(&entry->list); ++ list_for_each_entry_safe(entry, tmp, &target_list, list) + kfree(entry); +- } + if (ret >= 0) + *last_scanned_ret = max(*last_scanned_ret, start + len); + return ret; +diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c +--- a/fs/btrfs/delayed-inode.c ++++ b/fs/btrfs/delayed-inode.c +@@ -1523,10 +1523,10 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(delayed_node, delayed_item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %d", ++"error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %pe", + name_len, name, index, btrfs_root_id(delayed_node->root), + delayed_node->inode_id, dir->index_cnt, +- delayed_node->index_cnt, ret); ++ delayed_node->index_cnt, ERR_PTR(ret)); + btrfs_release_delayed_item(delayed_item); + btrfs_release_dir_index_item_space(trans); + mutex_unlock(&delayed_node->mutex); +@@ -1645,8 +1645,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + */ + if (ret < 0) { + btrfs_err(trans->fs_info, +-"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %d", +- index, btrfs_root_id(node->root), node->inode_id, ret); ++"metadata reservation failed for delayed dir item deletion, index: %llu, root: %llu, inode: %llu, error: %pe", ++ index, btrfs_root_id(node->root), node->inode_id, ERR_PTR(ret)); + btrfs_release_delayed_item(item); + goto end; + } +@@ -1655,8 +1655,8 @@ int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, + ret = __btrfs_add_delayed_item(node, item); + if (unlikely(ret)) { + btrfs_err(trans->fs_info, +-"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %d", +- btrfs_root_id(node->root), node->inode_id, index, ret); ++"failed to add delayed dir index item, root: %llu, inode: %llu, index: %llu, error: %pe", ++ btrfs_root_id(node->root), node->inode_id, index, ERR_PTR(ret)); + btrfs_delayed_item_release_metadata(dir->root, item); + btrfs_release_delayed_item(item); + } +diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c +--- a/fs/btrfs/direct-io.c ++++ b/fs/btrfs/direct-io.c +@@ -14,7 +14,6 @@ + #include "ordered-data.h" + + struct btrfs_dio_data { +- ssize_t submitted; + loff_t old_isize; + struct extent_changeset *data_reserved; + struct btrfs_ordered_extent *ordered; +@@ -151,7 +150,7 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + if (type != BTRFS_ORDERED_NOCOW) { + em = btrfs_create_io_em(inode, start, file_extent, type); + if (IS_ERR(em)) +- goto out; ++ return em; + } + + ordered = btrfs_alloc_ordered_extent(inode, start, file_extent, +@@ -168,7 +167,6 @@ static struct extent_map *btrfs_create_dio_extent(struct btrfs_inode *inode, + ASSERT(!dio_data->ordered); + dio_data->ordered = ordered; + } +- out: + + return em; + } +@@ -626,78 +624,81 @@ static int btrfs_dio_iomap_end(struct inode *inode, loff_t pos, loff_t length, + { + struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap); + struct btrfs_dio_data *dio_data = iter->private; +- size_t submitted = dio_data->submitted; + const bool write = !!(flags & IOMAP_WRITE); + int ret = 0; + +- if (!write && (iomap->type == IOMAP_HOLE)) { +- /* If reading from a hole, unlock and return */ +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (!write) { ++ /* ++ * Hole read, nothing is submitted, thus we have to unlock ++ * the whole range. ++ */ ++ if (iomap->type == IOMAP_HOLE) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, ++ pos + length - 1, NULL); ++ return 0; ++ } ++ /* ++ * Short read, needs to unlock the remaining range, and ++ * return -ENOTBLK so we can later fault in the pages and retry. ++ */ ++ if (written < length) { ++ btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos + written, ++ pos + length - 1, NULL); ++ return -ENOTBLK; ++ } ++ /* The full range is submitted, endio will do the unlock. */ + return 0; + } + +- if (submitted < length) { +- pos += submitted; +- length -= submitted; +- if (write) { +- /* +- * Got a short write and have updated the isize, need to +- * revert the isize change. +- * +- * Normally we need to update isize with extent lock hold, +- * but we're safe due to the following factors: +- * +- * - Only a single writer can be enlarging isize +- * Enlarging isize will take the exclusive inode lock. +- * +- * - Buffered readers need to wait for the OE we're holding +- * Buffered readers will lock extent and wait for OE +- * of the folio range, and since page cache is invalidated +- * the OE wait can not be skipped. +- * +- * So here we are safe to revert the isize before +- * finishing the OE, and no reader of the remaining range +- * can see the enlarged size. +- * +- * TODO: Extend the DIO_LOCKED lifespan for direct writes, +- * and only enlarge isize after a successful write. +- */ +- if (dio_data->updated_isize) { +- u64 new_isize; ++ if (written < length) { ++ /* ++ * Got a short write and have updated the i_size, need to revert ++ * the i_size change. ++ * ++ * Normally we need to update i_size with extent lock held, but ++ * we're safe due to the following factors: ++ * ++ * - Only a single writer can be enlarging i_size ++ * Enlarging i_size will take the exclusive inode lock. ++ * ++ * - Buffered readers need to wait for the OE we're holding ++ * Buffered readers will lock extent and wait for OE ++ * of the folio range, and since page cache is invalidated ++ * the OE wait cannot be skipped. ++ * ++ * So here we are safe to revert the isize before finishing the ++ * OE, and no reader of the remaining range can see the enlarged ++ * size. ++ * ++ * TODO: Extend the DIO_LOCKED lifespan for direct writes, ++ * and only enlarge isize after a successful write. ++ */ ++ if (dio_data->updated_isize) { ++ u64 new_isize; + +- if (submitted == 0) +- new_isize = dio_data->old_isize; +- else +- new_isize = max(dio_data->old_isize, pos); +- i_size_write(inode, new_isize); +- dio_data->updated_isize = false; +- } +- /* +- * We have a short write, if there is any range +- * that is submitted properly, that part will have +- * its own OE split from the original one. +- * +- * So for the OE at dio_data->ordered, it's the part +- * that is not submitted, and should be marked +- * as fully truncated. +- */ +- btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); +- btrfs_finish_ordered_extent(dio_data->ordered, +- pos, length, true); +- } else { +- btrfs_unlock_dio_extent(&BTRFS_I(inode)->io_tree, pos, +- pos + length - 1, NULL); ++ if (written == 0) ++ new_isize = dio_data->old_isize; ++ else ++ new_isize = max(dio_data->old_isize, pos + written); ++ i_size_write(inode, new_isize); ++ dio_data->updated_isize = false; + } ++ /* ++ * We have a short write, if there is any range that is submitted ++ * properly, that part will have its own OE split from the ++ * original one. ++ * ++ * So for the OE at dio_data->ordered, it's the part that is not ++ * submitted, and should be marked as fully truncated. ++ */ ++ btrfs_mark_ordered_extent_truncated(dio_data->ordered, 0); ++ btrfs_finish_ordered_extent(dio_data->ordered, ++ pos + written, length - written, true); + ret = -ENOTBLK; + } +- if (write) { +- btrfs_put_ordered_extent(dio_data->ordered); +- dio_data->ordered = NULL; +- } +- +- if (write) +- extent_changeset_free(dio_data->data_reserved); ++ btrfs_put_ordered_extent(dio_data->ordered); ++ dio_data->ordered = NULL; ++ extent_changeset_free(dio_data->data_reserved); + return ret; + } + +@@ -779,8 +780,6 @@ static void btrfs_dio_submit_io(const struct iomap_iter *iter, struct bio *bio, + dip->file_offset = file_offset; + dip->bytes = bio->bi_iter.bi_size; + +- dio_data->submitted += bio->bi_iter.bi_size; +- + /* + * Check if we are doing a partial write. If we are, we need to split + * the ordered extent to match the submitted bio. Hang on to the +@@ -824,13 +823,41 @@ static ssize_t btrfs_dio_read(struct kiocb *iocb, struct iov_iter *iter, + IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); + } + ++static bool need_stable_write(struct btrfs_inode *inode) ++{ ++ const u64 data_profile = btrfs_data_alloc_profile(inode->root->fs_info) & ++ BTRFS_BLOCK_GROUP_PROFILE_MASK; ++ ++ /* Data checksum requires stable buffer. */ ++ if (!(inode->flags & BTRFS_INODE_NODATASUM)) ++ return true; ++ /* ++ * Any profile with mirror/parity will require stable buffer. ++ * Otherwise the mirror may differ from each other. ++ * ++ * Thus only SINGLE and RAID0 doesn't require stable buffer. ++ */ ++ if (data_profile != 0 && data_profile != BTRFS_BLOCK_GROUP_RAID0) ++ return true; ++ return false; ++} ++ + static struct iomap_dio *btrfs_dio_write(struct kiocb *iocb, struct iov_iter *iter, + size_t done_before) + { + struct btrfs_dio_data data = { 0 }; ++ unsigned int dio_flags = IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED; ++ ++ if (need_stable_write(BTRFS_I(file_inode(iocb->ki_filp)))) { ++ /* For now no support for BOUNCE and NOWAIT direct write. */ ++ if (iocb->ki_flags & IOCB_NOWAIT) ++ return ERR_PTR(-EAGAIN); ++ ++ dio_flags |= IOMAP_DIO_BOUNCE; ++ } + + return __iomap_dio_rw(iocb, iter, &btrfs_dio_iomap_ops, &btrfs_dio_ops, +- IOMAP_DIO_PARTIAL | IOMAP_DIO_FSBLOCK_ALIGNED, &data, done_before); ++ dio_flags, &data, done_before); + } + + static ssize_t check_direct_IO(struct btrfs_fs_info *fs_info, +@@ -859,8 +886,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + ssize_t ret; + unsigned int ilock_flags = 0; + struct iomap_dio *dio; +- const u64 data_profile = btrfs_data_alloc_profile(fs_info) & +- BTRFS_BLOCK_GROUP_PROFILE_MASK; + + if (iocb->ki_flags & IOCB_NOWAIT) + ilock_flags |= BTRFS_ILOCK_TRY; +@@ -874,16 +899,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + if (iocb->ki_pos + iov_iter_count(from) <= i_size_read(inode) && IS_NOSEC(inode)) + ilock_flags |= BTRFS_ILOCK_SHARED; + +- /* +- * If our data profile has duplication (either extra mirrors or RAID56), +- * we can not trust the direct IO buffer, the content may change during +- * writeback and cause different contents written to different mirrors. +- * +- * Thus only RAID0 and SINGLE can go true zero-copy direct IO. +- */ +- if (data_profile != BTRFS_BLOCK_GROUP_RAID0 && data_profile != 0) +- goto buffered; +- + relock: + ret = btrfs_inode_lock(BTRFS_I(inode), ilock_flags); + if (ret < 0) +@@ -924,22 +939,6 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) + btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); + goto buffered; + } +- /* +- * We can't control the folios being passed in, applications can write +- * to them while a direct IO write is in progress. This means the +- * content might change after we calculated the data checksum. +- * Therefore we can end up storing a checksum that doesn't match the +- * persisted data. +- * +- * To be extra safe and avoid false data checksum mismatch, if the +- * inode requires data checksum, just fallback to buffered IO. +- * For buffered IO we have full control of page cache and can ensure +- * no one is modifying the content during writeback. +- */ +- if (!(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) { +- btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); +- goto buffered; +- } + + /* + * The iov_iter can be mapped to the same file range we are writing to. +diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c +--- a/fs/btrfs/disk-io.c ++++ b/fs/btrfs/disk-io.c +@@ -591,12 +591,13 @@ static const struct address_space_operations btree_aops = { + + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level) + { + if (btrfs_is_testing(fs_info)) + return alloc_test_extent_buffer(fs_info, bytenr); +- return alloc_extent_buffer(fs_info, bytenr, owner_root, level); ++ return alloc_extent_buffer(fs_info, pa, bytenr, owner_root, level); + } + + /* +@@ -609,12 +610,13 @@ struct extent_buffer *btrfs_find_create_tree_block( + struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check) + { ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf = NULL; + int ret; + + ASSERT(check); + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, check->owner_root, ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, check->owner_root, + check->level); + if (IS_ERR(buf)) + return buf; +@@ -667,10 +669,7 @@ static struct btrfs_root *btrfs_alloc_root(struct btrfs_fs_info *fs_info, + init_waitqueue_head(&root->log_commit_wait[1]); + INIT_LIST_HEAD(&root->log_ctxs[0]); + INIT_LIST_HEAD(&root->log_ctxs[1]); +- atomic_set(&root->log_commit[0], 0); +- atomic_set(&root->log_commit[1], 0); + atomic_set(&root->log_writers, 0); +- atomic_set(&root->log_batch, 0); + refcount_set(&root->refs, 1); + atomic_set(&root->snapshot_force_cow, 0); + atomic_set(&root->nr_swapfiles, 0); +@@ -2053,7 +2052,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + if (IS_ERR(log_tree_root->node)) { + ret = PTR_ERR(log_tree_root->node); + log_tree_root->node = NULL; +- btrfs_err(fs_info, "failed to read log tree with error: %d", ret); ++ btrfs_err(fs_info, "failed to read log tree with error: %pe", ERR_PTR(ret)); + btrfs_put_root(log_tree_root); + return ret; + } +@@ -2063,7 +2062,7 @@ static int btrfs_replay_log(struct btrfs_fs_info *fs_info, + btrfs_put_root(log_tree_root); + if (unlikely(ret)) { + ASSERT(BTRFS_FS_ERROR(fs_info) != 0); +- btrfs_err(fs_info, "failed to recover log trees with error: %d", ret); ++ btrfs_err(fs_info, "failed to recover log trees with error: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2304,8 +2303,8 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) + + return 0; + out: +- btrfs_warn(fs_info, "failed to read root (objectid=%llu): %d", +- location.objectid, ret); ++ btrfs_warn(fs_info, "failed to read root (objectid=%llu): %pe", ++ location.objectid, ERR_PTR(ret)); + return ret; + } + +@@ -2400,8 +2399,8 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, + int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + const struct btrfs_super_block *sb, int mirror_num) + { +- u64 nodesize = btrfs_super_nodesize(sb); +- u64 sectorsize = btrfs_super_sectorsize(sb); ++ const u32 nodesize = btrfs_super_nodesize(sb); ++ const u32 sectorsize = btrfs_super_sectorsize(sb); + int ret = 0; + const bool ignore_flags = btrfs_test_opt(fs_info, IGNORESUPERFLAGS); + +@@ -2443,24 +2442,24 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info, + */ + if (unlikely(!is_power_of_2(sectorsize) || sectorsize < BTRFS_MIN_BLOCKSIZE || + sectorsize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid sectorsize %llu", sectorsize); ++ btrfs_err(fs_info, "invalid sectorsize %u", sectorsize); + ret = -EINVAL; + } + + if (unlikely(!btrfs_supported_blocksize(sectorsize))) { + btrfs_err(fs_info, +- "sectorsize %llu not yet supported for page size %lu", ++ "sectorsize %u not yet supported for page size %lu", + sectorsize, PAGE_SIZE); + ret = -EINVAL; + } + + if (unlikely(!is_power_of_2(nodesize) || nodesize < sectorsize || + nodesize > BTRFS_MAX_METADATA_BLOCKSIZE)) { +- btrfs_err(fs_info, "invalid nodesize %llu", nodesize); ++ btrfs_err(fs_info, "invalid nodesize %u", nodesize); + ret = -EINVAL; + } + if (unlikely(nodesize != le32_to_cpu(sb->__unused_leafsize))) { +- btrfs_err(fs_info, "invalid leafsize %u, should be %llu", ++ btrfs_err(fs_info, "invalid leafsize %u, should be %u", + le32_to_cpu(sb->__unused_leafsize), nodesize); + ret = -EINVAL; + } +@@ -2910,7 +2909,6 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) + fs_info->nodesize = 4096; + fs_info->sectorsize = 4096; + fs_info->sectorsize_bits = ilog2(4096); +- fs_info->stripesize = 4096; + + /* Default compress algorithm when user does -o compress */ + fs_info->compress_type = BTRFS_COMPRESS_ZLIB; +@@ -2984,8 +2982,8 @@ static int btrfs_uuid_rescan_kthread(void *data) + ret = btrfs_uuid_tree_iterate(fs_info); + if (ret < 0) { + if (ret != -EINTR) +- btrfs_warn(fs_info, "iterating uuid_tree failed %d", +- ret); ++ btrfs_warn(fs_info, "iterating uuid_tree failed %pe", ++ ERR_PTR(ret)); + up(&fs_info->uuid_tree_rescan_sem); + return ret; + } +@@ -3088,7 +3086,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_rebuild_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to rebuild free space tree: %d", ret); ++ "failed to rebuild free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3099,7 +3097,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_delete_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to disable free space tree: %d", ret); ++ "failed to disable free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3110,7 +3108,8 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + */ + ret = btrfs_delete_orphan_free_space_entries(fs_info); + if (ret < 0) { +- btrfs_err(fs_info, "failed to delete orphan free space tree entries: %d", ret); ++ btrfs_err(fs_info, "failed to delete orphan free space tree entries: %pe", ++ ERR_PTR(ret)); + return ret; + } + /* +@@ -3144,7 +3143,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_recover_relocation(fs_info); + mutex_unlock(&fs_info->cleaner_mutex); + if (ret < 0) { +- btrfs_warn(fs_info, "failed to recover relocation: %d", ret); ++ btrfs_warn(fs_info, "failed to recover relocation: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -3154,7 +3153,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_free_space_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create free space tree: %d", ret); ++ "failed to create free space tree: %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3182,7 +3181,7 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info) + ret = btrfs_create_uuid_tree(fs_info); + if (ret) { + btrfs_warn(fs_info, +- "failed to create the UUID tree %d", ret); ++ "failed to create the UUID tree %pe", ERR_PTR(ret)); + return ret; + } + } +@@ -3372,7 +3371,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + { + u32 sectorsize; + u32 nodesize; +- u32 stripesize; + u64 generation; + u16 csum_type; + struct btrfs_super_block *disk_super; +@@ -3481,7 +3479,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + /* Set up fs_info before parsing mount options */ + nodesize = btrfs_super_nodesize(disk_super); + sectorsize = btrfs_super_sectorsize(disk_super); +- stripesize = sectorsize; + fs_info->dirty_metadata_batch = nodesize * (1 + ilog2(nr_cpu_ids)); + fs_info->delalloc_batch = sectorsize * 512 * (1 + ilog2(nr_cpu_ids)); + +@@ -3500,7 +3497,6 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + else + fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); + fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; +- fs_info->stripesize = stripesize; + fs_info->fs_devices->fs_info = fs_info; + + if (fs_info->sectorsize > PAGE_SIZE) +@@ -3566,7 +3562,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_read_sys_array(fs_info); + mutex_unlock(&fs_info->chunk_mutex); + if (ret) { +- btrfs_err(fs_info, "failed to read the system array: %d", ret); ++ btrfs_err(fs_info, "failed to read the system array: %pe", ERR_PTR(ret)); + goto fail_sb_buffer; + } + +@@ -3585,7 +3581,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + + ret = btrfs_read_chunk_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read chunk tree: %d", ret); ++ btrfs_err(fs_info, "failed to read chunk tree: %pe", ERR_PTR(ret)); + goto fail_tree_roots; + } + +@@ -3615,7 +3611,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_get_dev_zone_info_all_devices(fs_info); + if (ret) { + btrfs_err(fs_info, +- "zoned: failed to read device zone info: %d", ret); ++ "zoned: failed to read device zone info: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + +@@ -3638,72 +3634,73 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + ret = btrfs_verify_dev_extents(fs_info); + if (ret) { + btrfs_err(fs_info, +- "failed to verify dev extents against chunks: %d", +- ret); ++ "failed to verify dev extents against chunks: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + ret = btrfs_recover_balance(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to recover balance: %d", ret); ++ btrfs_err(fs_info, "failed to recover balance: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_stats(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_stats: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_stats: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_init_dev_replace(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init dev_replace: %d", ret); ++ btrfs_err(fs_info, "failed to init dev_replace: %pe", ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_check_zoned_mode(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize zoned mode: %d", +- ret); ++ btrfs_err(fs_info, "failed to initialize zoned mode: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_fsid(fs_devices); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs fsid interface: %d", +- ret); ++ btrfs_err(fs_info, "failed to init sysfs fsid interface: %pe", ++ ERR_PTR(ret)); + goto fail_block_groups; + } + + ret = btrfs_sysfs_add_mounted(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to init sysfs interface: %d", ret); ++ btrfs_err(fs_info, "failed to init sysfs interface: %pe", ERR_PTR(ret)); + goto fail_fsdev_sysfs; + } + + ret = btrfs_init_space_info(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to initialize space info: %d", ret); ++ btrfs_err(fs_info, "failed to initialize space info: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + ret = btrfs_read_block_groups(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to read block groups: %d", ret); ++ btrfs_err(fs_info, "failed to read block groups: %pe", ERR_PTR(ret)); + goto fail_sysfs; + } + + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + ret = btrfs_populate_fully_remapped_bgs_list(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %d", ret); ++ btrfs_err(fs_info, "failed to populate fully_remapped_bgs list: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + } + + ret = btrfs_init_writeback_bio_size(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to get optimum writeback size: %d", +- ret); ++ btrfs_err(fs_info, "failed to get optimum writeback size: %pe", ++ ERR_PTR(ret)); + goto fail_sysfs; + } + +@@ -3759,7 +3756,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + fs_info->fs_root = btrfs_get_fs_root(fs_info, BTRFS_FS_TREE_OBJECTID, true); + if (IS_ERR(fs_info->fs_root)) { + ret = PTR_ERR(fs_info->fs_root); +- btrfs_err(fs_info, "failed to read fs tree: %d", ret); ++ btrfs_err(fs_info, "failed to read fs tree: %pe", ERR_PTR(ret)); + fs_info->fs_root = NULL; + goto fail_qgroup; + } +@@ -3780,7 +3777,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device + btrfs_info(fs_info, "checking UUID tree"); + ret = btrfs_check_uuid_tree(fs_info); + if (ret) { +- btrfs_err(fs_info, "failed to check the UUID tree: %d", ret); ++ btrfs_err(fs_info, "failed to check the UUID tree: %pe", ERR_PTR(ret)); + close_ctree(fs_info); + return ret; + } +@@ -3896,8 +3893,8 @@ static int write_dev_supers(struct btrfs_device *device, + continue; + } else if (ret < 0) { + btrfs_err(device->fs_info, +- "couldn't get super block location for mirror %d error %d", +- i, ret); ++ "couldn't get super block location for mirror %d error %pe", ++ i, ERR_PTR(ret)); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -3915,8 +3912,8 @@ static int write_dev_supers(struct btrfs_device *device, + GFP_NOFS); + if (IS_ERR(folio)) { + btrfs_err(device->fs_info, +- "couldn't get super block page for bytenr %llu error %ld", +- bytenr, PTR_ERR(folio)); ++ "couldn't get super block page for bytenr %llu error %pe", ++ bytenr, folio); + atomic_inc(&device->sb_write_errors); + continue; + } +@@ -4398,6 +4395,21 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + */ + flush_workqueue(fs_info->fixup_workers); + ++ /* ++ * After we entered close_ctree() autodefrag could be running and before ++ * we parked the cleaner kthread, it dirtied folios of some inode. ++ * We don't want to leave any delalloc here, it may be flushed any time ++ * after this point and result in ordered extents that create delayed ++ * iputs after flushed the ordered extent queues further below, run ++ * delayed iputs and set BTRFS_FS_STATE_NO_DELAYED_IPUT. If we are ++ * mounted with flushoncommit, then btrfs_commit_super() called below ++ * will flush delalloc and wait for ordered extents but we end up ++ * getting delayed iputs than are never run. So flush delalloc and wait ++ * for ordered extents. ++ */ ++ btrfs_start_delalloc_roots(fs_info, LONG_MAX, false); ++ btrfs_wait_ordered_roots(fs_info, U64_MAX, NULL); ++ + /* + * Handle the error fs first, as it will flush and wait for all ordered + * extents. This will generate delayed iputs, thus we want to handle +@@ -4528,7 +4540,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + if (!btrfs_is_shutdown(fs_info)) { + ret = btrfs_commit_super(fs_info); + if (ret) +- btrfs_err(fs_info, "commit super block returned %d", ret); ++ btrfs_err(fs_info, "commit super block returned %pe", ERR_PTR(ret)); + } + } + +@@ -4567,6 +4579,13 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info) + free_root_pointers(fs_info, true); + btrfs_free_fs_roots(fs_info); + ++ /* ++ * Drop metadata left stranded ahead of a zone write pointer while the ++ * endio workqueues are still up, so the final iput() of the btree inode ++ * below does not hang submitting a write that can no longer complete. ++ */ ++ btrfs_zoned_release_dirty_metadata(fs_info); ++ + /* + * We must make sure there is not any read request to + * submit after we stop all workers. +@@ -5015,6 +5034,7 @@ static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info) + btrfs_assert_delayed_root_empty(fs_info); + btrfs_destroy_all_delalloc_inodes(fs_info); + btrfs_drop_all_logs(fs_info); ++ btrfs_zoned_release_dirty_metadata(fs_info); + btrfs_free_all_qgroup_pertrans(fs_info); + mutex_unlock(&fs_info->transaction_kthread_mutex); + +diff --git a/fs/btrfs/disk-io.h b/fs/btrfs/disk-io.h +--- a/fs/btrfs/disk-io.h ++++ b/fs/btrfs/disk-io.h +@@ -15,6 +15,7 @@ + struct block_device; + struct super_block; + struct extent_buffer; ++struct btrfs_eb_prealloc; + struct btrfs_device; + struct btrfs_fs_devices; + struct btrfs_fs_info; +@@ -48,6 +49,7 @@ struct extent_buffer *read_tree_block(struct btrfs_fs_info *fs_info, u64 bytenr, + struct btrfs_tree_parent_check *check); + struct extent_buffer *btrfs_find_create_tree_block( + struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 bytenr, u64 owner_root, + int level); + int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info); +diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c +--- a/fs/btrfs/extent-io-tree.c ++++ b/fs/btrfs/extent-io-tree.c +@@ -334,6 +334,21 @@ static inline struct extent_state *tree_search(struct extent_io_tree *tree, u64 + return tree_search_for_insert(tree, offset, NULL, NULL); + } + ++static void validate_extent_state(const struct extent_io_tree *tree, ++ const struct extent_state *state) ++{ ++ u32 blocksize; ++ ++ if (tree->owner != IO_TREE_INODE_IO) ++ return; ++ ++ blocksize = btrfs_extent_io_tree_to_fs_info(tree)->sectorsize; ++ ASSERT(IS_ALIGNED(state->start, blocksize) && ++ IS_ALIGNED(state->end + 1, blocksize), ++ "unaligned extent state, blocksize=%u start=%llu end=%llu state=0x%x", ++ blocksize, state->start, state->end, state->state); ++} ++ + #define extent_io_tree_panic(tree, state, opname, err) \ + btrfs_panic(btrfs_extent_io_tree_to_fs_info((tree)), (err), \ + "extent io tree error on %s state start %llu end %llu", \ +@@ -429,6 +444,8 @@ static struct extent_state *insert_state(struct extent_io_tree *tree, + const u64 end = state->end + 1; + const bool try_merge = !(bits & (EXTENT_LOCK_BITS | EXTENT_BOUNDARY)); + ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + + node = &tree->state.rb_node; +@@ -481,6 +498,8 @@ static void insert_state_fast(struct extent_io_tree *tree, + struct rb_node *parent, unsigned bits, + struct extent_changeset *changeset) + { ++ validate_extent_state(tree, state); ++ + set_state_bits(tree, state, bits, changeset); + rb_link_node(&state->rb_node, parent, node); + rb_insert_color(&state->rb_node, &tree->state); +@@ -533,6 +552,8 @@ static int split_state(struct extent_io_tree *tree, struct extent_state *orig, + } + } + ++ validate_extent_state(tree, orig); ++ validate_extent_state(tree, prealloc); + rb_link_node(&prealloc->rb_node, parent, node); + rb_insert_color(&prealloc->rb_node, &tree->state); + +diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c +--- a/fs/btrfs/extent-tree.c ++++ b/fs/btrfs/extent-tree.c +@@ -4757,7 +4757,7 @@ static noinline int find_free_extent(struct btrfs_root *root, + + /* Checks */ + ffe_ctl->search_start = round_up(ffe_ctl->found_offset, +- fs_info->stripesize); ++ fs_info->sectorsize); + + /* move on to the next group */ + if (ffe_ctl->search_start + ffe_ctl->num_bytes > +@@ -5260,10 +5260,11 @@ btrfs_init_new_buffer(struct btrfs_trans_handle *trans, struct btrfs_root *root, + enum btrfs_lock_nesting nest) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + struct extent_buffer *buf; + u64 lockdep_owner = owner; + +- buf = btrfs_find_create_tree_block(fs_info, bytenr, owner, level); ++ buf = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner, level); + if (IS_ERR(buf)) + return buf; + +@@ -5880,8 +5881,8 @@ static int maybe_drop_reference(struct btrfs_trans_handle *trans, struct btrfs_r + ret = btrfs_qgroup_trace_subtree(trans, next, generation, level - 1); + if (ret) { + btrfs_err_rl(root->fs_info, +-"error %d accounting shared subtree, quota is out of sync, rescan required", +- ret); ++"error %pe accounting shared subtree, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + +@@ -5917,6 +5918,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + struct walk_control *wc) + { + struct btrfs_fs_info *fs_info = root->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 generation; + u64 owner_root = 0; +@@ -5939,7 +5941,7 @@ static noinline int do_walk_down(struct btrfs_trans_handle *trans, + + bytenr = btrfs_node_blockptr(path->nodes[level], path->slots[level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, btrfs_root_id(root), ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, btrfs_root_id(root), + level - 1); + if (IS_ERR(next)) + return PTR_ERR(next); +@@ -6096,8 +6098,8 @@ static noinline int walk_up_proc(struct btrfs_trans_handle *trans, + ret = btrfs_qgroup_trace_leaf_items(trans, eb); + if (ret) { + btrfs_err_rl(fs_info, +- "error %d accounting leaf items, quota is out of sync, rescan required", +- ret); ++ "error %pe accounting leaf items, quota is out of sync, rescan required", ++ ERR_PTR(ret)); + } + } + } +@@ -6498,8 +6500,8 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc + ret = btrfs_qgroup_cleanup_dropped_subvolume(fs_info, rootid); + if (ret < 0) + btrfs_warn_rl(fs_info, +- "failed to cleanup qgroup 0/%llu: %d", +- rootid, ret); ++ "failed to cleanup qgroup 0/%llu: %pe", ++ rootid, ERR_PTR(ret)); + ret = 0; + } + /* +@@ -6914,8 +6916,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (bg_failed) + btrfs_warn(fs_info, +- "failed to trim %llu block group(s), first error %d", +- bg_failed, bg_ret); ++ "failed to trim %llu block group(s), first error %pe", ++ bg_failed, ERR_PTR(bg_ret)); + + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +@@ -6925,8 +6927,8 @@ int btrfs_trim_fs(struct btrfs_fs_info *fs_info, struct fstrim_range *range) + + if (dev_failed) + btrfs_warn(fs_info, +- "failed to trim %llu device(s), first error %d", +- dev_failed, dev_ret); ++ "failed to trim %llu device(s), first error %pe", ++ dev_failed, ERR_PTR(dev_ret)); + range->len = trimmed; + if (ret == -ERESTARTSYS || ret == -EINTR) + return ret; +diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c +--- a/fs/btrfs/extent_io.c ++++ b/fs/btrfs/extent_io.c +@@ -1392,6 +1392,22 @@ static void lock_extents_for_read(struct btrfs_inode *inode, u64 start, u64 end, + } + } + ++static void assert_folio_range(const struct btrfs_inode *inode, ++ u64 start, u64 end) ++{ ++ const u32 blocksize = inode->root->fs_info->sectorsize; ++ ++ /* ++ * For btrfs page cache, a folio always contains at least one block, ++ * so the range should always be block size aligned. ++ */ ++ ASSERT(IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize), ++ "blocksize=%u root=%lld ino=%llu start=%llu end=%llu mapping min order=%u", ++ blocksize, btrfs_root_id(inode->root), btrfs_ino(inode), ++ start, end, ++ mapping_min_folio_order(inode->vfs_inode.i_mapping)); ++} ++ + int btrfs_read_folio(struct file *file, struct folio *folio) + { + struct inode *vfs_inode = folio->mapping->host; +@@ -1407,6 +1423,7 @@ int btrfs_read_folio(struct file *file, struct folio *folio) + struct fsverity_info *vi = NULL; + int ret; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); + if (folio_pos(folio) < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +@@ -1698,13 +1715,13 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, + last_finished_delalloc_end = found_start + found_len; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %d", ++"failed to run delalloc range, root=%lld ino=%llu folio=%llu submit_bitmap=%*pbl start=%llu len=%u: %pe", + btrfs_root_id(inode->root), + btrfs_ino(inode), + folio_pos(folio), + blocks_per_folio, + bio_ctrl->submit_bitmap, +- found_start, found_len, ret); ++ found_start, found_len, ERR_PTR(ret)); + } else { + /* + * We've hit an error during previous delalloc range, +@@ -1914,6 +1931,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, + ASSERT(start >= folio_start, "start=%llu folio_start=%llu", start, folio_start); + ASSERT(end <= folio_end, "start=%llu len=%u folio_start=%llu folio_size=%zu", + start, len, folio_start, folio_size(folio)); ++ assert_folio_range(inode, folio_start, folio_end - 1); + + /* + * We are about to checksum and write out the data, so it must not be +@@ -2081,10 +2099,10 @@ static int extent_writepage(struct folio *folio, struct btrfs_bio_ctrl *bio_ctrl + return 0; + if (unlikely(ret < 0)) + btrfs_err_rl(fs_info, +-"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %d", ++"failed to submit blocks, root=%lld inode=%llu folio=%llu submit_bitmap=%*pbl: %pe", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio), blocks_per_folio, +- bio_ctrl->submit_bitmap, ret); ++ bio_ctrl->submit_bitmap, ERR_PTR(ret)); + + bio_ctrl->wbc->nr_to_write--; + +@@ -2976,13 +2994,25 @@ void btrfs_readahead(struct readahead_control *rac) + struct extent_map *em_cached = NULL; + struct fsverity_info *vi = NULL; + ++ assert_folio_range(inode, start, end); + lock_extents_for_read(inode, start, end, &cached_state); ++ /* We don't use cached state for a bulk unlock, just free it. */ ++ btrfs_free_extent_state(cached_state); + if (start < i_size_read(vfs_inode)) + vi = fsverity_get_info(vfs_inode); +- while ((folio = readahead_folio(rac)) != NULL) +- btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ while ((folio = readahead_folio(rac)) != NULL) { ++ /* ++ * Read start and end before btrfs_do_readpage(). It unlocks the ++ * folio, so our reference might not be valid after. ++ */ ++ const u64 folio_start = folio_pos(folio); ++ const u64 folio_end = folio_start + folio_size(folio) - 1; + +- btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state); ++ btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi); ++ /* Only unlock the range we locked, even if readahead expands. */ ++ if (folio_start >= start && folio_end <= end) ++ btrfs_unlock_extent(&inode->io_tree, folio_start, folio_end, NULL); ++ } + + if (em_cached) + btrfs_free_extent_map(em_cached); +@@ -3298,12 +3328,9 @@ void btrfs_uninhibit_all_eb_writeback(struct btrfs_trans_handle *trans) + trans->inhibited_ebs_hand = 0; + } + +-static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, +- u64 start) ++static void init_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct extent_buffer *eb, u64 start) + { +- struct extent_buffer *eb = NULL; +- +- eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS|__GFP_NOFAIL); + eb->start = start; + eb->len = fs_info->nodesize; + eb->fs_info = fs_info; +@@ -3316,7 +3343,15 @@ static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info + refcount_set(&eb->refs, 1); + + ASSERT(eb->len <= BTRFS_MAX_METADATA_BLOCKSIZE); ++} + ++static struct extent_buffer *__alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ u64 start) ++{ ++ struct extent_buffer *eb; ++ ++ eb = kmem_cache_zalloc(extent_buffer_cache, GFP_NOFS | __GFP_NOFAIL); ++ init_extent_buffer(fs_info, eb, start); + return eb; + } + +@@ -3613,7 +3648,7 @@ static bool check_eb_alignment(struct btrfs_fs_info *fs_info, u64 start) + * The caller needs to free the existing folios and retry using the same order. + */ + static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, +- struct btrfs_folio_state *prealloc, ++ struct btrfs_eb_prealloc *pa, + struct extent_buffer **found_eb_ret) + { + +@@ -3635,6 +3670,7 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + if (!ret) + goto finish; + ++ /* ret == -EEXIST: a folio already lives at this index. */ + existing_folio = filemap_lock_folio(mapping, index + i); + /* The page cache only exists for a very short time, just retry. */ + if (IS_ERR(existing_folio)) +@@ -3643,7 +3679,27 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + /* For now, we should only have single-page folios for btree inode. */ + ASSERT(folio_nr_pages(existing_folio) == 1); + ++ /* ++ * TODO: Special handling for a corner case where the order of ++ * folios mismatch between the new eb and filemap. ++ * ++ * This happens when: ++ * ++ * - the new eb is using higher order folio ++ * ++ * - the filemap is still using 0-order folios for the range ++ * This can happen at the previous eb allocation, and we don't ++ * have higher order folio for the call. ++ * ++ * - the existing eb has already been freed ++ * ++ * In this case, we have to free the existing folios first, and ++ * re-allocate using the same order. ++ * Thankfully this is not going to happen yet, as we're still ++ * using 0-order folios. ++ */ + if (folio_size(existing_folio) != eb->folio_size) { ++ DEBUG_WARN("folio order mismatch between new eb and filemap"); + folio_unlock(existing_folio); + folio_put(existing_folio); + return -EAGAIN; +@@ -3674,8 +3730,10 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + eb->folio_size = folio_size(eb->folios[i]); + eb->folio_shift = folio_shift(eb->folios[i]); + /* Should not fail, as we have preallocated the memory. */ +- ret = attach_extent_buffer_folio(eb, eb->folios[i], prealloc); ++ ret = attach_extent_buffer_folio(eb, eb->folios[i], pa->bfs); + ASSERT(!ret); ++ /* The subpage state, if any, is now attached to the folio or freed. */ ++ pa->bfs = NULL; + /* + * To inform we have an extra eb under allocation, so that + * detach_extent_buffer_page() won't release the folio private when the +@@ -3690,13 +3748,104 @@ static int attach_eb_folio_to_filemap(struct extent_buffer *eb, int i, + return 0; + } + ++/* ++ * Allocate the extent_buffer, its folios, and btrfs_folio_state, if needed. ++ * ++ * @pa: The holder struct to do the allocation in. ++ * @nowait: Whether to do a speculative GFP_NOWAIT allocation while holding locks. ++ * ++ * Return 0 on success and a negative errno otherwise. On failure, pa->eb/bfs ++ * will be NULL. If @nowait=true, then on ENOMEM, mark @pa->needs_prealloc and ++ * return -EAGAIN to signal the caller to unlock and retry. ++ */ ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait) ++{ ++ gfp_t gfp = nowait ? GFP_NOWAIT : GFP_NOFS | __GFP_NOFAIL; ++ int ret; ++ ++ ASSERT(!pa->eb, "unexpected non-null eb: %p", pa->eb); ++ ASSERT(!pa->bfs, "unexpected non-null bfs: %p", pa->bfs); ++ pa->needs_prealloc = false; ++ ++ pa->eb = kmem_cache_zalloc(extent_buffer_cache, gfp); ++ if (!pa->eb) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ /* alloc_eb_folio_array() needs len; init_extent_buffer() sets it again later. */ ++ pa->eb->len = fs_info->nodesize; ++ ++ /* ++ * Preallocate folio private for subpage case, so that we won't ++ * allocate memory with i_private_lock nor page lock hold. ++ * ++ * The memory will be freed by attach_extent_buffer_page() or freed ++ * manually if we exit earlier. ++ */ ++ if (btrfs_meta_is_subpage(fs_info)) { ++ pa->bfs = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, ++ BTRFS_SUBPAGE_METADATA, gfp); ++ if (IS_ERR(pa->bfs)) { ++ ret = PTR_ERR(pa->bfs); ++ pa->bfs = NULL; ++ goto free_eb; ++ } ++ } ++ ++ /* ++ * Allocate pages without attaching them. Caller is ultimately responsible ++ * for attaching the folios to the mapping with attach_eb_folio_to_filemap(). ++ */ ++ ret = alloc_eb_folio_array(pa->eb, gfp | __GFP_MOVABLE); ++ if (ret < 0) ++ goto free_bfs; ++ ++ return 0; ++ ++free_bfs: ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++free_eb: ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++out: ++ if (nowait && ret == -ENOMEM) { ++ pa->needs_prealloc = true; ++ ret = -EAGAIN; ++ } ++ return ret; ++} ++ ++/* ++ * Used to cleanup a btrfs_eb_prealloc which had its contents allocated but ++ * folios not yet attached and eb/bfs consumed, and refs still 0. ++ * ++ * Safe to call on a fully used btrfs_eb_prealloc as the internal structs will ++ * be null once they are owned by the context using them. ++ */ ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa) ++{ ++ if (!pa->eb) ++ return; ++ ++ for (int i = 0; i < num_extent_pages(pa->eb); i++) { ++ if (pa->eb->folios[i]) ++ folio_put(pa->eb->folios[i]); ++ } ++ btrfs_free_folio_state(pa->bfs); ++ kmem_cache_free(extent_buffer_cache, pa->eb); ++ pa->eb = NULL; ++ pa->bfs = NULL; ++} ++ + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level) + { + int attached = 0; + struct extent_buffer *eb; + struct extent_buffer *existing_eb = NULL; +- struct btrfs_folio_state *prealloc = NULL; + u64 lockdep_owner = owner_root; + bool page_contig = true; + bool uptodate = true; +@@ -3720,9 +3869,14 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + if (eb) + return eb; + +- eb = __alloc_extent_buffer(fs_info, start); +- if (!eb) +- return ERR_PTR(-ENOMEM); ++ if (!pa->eb) { ++ ret = btrfs_init_eb_prealloc(fs_info, pa, pa->supports_nowait); ++ if (ret) ++ return ERR_PTR(ret); ++ } ++ eb = pa->eb; ++ pa->eb = NULL; ++ init_extent_buffer(fs_info, eb, start); + + /* + * The reloc trees are just snapshots, so we need them to appear to be +@@ -3733,66 +3887,18 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + + btrfs_set_buffer_lockdep_class(lockdep_owner, eb, level); + +- /* +- * Preallocate folio private for subpage case, so that we won't +- * allocate memory with i_private_lock nor page lock hold. +- * +- * The memory will be freed by attach_extent_buffer_page() or freed +- * manually if we exit earlier. +- */ +- if (btrfs_meta_is_subpage(fs_info)) { +- prealloc = btrfs_alloc_folio_state(fs_info, PAGE_SIZE, BTRFS_SUBPAGE_METADATA); +- if (IS_ERR(prealloc)) { +- ret = PTR_ERR(prealloc); +- goto out; +- } +- } +- +-reallocate: +- /* +- * Allocate all pages first. These will be attached to btree_inode->i_mapping +- * below (added to LRU, served by btree_migrate_folio), so request +- * __GFP_MOVABLE so the page allocator places them in MOVABLE pageblocks. +- */ +- ret = alloc_eb_folio_array(eb, GFP_NOFS | __GFP_NOFAIL | __GFP_MOVABLE); +- if (ret < 0) { +- btrfs_free_folio_state(prealloc); +- goto out; +- } +- + /* Attach all pages to the filemap. */ + for (int i = 0; i < num_extent_folios(eb); i++) { + struct folio *folio; + +- ret = attach_eb_folio_to_filemap(eb, i, prealloc, &existing_eb); ++ ret = attach_eb_folio_to_filemap(eb, i, pa, &existing_eb); + if (ret > 0) { + ASSERT(existing_eb); + goto out; + } +- +- /* +- * TODO: Special handling for a corner case where the order of +- * folios mismatch between the new eb and filemap. +- * +- * This happens when: +- * +- * - the new eb is using higher order folio +- * +- * - the filemap is still using 0-order folios for the range +- * This can happen at the previous eb allocation, and we don't +- * have higher order folio for the call. +- * +- * - the existing eb has already been freed +- * +- * In this case, we have to free the existing folios first, and +- * re-allocate using the same order. +- * Thankfully this is not going to happen yet, as we're still +- * using 0-order folios. +- */ +- if (unlikely(ret == -EAGAIN)) { +- DEBUG_WARN("folio order mismatch between new eb and filemap"); +- goto reallocate; +- } ++ /* -EAGAIN: folio order mismatch, unreachable with 0-order folios. */ ++ if (ret < 0) ++ goto out; + attached++; + + /* +@@ -3869,6 +3975,10 @@ struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, + out: + WARN_ON(!refcount_dec_and_test(&eb->refs)); + ++ /* Attach hands off pa->bfs; free it if we bailed first. */ ++ btrfs_free_folio_state(pa->bfs); ++ pa->bfs = NULL; ++ + /* + * Any attached folios need to be detached before we unlock them. This + * is because when we're inserting our new folios into the mapping, and +@@ -4030,6 +4140,32 @@ void free_extent_buffer_stale(struct extent_buffer *eb) + release_extent_buffer(eb); + } + ++static void clear_extent_buffer_dirty(struct extent_buffer *eb) ++{ ++ struct btrfs_fs_info *fs_info = eb->fs_info; ++ ++ if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ return; ++ ++ buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); ++ percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, ++ fs_info->dirty_metadata_batch); ++ ++ for (int i = 0; i < num_extent_folios(eb); i++) { ++ struct folio *folio = eb->folios[i]; ++ bool last; ++ ++ if (!folio_test_dirty(folio)) ++ continue; ++ folio_lock(folio); ++ last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); ++ if (last) ++ btrfs_clear_folio_dirty_tag(folio); ++ folio_unlock(folio); ++ } ++ WARN_ON(refcount_read(&eb->refs) == 0); ++} ++ + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *eb) + { +@@ -4054,26 +4190,42 @@ void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + return; + } + +- if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++} ++ ++/* ++ * On a zoned filesystem a freed tree block is kept dirty and flagged as ++ * EXTENT_BUFFER_ZONED_ZEROOUT so a later writeback zeroes it out and advances ++ * the zone write pointer. Such buffers still dirty when the filesystem is torn ++ * down can no longer be written back and are stale; if left dirty they hang the ++ * final iput() of the btree inode. Drop their dirty state, and the deferred ++ * zero-out along with it. ++ */ ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info) ++{ ++ struct eb_batch batch; ++ unsigned long index = 0; ++ ++ if (!btrfs_is_zoned(fs_info)) + return; + +- buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY); +- percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len, +- fs_info->dirty_metadata_batch); ++ btrfs_zoned_meta_io_lock(fs_info); ++ eb_batch_init(&batch); ++ while (buffer_tree_get_ebs_tag(fs_info, &index, ULONG_MAX, ++ PAGECACHE_TAG_DIRTY, &batch)) { ++ struct extent_buffer *eb; + +- for (int i = 0; i < num_extent_folios(eb); i++) { +- struct folio *folio = eb->folios[i]; +- bool last; +- +- if (!folio_test_dirty(folio)) +- continue; +- folio_lock(folio); +- last = btrfs_meta_folio_clear_and_test_dirty(folio, eb); +- if (last) +- btrfs_clear_folio_dirty_tag(folio); +- folio_unlock(folio); ++ while ((eb = eb_batch_next(&batch)) != NULL) { ++ btrfs_tree_lock(eb); ++ if (test_and_clear_bit(EXTENT_BUFFER_ZONED_ZEROOUT, ++ &eb->bflags)) ++ clear_extent_buffer_dirty(eb); ++ btrfs_tree_unlock(eb); ++ } ++ eb_batch_release(&batch); ++ cond_resched(); + } +- WARN_ON(refcount_read(&eb->refs) == 0); ++ btrfs_zoned_meta_io_unlock(fs_info); + } + + void set_extent_buffer_dirty(struct extent_buffer *eb) +@@ -4923,6 +5075,8 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + .level = level, + .transid = gen + }; ++ /* Readahead is best effort so prefer to fail rather than block in reclaim. */ ++ struct btrfs_eb_prealloc pa = { .supports_nowait = true }; + struct extent_buffer *eb; + int ret; + +@@ -4931,7 +5085,7 @@ void btrfs_readahead_tree_block(struct btrfs_fs_info *fs_info, + check.has_first_key = true; + } + +- eb = btrfs_find_create_tree_block(fs_info, bytenr, owner_root, level); ++ eb = btrfs_find_create_tree_block(fs_info, &pa, bytenr, owner_root, level); + if (IS_ERR(eb)) + return; + +diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h +--- a/fs/btrfs/extent_io.h ++++ b/fs/btrfs/extent_io.h +@@ -119,6 +119,25 @@ struct extent_buffer { + #endif + }; + ++/* ++ * Wrapper struct for managing preallocating an extent_buffer, its folios and a ++ * btrfs_folio_state if needed. ++ * ++ * Only used to mediate allocation, do not refer to the eb directly if not ++ * returned from a successful eb allocating API. ++ * ++ * The eb folios and bfs should generally not be fully attached, except briefly ++ * before they are NULLed in the struct after successful attachment. ++ */ ++struct btrfs_eb_prealloc { ++ struct extent_buffer *eb; ++ struct btrfs_folio_state *bfs; ++ /* eb alloc may use GFP_NOWAIT; caller can drop locks and retry. */ ++ bool supports_nowait; ++ /* GFP_NOWAIT eb alloc failed; preallocate again and retry. */ ++ bool needs_prealloc; ++}; ++ + struct btrfs_eb_write_context { + struct writeback_control *wbc; + struct extent_buffer *eb; +@@ -271,7 +290,11 @@ int set_folio_extent_mapped(struct folio *folio); + void clear_folio_extent_mapped(struct folio *folio); + + struct extent_buffer *alloc_extent_buffer(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, + u64 start, u64 owner_root, int level); ++int btrfs_init_eb_prealloc(struct btrfs_fs_info *fs_info, ++ struct btrfs_eb_prealloc *pa, bool nowait); ++void btrfs_free_eb_prealloc(struct btrfs_eb_prealloc *pa); + struct extent_buffer *alloc_dummy_extent_buffer(struct btrfs_fs_info *fs_info, + u64 start); + struct extent_buffer *btrfs_clone_extent_buffer(const struct extent_buffer *src); +@@ -393,6 +416,7 @@ void extent_clear_unlock_delalloc(struct btrfs_inode *inode, u64 start, u64 end, + u32 bits_to_clear, unsigned long page_ops); + void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans, + struct extent_buffer *buf); ++void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info); + + static inline void btrfs_clear_folio_dirty_tag(struct folio *folio) + { +diff --git a/fs/btrfs/fiemap.c b/fs/btrfs/fiemap.c +--- a/fs/btrfs/fiemap.c ++++ b/fs/btrfs/fiemap.c +@@ -641,7 +641,7 @@ static int extent_fiemap(struct btrfs_inode *inode, + u64 prev_extent_end; + u64 range_start; + u64 range_end; +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + bool stopped = false; + int ret; + +diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c +--- a/fs/btrfs/file.c ++++ b/fs/btrfs/file.c +@@ -1573,8 +1573,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + else + btrfs_inode_lock(inode, BTRFS_ILOCK_MMAP); + +- atomic_inc(&root->log_batch); +- + /* + * Before we acquired the inode's lock and the mmap lock, someone may + * have dirtied more pages in the target range. We need to make sure +@@ -1657,8 +1655,6 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) + if (ret) + goto out_release_extents; + +- atomic_inc(&root->log_batch); +- + if (skip_inode_logging(&ctx)) { + /* + * We've had everything committed since the last time we were +@@ -2663,8 +2659,8 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len) + + lockstart = round_up(offset, fs_info->sectorsize); + lockend = round_down(offset + len, fs_info->sectorsize) - 1; +- same_block = (BTRFS_BYTES_TO_BLKS(fs_info, offset)) +- == (BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)); ++ same_block = (offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits); + /* + * Only do this if we are in the same block and we aren't doing the + * entire block. +@@ -2868,7 +2864,7 @@ enum { + static int btrfs_zero_range_check_range_boundary(struct btrfs_inode *inode, + u64 offset) + { +- const u64 sectorsize = inode->root->fs_info->sectorsize; ++ const u32 sectorsize = inode->root->fs_info->sectorsize; + struct extent_map *em; + int ret; + +@@ -2898,7 +2894,7 @@ static int btrfs_zero_range(struct inode *inode, + struct extent_changeset *data_reserved = NULL; + int ret; + u64 alloc_hint = 0; +- const u64 sectorsize = fs_info->sectorsize; ++ const u32 sectorsize = fs_info->sectorsize; + const u64 orig_start = offset; + const u64 orig_end = offset + len - 1; + u64 alloc_start = round_down(offset, sectorsize); +@@ -2947,8 +2943,8 @@ static int btrfs_zero_range(struct inode *inode, + } + btrfs_free_extent_map(em); + +- if (BTRFS_BYTES_TO_BLKS(fs_info, offset) == +- BTRFS_BYTES_TO_BLKS(fs_info, offset + len - 1)) { ++ if ((offset >> fs_info->sectorsize_bits) == ++ ((offset + len - 1) >> fs_info->sectorsize_bits)) { + em = btrfs_get_extent(BTRFS_I(inode), NULL, alloc_start, sectorsize); + if (IS_ERR(em)) { + ret = PTR_ERR(em); +diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c +--- a/fs/btrfs/fs.c ++++ b/fs/btrfs/fs.c +@@ -127,20 +127,9 @@ void btrfs_csum_final(struct btrfs_csum_ctx *ctx, u8 *out) + } + + /* +- * We support the following block sizes for all systems: +- * +- * - 4K +- * This is the most common block size. For PAGE SIZE > 4K cases the subpage +- * mode is used. +- * +- * - PAGE_SIZE +- * The straightforward block size to support. +- * +- * And extra support for the following block sizes based on the kernel config: +- * +- * - MIN_BLOCKSIZE +- * This is either 4K (regular builds) or 2K (debug builds) +- * This allows testing subpage routines on x86_64. ++ * For regular builds, any block size <= page size is supported. ++ * For experimental builds, any block size between BTRFS_MIN_BLOCKSIZE ++ * and BTRFS_MAX_BLOCKSIZE (inclusive) is supported. + */ + bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + { +@@ -148,7 +137,7 @@ bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) + ASSERT(is_power_of_2(blocksize) && blocksize >= BTRFS_MIN_BLOCKSIZE && + blocksize <= BTRFS_MAX_BLOCKSIZE); + +- if (blocksize == PAGE_SIZE || blocksize == SZ_4K || blocksize == BTRFS_MIN_BLOCKSIZE) ++ if (blocksize <= PAGE_SIZE) + return true; + #ifdef CONFIG_BTRFS_EXPERIMENTAL + /* +diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h +--- a/fs/btrfs/fs.h ++++ b/fs/btrfs/fs.h +@@ -289,7 +289,8 @@ enum { + BTRFS_MOUNT_IGNOREBADROOTS | \ + BTRFS_MOUNT_IGNOREDATACSUMS | \ + BTRFS_MOUNT_IGNOREMETACSUMS | \ +- BTRFS_MOUNT_IGNORESUPERFLAGS) ++ BTRFS_MOUNT_IGNORESUPERFLAGS | \ ++ BTRFS_MOUNT_USEBACKUPROOT) + + /* + * Compat flags that we support. If any incompat flags are set other than the +@@ -890,7 +891,6 @@ struct btrfs_fs_info { + u32 sectorsize_bits; + u32 block_min_order; + u32 block_max_order; +- u32 stripesize; + u32 writeback_bio_size; + u32 csum_size; + u32 csums_per_leaf; +@@ -1060,8 +1060,6 @@ static inline u64 btrfs_calc_metadata_size(const struct btrfs_fs_info *fs_info, + #define BTRFS_MAX_EXTENT_ITEM_SIZE(r) ((BTRFS_LEAF_DATA_SIZE(r->fs_info) >> 4) - \ + sizeof(struct btrfs_item)) + +-#define BTRFS_BYTES_TO_BLKS(fs_info, bytes) ((bytes) >> (fs_info)->sectorsize_bits) +- + static inline bool btrfs_is_zoned(const struct btrfs_fs_info *fs_info) + { + return IS_ENABLED(CONFIG_BLK_DEV_ZONED) && fs_info->zone_size > 0; +diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c +--- a/fs/btrfs/inode.c ++++ b/fs/btrfs/inode.c +@@ -250,8 +250,8 @@ static void print_data_reloc_error(const struct btrfs_inode *inode, u64 file_off + + ret = extent_from_logical(fs_info, logical, &path, &found_key, &flags); + if (ret < 0) { +- btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %d", +- logical, ret); ++ btrfs_err_rl(fs_info, "failed to lookup extent item for logical %llu: %pe", ++ logical, ERR_PTR(ret)); + return; + } + eb = path.nodes[0]; +@@ -869,7 +869,7 @@ static void compress_file_range(struct btrfs_work *work) + struct btrfs_inode *inode = async_chunk->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct compressed_bio *cb = NULL; +- u64 blocksize = fs_info->sectorsize; ++ const u32 blocksize = fs_info->sectorsize; + u64 start = async_chunk->start; + u64 end = async_chunk->end; + u64 actual_end; +@@ -1019,9 +1019,10 @@ static void submit_uncompressed_range(struct btrfs_inode *inode, + btrfs_folio_end_lock(inode->root->fs_info, locked_folio, + start, async_extent->ram_size); + btrfs_err_rl(inode->root->fs_info, +- "%s failed, root=%llu inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%llu inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), +- btrfs_ino(inode), start, async_extent->ram_size, ret); ++ btrfs_ino(inode), start, async_extent->ram_size, ++ ERR_PTR(ret)); + } + } + +@@ -1508,10 +1509,10 @@ static noinline int cow_file_range(struct btrfs_inode *inode, + end - start - cur_alloc_size + 1, NULL); + } + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu cur_alloc_size=%u: %pe", + __func__, btrfs_root_id(inode->root), + btrfs_ino(inode), orig_start, end + 1 - orig_start, +- start, cur_alloc_size, ret); ++ start, cur_alloc_size, ERR_PTR(ret)); + return ret; + } + +@@ -1962,9 +1963,9 @@ static int nocow_one_range(struct btrfs_inode *inode, struct folio *locked_folio + PAGE_UNLOCK | PAGE_START_WRITEBACK | + PAGE_END_WRITEBACK); + btrfs_err(inode->root->fs_info, +- "%s failed, root=%lld inode=%llu start=%llu len=%llu: %d", ++ "%s failed, root=%lld inode=%llu start=%llu len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), +- file_pos, len, ret); ++ file_pos, len, ERR_PTR(ret)); + return ret; + } + +@@ -2285,10 +2286,10 @@ static noinline int run_delalloc_nocow(struct btrfs_inode *inode, + } + btrfs_free_path(path); + btrfs_err(fs_info, +-"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %d", ++"%s failed, root=%llu inode=%llu start=%llu len=%llu cur_offset=%llu oe_cleanup=%llu oe_cleanup_len=%llu untouched_start=%llu untouched_len=%llu: %pe", + __func__, btrfs_root_id(inode->root), btrfs_ino(inode), + start, end + 1 - start, cur_offset, oe_cleanup_start, oe_cleanup_len, +- untouched_start, untouched_len, ret); ++ untouched_start, untouched_len, ERR_PTR(ret)); + return ret; + } + +@@ -3065,7 +3066,7 @@ static int insert_reserved_file_extent(struct btrfs_trans_handle *trans, + u64 qgroup_reserved) + { + struct btrfs_root *root = inode->root; +- const u64 sectorsize = root->fs_info->sectorsize; ++ const u32 sectorsize = root->fs_info->sectorsize; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_key ins; +@@ -3926,7 +3927,7 @@ int btrfs_orphan_cleanup(struct btrfs_root *root) + + out: + if (ret) +- btrfs_err(fs_info, "could not do orphan cleanup %d", ret); ++ btrfs_err(fs_info, "could not do orphan cleanup %pe", ERR_PTR(ret)); + return ret; + } + +@@ -4229,8 +4230,8 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path + ret = btrfs_load_inode_props(inode, path); + if (ret) + btrfs_err(fs_info, +- "error loading props for ino %llu (root %llu): %d", +- btrfs_ino(inode), btrfs_root_id(root), ret); ++ "error loading props for ino %llu (root %llu): %pe", ++ btrfs_ino(inode), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -6844,8 +6845,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, + } + if (ret) { + btrfs_err(fs_info, +- "error inheriting props for ino %llu (root %llu): %d", +- btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ret); ++ "error inheriting props for ino %llu (root %llu): %pe", ++ btrfs_ino(BTRFS_I(inode)), btrfs_root_id(root), ERR_PTR(ret)); + } + + /* +@@ -10224,6 +10225,8 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct btrfs_swapfile_pin *sp; + struct rb_node *node, *next; ++ u64 bg_bytes_released = 0; ++ u32 bg_nr_released = 0; + + spin_lock(&fs_info->swapfile_pins_lock); + node = rb_first(&fs_info->swapfile_pins); +@@ -10233,15 +10236,24 @@ static void btrfs_free_swapfile_pins(struct inode *inode) + if (sp->inode == inode) { + rb_erase(&sp->node, &fs_info->swapfile_pins); + if (sp->is_block_group) { +- btrfs_dec_block_group_swap_extents(sp->ptr, ++ struct btrfs_block_group *bg = sp->ptr; ++ ++ bg_bytes_released += bg->length; ++ bg_nr_released++; ++ btrfs_dec_block_group_swap_extents(bg, + sp->bg_extent_count); +- btrfs_put_block_group(sp->ptr); ++ btrfs_put_block_group(bg); + } + kfree(sp); + } + node = next; + } + spin_unlock(&fs_info->swapfile_pins_lock); ++ btrfs_info(fs_info, ++"swapfile deactivated on root %llu ino %llu, released %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), bg_bytes_released, ++ bg_nr_released); + } + + struct btrfs_swap_info { +@@ -10319,8 +10331,10 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + struct btrfs_backref_share_check_ctx *backref_ctx = NULL; + struct btrfs_path *path = NULL; + int ret = 0; ++ u32 pinned_bg_nr = 0; + u64 isize; + u64 prev_extent_end = 0; ++ u64 pinned_bg_size = 0; + + /* + * Acquire the inode's mmap lock to prevent races with memory mapped +@@ -10570,6 +10584,9 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + ret = 0; + else + goto out; ++ } else { ++ pinned_bg_size += bg->length; ++ pinned_bg_nr++; + } + + if (bsi.block_len && +@@ -10617,6 +10634,14 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file, + if (ret) + return ret; + ++ btrfs_info(fs_info, ++"swapfile activated on root %llu ino %llu, pinned down %llu bytes from %u block group(s)", ++ btrfs_root_id(BTRFS_I(inode)->root), ++ btrfs_ino(BTRFS_I(inode)), ++ pinned_bg_size, pinned_bg_nr); ++ btrfs_warn(fs_info, ++"block groups with swapfile extents will not be scrubbed or balanced"); ++ + if (device) + sis->bdev = device->bdev; + *span = bsi.highest_ppage - bsi.lowest_ppage + 1; +diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -400,9 +400,9 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + + /* + * 1 for inode item +- * 2 for properties ++ * 1 for property + */ +- trans = btrfs_start_transaction(root, 3); ++ trans = btrfs_start_transaction(root, 2); + if (IS_ERR(trans)) + return PTR_ERR(trans); + +@@ -1143,13 +1143,13 @@ static noinline int btrfs_ioctl_resize(struct file *file, + } + + static noinline int __btrfs_ioctl_snap_create(struct file *file, +- struct mnt_idmap *idmap, + const char *name, unsigned long fd, bool subvol, + bool readonly, + struct btrfs_qgroup_inherit *inherit) + { + int ret; + struct qstr qname = QSTR(name); ++ struct mnt_idmap *idmap = file_mnt_idmap(file); + + if (!S_ISDIR(file_inode(file)->i_mode)) + return -ENOTDIR; +@@ -1227,8 +1227,7 @@ static noinline int btrfs_ioctl_snap_create(struct file *file, + if (ret < 0) + return ret; + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + false, NULL); + } + +@@ -1271,8 +1270,7 @@ static noinline int btrfs_ioctl_snap_create_v2(struct file *file, + return ret; + } + +- return __btrfs_ioctl_snap_create(file, file_mnt_idmap(file), +- vol_args->name, vol_args->fd, subvol, ++ return __btrfs_ioctl_snap_create(file, vol_args->name, vol_args->fd, subvol, + readonly, inherit); + } + +@@ -1664,13 +1662,11 @@ static noinline int btrfs_ioctl_tree_search_v2(struct btrfs_root *root, + } + + /* +- * Search INODE_REFs to identify path name of 'dirid' directory +- * in a 'tree_id' tree. and sets path name to 'name'. ++ * Search for an INODE_REF in a 'root' tree which identifies the path name of ++ * 'dirid'. When found, it sets 'name' with the path name. + */ +-static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, +- u64 tree_id, u64 dirid, char *name) ++static noinline int btrfs_search_path_in_tree(struct btrfs_root *root, u64 dirid, char *name) + { +- struct btrfs_root *root; + struct btrfs_key key; + char *ptr; + int ret = -1; +@@ -1692,13 +1688,6 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + + ptr = &name[BTRFS_INO_LOOKUP_PATH_MAX - 1]; + +- root = btrfs_get_fs_root(info, tree_id, true); +- if (IS_ERR(root)) { +- ret = PTR_ERR(root); +- root = NULL; +- goto out; +- } +- + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; +@@ -1706,11 +1695,9 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + while (1) { + ret = btrfs_search_backwards(root, &key, path); + if (ret < 0) +- goto out; +- else if (ret > 0) { +- ret = -ENOENT; +- goto out; +- } ++ return ret; ++ else if (ret > 0) ++ return -ENOENT; + + l = path->nodes[0]; + slot = path->slots[0]; +@@ -1719,10 +1706,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; +- if (ptr < name) { +- ret = -ENAMETOOLONG; +- goto out; +- } ++ if (ptr < name) ++ return -ENAMETOOLONG; + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, (unsigned long)(iref + 1), len); +@@ -1737,10 +1722,8 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, + } + memmove(name, ptr, total_len); + name[total_len] = '\0'; +- ret = 0; +-out: +- btrfs_put_root(root); +- return ret; ++ ++ return 0; + } + + static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, +@@ -1884,6 +1867,7 @@ static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, + static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + void __user *argp) + { ++ bool new_root = false; + struct btrfs_ioctl_ino_lookup_args AUTO_KFREE(args); + int ret = 0; + +@@ -1897,6 +1881,8 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + */ + if (args->treeid == 0) + args->treeid = btrfs_root_id(root); ++ else ++ new_root = true; + + if (args->objectid == BTRFS_FIRST_FREE_OBJECTID) { + args->name[0] = 0; +@@ -1908,9 +1894,14 @@ static noinline int btrfs_ioctl_ino_lookup(struct btrfs_root *root, + goto out; + } + +- ret = btrfs_search_path_in_tree(root->fs_info, +- args->treeid, args->objectid, +- args->name); ++ if (new_root) { ++ root = btrfs_get_fs_root(root->fs_info, args->treeid, true); ++ if (IS_ERR(root)) ++ return PTR_ERR(root); ++ } ++ ret = btrfs_search_path_in_tree(root, args->objectid, args->name); ++ if (new_root) ++ btrfs_put_root(root); + + out: + if (ret == 0 && copy_to_user(argp, args, sizeof(*args))) +@@ -2848,8 +2839,8 @@ static long btrfs_ioctl_default_subvol(struct file *file, void __user *argp) + else + ret = -ENOENT; + btrfs_err(fs_info, +- "could not find default diritem for dir %llu: %d", +- dir_id, ret); ++ "could not find default diritem for dir %llu: %pe", ++ dir_id, ERR_PTR(ret)); + goto out_free; + } + +@@ -3620,7 +3611,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_assign_args AUTO_KFREE(sa); + struct btrfs_qgroup_list AUTO_KFREE(prealloc); + struct btrfs_trans_handle *trans; +@@ -3651,10 +3642,20 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + } + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 2 BTRFS_QGROUP_RELATION_KEY items. */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3678,6 +3679,7 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + "qgroup status update failed after %s relation, marked as inconsistent", + sa->assign ? "adding" : "deleting"); + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3689,7 +3691,8 @@ static long btrfs_ioctl_qgroup_assign(struct file *file, void __user *arg) + static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); +- struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_root *quota_root; + struct btrfs_ioctl_qgroup_create_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3698,7 +3701,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3721,13 +3724,23 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* + * 1 BTRFS_QGROUP_INFO_KEY item. + * 1 BTRFS_QGROUP_LIMIT_KEY item. + */ +- trans = btrfs_start_transaction(root, 2); ++ trans = btrfs_start_transaction(quota_root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3738,6 +3751,7 @@ static long btrfs_ioctl_qgroup_create(struct file *file, void __user *arg) + } + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +@@ -3750,6 +3764,8 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + { + struct inode *inode = file_inode(file); + struct btrfs_root *root = BTRFS_I(inode)->root; ++ struct btrfs_root *quota_root; ++ struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_ioctl_qgroup_limit_args AUTO_KFREE(sa); + struct btrfs_trans_handle *trans; + int ret; +@@ -3759,7 +3775,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + +- if (!btrfs_qgroup_enabled(root->fs_info)) ++ if (!btrfs_qgroup_enabled(fs_info)) + return -ENOTCONN; + + ret = mnt_want_write_file(file); +@@ -3772,10 +3788,20 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + goto drop_write; + } + ++ mutex_lock(&fs_info->qgroup_ioctl_lock); ++ quota_root = btrfs_grab_root(fs_info->quota_root); ++ mutex_unlock(&fs_info->qgroup_ioctl_lock); ++ ++ if (!quota_root) { ++ ret = -ENOTCONN; ++ goto drop_write; ++ } ++ + /* 1 BTRFS_QGROUP_LIMIT_KEY item. */ +- trans = btrfs_start_transaction(root, 1); ++ trans = btrfs_start_transaction(quota_root, 1); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); ++ btrfs_put_root(quota_root); + goto drop_write; + } + +@@ -3788,6 +3814,7 @@ static long btrfs_ioctl_qgroup_limit(struct file *file, void __user *arg) + ret = btrfs_limit_qgroup(trans, qgroupid, &sa->lim); + + err = btrfs_end_transaction(trans); ++ btrfs_put_root(quota_root); + if (err && !ret) + ret = err; + +diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c +--- a/fs/btrfs/messages.c ++++ b/fs/btrfs/messages.c +@@ -279,7 +279,6 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + unsigned int line, int error, const char *fmt, ...) + { + char *s_id = ""; +- const char *errstr; + struct va_format vaf = { .fmt = fmt }; + va_list args; + +@@ -289,13 +288,12 @@ void __btrfs_panic(const struct btrfs_fs_info *fs_info, const char *function, + va_start(args, fmt); + vaf.va = &args; + +- errstr = btrfs_decode_error(error); + if (fs_info && (btrfs_test_opt(fs_info, PANIC_ON_FATAL_ERROR))) +- panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %s)\n", +- s_id, function, line, &vaf, error, errstr); ++ panic(KERN_CRIT "BTRFS panic (device %s) in %s:%d: %pV (errno=%d %pe)\n", ++ s_id, function, line, &vaf, error, ERR_PTR(error)); + +- btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %s)", +- function, line, &vaf, error, errstr); ++ btrfs_crit(fs_info, "panic in %s:%d: %pV (errno=%d %pe)", ++ function, line, &vaf, error, ERR_PTR(error)); + va_end(args); + /* Caller calls BUG() */ + } +diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c +--- a/fs/btrfs/qgroup.c ++++ b/fs/btrfs/qgroup.c +@@ -3915,8 +3915,8 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + ret = PTR_ERR(trans); + trans = NULL; + btrfs_err(fs_info, +- "fail to start transaction for status update: %d", +- ret); ++ "fail to start transaction for status update: %pe", ++ ERR_PTR(ret)); + } + } else { + trans = NULL; +@@ -3931,7 +3931,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + + if (ret2 < 0) { + ret = ret2; +- btrfs_err(fs_info, "fail to update qgroup status: %d", ret); ++ btrfs_err(fs_info, "fail to update qgroup status: %pe", ERR_PTR(ret)); + } + } + fs_info->qgroup_rescan_running = false; +@@ -3952,7 +3952,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) + btrfs_info(fs_info, "qgroup scan completed%s", + ret > 0 ? " (inconsistency flag cleared)" : ""); + } else { +- btrfs_err(fs_info, "qgroup scan failed with %d", ret); ++ btrfs_err(fs_info, "qgroup scan failed with %pe", ERR_PTR(ret)); + } + } + +diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c +--- a/fs/btrfs/raid56.c ++++ b/fs/btrfs/raid56.c +@@ -2997,13 +2997,11 @@ void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio) + * This is due to the fact rbio has its own page management for its cache. + */ + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical) ++ void *vaddr, u64 data_logical) + { + struct btrfs_fs_info *fs_info = rbio->bioc->fs_info; + const u64 offset_in_full_stripe = data_logical - + rbio->bioc->full_stripe_logical; +- unsigned int findex = 0; +- unsigned int foffset = 0; + int ret; + + /* +@@ -3026,18 +3024,10 @@ void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, + cur_off < offset_in_full_stripe + BTRFS_STRIPE_LEN; + cur_off += PAGE_SIZE) { + const unsigned int pindex = cur_off >> PAGE_SHIFT; +- void *kaddr; + +- kaddr = kmap_local_page(rbio->stripe_pages[pindex]); +- memcpy_from_folio(kaddr, data_folios[findex], foffset, PAGE_SIZE); +- kunmap_local(kaddr); +- +- foffset += PAGE_SIZE; +- ASSERT(foffset <= folio_size(data_folios[findex])); +- if (foffset == folio_size(data_folios[findex])) { +- findex++; +- foffset = 0; +- } ++ ASSERT(cur_off - offset_in_full_stripe + PAGE_SIZE <= BTRFS_STRIPE_LEN); ++ memcpy_to_page(rbio->stripe_pages[pindex], 0, ++ vaddr + cur_off - offset_in_full_stripe, PAGE_SIZE); + } + bitmap_set(rbio->stripe_uptodate_bitmap, + offset_in_full_stripe >> fs_info->sectorsize_bits, +diff --git a/fs/btrfs/raid56.h b/fs/btrfs/raid56.h +--- a/fs/btrfs/raid56.h ++++ b/fs/btrfs/raid56.h +@@ -283,7 +283,7 @@ struct btrfs_raid_bio *raid56_parity_alloc_scrub_rbio(struct bio *bio, + void raid56_parity_submit_scrub_rbio(struct btrfs_raid_bio *rbio); + + void raid56_parity_cache_data_folios(struct btrfs_raid_bio *rbio, +- struct folio **data_folios, u64 data_logical); ++ void *vaddr, u64 data_logical); + + int btrfs_alloc_stripe_hash_table(struct btrfs_fs_info *info); + void btrfs_free_stripe_hash_table(struct btrfs_fs_info *info); +diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c +--- a/fs/btrfs/reflink.c ++++ b/fs/btrfs/reflink.c +@@ -20,30 +20,31 @@ + #define BTRFS_MAX_DEDUPE_LEN SZ_16M + + static int clone_finish_inode_update(struct btrfs_trans_handle *trans, +- struct inode *inode, ++ struct btrfs_inode *inode, + u64 endoff, + const u64 destoff, + const u64 olen, + bool no_time_update) + { ++ struct inode *vfs_inode = &inode->vfs_inode; + int ret; + +- inode_inc_iversion(inode); +- if (!no_time_update) { +- inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); +- } ++ inode_inc_iversion(vfs_inode); ++ if (!no_time_update) ++ inode_set_mtime_to_ts(vfs_inode, inode_set_ctime_current(vfs_inode)); ++ + /* + * We round up to the block size at eof when determining which + * extents to clone above, but shouldn't round up the file size. + */ + if (endoff > destoff + olen) + endoff = destoff + olen; +- if (endoff > inode->i_size) { +- i_size_write(inode, endoff); +- btrfs_inode_safe_disk_i_size_write(BTRFS_I(inode), 0); ++ if (endoff > vfs_inode->i_size) { ++ i_size_write(vfs_inode, endoff); ++ btrfs_inode_safe_disk_i_size_write(inode, 0); + } + +- ret = btrfs_update_inode(trans, BTRFS_I(inode)); ++ ret = btrfs_update_inode(trans, inode); + if (unlikely(ret)) { + btrfs_abort_transaction(trans, ret); + btrfs_end_transaction(trans); +@@ -392,11 +393,11 @@ static int clone_copy_inline_extent(struct btrfs_inode *inode, + * @destoff: Offset within @inode to start clone + * @no_time_update: Whether to update mtime/ctime on the target inode + */ +-static int btrfs_clone(struct inode *src, struct inode *inode, ++static int btrfs_clone(struct btrfs_inode *src, struct btrfs_inode *inode, + const u64 off, const u64 olen, const u64 olen_aligned, + const u64 destoff, bool no_time_update) + { +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; + BTRFS_PATH_AUTO_FREE(path); + struct extent_buffer *leaf; + struct btrfs_trans_handle *trans; +@@ -420,7 +421,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + + path->reada = READA_FORWARD; + /* Clone data */ +- key.objectid = btrfs_ino(BTRFS_I(src)); ++ key.objectid = btrfs_ino(src); + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = off; + +@@ -436,8 +437,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + u64 drop_start; + + /* Note the key will change type as we walk through the tree */ +- ret = btrfs_search_slot(NULL, BTRFS_I(src)->root, &key, path, +- 0, 0); ++ ret = btrfs_search_slot(NULL, src->root, &key, path, 0, 0); + if (ret < 0) + goto out; + /* +@@ -455,7 +455,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + nritems = btrfs_header_nritems(path->nodes[0]); + process_slot: + if (path->slots[0] >= nritems) { +- ret = btrfs_next_leaf(BTRFS_I(src)->root, path); ++ ret = btrfs_next_leaf(src->root, path); + if (ret < 0) + goto out; + if (ret > 0) +@@ -466,8 +466,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + slot = path->slots[0]; + + btrfs_item_key_to_cpu(leaf, &key, slot); +- if (key.type > BTRFS_EXTENT_DATA_KEY || +- key.objectid != btrfs_ino(BTRFS_I(src))) ++ if (key.type > BTRFS_EXTENT_DATA_KEY || key.objectid != btrfs_ino(src)) + break; + + ASSERT(key.type == BTRFS_EXTENT_DATA_KEY, "key.type=%u", key.type); +@@ -514,7 +513,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + btrfs_release_path(path); + + memcpy(&new_key, &key, sizeof(new_key)); +- new_key.objectid = btrfs_ino(BTRFS_I(inode)); ++ new_key.objectid = btrfs_ino(inode); + if (off <= key.offset) + new_key.offset = key.offset + destoff - off; + else +@@ -558,7 +557,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + clone_info.extent_buf = buf; + clone_info.is_new_extent = false; + clone_info.update_times = !no_time_update; +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + drop_start, new_key.offset + datal - 1, + &clone_info, &trans); + if (ret) +@@ -582,7 +581,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + goto out; + } + +- ret = clone_copy_inline_extent(BTRFS_I(inode), path, &new_key, ++ ret = clone_copy_inline_extent(inode, path, &new_key, + drop_start, datal, size, + comp, buf, &trans); + if (ret) +@@ -605,9 +604,9 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * the checksums problem on fsync. + */ + if (extent_gen == trans->transid && disko > 0) +- BTRFS_I(src)->last_reflink_trans = trans->transid; ++ src->last_reflink_trans = trans->transid; + +- BTRFS_I(inode)->last_reflink_trans = trans->transid; ++ inode->last_reflink_trans = trans->transid; + + last_dest_end = ALIGN(new_key.offset + datal, + fs_info->sectorsize); +@@ -653,10 +652,10 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + * set by previous calls to btrfs_replace_file_extents() that + * replaced file extent items. + */ +- if (last_dest_end >= i_size_read(inode)) +- btrfs_set_inode_full_sync(BTRFS_I(inode)); ++ if (last_dest_end >= i_size_read(&inode->vfs_inode)) ++ btrfs_set_inode_full_sync(inode); + +- ret = btrfs_replace_file_extents(BTRFS_I(inode), path, ++ ret = btrfs_replace_file_extents(inode, path, + last_dest_end, destoff + len - 1, NULL, &trans); + if (ret) + goto out; +@@ -666,7 +665,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, + } + + out: +- clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &BTRFS_I(inode)->runtime_flags); ++ clear_bit(BTRFS_INODE_NO_DELALLOC_FLUSH, &inode->runtime_flags); + + return ret; + } +@@ -688,10 +687,10 @@ static void btrfs_double_mmap_unlock(struct btrfs_inode *inode1, struct btrfs_in + static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + struct btrfs_inode *dst, u64 dst_loff) + { +- const u64 end = dst_loff + len - 1; + struct extent_state *cached_state = NULL; + struct btrfs_fs_info *fs_info = src->root->fs_info; +- const u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; ++ const u64 end = round_up(dst_loff + len, bs) - 1; + int ret; + + /* +@@ -701,8 +700,7 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + * mode. + */ + btrfs_lock_extent(&dst->io_tree, dst_loff, end, &cached_state); +- ret = btrfs_clone(&src->vfs_inode, &dst->vfs_inode, loff, len, +- ALIGN(len, bs), dst_loff, true); ++ ret = btrfs_clone(src, dst, loff, len, ALIGN(len, bs), dst_loff, true); + btrfs_unlock_extent(&dst->io_tree, dst_loff, end, &cached_state); + + btrfs_btree_balance_dirty(fs_info); +@@ -710,12 +708,12 @@ static int btrfs_extent_same_range(struct btrfs_inode *src, u64 loff, u64 len, + return ret; + } + +-static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, +- struct inode *dst, u64 dst_loff) ++static int btrfs_extent_same(struct btrfs_inode *src, u64 loff, u64 olen, ++ struct btrfs_inode *dst, u64 dst_loff) + { + int ret = 0; + u64 i, tail_len, chunk_count; +- struct btrfs_root *root_dst = BTRFS_I(dst)->root; ++ struct btrfs_root *root_dst = dst->root; + + spin_lock(&root_dst->root_item_lock); + if (root_dst->send_in_progress) { +@@ -733,8 +731,8 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + chunk_count = div_u64(olen, BTRFS_MAX_DEDUPE_LEN); + + for (i = 0; i < chunk_count; i++) { +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, BTRFS_MAX_DEDUPE_LEN, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, BTRFS_MAX_DEDUPE_LEN, ++ dst, dst_loff); + if (ret) + goto out; + +@@ -743,8 +741,7 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 olen, + } + + if (tail_len > 0) +- ret = btrfs_extent_same_range(BTRFS_I(src), loff, tail_len, +- BTRFS_I(dst), dst_loff); ++ ret = btrfs_extent_same_range(src, loff, tail_len, dst, dst_loff); + out: + spin_lock(&root_dst->root_item_lock); + root_dst->dedupe_in_progress--; +@@ -757,12 +754,14 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + u64 off, u64 olen, u64 destoff) + { + struct extent_state *cached_state = NULL; +- struct inode *inode = file_inode(file); +- struct inode *src = file_inode(file_src); +- struct btrfs_fs_info *fs_info = inode_to_fs_info(inode); ++ struct btrfs_inode *inode = BTRFS_I(file_inode(file)); ++ struct btrfs_inode *src = BTRFS_I(file_inode(file_src)); ++ struct btrfs_fs_info *fs_info = inode->root->fs_info; ++ const u64 src_isize = src->vfs_inode.i_size; ++ const u64 inode_isize = inode->vfs_inode.i_size; + int ret; + u64 len = olen; +- u64 bs = fs_info->sectorsize; ++ const u32 bs = fs_info->sectorsize; + u64 end; + + /* +@@ -771,13 +770,13 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * if the file size is not blocksize aligned. So we don't need to check + * for that case here. + */ +- if (off + len == src->i_size) +- len = ALIGN(src->i_size, bs) - off; ++ if (off + len == src_isize) ++ len = ALIGN(src_isize, bs) - off; + +- if (destoff > inode->i_size) { +- const u64 wb_start = ALIGN_DOWN(inode->i_size, bs); ++ if (destoff > inode_isize) { ++ const u64 wb_start = ALIGN_DOWN(inode_isize, bs); + +- ret = btrfs_cont_expand(BTRFS_I(inode), inode->i_size, destoff); ++ ret = btrfs_cont_expand(inode, inode_isize, destoff); + if (ret) + return ret; + /* +@@ -789,8 +788,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * we found the previous extent covering eof and before we + * attempted to increment its reference count). + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), wb_start, +- destoff - wb_start); ++ ret = btrfs_wait_ordered_range(inode, wb_start, destoff - wb_start); + if (ret) + return ret; + } +@@ -801,10 +799,10 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * because we have already locked the inode's i_mmap_lock in exclusive + * mode. + */ +- end = destoff + len - 1; +- btrfs_lock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ end = round_up(destoff + len, bs) - 1; ++ btrfs_lock_extent(&inode->io_tree, destoff, end, &cached_state); + ret = btrfs_clone(src, inode, off, olen, len, destoff, false); +- btrfs_unlock_extent(&BTRFS_I(inode)->io_tree, destoff, end, &cached_state); ++ btrfs_unlock_extent(&inode->io_tree, destoff, end, &cached_state); + if (ret < 0) + return ret; + +@@ -818,7 +816,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * could come from some range other than the copied inline extent's + * destination range and we have no way to know that. + */ +- ret = btrfs_wait_ordered_range(BTRFS_I(inode), destoff, len); ++ ret = btrfs_wait_ordered_range(inode, destoff, len); + if (ret < 0) + return ret; + +@@ -826,7 +824,7 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, + * Invalidate page cache so that future reads will see the cloned data + * immediately and not the previous data. + */ +- ret = filemap_invalidate_inode(inode, false, destoff, end); ++ ret = filemap_invalidate_inode(&inode->vfs_inode, false, destoff, end); + if (ret < 0) + return ret; + +@@ -841,7 +839,7 @@ static int btrfs_remap_file_range_prep(struct file *file_in, loff_t pos_in, + { + struct btrfs_inode *inode_in = BTRFS_I(file_inode(file_in)); + struct btrfs_inode *inode_out = BTRFS_I(file_inode(file_out)); +- u64 bs = inode_out->root->fs_info->sectorsize; ++ const u32 bs = inode_out->root->fs_info->sectorsize; + u64 wb_len; + int ret; + +@@ -934,7 +932,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + bool same_inode = dst_inode == src_inode; + int ret; + +- if (btrfs_is_shutdown(inode_to_fs_info(file_inode(src_file)))) ++ if (btrfs_is_shutdown(src_inode->root->fs_info)) + return -EIO; + + if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY)) +@@ -953,8 +951,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, + goto out_unlock; + + if (remap_flags & REMAP_FILE_DEDUP) +- ret = btrfs_extent_same(&src_inode->vfs_inode, off, len, +- &dst_inode->vfs_inode, destoff); ++ ret = btrfs_extent_same(src_inode, off, len, dst_inode, destoff); + else + ret = btrfs_clone_files(dst_file, src_file, off, len, destoff); + +diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c +--- a/fs/btrfs/relocation.c ++++ b/fs/btrfs/relocation.c +@@ -339,14 +339,15 @@ static struct btrfs_backref_node *walk_down_backref( + + static bool reloc_root_is_dead(const struct btrfs_root *root) + { +- /* +- * Pair with set_bit/clear_bit in clean_dirty_subvols and +- * btrfs_update_reloc_root. We need to see the updated bit before +- * trying to access reloc_root +- */ +- smp_rmb(); + if (test_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state)) + return true; ++ /* ++ * Pairs with set_bit/clear_bit in clear_reloc_root() and ++ * btrfs_update_reloc_root(). We need to see the updated bit before ++ * trying to access root->reloc_root in our callers. ++ */ ++ smp_rmb(); ++ + return false; + } + +@@ -4151,10 +4152,10 @@ static int copy_remapped_data(struct btrfs_fs_info *fs_info, u64 old_addr, + u64 new_addr, u64 length) + { + int ret; +- u64 copy_len = min_t(u64, length, SZ_1M); ++ const u64 copy_len = min_t(u64, length, SZ_1M); + struct page **pages; + struct reloc_io_private priv; +- unsigned int nr_pages = DIV_ROUND_UP(length, PAGE_SIZE); ++ const unsigned int nr_pages = DIV_ROUND_UP(copy_len, PAGE_SIZE); + + pages = kzalloc_objs(struct page *, nr_pages, GFP_NOFS); + if (!pages) +diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c +--- a/fs/btrfs/root-tree.c ++++ b/fs/btrfs/root-tree.c +@@ -265,15 +265,15 @@ int btrfs_find_orphan_roots(struct btrfs_fs_info *fs_info) + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + btrfs_err(fs_info, +- "failed to join transaction to delete orphan item: %d", +- ret); ++ "failed to join transaction to delete orphan item: %pe", ++ ERR_PTR(ret)); + return ret; + } + ret = btrfs_del_orphan_item(trans, tree_root, root_objectid); + btrfs_end_transaction(trans); + if (ret) { + btrfs_err(fs_info, +- "failed to delete root orphan item: %d", ret); ++ "failed to delete root orphan item: %pe", ERR_PTR(ret)); + return ret; + } + continue; +diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c +--- a/fs/btrfs/scrub.c ++++ b/fs/btrfs/scrub.c +@@ -57,12 +57,6 @@ struct scrub_ctx; + + #define SCRUB_TOTAL_STRIPES (SCRUB_GROUPS_PER_SCTX * SCRUB_STRIPES_PER_GROUP) + +-/* +- * The following value times PAGE_SIZE needs to be large enough to match the +- * largest node/leaf/sector size that shall be supported. +- */ +-#define SCRUB_MAX_SECTORS_PER_BLOCK (BTRFS_MAX_METADATA_BLOCKSIZE / SZ_4K) +- + /* Represent one sector and its needed info to verify the content. */ + struct scrub_sector_verification { + union { +@@ -129,19 +123,17 @@ enum { + scrub_bitmap_nr_last, + }; + +-#define SCRUB_STRIPE_MAX_FOLIOS (BTRFS_STRIPE_LEN / PAGE_SIZE) +- + /* + * Represent one contiguous range with a length of BTRFS_STRIPE_LEN. + */ + struct scrub_stripe { + struct scrub_ctx *sctx; + struct btrfs_block_group *bg; +- +- struct folio *folios[SCRUB_STRIPE_MAX_FOLIOS]; + struct scrub_sector_verification *sectors; +- + struct btrfs_device *dev; ++ ++ void *buffer; ++ + u64 logical; + u64 physical; + +@@ -227,6 +219,9 @@ struct scrub_ctx { + refcount_t refs; + }; + ++static_assert(BTRFS_STRIPE_LEN >= PAGE_SIZE); ++static_assert(IS_ALIGNED(BTRFS_STRIPE_LEN, PAGE_SIZE)); ++ + #define scrub_calc_start_bit(stripe, name, block_nr) \ + ({ \ + unsigned int __start_bit; \ +@@ -338,13 +333,10 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + if (!stripe) + return; + +- for (int i = 0; i < SCRUB_STRIPE_MAX_FOLIOS; i++) { +- if (stripe->folios[i]) +- folio_put(stripe->folios[i]); +- stripe->folios[i] = NULL; +- } ++ kvfree(stripe->buffer); + kfree(stripe->sectors); + kfree(stripe->csums); ++ stripe->buffer = NULL; + stripe->sectors = NULL; + stripe->csums = NULL; + stripe->sctx = NULL; +@@ -354,9 +346,6 @@ static void release_scrub_stripe(struct scrub_stripe *stripe) + static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + struct scrub_stripe *stripe) + { +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- int ret; +- + memset(stripe, 0, sizeof(*stripe)); + + stripe->nr_sectors = BTRFS_STRIPE_LEN >> fs_info->sectorsize_bits; +@@ -367,11 +356,8 @@ static int init_scrub_stripe(struct btrfs_fs_info *fs_info, + atomic_set(&stripe->pending_io, 0); + spin_lock_init(&stripe->write_error_lock); + +- ASSERT(BTRFS_STRIPE_LEN >> min_folio_shift <= SCRUB_STRIPE_MAX_FOLIOS); +- ret = btrfs_alloc_folio_array(BTRFS_STRIPE_LEN >> min_folio_shift, +- fs_info->block_min_order, stripe->folios, +- GFP_NOFS); +- if (ret < 0) ++ stripe->buffer = kvmalloc(BTRFS_STRIPE_LEN, GFP_NOFS); ++ if (!stripe->buffer) + goto error; + + stripe->sectors = kzalloc_objs(struct scrub_sector_verification, +@@ -682,32 +668,18 @@ static int fill_writer_pointer_gap(struct scrub_ctx *sctx, u64 physical) + return ret; + } + +-static void *scrub_stripe_get_kaddr(struct scrub_stripe *stripe, int sector_nr) ++/* ++ * Unlike the existing csum which is based on paddr, this version is fully on ++ * vaddr, so no extra per-page iteration needed. ++ */ ++static void scrub_calc_vaddr_csum(struct btrfs_fs_info *fs_info, ++ void *vaddr, unsigned int len, u8 *dest) + { +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; ++ struct btrfs_csum_ctx csum; + +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- return folio_address(folio) + offset_in_folio(folio, offset); +-} +- +-static phys_addr_t scrub_stripe_get_paddr(struct scrub_stripe *stripe, int sector_nr) +-{ +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; +- u32 offset = (sector_nr << fs_info->sectorsize_bits); +- const struct folio *folio = stripe->folios[offset >> min_folio_shift]; +- +- /* stripe->folios[] is allocated by us and no highmem is allowed. */ +- ASSERT(folio); +- ASSERT(!folio_test_highmem(folio)); +- /* And the range must be contained inside the folio. */ +- ASSERT(offset_in_folio(folio, offset) + fs_info->sectorsize <= folio_size(folio)); +- return page_to_phys(folio_page(folio, 0)) + offset_in_folio(folio, offset); ++ btrfs_csum_init(&csum, fs_info->csum_type); ++ btrfs_csum_update(&csum, vaddr, len); ++ btrfs_csum_final(&csum, dest); + } + + static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr) +@@ -715,19 +687,10 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; + const u64 logical = stripe->logical + (sector_nr << fs_info->sectorsize_bits); +- void *first_kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); +- struct btrfs_header *header = first_kaddr; +- struct btrfs_csum_ctx csum; +- u8 on_disk_csum[BTRFS_CSUM_SIZE]; ++ void *first_vaddr = stripe->buffer + (sector_nr << fs_info->sectorsize_bits); ++ struct btrfs_header *header = first_vaddr; + u8 calculated_csum[BTRFS_CSUM_SIZE]; + +- /* +- * Here we don't have a good way to attach the pages (and subpages) +- * to a dummy extent buffer, thus we have to directly grab the members +- * from pages. +- */ +- memcpy(on_disk_csum, header->csum, fs_info->csum_size); +- + if (logical != btrfs_stack_header_bytenr(header)) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); +@@ -759,23 +722,15 @@ static void scrub_verify_one_metadata(struct scrub_stripe *stripe, int sector_nr + } + + /* Now check tree block csum. */ +- btrfs_csum_init(&csum, fs_info->csum_type); +- btrfs_csum_update(&csum, first_kaddr + BTRFS_CSUM_SIZE, +- fs_info->sectorsize - BTRFS_CSUM_SIZE); +- +- for (int i = sector_nr + 1; i < sector_nr + sectors_per_tree; i++) { +- btrfs_csum_update(&csum, scrub_stripe_get_kaddr(stripe, i), +- fs_info->sectorsize); +- } +- +- btrfs_csum_final(&csum, calculated_csum); +- if (memcmp(calculated_csum, on_disk_csum, fs_info->csum_size) != 0) { ++ scrub_calc_vaddr_csum(fs_info, first_vaddr + BTRFS_CSUM_SIZE, ++ fs_info->nodesize - BTRFS_CSUM_SIZE, calculated_csum); ++ if (memcmp(calculated_csum, header->csum, fs_info->csum_size) != 0) { + scrub_bitmap_set_meta_error(stripe, sector_nr, sectors_per_tree); + scrub_bitmap_set_error(stripe, sector_nr, sectors_per_tree); + btrfs_warn_rl(fs_info, + "scrub: tree block %llu mirror %u has bad csum, has " BTRFS_CSUM_FMT " want " BTRFS_CSUM_FMT, + logical, stripe->mirror_num, +- BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, on_disk_csum), ++ BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, header->csum), + BTRFS_CSUM_FMT_VALUE(fs_info->csum_size, calculated_csum)); + return; + } +@@ -801,9 +756,7 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + struct scrub_sector_verification *sector = &stripe->sectors[sector_nr]; + const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits; +- phys_addr_t paddr = scrub_stripe_get_paddr(stripe, sector_nr); + u8 csum_buf[BTRFS_CSUM_SIZE]; +- int ret; + + ASSERT(sector_nr >= 0 && sector_nr < stripe->nr_sectors); + +@@ -846,8 +799,10 @@ static void scrub_verify_one_sector(struct scrub_stripe *stripe, int sector_nr) + return; + } + +- ret = btrfs_check_block_csum(fs_info, paddr, csum_buf, sector->csum); +- if (ret < 0) { ++ scrub_calc_vaddr_csum(fs_info, ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ fs_info->sectorsize, csum_buf); ++ if (memcmp(csum_buf, sector->csum, fs_info->csum_size)) { + scrub_bitmap_set_bit_csum_error(stripe, sector_nr); + scrub_bitmap_set_bit_error(stripe, sector_nr); + } else { +@@ -870,16 +825,51 @@ static void scrub_verify_one_stripe(struct scrub_stripe *stripe, unsigned long b + } + } + +-static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first_bvec) ++static unsigned int calc_sector_number(const struct btrfs_bio *bbio) + { +- int i; ++ const struct scrub_stripe *stripe = bbio->private; ++ const struct btrfs_fs_info *fs_info = stripe->bg->fs_info; + +- for (i = 0; i < stripe->nr_sectors; i++) { +- if (scrub_stripe_get_kaddr(stripe, i) == bvec_virt(first_bvec)) +- break; ++ /* Scrub bbios all have their @file_offset set to the logical bytenr. */ ++ ASSERT(bbio->file_offset >= stripe->logical && ++ bbio->file_offset < stripe->logical + (stripe->nr_sectors << ++ fs_info->sectorsize_bits), ++ "scrub bio logical=%llu stripe logical=%llu stripe len=%u", ++ bbio->file_offset, stripe->logical, ++ stripe->nr_sectors << fs_info->sectorsize_bits); ++ return (bbio->file_offset - stripe->logical) >> fs_info->sectorsize_bits; ++} ++ ++/* ++ * Common handling of read endio. ++ * ++ * The bbio will be released, so no more access to @bbio after this function. ++ */ ++static void scrub_read_endio_common(struct btrfs_bio *bbio) ++{ ++ struct scrub_stripe *stripe = bbio->private; ++ struct btrfs_fs_info *fs_info = stripe->bg->fs_info; ++ unsigned int sector_nr = calc_sector_number(bbio); ++ const u32 bio_size = bio_get_size(&bbio->bio); ++ const u32 sectors = bio_size >> fs_info->sectorsize_bits; ++ ++ ++ /* ++ * For vmallocated space, readers need to call invalidate_kernel_vmap_range() ++ * to manage the coherency between kernel mapping and devie space mapping. ++ */ ++ if (is_vmalloc_addr(stripe->buffer)) ++ invalidate_kernel_vmap_range( ++ stripe->buffer + (sector_nr << fs_info->sectorsize_bits), ++ bio_size); ++ ++ if (bbio->bio.bi_status) { ++ scrub_bitmap_set_io_error(stripe, sector_nr, sectors); ++ scrub_bitmap_set_error(stripe, sector_nr, sectors); ++ } else { ++ scrub_bitmap_clear_io_error(stripe, sector_nr, sectors); + } +- ASSERT(i < stripe->nr_sectors); +- return i; ++ bio_put(&bbio->bio); + } + + /* +@@ -891,22 +881,9 @@ static int calc_sector_number(struct scrub_stripe *stripe, struct bio_vec *first + static void scrub_repair_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- scrub_bitmap_set_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, +- bio_size >> fs_info->sectorsize_bits); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) + wake_up(&stripe->io_wait); + } +@@ -921,30 +898,35 @@ static void scrub_bio_add_sector(struct btrfs_bio *bbio, struct scrub_stripe *st + int sector_nr) + { + struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info; +- void *kaddr = scrub_stripe_get_kaddr(stripe, sector_nr); ++ const u32 offset = sector_nr << fs_info->sectorsize_bits; + int ret; + +- ret = bio_add_page(&bbio->bio, virt_to_page(kaddr), fs_info->sectorsize, +- offset_in_page(kaddr)); +- /* +- * Caller should ensure the bbio has enough size. +- * And we cannot use __bio_add_page(), which doesn't do any merge. +- * +- * Meanwhile for scrub_submit_initial_read() we fully rely on the merge +- * to create the minimal amount of bio vectors, for fs block size < page +- * size cases. +- */ ++ ASSERT(offset + fs_info->sectorsize <= BTRFS_STRIPE_LEN); ++ ++ if (is_vmalloc_addr(stripe->buffer)) { ++ ret = bio_add_vmalloc(&bbio->bio, stripe->buffer + offset, fs_info->sectorsize); ++ ASSERT(ret == true); ++ return; ++ } ++ ret = bio_add_page(&bbio->bio, virt_to_page(stripe->buffer + offset), ++ fs_info->sectorsize, offset_in_page(stripe->buffer + offset)); + ASSERT(ret == fs_info->sectorsize); + } + + static struct btrfs_bio *alloc_scrub_bbio(struct btrfs_fs_info *fs_info, +- unsigned int nr_vecs, blk_opf_t opf, ++ blk_opf_t opf, + u64 logical, + btrfs_bio_end_io_t end_io, void *private) + { + struct btrfs_bio *bbio; + +- bbio = btrfs_bio_alloc(nr_vecs, opf, BTRFS_I(fs_info->btree_inode), ++ /* ++ * Stripe->buffer is allocated by kvmalloc(), which can be pages at ++ * different physical addresses, we have to ensure the bbio is large ++ * enough to contain the full stripe. ++ */ ++ bbio = btrfs_bio_alloc(BTRFS_STRIPE_LEN >> PAGE_SHIFT, opf, ++ BTRFS_I(fs_info->btree_inode), + logical, end_io, private); + bbio->is_scrub = true; + bbio->bio.bi_iter.bi_sector = logical >> SECTOR_SHIFT; +@@ -976,7 +958,7 @@ static void scrub_stripe_submit_repair_read(struct scrub_stripe *stripe, + } + + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical + (i << fs_info->sectorsize_bits), + scrub_repair_read_endio, stripe); + +@@ -1249,20 +1231,9 @@ static void scrub_stripe_read_repair_worker(struct work_struct *work) + static void scrub_read_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); +- int num_sectors; +- const u32 bio_size = bio_get_size(&bbio->bio); + +- ASSERT(sector_nr < stripe->nr_sectors); +- num_sectors = bio_size >> stripe->bg->fs_info->sectorsize_bits; ++ scrub_read_endio_common(bbio); + +- if (bbio->bio.bi_status) { +- scrub_bitmap_set_io_error(stripe, sector_nr, num_sectors); +- scrub_bitmap_set_error(stripe, sector_nr, num_sectors); +- } else { +- scrub_bitmap_clear_io_error(stripe, sector_nr, num_sectors); +- } +- bio_put(&bbio->bio); + if (atomic_dec_and_test(&stripe->pending_io)) { + wake_up(&stripe->io_wait); + INIT_WORK(&stripe->work, scrub_stripe_read_repair_worker); +@@ -1274,7 +1245,7 @@ static void scrub_write_endio(struct btrfs_bio *bbio) + { + struct scrub_stripe *stripe = bbio->private; + struct btrfs_fs_info *fs_info = stripe->bg->fs_info; +- int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio)); ++ unsigned int sector_nr = calc_sector_number(bbio); + const u32 bio_size = bio_get_size(&bbio->bio); + + if (bbio->bio.bi_status) { +@@ -1353,7 +1324,7 @@ static void scrub_write_sectors(struct scrub_ctx *sctx, struct scrub_stripe *str + bbio = NULL; + } + if (!bbio) +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_WRITE, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_WRITE, + stripe->logical + (sector_nr << fs_info->sectorsize_bits), + scrub_write_endio, stripe); + scrub_bio_add_sector(bbio, stripe, sector_nr); +@@ -1848,7 +1819,7 @@ static void scrub_submit_extent_sector_read(struct scrub_stripe *stripe) + continue; + } + +- bbio = alloc_scrub_bbio(fs_info, stripe->nr_sectors, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + logical, scrub_read_endio, stripe); + } + +@@ -1873,7 +1844,6 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + { + struct btrfs_fs_info *fs_info = sctx->fs_info; + struct btrfs_bio *bbio; +- const u32 min_folio_shift = PAGE_SHIFT + fs_info->block_min_order; + unsigned int nr_sectors = stripe_length(stripe) >> fs_info->sectorsize_bits; + int mirror = stripe->mirror_num; + +@@ -1886,7 +1856,7 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx, + return; + } + +- bbio = alloc_scrub_bbio(fs_info, BTRFS_STRIPE_LEN >> min_folio_shift, REQ_OP_READ, ++ bbio = alloc_scrub_bbio(fs_info, REQ_OP_READ, + stripe->logical, scrub_read_endio, stripe); + /* Read the whole range inside the chunk boundary. */ + for (unsigned int cur = 0; cur < nr_sectors; cur++) +@@ -2142,7 +2112,7 @@ static int scrub_raid56_cached_parity(struct scrub_ctx *sctx, + for (int i = 0; i < data_stripes; i++) { + struct scrub_stripe *stripe = &sctx->raid56_data_stripes[i]; + +- raid56_parity_cache_data_folios(rbio, stripe->folios, ++ raid56_parity_cache_data_folios(rbio, stripe->buffer, + full_stripe_start + (i << BTRFS_STRIPE_LEN_SHIFT)); + } + raid56_parity_submit_scrub_rbio(rbio); +@@ -3095,14 +3065,6 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start, + /* At mount time we have ensured nodesize is in the range of [4K, 64K]. */ + ASSERT(fs_info->nodesize <= BTRFS_STRIPE_LEN); + +- /* +- * SCRUB_MAX_SECTORS_PER_BLOCK is calculated using the largest possible +- * value (max nodesize / min sectorsize), thus nodesize should always +- * be fine. +- */ +- ASSERT(fs_info->nodesize <= +- SCRUB_MAX_SECTORS_PER_BLOCK << fs_info->sectorsize_bits); +- + /* Allocate outside of device_list_mutex */ + sctx = scrub_setup_ctx(fs_info, is_dev_replace); + if (IS_ERR(sctx)) +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -130,10 +130,10 @@ static_assert(offsetof(struct backref_cache_entry, entry) == 0); + #define SEND_MAX_DIR_CREATED_CACHE_SIZE 64 + + /* +- * Max number of entries in the cache that stores directories that were already +- * created. The cache uses raw struct btrfs_lru_cache_entry entries, so it uses +- * at most 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but +- * the kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). ++ * Maximum number of entries in the cache that stores utimes values for directories. ++ * The cache uses raw struct btrfs_lru_cache_entry entries, so it uses at most ++ * 4096 bytes - sizeof(struct btrfs_lru_cache_entry) is 48 bytes, but the ++ * kmalloc-64 slab is used, so we get 4096 bytes (64 bytes * 64). + */ + #define SEND_MAX_DIR_UTIMES_CACHE_SIZE 64 + +@@ -625,9 +625,8 @@ static void fs_path_unreverse(struct fs_path *p) + static inline bool is_current_inode_path(const struct send_ctx *sctx, + const struct fs_path *path) + { +- const struct fs_path *cur = &sctx->cur_inode_path; +- +- return (strncmp(path->start, cur->start, fs_path_len(cur)) == 0); ++ /* Paths are always nul terminated. */ ++ return (strcmp(path->start, sctx->cur_inode_path.start) == 0); + } + + static struct btrfs_path *alloc_path_for_send(void) +@@ -6033,7 +6032,7 @@ static int send_write_or_clone(struct send_ctx *sctx, + int ret = 0; + u64 offset = key->offset; + u64 end; +- u64 bs = sctx->send_root->fs_info->sectorsize; ++ const u32 bs = sctx->send_root->fs_info->sectorsize; + struct btrfs_file_extent_item *ei; + u64 disk_byte; + u64 data_offset; +@@ -8251,7 +8250,7 @@ long btrfs_ioctl_send(struct btrfs_root *send_root, const struct btrfs_ioctl_sen + } + + if (sort_clone_roots) { +- for (i = 0; i < sctx->clone_roots_cnt; i++) { ++ for (i = 0; sctx && i < sctx->clone_roots_cnt; i++) { + btrfs_root_dec_send_in_progress( + sctx->clone_roots[i].root); + btrfs_put_root(sctx->clone_roots[i].root); +diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c +--- a/fs/btrfs/space-info.c ++++ b/fs/btrfs/space-info.c +@@ -2156,7 +2156,7 @@ static bool do_reclaim_sweep(struct btrfs_space_info *space_info, int raid) + will_reclaim = true; + reclaim = true; + } +- bg->reclaim_mark++; ++ bg->reclaim_mark = true; + spin_unlock(&bg->lock); + if (reclaim) + btrfs_mark_bg_to_reclaim(bg); +diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c +--- a/fs/btrfs/subpage.c ++++ b/fs/btrfs/subpage.c +@@ -59,7 +59,7 @@ int btrfs_attach_folio_state(const struct btrfs_fs_info *fs_info, + if (type == BTRFS_SUBPAGE_DATA && !btrfs_is_subpage(fs_info, folio)) + return 0; + +- bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type); ++ bfs = btrfs_alloc_folio_state(fs_info, folio_size(folio), type, GFP_NOFS); + if (IS_ERR(bfs)) + return PTR_ERR(bfs); + +@@ -86,7 +86,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + } + + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type) ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp) + { + struct btrfs_folio_state *ret; + unsigned int real_size; +@@ -96,7 +97,7 @@ struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs + real_size = struct_size(ret, bitmaps, + BITS_TO_LONGS(btrfs_bitmap_nr_max * + (fsize >> fs_info->sectorsize_bits))); +- ret = kzalloc(real_size, GFP_NOFS); ++ ret = kzalloc(real_size, gfp); + if (!ret) + return ERR_PTR(-ENOMEM); + +diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h +--- a/fs/btrfs/subpage.h ++++ b/fs/btrfs/subpage.h +@@ -110,7 +110,8 @@ void btrfs_detach_folio_state(const struct btrfs_fs_info *fs_info, struct folio + + /* Allocate additional data where page represents more than one sector */ + struct btrfs_folio_state *btrfs_alloc_folio_state(const struct btrfs_fs_info *fs_info, +- size_t fsize, enum btrfs_folio_type type); ++ size_t fsize, enum btrfs_folio_type type, ++ gfp_t gfp); + static inline void btrfs_free_folio_state(struct btrfs_folio_state *bfs) + { + kfree(bfs); +diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c +--- a/fs/btrfs/super.c ++++ b/fs/btrfs/super.c +@@ -129,7 +129,6 @@ enum { + + /* Rescue options */ + Opt_rescue, +- Opt_usebackuproot, + + /* Debugging options */ + Opt_enospc_debug, +@@ -249,8 +248,6 @@ static const struct fs_parameter_spec btrfs_fs_parameters[] = { + + /* Rescue options. */ + fsparam_enum("rescue", Opt_rescue, btrfs_parameter_rescue), +- /* Deprecated, with alias rescue=usebackuproot */ +- __fsparam(NULL, "usebackuproot", Opt_usebackuproot, fs_param_deprecated, NULL), + /* For compatibility only, alias for "rescue=nologreplay". */ + fsparam_flag("norecovery", Opt_norecovery), + +@@ -514,19 +511,20 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_clear_opt(ctx->mount_opt, NODISCARD); + break; + case Opt_space_cache: +- if (result.negated) { +- btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); +- btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- } else { +- btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); +- } ++ if (!result.negated) ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_version: + switch (result.uint_32) { + case Opt_space_cache_v1: +- btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); ++ btrfs_warn(NULL, ++ "v1 space cache is deprecated, falling back to no space cache"); ++ btrfs_set_opt(ctx->mount_opt, NOSPACECACHE); ++ btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); + btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case Opt_space_cache_v2: +@@ -560,14 +558,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + else + btrfs_set_opt(ctx->mount_opt, AUTO_DEFRAG); + break; +- case Opt_usebackuproot: +- btrfs_warn(NULL, +- "'usebackuproot' is deprecated, use 'rescue=usebackuproot' instead"); +- btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); +- +- /* If we're loading the backup roots we can't trust the space cache. */ +- btrfs_set_opt(ctx->mount_opt, CLEAR_CACHE); +- break; + case Opt_skip_balance: + btrfs_set_opt(ctx->mount_opt, SKIP_BALANCE); + break; +@@ -620,6 +610,7 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + btrfs_set_opt(ctx->mount_opt, IGNORESUPERFLAGS); + btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS); + btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY); ++ btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + break; + default: + btrfs_info(NULL, "unrecognized rescue option '%s'", +@@ -668,7 +659,6 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) + */ + static void btrfs_clear_oneshot_options(struct btrfs_fs_info *fs_info) + { +- btrfs_clear_opt(fs_info->mount_opt, USEBACKUPROOT); + btrfs_clear_opt(fs_info->mount_opt, CLEAR_CACHE); + btrfs_clear_opt(fs_info->mount_opt, NOSPACECACHE); + } +@@ -692,7 +682,8 @@ bool btrfs_check_options(const struct btrfs_fs_info *info, + bool ret = true; + + if (!(flags & SB_RDONLY) && +- (check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || ++ (check_ro_option(info, *mount_opt, BTRFS_MOUNT_USEBACKUPROOT, "usebackuproot") || ++ check_ro_option(info, *mount_opt, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREBADROOTS, "ignorebadroots") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREDATACSUMS, "ignoredatacsums") || + check_ro_option(info, *mount_opt, BTRFS_MOUNT_IGNOREMETACSUMS, "ignoremetacsums") || +@@ -982,7 +973,7 @@ static int btrfs_fill_super(struct super_block *sb, + + ret = open_ctree(sb, fs_devices); + if (ret) { +- btrfs_err(fs_info, "open_ctree failed: %d", ret); ++ btrfs_err(fs_info, "open_ctree failed: %pe", ERR_PTR(ret)); + return ret; + } + +diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c +--- a/fs/btrfs/sysfs.c ++++ b/fs/btrfs/sysfs.c +@@ -1336,7 +1336,7 @@ char *btrfs_get_mod_read_policy(void) + return read_policy; + } + +-/* Set perms to 0, disable /sys/module/btrfs/parameter/read_policy interface. */ ++/* Set perms to 0, disable /sys/module/btrfs/parameters/read_policy interface. */ + module_param(read_policy, charp, 0); + MODULE_PARM_DESC(read_policy, + "Global read policy: pid (default), round-robin[:], devid[:]"); +diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c +--- a/fs/btrfs/transaction.c ++++ b/fs/btrfs/transaction.c +@@ -1528,12 +1528,8 @@ static noinline int commit_fs_roots(struct btrfs_trans_handle *trans) + ASSERT(atomic_read(&root->log_writers) == 0, + "atomic_read(&root->log_writers)=%d", + atomic_read(&root->log_writers)); +- ASSERT(atomic_read(&root->log_commit[0]) == 0, +- "atomic_read(&root->log_commit[0])=%d", +- atomic_read(&root->log_commit[0])); +- ASSERT(atomic_read(&root->log_commit[1]) == 0, +- "atomic_read(&root->log_commit[1])=%d", +- atomic_read(&root->log_commit[1])); ++ ASSERT(!root->log_commit[0]); ++ ASSERT(!root->log_commit[1]); + + radix_tree_tag_clear(&fs_info->fs_roots_radix, + (unsigned long)btrfs_root_id(root), +@@ -1651,7 +1647,7 @@ static int qgroup_account_snapshot(struct btrfs_trans_handle *trans, + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { + btrfs_err(fs_info, +-"error while writing out transaction during qgroup snapshot accounting: %d", ret); ++"error while writing out transaction during qgroup snapshot accounting: %pe", ERR_PTR(ret)); + return ret; + } + +@@ -2597,7 +2593,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) + + ret = btrfs_write_and_wait_transaction(trans); + if (unlikely(ret)) { +- btrfs_err(fs_info, "error while writing out transaction: %d", ret); ++ btrfs_err(fs_info, "error while writing out transaction: %pe", ERR_PTR(ret)); + /* + * Abort before releasing tree_log_mutex, so a log sync waiting + * on it sees the fs error and skips writing super_for_commit +@@ -2764,8 +2760,8 @@ void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans, + WRITE_ONCE(trans->transaction->aborted, error); + trace_btrfs_transaction_abort(trans); + if (first_hit) { +- btrfs_err(fs_info, "Transaction %llu aborted (error %d)", +- trans->transid, error); ++ btrfs_err(fs_info, "Transaction %llu aborted (%pe)", ++ trans->transid, ERR_PTR(error)); + if (error == -ENOSPC) + btrfs_dump_space_info_for_trans_abort(fs_info); + } +diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c +--- a/fs/btrfs/tree-log.c ++++ b/fs/btrfs/tree-log.c +@@ -221,7 +221,7 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans, + static int link_to_fixup_dir(struct walk_control *wc, u64 objectid); + static noinline int replay_dir_deletes(struct walk_control *wc, + u64 dirid, bool del_all); +-static void wait_log_commit(struct btrfs_root *root, int transid); ++static bool wait_log_commit(struct btrfs_root *root, int transid); + + /* + * tree logging is a special write ahead log used to make sure that +@@ -305,24 +305,13 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + if (btrfs_need_log_full_commit(trans)) { + ret = BTRFS_LOG_FORCE_COMMIT; + goto out; + } + +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } +- +- if (!root->log_start_pid) { +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; +- } else if (root->log_start_pid != current->pid) { +- set_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- } + } else { + /* + * This means fs_info->log_root_tree was already created +@@ -340,8 +329,6 @@ static int start_log_trans(struct btrfs_trans_handle *trans, + goto out; + + set_bit(BTRFS_ROOT_HAS_LOG_TREE, &root->state); +- clear_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state); +- root->log_start_pid = current->pid; + } + + atomic_inc(&root->log_writers); +@@ -372,13 +359,9 @@ static int join_running_log_trans(struct btrfs_root *root) + mutex_lock(&root->log_mutex); + again: + if (root->log_root) { +- int index = (root->log_transid + 1) % 2; +- + ret = 0; +- if (zoned && atomic_read(&root->log_commit[index])) { +- wait_log_commit(root, root->log_transid - 1); ++ if (zoned && wait_log_commit(root, root->log_transid - 1)) + goto again; +- } + atomic_inc(&root->log_writers); + } + mutex_unlock(&root->log_mutex); +@@ -2986,6 +2969,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + { + struct btrfs_trans_handle *trans = wc->trans; + struct btrfs_fs_info *fs_info = wc->log->fs_info; ++ struct btrfs_eb_prealloc pa = { 0 }; + u64 bytenr; + u64 ptr_gen; + struct extent_buffer *next; +@@ -3010,7 +2994,7 @@ static noinline int walk_down_log_tree(struct btrfs_path *path, int *level, + check.has_first_key = true; + btrfs_node_key_to_cpu(cur, &check.first_key, path->slots[*level]); + +- next = btrfs_find_create_tree_block(fs_info, bytenr, ++ next = btrfs_find_create_tree_block(fs_info, &pa, bytenr, + btrfs_header_owner(cur), + *level - 1); + if (IS_ERR(next)) { +@@ -3181,10 +3165,14 @@ static int update_log_root(struct btrfs_trans_handle *trans, + return ret; + } + +-static void wait_log_commit(struct btrfs_root *root, int transid) ++/* Returns true if we had to wait, false otherwise. */ ++static bool wait_log_commit(struct btrfs_root *root, int transid) + { + DEFINE_WAIT(wait); +- int index = transid % 2; ++ const int index = (transid >= 0 ? transid % 2 : -transid % 2); ++ ++ if (!root->log_commit[index]) ++ return false; + + /* + * we only allow two pending log transactions at a time, +@@ -3195,15 +3183,17 @@ static void wait_log_commit(struct btrfs_root *root, int transid) + prepare_to_wait(&root->log_commit_wait[index], + &wait, TASK_UNINTERRUPTIBLE); + +- if (!(root->log_transid_committed < transid && +- atomic_read(&root->log_commit[index]))) +- break; +- + mutex_unlock(&root->log_mutex); + schedule(); + mutex_lock(&root->log_mutex); ++ ++ if (!(root->log_transid_committed < transid && ++ root->log_commit[index])) ++ break; + } + finish_wait(&root->log_commit_wait[index], &wait); ++ ++ return true; + } + + static void wait_for_writer(struct btrfs_root *root) +@@ -3307,15 +3297,15 @@ static inline void btrfs_remove_all_log_ctxs(struct btrfs_root *root, + int btrfs_sync_log(struct btrfs_trans_handle *trans, + struct btrfs_root *root, struct btrfs_log_ctx *ctx) + { +- int index1; +- int index2; + int mark; + int ret; + struct btrfs_fs_info *fs_info = root->fs_info; + struct btrfs_root *log = root->log_root; + struct btrfs_root *log_root_tree = fs_info->log_root_tree; + struct btrfs_root_item new_root_item; +- int log_transid = 0; ++ int log_transid = ctx->log_transid; ++ int index1 = log_transid % 2; ++ int index2; + struct btrfs_log_ctx root_log_ctx; + struct blk_plug plug; + u64 log_root_start; +@@ -3323,41 +3313,25 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + mutex_lock(&root->log_mutex); + trace_btrfs_sync_log_enter(trans, root, ctx); +- log_transid = ctx->log_transid; + if (root->log_transid_committed >= log_transid) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + +- index1 = log_transid % 2; +- if (atomic_read(&root->log_commit[index1])) { +- wait_log_commit(root, log_transid); ++ if (wait_log_commit(root, log_transid)) { + trace_btrfs_sync_log_exit(trans, root, ctx, ctx->log_ret); + mutex_unlock(&root->log_mutex); + return ctx->log_ret; + } + ASSERT(log_transid == root->log_transid, + "log_transid=%d root->log_transid=%d", log_transid, root->log_transid); +- atomic_set(&root->log_commit[index1], 1); ++ root->log_commit[index1] = true; + + /* wait for previous tree log sync to complete */ +- if (atomic_read(&root->log_commit[(index1 + 1) % 2])) +- wait_log_commit(root, log_transid - 1); ++ wait_log_commit(root, log_transid - 1); + +- while (1) { +- int batch = atomic_read(&root->log_batch); +- /* when we're on an ssd, just kick the log commit out */ +- if (!btrfs_test_opt(fs_info, SSD) && +- test_bit(BTRFS_ROOT_MULTI_LOG_TASKS, &root->state)) { +- mutex_unlock(&root->log_mutex); +- schedule_timeout_uninterruptible(1); +- mutex_lock(&root->log_mutex); +- } +- wait_for_writer(root); +- if (batch == atomic_read(&root->log_batch)) +- break; +- } ++ wait_for_writer(root); + + /* bail out if we need to do a full commit */ + if (btrfs_need_log_full_commit(trans)) { +@@ -3414,7 +3388,6 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + btrfs_set_root_log_transid(root, root->log_transid + 1); + log->log_transid = root->log_transid; +- root->log_start_pid = 0; + /* + * IO has been started, blocks of the log tree have WRITTEN flag set + * in their headers. new modifications of the log will be written to +@@ -3473,7 +3446,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + goto out; + } + +- if (atomic_read(&log_root_tree->log_commit[index2])) { ++ if (log_root_tree->log_commit[index2]) { + blk_finish_plug(&plug); + ret = btrfs_wait_tree_log_extents(log, mark); + wait_log_commit(log_root_tree, +@@ -3487,12 +3460,9 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + ASSERT(root_log_ctx.log_transid == log_root_tree->log_transid, + "root_log_ctx.log_transid=%d log_root_tree->log_transid=%d", + root_log_ctx.log_transid, log_root_tree->log_transid); +- atomic_set(&log_root_tree->log_commit[index2], 1); ++ log_root_tree->log_commit[index2] = true; + +- if (atomic_read(&log_root_tree->log_commit[(index2 + 1) % 2])) { +- wait_log_commit(log_root_tree, +- root_log_ctx.log_transid - 1); +- } ++ wait_log_commit(log_root_tree, root_log_ctx.log_transid - 1); + + /* + * now that we've moved on to the tree of log tree roots, +@@ -3590,7 +3560,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + + /* + * We know there can only be one task here, since we have not yet set +- * root->log_commit[index1] to 0 and any task attempting to sync the ++ * root->log_commit[index1] to false and any task attempting to sync the + * log must wait for the previous log transaction to commit if it's + * still in progress or wait for the current log transaction commit if + * someone else already started it. We use <= and not < because the +@@ -3606,7 +3576,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + btrfs_remove_all_log_ctxs(log_root_tree, index2, ret); + + log_root_tree->log_transid_committed++; +- atomic_set(&log_root_tree->log_commit[index2], 0); ++ log_root_tree->log_commit[index2] = false; + mutex_unlock(&log_root_tree->log_mutex); + + /* +@@ -3619,7 +3589,7 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans, + mutex_lock(&root->log_mutex); + btrfs_remove_all_log_ctxs(root, index1, ret); + root->log_transid_committed++; +- atomic_set(&root->log_commit[index1], 0); ++ root->log_commit[index1] = false; + mutex_unlock(&root->log_mutex); + + /* +@@ -5622,6 +5592,15 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans, + if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0) + return 0; + ++ /* ++ * If there are no prealloc extents (which can be located past i_size), ++ * and disk space used is greater than or equals to i_size, then there ++ * are no holes. ++ */ ++ if (!(inode->flags & BTRFS_INODE_PREALLOC) && ++ i_size <= inode_get_bytes(&inode->vfs_inode)) ++ return 0; ++ + key.objectid = ino; + key.type = BTRFS_EXTENT_DATA_KEY; + key.offset = 0; +diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c +--- a/fs/btrfs/verity.c ++++ b/fs/btrfs/verity.c +@@ -652,7 +652,7 @@ static int btrfs_end_enable_verity(struct file *filp, const void *desc, + rollback_ret = rollback_verity(inode); + if (rollback_ret) + btrfs_err(inode->root->fs_info, +- "failed to rollback verity items: %d", rollback_ret); ++ "failed to rollback verity items: %pe", ERR_PTR(rollback_ret)); + return ret; + } + +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,41 +740,6 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + +-static bool is_same_device(struct btrfs_device *device, const char *new_path) +-{ +- struct path old = { .mnt = NULL, .dentry = NULL }; +- struct path new = { .mnt = NULL, .dentry = NULL }; +- char AUTO_KFREE(old_path); +- bool is_same = false; +- int ret; +- +- if (!device->name) +- goto out; +- +- old_path = kzalloc(PATH_MAX, GFP_NOFS); +- if (!old_path) +- goto out; +- +- rcu_read_lock(); +- ret = strscpy(old_path, rcu_dereference(device->name), PATH_MAX); +- rcu_read_unlock(); +- if (ret < 0) +- goto out; +- +- ret = kern_path(old_path, LOOKUP_FOLLOW, &old); +- if (ret) +- goto out; +- ret = kern_path(new_path, LOOKUP_FOLLOW, &new); +- if (ret) +- goto out; +- if (path_equal(&old, &new)) +- is_same = true; +-out: +- path_put(&old); +- path_put(&new); +- return is_same; +-} +- + /* + * Add new device to list of registered devices + * +@@ -895,7 +860,7 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || !is_same_device(device, path)) { ++ } else if (!device->name || device->devt != path_devt) { + const char *old_name; + + /* +@@ -4592,7 +4557,7 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info) + if (ret == -ENOSPC) { + enospc_errors++; + } else if (ret == -ETXTBSY) { +- btrfs_info(fs_info, ++ btrfs_warn(fs_info, + "skipping relocation of block group %llu due to active swapfile", + found_key.offset); + ret = 0; +@@ -6053,6 +6018,19 @@ struct btrfs_chunk_map *btrfs_alloc_chunk_map(int num_stripes, gfp_t gfp) + return map; + } + ++static void set_real_chunk_type(struct btrfs_chunk_map *map) ++{ ++ map->type = map->on_disk_type; ++ if (likely((map->on_disk_type & BTRFS_BLOCK_GROUP_RAID56_MASK) == 0 || ++ nr_data_stripes(map) > 1)) ++ return; ++ if (map->on_disk_type & BTRFS_BLOCK_GROUP_RAID5) ++ map->type |= BTRFS_BLOCK_GROUP_RAID1; ++ else ++ map->type |= BTRFS_BLOCK_GROUP_RAID1C3; ++ map->type &= ~BTRFS_BLOCK_GROUP_RAID56_MASK; ++} ++ + static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + struct alloc_chunk_ctl *ctl, + struct btrfs_device_info *devices_info) +@@ -6071,11 +6049,10 @@ static struct btrfs_block_group *create_chunk(struct btrfs_trans_handle *trans, + map->start = start; + map->chunk_len = ctl->chunk_size; + map->stripe_size = ctl->stripe_size; +- map->type = type; +- map->io_align = BTRFS_STRIPE_LEN; +- map->io_width = BTRFS_STRIPE_LEN; ++ map->on_disk_type = type; + map->sub_stripes = ctl->sub_stripes; + map->num_stripes = ctl->num_stripes; ++ set_real_chunk_type(map); + + for (int i = 0; i < ctl->ndevs; i++) { + for (int j = 0; j < ctl->dev_stripes; j++) { +@@ -6254,7 +6231,7 @@ int btrfs_chunk_alloc_add_chunk_item(struct btrfs_trans_handle *trans, + btrfs_set_stack_chunk_length(chunk, bg->length); + btrfs_set_stack_chunk_owner(chunk, BTRFS_EXTENT_TREE_OBJECTID); + btrfs_set_stack_chunk_stripe_len(chunk, BTRFS_STRIPE_LEN); +- btrfs_set_stack_chunk_type(chunk, map->type); ++ btrfs_set_stack_chunk_type(chunk, map->on_disk_type); + btrfs_set_stack_chunk_num_stripes(chunk, map->num_stripes); + btrfs_set_stack_chunk_io_align(chunk, BTRFS_STRIPE_LEN); + btrfs_set_stack_chunk_io_width(chunk, BTRFS_STRIPE_LEN); +@@ -7636,9 +7613,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + map->start = logical; + map->chunk_len = length; + map->num_stripes = num_stripes; +- map->io_width = btrfs_chunk_io_width(leaf, chunk); +- map->io_align = btrfs_chunk_io_align(leaf, chunk); +- map->type = type; ++ map->on_disk_type = type; + /* + * We can't use the sub_stripes value, as for profiles other than + * RAID10, they may have 0 as sub_stripes for filesystems created by +@@ -7649,6 +7624,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf, + */ + map->sub_stripes = btrfs_raid_array[index].sub_stripes; + map->verified_stripes = 0; ++ set_real_chunk_type(map); + + if (num_stripes > 0) + map->stripe_size = btrfs_calc_stripe_length(map); +diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h +--- a/fs/btrfs/volumes.h ++++ b/fs/btrfs/volumes.h +@@ -632,9 +632,15 @@ struct btrfs_chunk_map { + u64 start; + u64 chunk_len; + u64 stripe_size; ++ /* ++ * The real type that is utilized during logical address mapping. ++ * ++ * For most profiles it matches @on_disk_type, but for single-data-RAID56, ++ * the real type will be set to RAID1/RAID1C3, to avoid unsupported ++ * operations from raid56 lib. ++ */ + u64 type; +- int io_align; +- int io_width; ++ u64 on_disk_type; + int num_stripes; + int sub_stripes; + struct btrfs_io_stripe stripes[]; +diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h +--- a/include/trace/events/btrfs.h ++++ b/include/trace/events/btrfs.h +@@ -1613,9 +1613,9 @@ TRACE_EVENT(btrfs_sync_log_enter, + __entry->log_transid_committed = + data_race(root->log_transid_committed); + __entry->log_committing = +- atomic_read(&root->log_commit[ctx->log_transid % 2]); ++ data_race(root->log_commit[ctx->log_transid % 2]); + __entry->log_committing_prev = +- atomic_read(&root->log_commit[(ctx->log_transid + 1) % 2]); ++ data_race(root->log_commit[(ctx->log_transid + 1) % 2]); + __entry->log_writers = atomic_read(&root->log_writers); + ), + diff --git a/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch.sig b/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch.sig new file mode 100644 index 0000000..9d469a9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0300-btrfs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch new file mode 100644 index 0000000..8cc1ec5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch @@ -0,0 +1,92 @@ +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 +@@ -627,7 +627,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/ioctl.c b/fs/btrfs/ioctl.c +--- a/fs/btrfs/ioctl.c ++++ b/fs/btrfs/ioctl.c +@@ -384,6 +384,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags &= ~BTRFS_INODE_COMPRESS; + inode_flags |= BTRFS_INODE_NOCOMPRESS; + } else if (fsflags & FS_COMPR_FL) { ++ enum btrfs_compression_type comp_type; + + if (IS_SWAPFILE(&inode->vfs_inode)) + return -ETXTBSY; +@@ -391,9 +392,23 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap, + inode_flags |= BTRFS_INODE_COMPRESS; + inode_flags &= ~BTRFS_INODE_NOCOMPRESS; + +- comp = btrfs_compress_type2str(fs_info->compress_type); +- if (!comp || comp[0] == 0) +- comp = btrfs_compress_type2str(BTRFS_COMPRESS_ZLIB); ++ /* ++ * Keep the algorithm recorded in the compression property, ++ * otherwise changing an unrelated attribute would reset it to ++ * the mount default, since FS_IOC_SETFLAGS callers write back ++ * the whole flag set they got from FS_IOC_GETFLAGS and that ++ * includes FS_COMPR_FL for any inode carrying the property. ++ * ++ * Inodes with the compress flag set but no property keep using ++ * the mount default, so they behave as before. ++ */ ++ if (inode->prop_compress) ++ comp_type = inode->prop_compress; ++ else if (fs_info->compress_type) ++ comp_type = fs_info->compress_type; ++ else ++ comp_type = BTRFS_COMPRESS_ZLIB; ++ comp = btrfs_compress_type2str(comp_type); + } else { + inode_flags &= ~(BTRFS_INODE_COMPRESS | BTRFS_INODE_NOCOMPRESS); + } +diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c +--- a/fs/btrfs/send.c ++++ b/fs/btrfs/send.c +@@ -6417,6 +6417,13 @@ static int process_extent(struct send_ctx *sctx, + + if (S_ISLNK(sctx->cur_inode_mode)) + return 0; ++ if (unlikely(!S_ISREG(sctx->cur_inode_mode))) { ++ btrfs_crit(sctx->send_root->fs_info, ++ "send: extent for non-regular inode %llu root %llu mode 0%llo", ++ key->objectid, btrfs_root_id(sctx->send_root), ++ sctx->cur_inode_mode & S_IFMT); ++ return -EUCLEAN; ++ } + + if (sctx->parent_root && !sctx->cur_inode_new) { + ret = is_extent_unchanged(sctx, path, key); +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2710,6 +2710,7 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + { + struct btrfs_block_group *block_group; + u64 min_alloc_bytes; ++ int ret = 0; + + if (!btrfs_is_zoned(fs_info)) + return 0; +@@ -2729,11 +2730,11 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 leng + block_group->start + block_group->zone_capacity) + goto out; + +- do_zone_finish(block_group, true); ++ ret = do_zone_finish(block_group, true); + + out: + btrfs_put_block_group(block_group); +- return 0; ++ return ret; + } + + static void btrfs_zone_finish_endio_workfn(struct work_struct *work) diff --git a/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch.sig new file mode 100644 index 0000000..f05ee3b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0301-btrfs-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch b/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch new file mode 100644 index 0000000..d949f95 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch @@ -0,0 +1,109 @@ +diff --git c/fs/btrfs/zstd.c i/fs/btrfs/zstd.c +--- c/fs/btrfs/zstd.c ++++ i/fs/btrfs/zstd.c +@@ -589,10 +589,48 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb) + return ret; + } + ++/* ++ * Map the destination for the next chunk of output. ++ * ++ * @decompressed is the offset of the next output byte inside the fully ++ * decompressed extent. If that offset has reached the current destination ++ * segment, its page-bounded bio_vec is kmapped so that zstd can write into the ++ * page cache directly, and the number of bytes writable there is returned. ++ * Otherwise @kaddr_ret is set to NULL and the number of bytes to skip before ++ * that segment is returned. This covers both the initial prefix and gaps in ++ * the destination bio. ++ */ ++static u32 zstd_map_dest(struct compressed_bio *cb, u32 decompressed, ++ void **kaddr_ret) ++{ ++ struct bio *orig_bio = &cb->orig_bbio->bio; ++ struct bio_vec bvec; ++ u32 bvec_offset; ++ u32 off; ++ ++ bvec = bio_iter_iovec(orig_bio, orig_bio->bi_iter); ++ /* ++ * cb->start may underflow, but subtracting that value can still give us ++ * the correct offset inside the full decompressed extent. ++ */ ++ bvec_offset = page_offset(bvec.bv_page) + bvec.bv_offset - cb->start; ++ ++ if (decompressed < bvec_offset) { ++ *kaddr_ret = NULL; ++ return bvec_offset - decompressed; ++ } ++ ++ off = decompressed - bvec_offset; ++ ASSERT(off < bvec.bv_len); ++ *kaddr_ret = bvec_kmap_local(&bvec) + off; ++ return bvec.bv_len - off; ++} ++ + int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + { + struct btrfs_fs_info *fs_info = cb_to_fs_info(cb); + struct workspace *workspace = list_entry(ws, struct workspace, list); ++ struct bio *orig_bio = &cb->orig_bbio->bio; + struct folio_iter fi; + size_t srclen = bio_get_size(&cb->bbio.bio); + zstd_dstream *stream; +@@ -600,7 +638,6 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + const unsigned int min_folio_size = btrfs_min_folio_size(fs_info); + unsigned long folio_in_index = 0; + unsigned long total_folios_in = DIV_ROUND_UP(srclen, min_folio_size); +- unsigned long buf_start; + unsigned long total_out = 0; + + bio_first_folio(&fi, &cb->bbio.bio, 0); +@@ -624,15 +661,26 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + workspace->in_buf.pos = 0; + workspace->in_buf.size = min_t(size_t, srclen, min_folio_size); + +- workspace->out_buf.dst = workspace->buf; +- workspace->out_buf.pos = 0; +- workspace->out_buf.size = fs_info->sectorsize; +- +- while (1) { ++ while (orig_bio->bi_iter.bi_size) { + size_t ret2; ++ void *kaddr; ++ u32 dstlen; ++ ++ dstlen = zstd_map_dest(cb, total_out, &kaddr); ++ if (kaddr) { ++ workspace->out_buf.dst = kaddr; ++ workspace->out_buf.size = dstlen; ++ } else { ++ workspace->out_buf.dst = workspace->buf; ++ workspace->out_buf.size = min_t(u32, dstlen, ++ fs_info->sectorsize); ++ } ++ workspace->out_buf.pos = 0; + + ret2 = zstd_decompress_stream(stream, &workspace->out_buf, + &workspace->in_buf); ++ if (kaddr) ++ kunmap_local(kaddr); + if (unlikely(zstd_is_error(ret2))) { + struct btrfs_inode *inode = cb->bbio.inode; + +@@ -643,14 +691,9 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) + ret = -EIO; + goto done; + } +- buf_start = total_out; + total_out += workspace->out_buf.pos; +- workspace->out_buf.pos = 0; +- +- ret = btrfs_decompress_buf2page(workspace->out_buf.dst, +- total_out - buf_start, cb, buf_start); +- if (ret == 0) +- break; ++ if (kaddr) ++ bio_advance(orig_bio, workspace->out_buf.pos); + + if (workspace->in_buf.pos >= srclen) + break; diff --git a/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch.sig b/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch.sig new file mode 100644 index 0000000..a75cf51 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0302-btrfs-zstd-decompress-direct-to-page.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch new file mode 100644 index 0000000..42654e7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch @@ -0,0 +1,97 @@ +diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c +--- a/fs/btrfs/block-group.c ++++ b/fs/btrfs/block-group.c +@@ -3074,6 +3074,18 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + ++ /* ++ * Ensure the corresponding space_info object is created and ++ * assigned to our block group. We want our bg to be added to the rbtree ++ * with its ->space_info set. ++ * ++ * On a zoned filesystem btrfs_add_new_free_space() ends up in ++ * __btrfs_add_free_space_zoned(), which dereferences ++ * block_group->space_info, so it has to be set beforehand. ++ */ ++ cache->space_info = space_info; ++ ASSERT(cache->space_info); ++ + ret = btrfs_add_new_free_space(cache, chunk_offset, chunk_offset + size, NULL); + btrfs_free_excluded_extents(cache); + if (ret) { +@@ -3081,14 +3093,6 @@ struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *tran + return ERR_PTR(ret); + } + +- /* +- * Ensure the corresponding space_info object is created and +- * assigned to our block group. We want our bg to be added to the rbtree +- * with its ->space_info set. +- */ +- cache->space_info = space_info; +- ASSERT(cache->space_info); +- + ret = btrfs_add_block_group_cache(cache); + if (ret) { + btrfs_remove_free_space_cache(cache); +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -1909,6 +1909,16 @@ static int check_inode_ref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ if (unlikely(btrfs_is_fstree(btrfs_header_owner(leaf)) && ++ (key->offset < BTRFS_FIRST_FREE_OBJECTID || ++ key->offset > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid offset for ref key, have %llu expect [%llu, %lld]", ++ key->offset, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + ptr = btrfs_item_ptr_offset(leaf, slot); + end = ptr + btrfs_item_size(leaf, slot); + while (ptr < end) { +@@ -1952,12 +1962,14 @@ static int check_inode_extref(struct extent_buffer *leaf, + { + unsigned long ptr = btrfs_item_ptr_offset(leaf, slot); + unsigned long end = ptr + btrfs_item_size(leaf, slot); ++ const bool is_fstree = btrfs_is_fstree(btrfs_header_owner(leaf)); + + if (unlikely(!check_prev_ino(leaf, key, slot, prev_key))) + return -EUCLEAN; + + while (ptr < end) { + struct btrfs_inode_extref *extref = (struct btrfs_inode_extref *)ptr; ++ u64 parent; + u16 namelen; + + if (unlikely(ptr + sizeof(*extref) > end)) { +@@ -1967,7 +1979,24 @@ static int check_inode_extref(struct extent_buffer *leaf, + return -EUCLEAN; + } + ++ parent = btrfs_inode_extref_parent(leaf, extref); ++ if (unlikely(is_fstree && (parent < BTRFS_FIRST_FREE_OBJECTID || ++ parent > BTRFS_LAST_FREE_OBJECTID))) { ++ inode_ref_err(leaf, slot, ++ "invalid parent for extref key, have %llu expect [%llu, %lld]", ++ parent, BTRFS_FIRST_FREE_OBJECTID, ++ BTRFS_LAST_FREE_OBJECTID); ++ return -EUCLEAN; ++ } ++ + namelen = btrfs_inode_extref_name_len(leaf, extref); ++ if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) { ++ inode_ref_err(leaf, slot, ++ "invalid inode extref name length, has %u expect [1, %u]", ++ namelen, BTRFS_NAME_LEN); ++ return -EUCLEAN; ++ } ++ + if (unlikely(ptr + sizeof(*extref) + namelen > end)) { + inode_ref_err(leaf, slot, + "inode extref overflow, ptr %lu end %lu namelen %u", diff --git a/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch.sig new file mode 100644 index 0000000..c75c8f3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0303-btrfs-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch b/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch new file mode 100644 index 0000000..7d1ab48 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch @@ -0,0 +1,77 @@ +diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c +--- a/fs/btrfs/tree-checker.c ++++ b/fs/btrfs/tree-checker.c +@@ -2306,7 +2306,7 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key + + if (unlikely(btrfs_item_size(leaf, slot) != 0)) { + generic_err(leaf, slot, +- "invalid item size for free space info, has %u expect 0", ++ "invalid item size for free space extent, has %u expect 0", + btrfs_item_size(leaf, slot)); + return -EUCLEAN; + } +diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c +--- a/fs/btrfs/volumes.c ++++ b/fs/btrfs/volumes.c +@@ -740,6 +740,36 @@ const u8 *btrfs_sb_fsid_ptr(const struct btrfs_super_block *sb) + return has_metadata_uuid ? sb->metadata_uuid : sb->fsid; + } + ++static bool should_rename_device(const struct btrfs_device *dev) ++{ ++ bool ret; ++ const char *old_name; ++ ++ rcu_read_lock(); ++ old_name = rcu_dereference(dev->name); ++ /* ++ * For systems booted without an initramfs, the rootfs has the device ++ * name "/dev/root". ++ * ++ * Although using btrfs without an initramfs is not recommended (if a ++ * new device is added to the rootfs, the system can no longer boot, as ++ * there is no way to register all devices), there is still a minority ++ * of users doing this. ++ * ++ * And after the system is up, a later device scan on the real block ++ * device file will never get this device's name updated, as the ++ * device->devt is still the same. ++ * ++ * Here we add one and only one exception for "/dev/root", to allow the ++ * device name to be updated even if the new path points to the same ++ * block device. ++ */ ++ ret = (strcmp(old_name, "/dev/root") == 0); ++ rcu_read_unlock(); ++ ++ return ret; ++} ++ + /* + * Add new device to list of registered devices + * +@@ -860,7 +890,8 @@ static noinline struct btrfs_device *device_list_add(const char *path, + MAJOR(path_devt), MINOR(path_devt), + current->comm, task_pid_nr(current)); + +- } else if (!device->name || device->devt != path_devt) { ++ } else if (!device->name || device->devt != path_devt || ++ should_rename_device(device)) { + const char *old_name; + + /* +diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c +--- a/fs/btrfs/zoned.c ++++ b/fs/btrfs/zoned.c +@@ -2688,6 +2688,11 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) + + switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) { + case 0: /* single */ ++ case BTRFS_BLOCK_GROUP_RAID0: ++ case BTRFS_BLOCK_GROUP_RAID1: ++ case BTRFS_BLOCK_GROUP_RAID1C3: ++ case BTRFS_BLOCK_GROUP_RAID1C4: ++ case BTRFS_BLOCK_GROUP_RAID10: + ret = (atomic_read(&zinfo->active_zones_left) >= (1 + reserved)); + break; + case BTRFS_BLOCK_GROUP_DUP: diff --git a/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch.sig new file mode 100644 index 0000000..d471f2e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0304-btrfs-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch b/pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch new file mode 100644 index 0000000..38a6427 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -2289,6 +2289,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); + +@@ -2909,6 +2913,11 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, + + /* we could have extended the file */ + if (!(mode & FALLOC_FL_KEEP_SIZE)) { ++ loff_t oldsize = i_size_read(inode); ++ ++ if (offset + length > oldsize) ++ truncate_pagecache_range(inode, oldsize, ++ offset + length - 1); + if (fuse_write_update_attr(inode, offset + length, length)) + file_update_time(file); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch.sig b/pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch.sig new file mode 100644 index 0000000..e4796a2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0310-fuse-eof-zeroing.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch b/pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch new file mode 100644 index 0000000..ca3c02a --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -213,10 +213,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); + } +@@ -233,7 +236,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq, + if (fiq->connected) { + fiq->forget_list_tail->next = forget; + fiq->forget_list_tail = forget; +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } else { + kfree(forget); + spin_unlock(&fiq->lock); +@@ -255,7 +258,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req) + list_del_init(&req->intr_entry); + spin_unlock(&fiq->lock); + } else { +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + } else { + spin_unlock(&fiq->lock); +@@ -285,11 +288,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique); + + static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req) + { ++ bool sync = test_and_clear_bit(FR_SYNC_WAKEUP, &req->flags); ++ + spin_lock(&fiq->lock); + if (fiq->connected) { + fuse_request_assign_unique_locked(fiq, req); + list_add_tail(&req->list, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, sync); + } else { + spin_unlock(&fiq->lock); + req->out.h.error = -ENOTCONN; +@@ -752,6 +757,11 @@ static void __fuse_request_send(struct fuse_req *req) + /* acquire extra reference, since request is still needed after + fuse_request_end() */ + __fuse_get_request(req); ++ /* ++ * This is a synchronous request: the caller will block waiting for ++ * the answer. Hint the scheduler via wake_up_sync(). ++ */ ++ set_bit(FR_SYNC_WAKEUP, &req->flags); + fuse_send_one(fiq, req); + + request_wait_answer(req); +@@ -1806,7 +1816,7 @@ void fuse_chan_resend(struct fuse_chan *fch) + } + /* iq and pq requests are both oldest to newest */ + list_splice(&to_queue, &fiq->pending); +- fuse_dev_wake_and_unlock(fiq); ++ fuse_dev_wake_and_unlock(fiq, false); + } + + /* Look up request on processing list by unique ID */ +diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h +--- a/fs/fuse/fuse_dev_i.h ++++ b/fs/fuse/fuse_dev_i.h +@@ -38,6 +38,8 @@ struct fuse_iqueue; + * @FR_PRIVATE: request is on private list + * @FR_ASYNC: request is asynchronous + * @FR_URING: request is handled through fuse-io-uring ++ * @FR_SYNC_WAKEUP: use synchronous wakeup when queueing this request to ++ * give the scheduler a hint about the waker task + */ + enum fuse_req_flag { + FR_ISREPLY, +@@ -53,6 +55,7 @@ enum fuse_req_flag { + FR_PRIVATE, + FR_ASYNC, + FR_URING, ++ FR_SYNC_WAKEUP, + }; + + /** +diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c +--- a/fs/fuse/inode.c ++++ b/fs/fuse/inode.c +@@ -1420,6 +1420,7 @@ static void process_init_reply(struct fuse_args *args, int error) + + fm->sb->s_bdi->ra_pages = + min(fm->sb->s_bdi->ra_pages, ra_pages); ++ fm->sb->s_bdi->io_pages = fc->max_pages; + fc->minor = arg->minor; + fc->max_write = arg->minor < 5 ? 4096 : arg->max_write; + fc->max_write = max_t(unsigned, 4096, fc->max_write); diff --git a/pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch.sig b/pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch.sig new file mode 100644 index 0000000..9bbc4c6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0311-fuse-perf.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch b/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch new file mode 100644 index 0000000..8aaf4c6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch @@ -0,0 +1,38 @@ +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -1219,8 +1219,7 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + struct file *file = iocb->ki_filp; + struct fuse_file *ff = file->private_data; + struct fuse_mount *fm = ff->fm; +- unsigned int offset, i; +- bool short_write; ++ unsigned int i; + int err; + + for (i = 0; i < ap->num_folios; i++) +@@ -1235,24 +1234,9 @@ static ssize_t fuse_send_write_pages(struct fuse_io_args *ia, + if (!err && ia->write.out.size > count) + err = -EIO; + +- short_write = ia->write.out.size < count; +- offset = ap->descs[0].offset; +- count = ia->write.out.size; + for (i = 0; i < ap->num_folios; i++) { + struct folio *folio = ap->folios[i]; + +- if (err) { +- folio_clear_uptodate(folio); +- } else { +- if (count >= folio_size(folio) - offset) +- count -= folio_size(folio) - offset; +- else { +- if (short_write) +- folio_clear_uptodate(folio); +- count = 0; +- } +- offset = 0; +- } + if (ia->write.folio_locked && (i == ap->num_folios - 1)) + folio_unlock(folio); + folio_put(folio); diff --git a/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch.sig b/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch.sig new file mode 100644 index 0000000..391ede6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0312-fuse-writethrough-uptodate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch b/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch new file mode 100644 index 0000000..27fbcdc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch @@ -0,0 +1,13 @@ +diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c +--- a/fs/fuse/dev.c ++++ b/fs/fuse/dev.c +@@ -406,7 +406,8 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val) + fch->max_background = val; + fch->blocked = fch->num_background >= fch->max_background; + if (!fch->blocked) +- wake_up(&fch->blocked_waitq); ++ wake_up_nr(&fch->blocked_waitq, ++ fch->max_background - fch->num_background); + spin_unlock(&fch->bg_lock); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch.sig b/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch.sig new file mode 100644 index 0000000..cfd1cd7 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0313-fuse-background-wakeup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch b/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch new file mode 100644 index 0000000..eba1599 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch @@ -0,0 +1,119 @@ +diff --git a/fs/internal.h b/fs/internal.h +--- a/fs/internal.h ++++ b/fs/internal.h +@@ -204,6 +204,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, + int flag); + int chown_common(const struct path *path, uid_t user, gid_t group); + extern int vfs_open(const struct path *, struct file *); ++int vfs_open_consume(struct path *, struct file *); + + /* + * inode.c +diff --git a/fs/namei.c b/fs/namei.c +--- a/fs/namei.c ++++ b/fs/namei.c +@@ -4652,6 +4652,7 @@ static const char *open_last_lookups(struct nameidata *nd, + static int do_open(struct nameidata *nd, + struct file *file, const struct open_flags *op) + { ++ struct vfsmount *mnt; + struct mnt_idmap *idmap; + int open_flag = op->open_flag; + bool do_truncate; +@@ -4693,11 +4694,17 @@ static int do_open(struct nameidata *nd, + error = mnt_want_write(nd->path.mnt); + if (error) + return error; ++ /* ++ * A dedicated reference is needed because after the call to ++ * vfs_open_consume() we no longer own the reference in nd->path.mnt ++ * while we need to undo write acess below. ++ */ ++ mnt = mntget(nd->path.mnt); + do_truncate = true; + } + error = may_open(idmap, &nd->path, acc_mode, open_flag); + if (!error && !(file->f_mode & FMODE_OPENED)) +- error = vfs_open(&nd->path, file); ++ error = vfs_open_consume(&nd->path, file); + if (!error) + error = security_file_post_open(file, op->acc_mode); + if (!error && do_truncate) +@@ -4706,8 +4713,10 @@ static int do_open(struct nameidata *nd, + WARN_ON(1); + error = -EINVAL; + } +- if (do_truncate) +- mnt_drop_write(nd->path.mnt); ++ if (do_truncate) { ++ mnt_drop_write(mnt); ++ mntput(mnt); ++ } + return error; + } + +diff --git a/fs/open.c b/fs/open.c +--- a/fs/open.c ++++ b/fs/open.c +@@ -882,6 +882,11 @@ static inline int file_get_write_access(struct file *f) + return error; + } + ++/* ++ * Populate struct file ++ * ++ * NOTE: it assumes f_path is populated and consumes the caller's reference. ++ */ + static int do_dentry_open(struct file *f, + int (*open)(struct inode *, struct file *)) + { +@@ -889,7 +894,6 @@ static int do_dentry_open(struct file *f, + struct inode *inode = f->f_path.dentry->d_inode; + int error; + +- path_get(&f->f_path); + f->f_inode = inode; + f->f_mapping = inode->i_mapping; + f->f_wb_err = filemap_sample_wb_err(f->f_mapping); +@@ -1006,6 +1010,7 @@ int finish_open(struct file *file, struct dentry *dentry, + BUG_ON(file->f_mode & FMODE_OPENED); /* once it's opened, it's opened */ + + file->__f_path.dentry = dentry; ++ path_get(&file->f_path); + return do_dentry_open(file, open); + } + EXPORT_SYMBOL(finish_open); +@@ -1049,6 +1054,7 @@ int vfs_open(const struct path *path, struct file *file) + int ret; + + file->__f_path = *path; ++ path_get(&file->f_path); + ret = do_dentry_open(file, NULL); + if (!ret) { + /* +@@ -1061,6 +1067,25 @@ int vfs_open(const struct path *path, struct file *file) + return ret; + } + ++/** ++ * vfs_open_consume - open the file at the given path and consume the reference ++ * @path: path to open ++ * @file: newly allocated file with f_flag initialized ++ */ ++int vfs_open_consume(struct path *path, struct file *file) ++{ ++ int ret; ++ ++ file->__f_path = *path; ++ path->mnt = NULL; ++ path->dentry = NULL; ++ ret = do_dentry_open(file, NULL); ++ if (!ret) { ++ fsnotify_open(file); ++ } ++ return ret; ++} ++ + struct file *dentry_open(const struct path *path, int flags, + const struct cred *cred) + { diff --git a/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch.sig b/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch.sig new file mode 100644 index 0000000..c0764ef Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0320-vfs-open-consume-path-reference.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch b/pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch new file mode 100644 index 0000000..9a8f97f --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig b/pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig new file mode 100644 index 0000000..f46a1a4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch b/pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch new file mode 100644 index 0000000..278fc0c --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0360-gpu-mem-cgroup.patch.sig b/pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch.sig new file mode 100644 index 0000000..ddced6d Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0360-gpu-mem-cgroup.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch b/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch new file mode 100644 index 0000000..32f3010 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_alpm.c b/drivers/gpu/drm/i915/display/intel_alpm.c +index c6963ea420cc..c38d613f15d6 100644 +--- a/drivers/gpu/drm/i915/display/intel_alpm.c ++++ b/drivers/gpu/drm/i915/display/intel_alpm.c +@@ -399,7 +399,7 @@ static void lnl_alpm_configure(struct intel_dp *intel_dp, + ALPM_CTL_AUX_LESS_SLEEP_HOLD_TIME_50_SYMBOLS | + ALPM_CTL_AUX_LESS_WAKE_TIME(crtc_state->alpm_state.aux_less_wake_lines); + +- if (intel_dp->as_sdp_supported) { ++ if (intel_dp->as_sdp_supported && crtc_state->has_panel_replay) { + u32 pr_alpm_ctl = get_pr_alpm_as_sdp_transmission_time(crtc_state); + + if (crtc_state->link_off_after_as_sdp_when_pr_active) diff --git a/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig b/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig new file mode 100644 index 0000000..b591f47 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch b/pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch new file mode 100644 index 0000000..3e88cea --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0401-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-muqss/0401-psr2-early-transport-panels.patch.sig b/pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch.sig new file mode 100644 index 0000000..1b2c039 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0401-psr2-early-transport-panels.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..27e9b7d --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch @@ -0,0 +1,172 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -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 +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2481,6 +2481,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); + } + + /** +@@ -2512,6 +2513,7 @@ void intel_psr_pause(struct intel_dp *intel_dp) + + cancel_work_sync(&psr->work); + cancel_delayed_work_sync(&psr->dc3co_work); ++ cancel_delayed_work_sync(&psr->panel_replay_reenable_work); + } + + /** +@@ -3545,6 +3547,17 @@ static void intel_psr_handle_irq(struct intel_dp *intel_dp) + drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + } + ++#define PANEL_REPLAY_REENABLE_DELAY_MS 50 ++ ++static bool panel_replay_exit_on_flush_enabled(struct intel_dp *intel_dp) ++{ ++ return intel_dp_is_edp(intel_dp) && ++ intel_dp->psr.panel_replay_enabled && ++ intel_dp->psr.sel_update_enabled && ++ intel_has_dpcd_quirk(intel_dp, ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); ++} ++ + static void intel_psr_work(struct work_struct *work) + { + struct intel_dp *intel_dp = +@@ -3563,6 +3576,11 @@ static void intel_psr_work(struct work_struct *work) + if (intel_dp->psr.pause_counter) + goto unlock; + ++ /* The dedicated delayed work owns re-entry while the quirk is armed. */ ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * We have to make sure PSR is ready for re-enable + * otherwise it keeps disabled until next full enable/disable cycle. +@@ -3572,6 +3590,10 @@ static void intel_psr_work(struct work_struct *work) + if (!__psr_wait_for_idle_locked(intel_dp)) + goto unlock; + ++ if (panel_replay_exit_on_flush_enabled(intel_dp) && ++ delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work)) ++ goto unlock; ++ + /* + * The delayed work can race with an invalidate hence we need to + * recheck. Since psr_flush first clears this and then reschedules we +@@ -3585,6 +3607,33 @@ static void intel_psr_work(struct work_struct *work) + mutex_unlock(&intel_dp->psr.lock); + } + ++static void panel_replay_reenable_work(struct work_struct *work) ++{ ++ struct intel_dp *intel_dp = ++ container_of(work, typeof(*intel_dp), ++ psr.panel_replay_reenable_work.work); ++ ++ mutex_lock(&intel_dp->psr.lock); ++ ++ if (!intel_dp->psr.enabled || ++ !panel_replay_exit_on_flush_enabled(intel_dp) || ++ intel_dp->psr.pause_counter || ++ READ_ONCE(intel_dp->psr.irq_aux_error)) ++ goto unlock; ++ ++ if (!__psr_wait_for_idle_locked(intel_dp)) ++ goto unlock; ++ ++ /* Recheck for activity or a newer deadline after the unlocked wait. */ ++ if (delayed_work_pending(&intel_dp->psr.panel_replay_reenable_work) || ++ intel_dp->psr.busy_frontbuffer_bits || intel_dp->psr.active) ++ goto unlock; ++ ++ intel_psr_activate(intel_dp); ++unlock: ++ mutex_unlock(&intel_dp->psr.lock); ++} ++ + static void intel_psr_configure_full_frame_update(struct intel_dp *intel_dp) + { + struct intel_display *display = to_intel_display(intel_dp); +@@ -3656,8 +3705,11 @@ void intel_psr_invalidate(struct intel_display *display, + INTEL_FRONTBUFFER_ALL_MASK(intel_dp->psr.pipe); + intel_dp->psr.busy_frontbuffer_bits |= pipe_frontbuffer_bits; + +- if (pipe_frontbuffer_bits) ++ if (pipe_frontbuffer_bits) { ++ if (panel_replay_exit_on_flush_enabled(intel_dp)) ++ cancel_delayed_work(&intel_dp->psr.panel_replay_reenable_work); + _psr_invalidate_handle(intel_dp); ++ } + + mutex_unlock(&intel_dp->psr.lock); + } +@@ -3773,6 +3825,15 @@ void intel_psr_flush(struct intel_display *display, + if (intel_dp->psr.pause_counter) + goto unlock; + ++ if (pipe_frontbuffer_bits && ++ panel_replay_exit_on_flush_enabled(intel_dp)) { ++ intel_psr_exit(intel_dp); ++ mod_delayed_work(display->wq.unordered, ++ &intel_dp->psr.panel_replay_reenable_work, ++ msecs_to_jiffies(PANEL_REPLAY_REENABLE_DELAY_MS)); ++ goto unlock; ++ } ++ + if (origin == ORIGIN_FLIP || + (origin == ORIGIN_CURSOR_UPDATE && + !intel_dp->psr.psr2_sel_fetch_enabled)) { +@@ -3836,6 +3897,8 @@ void intel_psr_init(struct intel_dp *intel_dp) + + INIT_WORK(&intel_dp->psr.work, intel_psr_work); + INIT_DELAYED_WORK(&intel_dp->psr.dc3co_work, tgl_dc3co_disable_work); ++ INIT_DELAYED_WORK(&intel_dp->psr.panel_replay_reenable_work, ++ panel_replay_reenable_work); + mutex_init(&intel_dp->psr.lock); + } + +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -94,6 +94,14 @@ static void quirk_disable_edp_panel_replay(struct intel_dp *intel_dp) + drm_info(display->drm, "Applying disable Panel Replay quirk\n"); + } + ++static void quirk_panel_replay_exit_on_flush(struct intel_dp *intel_dp) ++{ ++ struct intel_display *display = to_intel_display(intel_dp); ++ ++ intel_set_dpcd_quirk(intel_dp, QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH); ++ drm_info(display->drm, "Applying Panel Replay exit on flush quirk\n"); ++} ++ + static void quirk_disable_psr2(struct intel_display *display) + { + intel_set_quirk(display, QUIRK_DISABLE_PSR2); +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.h b/drivers/gpu/drm/i915/display/intel_quirks.h +--- a/drivers/gpu/drm/i915/display/intel_quirks.h ++++ b/drivers/gpu/drm/i915/display/intel_quirks.h +@@ -23,6 +23,7 @@ enum intel_quirk_id { + QUIRK_EDP_LIMIT_RATE_HBR2, + QUIRK_DISABLE_EDP_PANEL_REPLAY, + QUIRK_DISABLE_PSR2, ++ QUIRK_PANEL_REPLAY_EXIT_ON_FLUSH, + }; + + void intel_init_quirks(struct intel_display *display); diff --git a/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..196c3ee Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0402-drm-i915-quirk-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..24c203e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -284,7 +284,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0db9, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + /* Dell XPS 16 DA16260 */ + { diff --git a/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..0393b90 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..8eb9b65 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -292,7 +292,7 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .subsystem_vendor = 0x1028, + .subsystem_device = 0x0dba, + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), +- .hook = quirk_disable_edp_panel_replay, ++ .hook = quirk_panel_replay_exit_on_flush, + }, + }; + diff --git a/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..095745c Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch b/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch new file mode 100644 index 0000000..1da7f71 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -267,6 +267,9 @@ static struct intel_quirk intel_quirks[] = { + + /* Xiaomi Book Pro 14 2026 */ + { 0xb081, 0x1d72, 0x2424, quirk_disable_psr2 }, ++ ++ /* Dell XPS 13 DX13260 */ ++ { DEVICE_ID_ANY, 0x1028, 0x0e54, quirk_disable_psr2 }, + }; + + static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { +@@ -294,6 +297,14 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_panel_replay_exit_on_flush, + }, ++ /* Dell XPS 13 DX13260 */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e54, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .hook = quirk_disable_edp_panel_replay, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig b/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig new file mode 100644 index 0000000..16a1fb9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch b/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch new file mode 100644 index 0000000..94d0d55 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch @@ -0,0 +1,19 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c +--- a/drivers/gpu/drm/i915/display/intel_quirks.c ++++ b/drivers/gpu/drm/i915/display/intel_quirks.c +@@ -305,6 +305,15 @@ static const struct intel_dpcd_quirk intel_dpcd_quirks[] = { + .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), + .hook = quirk_disable_edp_panel_replay, + }, ++ /* Dell XPS 13 DX13260 (Wildcat Lake), sink "Bamboo" */ ++ { ++ .device = DEVICE_ID_ANY, ++ .subsystem_vendor = 0x1028, ++ .subsystem_device = 0x0e53, ++ .sink_oui = SINK_OUI(0x00, 0x22, 0xb9), ++ .sink_device_id = SINK_DEVICE_ID(0x42, 0x61, 0x6d, 0x62, 0x6f, 0x6f), ++ .hook = quirk_panel_replay_exit_on_flush, ++ }, + }; + + void intel_init_quirks(struct intel_display *display) diff --git a/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig new file mode 100644 index 0000000..36a38a4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch b/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch new file mode 100644 index 0000000..c1f3ad5 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch @@ -0,0 +1,43 @@ +diff --git a/drivers/gpu/drm/xe/display/xe_display_bo.c b/drivers/gpu/drm/xe/display/xe_display_bo.c +--- a/drivers/gpu/drm/xe/display/xe_display_bo.c ++++ b/drivers/gpu/drm/xe/display/xe_display_bo.c +@@ -147,33 +147,12 @@ static struct drm_gem_object *xe_display_bo_fbdev_create(struct drm_device *drm, + struct xe_device *xe = to_xe_device(drm); + struct xe_bo *obj; + +- obj = ERR_PTR(-ENODEV); +- +- if (xe_display_bo_fbdev_prefer_stolen(xe, size)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), +- size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_STOLEN | +- XE_BO_FLAG_GGTT, +- false); +- if (!IS_ERR(obj)) +- drm_info(&xe->drm, "Allocated fbdev into stolen\n"); +- else +- drm_info(&xe->drm, "Allocated fbdev into stolen failed: %li\n", PTR_ERR(obj)); +- } else { +- drm_info(&xe->drm, "Allocating fbdev: Stolen memory not preferred.\n"); +- } +- +- if (IS_ERR(obj)) { +- obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, +- ttm_bo_type_kernel, +- XE_BO_FLAG_FORCE_WC | +- XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | +- XE_BO_FLAG_GGTT, +- false); +- } +- ++ obj = xe_bo_create_pin_map_novm(xe, xe_device_get_root_tile(xe), size, ++ ttm_bo_type_kernel, ++ XE_BO_FLAG_FORCE_WC | ++ XE_BO_FLAG_VRAM_IF_DGFX(xe_device_get_root_tile(xe)) | ++ XE_BO_FLAG_GGTT, ++ false); + if (IS_ERR(obj)) { + drm_err(&xe->drm, "failed to allocate framebuffer (%pe)\n", obj); + return ERR_PTR(-ENOMEM); diff --git a/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch.sig b/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch.sig new file mode 100644 index 0000000..5bc668b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0411-drm-xe-display-no-stolen-framebuffers.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch b/pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch new file mode 100644 index 0000000..21017c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0420-safe-window.patch.sig b/pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch.sig new file mode 100644 index 0000000..4564638 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0420-safe-window.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch b/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch new file mode 100644 index 0000000..b64db93 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch @@ -0,0 +1,112 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_fbc.c b/drivers/gpu/drm/i915/display/intel_fbc.c +--- a/drivers/gpu/drm/i915/display/intel_fbc.c ++++ b/drivers/gpu/drm/i915/display/intel_fbc.c +@@ -59,6 +59,7 @@ + #include "intel_fbc_regs.h" + #include "intel_frontbuffer.h" + #include "intel_parent.h" ++#include "skl_universal_plane.h" + + #define for_each_fbc_id(__display, __fbc_id) \ + for ((__fbc_id) = INTEL_FBC_A; (__fbc_id) < I915_MAX_FBCS; (__fbc_id)++) \ +@@ -1305,6 +1306,9 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + struct intel_display *display = to_intel_display(plane_state); + unsigned int effective_w, effective_h, max_w, max_h; + ++ if (DISPLAY_VER(display) >= 20) ++ return true; ++ + intel_fbc_max_surface_size(display, &max_w, &max_h); + + effective_w = plane_state->view.color_plane[0].x + +@@ -1315,10 +1319,18 @@ static bool intel_fbc_surface_size_ok(const struct intel_plane_state *plane_stat + return effective_w <= max_w && effective_h <= max_h; + } + +-static void intel_fbc_max_plane_size(struct intel_display *display, ++static void intel_fbc_max_plane_size(const struct intel_plane_state *plane_state, + unsigned int *w, unsigned int *h) + { +- if (DISPLAY_VER(display) >= 10) { ++ struct intel_display *display = to_intel_display(plane_state); ++ struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); ++ const struct drm_framebuffer *fb = plane_state->hw.fb; ++ unsigned int rotation = plane_state->hw.rotation; ++ ++ if (DISPLAY_VER(display) >= 20) { ++ *w = intel_plane_max_width(plane, fb, 0, rotation); ++ *h = 4096; ++ } else if (DISPLAY_VER(display) >= 10) { + *w = 5120; + *h = 4096; + } else if (DISPLAY_VER(display) >= 8 || display->platform.haswell) { +@@ -1335,10 +1347,9 @@ static void intel_fbc_max_plane_size(struct intel_display *display, + + static bool intel_fbc_plane_size_valid(const struct intel_plane_state *plane_state) + { +- struct intel_display *display = to_intel_display(plane_state); + unsigned int w, h, max_w, max_h; + +- intel_fbc_max_plane_size(display, &max_w, &max_h); ++ intel_fbc_max_plane_size(plane_state, &max_w, &max_h); + + w = drm_rect_width(&plane_state->uapi.src) >> 16; + h = drm_rect_height(&plane_state->uapi.src) >> 16; +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -1932,10 +1932,10 @@ static int intel_plane_min_height(struct intel_plane *plane, + return 1; + } + +-static int intel_plane_max_width(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_width) + return plane->max_width(fb, color_plane, rotation); +@@ -1943,10 +1943,10 @@ static int intel_plane_max_width(struct intel_plane *plane, + return INT_MAX; + } + +-static int intel_plane_max_height(struct intel_plane *plane, +- const struct drm_framebuffer *fb, +- int color_plane, +- unsigned int rotation) ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation) + { + if (plane->max_height) + return plane->max_height(fb, color_plane, rotation); +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.h b/drivers/gpu/drm/i915/display/skl_universal_plane.h +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.h ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.h +@@ -8,6 +8,7 @@ + + #include + ++struct drm_framebuffer; + struct intel_crtc; + struct intel_display; + struct intel_initial_plane_config; +@@ -42,5 +43,13 @@ bool icl_is_hdr_plane(struct intel_display *display, enum plane_id plane_id); + + u32 skl_plane_aux_dist(const struct intel_plane_state *plane_state, + int color_plane); ++int intel_plane_max_width(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); ++int intel_plane_max_height(struct intel_plane *plane, ++ const struct drm_framebuffer *fb, ++ int color_plane, ++ unsigned int rotation); + + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch.sig b/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch.sig new file mode 100644 index 0000000..41dee2a Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0430-fbc.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch b/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch new file mode 100644 index 0000000..a13b165 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch @@ -0,0 +1,67 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c +--- a/drivers/gpu/drm/i915/display/intel_bw.c ++++ b/drivers/gpu/drm/i915/display/intel_bw.c +@@ -52,6 +52,8 @@ + + #define DEPROGBWPCLIMIT 60 + ++#define PEAK_BW_THRESHOLD 20000 ++ + struct intel_psf_gv_point { + u8 clk; /* clock in multiples of 16.6666 MHz */ + }; +@@ -587,6 +589,33 @@ + return num_channels * (channel_width / 8) * dclk; + } + ++static void xe3_add_peakbw_threshold(struct intel_display *display) ++{ ++ u8 qgv_points = display->bw.max[0].num_qgv_points; ++ ++ if (!HAS_PEAK_BW_THRESHOLD(display)) ++ return; ++ ++ if (qgv_points >= I915_NUM_QGV_POINTS) { ++ drm_dbg_kms(display->drm, "QGV points maxed out; skipping peak bandwidth threshold.\n"); ++ return; ++ } ++ ++ if (qgv_points <= 1) ++ return; ++ ++ for (int i = 0; i < ARRAY_SIZE(display->bw.max); i++) { ++ struct intel_bw_info *bi = &display->bw.max[i]; ++ ++ bi->num_qgv_points++; ++ bi->peakbw[qgv_points] = PEAK_BW_THRESHOLD; ++ bi->deratedbw[qgv_points] = PEAK_BW_THRESHOLD; ++ } ++ ++ drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshod of %d\n", ++ qgv_points, PEAK_BW_THRESHOLD); ++} ++ + static int tgl_get_bw_info(struct intel_display *display, + const struct dram_info *dram_info, + const struct intel_soc_bw_params *soc_bw_params, +@@ -684,6 +713,9 @@ + } + } + ++ /* For xe3 cases add an extra qgv point for Peak bw threshold */ ++ xe3_add_peakbw_threshold(display); ++ + /* + * In case if SAGV is disabled in BIOS, we always get 1 + * SAGV point, but we can't send PCode commands to restrict it +diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h +--- a/drivers/gpu/drm/i915/display/intel_display_device.h ++++ b/drivers/gpu/drm/i915/display/intel_display_device.h +@@ -191,6 +191,7 @@ + #define HAS_MBUS_JOINING(__display) ((__display)->platform.alderlake_p || DISPLAY_VER(__display) >= 14) + #define HAS_MSO(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_OVERLAY(__display) (DISPLAY_INFO(__display)->has_overlay) ++#define HAS_PEAK_BW_THRESHOLD(__display) (DISPLAY_VER(__display) >= 30) + #define HAS_PIPEDMC(__display) (DISPLAY_VER(__display) >= 12) + #define HAS_PIXEL_NORMALIZER(__display) (DISPLAY_VER(__display) >= 35) + #define HAS_PSR(__display) (DISPLAY_INFO(__display)->has_psr) diff --git a/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch.sig b/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch.sig new file mode 100644 index 0000000..711523e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0440-xe3-peak-bandwidth.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch b/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch new file mode 100644 index 0000000..e2e1fac --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch @@ -0,0 +1,95 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c +--- a/drivers/gpu/drm/i915/display/intel_cursor.c ++++ b/drivers/gpu/drm/i915/display/intel_cursor.c +@@ -536,7 +536,8 @@ static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); +@@ -569,8 +570,10 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch) { ++ i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); + return; ++ } + + if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) { + if (crtc_state->enable_psr2_su_region_et) { +diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h +--- a/drivers/gpu/drm/i915/display/intel_display_types.h ++++ b/drivers/gpu/drm/i915/display/intel_display_types.h +@@ -1177,6 +1177,8 @@ struct intel_crtc_state { + bool has_sel_update; + bool enable_psr2_sel_fetch; + bool enable_psr2_su_region_et; ++ /* Drop the stale selective fetch enable bits as selective fetch is turned off */ ++ bool clear_psr2_sel_fetch; + bool req_psr2_sdp_prior_scanline; + bool has_panel_replay; + bool link_off_after_as_sdp_when_pr_active; +diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c +--- a/drivers/gpu/drm/i915/display/intel_psr.c ++++ b/drivers/gpu/drm/i915/display/intel_psr.c +@@ -2941,6 +2941,8 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) + { + struct intel_display *display = to_intel_display(state); ++ const struct intel_crtc_state *old_crtc_state = ++ intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + struct intel_plane_state *new_plane_state, *old_plane_state; + struct intel_plane *plane; +@@ -2953,6 +2955,19 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, + bool full_update = false, su_area_changed; + int i, ret; + ++ /* ++ * Selective fetch is not always usable, for instance it is dropped ++ * while pipe CRC is active. The planes keep their selective fetch ++ * enable bit set in hardware over that, and a plane disabled while ++ * selective fetch is off never gets the bit cleared. Once selective ++ * fetch comes back the hardware would resume fetching for a plane that ++ * is no longer enabled and keep its DDB range reserved, so have the ++ * plane update drop the bit for every plane of the pipe as selective ++ * fetch is turned off. ++ */ ++ crtc_state->clear_psr2_sel_fetch = old_crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->enable_psr2_sel_fetch; ++ + if (!crtc_state->enable_psr2_sel_fetch) + return 0; + +diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c +--- a/drivers/gpu/drm/i915/display/skl_universal_plane.c ++++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c +@@ -885,7 +885,8 @@ static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) ++ if (!crtc_state->enable_psr2_sel_fetch && ++ !crtc_state->clear_psr2_sel_fetch) + return; + + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); +@@ -1634,10 +1635,8 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + +- if (!crtc_state->enable_psr2_sel_fetch) +- return; +- +- if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) ++ if (crtc_state->enable_psr2_sel_fetch && ++ drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) + intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), + SEL_FETCH_PLANE_CTL_ENABLE); + else diff --git a/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig b/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig new file mode 100644 index 0000000..fa39ab5 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch b/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch new file mode 100644 index 0000000..820d377 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dsb.c b/drivers/gpu/drm/i915/display/intel_dsb.c +--- a/drivers/gpu/drm/i915/display/intel_dsb.c ++++ b/drivers/gpu/drm/i915/display/intel_dsb.c +@@ -902,9 +902,17 @@ void intel_dsb_wait_for_delayed_vblank(struct intel_atomic_state *state, + * the hardware itself guarantees that we're SCL lines + * away from the delayed vblank, and we won't be inside + * the vmin safe window so this extra wait does nothing. ++ * ++ * Experimentally, DSB may observe a slightly stale ++ * PIPEDSL value. When the actual scanline has just reached ++ * safe_window_start, WAIT_DSL_OUT may complete immediately ++ * due to the stale value. ++ * ++ * Shift the start back by one scanline to ensure the wait ++ * window is entered reliably. + */ + intel_dsb_wait_scanline_out(state, dsb, +- intel_vrr_safe_window_start(crtc_state), ++ intel_vrr_safe_window_start(crtc_state) - 1, + intel_vrr_vmin_safe_window_end(crtc_state)); + /* + * When the push is sent during vblank it will trigger diff --git a/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig b/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig new file mode 100644 index 0000000..cd6b14b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch b/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch new file mode 100644 index 0000000..9efda11 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_display_params.c b/drivers/gpu/drm/i915/display/intel_display_params.c +--- a/drivers/gpu/drm/i915/display/intel_display_params.c ++++ b/drivers/gpu/drm/i915/display/intel_display_params.c +@@ -120,6 +120,10 @@ intel_display_param_named_unsafe(enable_psr, int, 0400, + "(0=disabled, 1=enable up to PSR1, 2=enable up to PSR2) " + "Default: -1 (use per-chip default)"); + ++intel_display_param_named_unsafe(enable_dc_balance, bool, 0400, ++ "Enable VRR DC balance (0=disabled, 1=enabled). " ++ "Default: 0 (disabled)"); ++ + intel_display_param_named_unsafe(enable_panel_replay, int, 0400, + "Enable Panel Replay (0=disabled, 1=enabled). Default: -1 (use per-chip default)"); + +diff --git a/drivers/gpu/drm/i915/display/intel_display_params.h b/drivers/gpu/drm/i915/display/intel_display_params.h +--- a/drivers/gpu/drm/i915/display/intel_display_params.h ++++ b/drivers/gpu/drm/i915/display/intel_display_params.h +@@ -46,6 +46,7 @@ struct drm_printer; + param(bool, enable_dp_mst, true, 0600) \ + param(int, enable_fbc, -1, 0600) \ + param(int, enable_psr, -1, 0600) \ ++ param(bool, enable_dc_balance, false, 0600) \ + param(int, enable_panel_replay, -1, 0600) \ + param(bool, psr_safest_params, false, 0400) \ + param(bool, enable_psr2_sel_fetch, true, 0400) \ +diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c +--- a/drivers/gpu/drm/i915/display/intel_vrr.c ++++ b/drivers/gpu/drm/i915/display/intel_vrr.c +@@ -383,10 +383,12 @@ static bool intel_vrr_dc_balance_possible(const struct intel_crtc_state *crtc_st + static void + intel_vrr_dc_balance_compute_config(struct intel_crtc_state *crtc_state) + { ++ struct intel_display *display = to_intel_display(crtc_state); + int guardband_usec, adjustment_usec; + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + +- if (!intel_vrr_dc_balance_possible(crtc_state) || !crtc_state->vrr.enable) ++ if (!intel_vrr_dc_balance_possible(crtc_state) || ++ !crtc_state->vrr.enable || !display->params.enable_dc_balance) + return; + + crtc_state->vrr.dc_balance.vmax = crtc_state->vrr.vmax; diff --git a/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig b/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig new file mode 100644 index 0000000..c8a67fa Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0444-drm-i915-vrr-dc-balance-off-by-default.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch b/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch new file mode 100644 index 0000000..02991bc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch @@ -0,0 +1,456 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -104,6 +104,7 @@ + #include "ivsrcid/dcn/irqsrcs_dcn_1_0.h" + + #include "modules/inc/mod_freesync.h" ++#include "modules/inc/mod_info_packet.h" + #include "modules/inc/mod_power.h" + #include "modules/power/power_helpers.h" + +@@ -10088,6 +10089,9 @@ static void update_freesync_state_on_stream( + &vrr_infopacket, + pack_sdp_v1_3); + ++ if (new_stream->sink->sink_signal == SIGNAL_TYPE_HDMI_FRL) ++ mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); ++ + new_crtc_state->freesync_vrr_info_changed |= + (memcmp(&new_crtc_state->vrr_infopacket, + &vrr_infopacket, +@@ -10099,6 +10103,36 @@ static void update_freesync_state_on_stream( + new_stream->vrr_infopacket = vrr_infopacket; + new_stream->allow_freesync = mod_freesync_get_freesync_enabled(&vrr_params); + ++ /* ++ * HDMI ALLM: when Gaming-VRR is active (VRR_EN=1) and the sink ++ * advertises ALLM in the SCDS, the Source shall transmit the HF-VSIF ++ * with ALLM_Mode=1 (HDMI 2.1 Section 7.6.6). ++ */ ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { ++ struct dc_info_packet hf_vsif = {0}; ++ bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; ++ bool allm = sink_allm && ++ (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr_params.state == VRR_STATE_ACTIVE_FIXED); ++ bool allm_changed; ++ ++ if (allm) ++ mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ ++ allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, ++ sizeof(hf_vsif)) != 0; ++ new_crtc_state->freesync_vrr_info_changed |= allm_changed; ++ new_stream->hfvsif_infopacket = hf_vsif; ++ ++ if (allm_changed) ++ drm_dbg_driver(adev_to_drm(adev), ++ "ALLM: flip on crtc=%u: sink_allm=%d vrr_state=%d -> ALLM_Mode=%d\n", ++ new_crtc_state->base.crtc->base.id, ++ sink_allm, ++ vrr_params.state, allm); ++ } ++ + if (new_crtc_state->freesync_vrr_info_changed) + drm_dbg_kms(adev_to_drm(adev), "VRR packet update: crtc=%u enabled=%d state=%d", + new_crtc_state->base.crtc->base.id, +@@ -10665,9 +10699,12 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + } + + if (acrtc_state->stream) { +- if (acrtc_state->freesync_vrr_info_changed) ++ if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; ++ bundle->stream_update.hfvsif_infopacket = ++ &acrtc_state->stream->hfvsif_infopacket; ++ } + } + } + +@@ -12069,6 +12106,14 @@ static void get_freesync_config_for_crtc( + } + out: + new_crtc_state->freesync_config = config; ++ ++ drm_dbg_driver(new_con_state->base.connector->dev, ++ "VRR: cfg vrr_enabled=%d vrr_supported=%d fs_capable=%d vrefresh=%d min=%d max=%d state=%d\n", ++ new_crtc_state->base.vrr_enabled, ++ new_crtc_state->vrr_supported, ++ new_con_state->freesync_capable, vrefresh, ++ aconnector->min_vfreq, aconnector->max_vfreq, ++ config.state); + } + + static void reset_freesync_config_for_crtc( +@@ -14031,6 +14076,15 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (!adev->dm.freesync_module || !dc_supports_vrr(sink->ctx->dce_version)) + goto update; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: enter signal=%d hdmi_vrr=%d mrange[%d-%d] hdmi.vrr_cap[sup=%d min=%d max=%d]\n", ++ sink->sink_signal, connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.monitor_range.min_vfreq, ++ connector->display_info.monitor_range.max_vfreq, ++ connector->display_info.hdmi.vrr_cap.supported, ++ connector->display_info.hdmi.vrr_cap.vrr_min, ++ connector->display_info.hdmi.vrr_cap.vrr_max); ++ + edid = drm_edid_raw(drm_edid); // FIXME: Get rid of drm_edid_raw() + + /* Some eDP panels only have the refresh rate range info in DisplayID */ +@@ -14056,7 +14110,9 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + amdgpu_dm_connector->as_type = ADAPTIVE_SYNC_TYPE_EDP; + } + +- } else if (drm_edid && sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) { ++ } else if (drm_edid && ++ (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ sink->sink_signal == SIGNAL_TYPE_HDMI_FRL)) { + i = parse_hdmi_amd_vsdb(amdgpu_dm_connector, edid, &vsdb_info); + if (i >= 0) { + amdgpu_dm_connector->vsdb_info = vsdb_info; +@@ -14072,6 +14128,59 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + connector->display_info.monitor_range.max_vfreq = vsdb_info.max_refresh_rate_hz; + } + } ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: amd_vsdb i=%d fs_sup=%d min=%d max=%d fs_capable=%d\n", ++ i, vsdb_info.freesync_supported, ++ vsdb_info.min_refresh_rate_hz, ++ vsdb_info.max_refresh_rate_hz, freesync_capable); ++ ++ /* ++ * If AMD VSDB didn't provide a valid FreeSync range, fall back to ++ * the HDMI 2.1 VRR capability parsed from the HF-VSDB. ++ */ ++ if (!freesync_capable && connector->display_info.hdmi.vrr_cap.supported) { ++ struct drm_hdmi_vrr_cap *vrr_cap = ++ &connector->display_info.hdmi.vrr_cap; ++ ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: HF-VSDB fallback: hdmi_vrr=1 vrr_cap[sup=%d min=%d max=%d] mrange_max=%d\n", ++ vrr_cap->supported, vrr_cap->vrr_min, vrr_cap->vrr_max, ++ connector->display_info.monitor_range.max_vfreq); ++ ++ if (vrr_cap->supported && vrr_cap->vrr_min > 0) { ++ amdgpu_dm_connector->min_vfreq = vrr_cap->vrr_min; ++ amdgpu_dm_connector->max_vfreq = vrr_cap->vrr_max ? ++ vrr_cap->vrr_max : ++ connector->display_info.monitor_range.max_vfreq; ++ ++ /* ++ * VRRMAX = 0 in the HF-VSDB means "up to the Base ++ * Refresh Rate". If the EDID also did not provide a ++ * monitor range max, fall back to the Base Refresh ++ * Rate (the highest refresh rate of the preferred ++ * timing) so a valid VRR range is still reported to ++ * userspace. ++ */ ++ if (!amdgpu_dm_connector->max_vfreq) { ++ struct drm_display_mode *brr_mode = ++ get_highest_refresh_rate_mode(amdgpu_dm_connector, true); ++ ++ if (brr_mode) ++ amdgpu_dm_connector->max_vfreq = ++ drm_mode_vrefresh(brr_mode); ++ } ++ ++ if (amdgpu_dm_connector->max_vfreq - ++ amdgpu_dm_connector->min_vfreq > 10) ++ freesync_capable = true; ++ ++ connector->display_info.monitor_range.min_vfreq = ++ amdgpu_dm_connector->min_vfreq; ++ connector->display_info.monitor_range.max_vfreq = ++ amdgpu_dm_connector->max_vfreq; ++ } ++ } + } + + if (amdgpu_dm_connector->dc_link) +@@ -14113,6 +14222,11 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + if (dm_con_state) + dm_con_state->freesync_capable = freesync_capable; + ++ drm_dbg_driver(adev_to_drm(adev), ++ "VRR: caps result: freesync_capable=%d min_vfreq=%d max_vfreq=%d\n", ++ freesync_capable, amdgpu_dm_connector->min_vfreq, ++ amdgpu_dm_connector->max_vfreq); ++ + if (connector->state && amdgpu_dm_connector->dc_link && !freesync_capable && + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported) { + amdgpu_dm_connector->dc_link->replay_settings.config.replay_supported = false; +diff --git a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +--- a/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h ++++ b/drivers/gpu/drm/amd/display/modules/inc/mod_info_packet.h +@@ -67,6 +67,10 @@ struct AS_Df_params { + struct frame_duration_op decrease; + }; + ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket); ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, const struct AS_Df_params *param, + struct dc_info_packet *info_packet); +diff --git a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +--- a/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c ++++ b/drivers/gpu/drm/amd/display/modules/info_packet/info_packet.c +@@ -291,6 +291,21 @@ void set_vsc_packet_colorimetry_data( + info_packet->sb[18] = 0; + } + ++static void set_field_with_mask(unsigned char *dest, unsigned int mask, unsigned int value) ++{ ++ unsigned int shift = 0; ++ ++ if (!mask || !dest) ++ return; ++ ++ while (!((mask >> shift) & 1)) ++ shift++; ++ ++ *dest = *dest & ~mask; ++ value = value & (mask >> shift); ++ *dest = *dest | (value << shift); ++} ++ + void mod_build_vsc_infopacket(const struct dc_stream_state *stream, + struct dc_info_packet *info_packet, + enum dc_color_space cs, +@@ -644,6 +659,100 @@ void mod_build_hf_vsif_infopacket(const struct dc_stream_state *stream, + info_packet->valid = true; + } + ++static void build_vtem_infopacket_data(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ unsigned int field_rate_in_hz; ++ ++ /* FVA Factor setting */ ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__FVA_FACTOR_M1, ++ (fva_factor > 0) ? (fva_factor - 1) : 0); ++ /* VRR Parameters */ ++ if (vrr->state == VRR_STATE_ACTIVE_VARIABLE || ++ vrr->state == VRR_STATE_ACTIVE_FIXED) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 1); ++ } else { ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__VRR_EN, 0); ++ } ++ ++ if (vrr->state == VRR_STATE_ACTIVE_FIXED) ++ set_field_with_mask(&infopacket->sb[VTEM_MD0], MASK_VTEM_MD0__M_CONST, vrr->m_const); ++ ++ if (!stream->timing.vic) { ++ set_field_with_mask(&infopacket->sb[VTEM_MD1], MASK_VTEM_MD1__BASE_VFRONT, ++ stream->timing.v_front_porch); ++ ++ ++ /* TODO: In dal2, we check mode flags for a reduced blanking timing. ++ * Need a way to relay that information to this function. ++ * if("ReducedBlanking") ++ * { ++ * set_field_with_mask(&infopacket->sb[VRR_VTEM_MD2], MASK__VRR_VTEM_MD2__RB, 1; ++ * } ++ */ ++ ++ field_rate_in_hz = stream->timing.pix_clk_100hz * 100; ++ field_rate_in_hz /= stream->timing.h_total; ++ field_rate_in_hz = (field_rate_in_hz + stream->timing.v_total / 2) ++ / stream->timing.v_total; ++ ++ set_field_with_mask(&infopacket->sb[VTEM_MD2], MASK_VTEM_MD2__BASE_REFRESH_RATE_98, ++ field_rate_in_hz >> 8); ++ set_field_with_mask(&infopacket->sb[VTEM_MD3], MASK_VTEM_MD3__BASE_REFRESH_RATE_07, ++ field_rate_in_hz); ++ ++ } ++ ++ /* ++ * When no VTEM feature is enabled (neither VRR nor FVA), signal a ++ * zero-length data set (MLDS) by clearing Data_Set_Length. HDMI 2.1 ++ * 10.10.2.4 requires the Source to either stop transmitting the VTEM ++ * or set Data_Set_Length = 0 when no feature is enabled; keeping the ++ * VTEM with Data_Set_Length = 0 preserves the every-MTW cadence while ++ * staying compliant (e.g. HDMI GCTS HF1-58 step 6.2). ++ */ ++ if (vrr->state != VRR_STATE_ACTIVE_VARIABLE && ++ vrr->state != VRR_STATE_ACTIVE_FIXED && fva_factor == 0) ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], ++ MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 0); ++ ++ infopacket->valid = true; ++} ++ ++static void build_infopacket_header_vtem(enum signal_type signal, ++ struct dc_info_packet *infopacket) ++{ ++ /* HEADER */ ++ ++ /* HB0, HB1, HB2 indicates PacketType VTEMPacket */ ++ infopacket->hb0 = 0x7F; ++ infopacket->hb1 = 0xC0; ++ infopacket->hb2 = 0x00; /* sequence_index */ ++ ++ set_field_with_mask(&infopacket->sb[VTEM_PB0], MASK_VTEM_PB0__VFR, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB2], MASK_VTEM_PB2__ORGANIZATION_ID, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB3], MASK_VTEM_PB3__DATA_SET_TAG_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB4], MASK_VTEM_PB4__DATA_SET_TAG_LSB, 1); ++ set_field_with_mask(&infopacket->sb[VTEM_PB5], MASK_VTEM_PB5__DATA_SET_LENGTH_MSB, 0); ++ set_field_with_mask(&infopacket->sb[VTEM_PB6], MASK_VTEM_PB6__DATA_SET_LENGTH_LSB, 4); ++} ++ ++void mod_build_infopacket_vtem(const struct dc_stream_state *stream, ++ const struct mod_vrr_params *vrr, int fva_factor, ++ struct dc_info_packet *infopacket) ++{ ++ /* VTEM info packet for HdmiVrr */ ++ ++ memset(infopacket, 0, sizeof(struct dc_info_packet)); ++ ++ /* VTEM Packet is structured differently */ ++ build_infopacket_header_vtem(stream->signal, infopacket); ++ build_vtem_infopacket_data(stream, vrr, fva_factor, infopacket); ++ ++ infopacket->valid = true; ++} ++ + void mod_build_adaptive_sync_infopacket(const struct dc_stream_state *stream, + enum adaptive_sync_type asType, + const struct AS_Df_params *param, +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -6182,6 +6182,33 @@ static void drm_parse_ycbcr420_deep_color_info(struct drm_connector *connector, + hdmi->y420_dc_modes = dc_mask; + } + ++static void drm_parse_hdmi_gaming_info(struct drm_hdmi_info *hdmi, const u8 *db) ++{ ++ struct drm_hdmi_vrr_cap *vrr = &hdmi->vrr_cap; ++ ++ if (cea_db_payload_len(db) < 8) ++ return; ++ ++ hdmi->fapa_start_location = db[8] & DRM_EDID_FAPA_START_LOCATION; ++ hdmi->allm = db[8] & DRM_EDID_ALLM; ++ vrr->fva = db[8] & DRM_EDID_FVA; ++ vrr->cnmvrr = db[8] & DRM_EDID_CNMVRR; ++ vrr->cinema_vrr = db[8] & DRM_EDID_CINEMA_VRR; ++ vrr->mdelta = db[8] & DRM_EDID_MDELTA; ++ ++ if (cea_db_payload_len(db) < 9) ++ return; ++ ++ vrr->vrr_min = db[9] & DRM_EDID_VRR_MIN_MASK; ++ vrr->supported = (vrr->vrr_min > 0 && vrr->vrr_min <= 48); ++ ++ if (cea_db_payload_len(db) < 10) ++ return; ++ ++ vrr->vrr_max = (db[9] & DRM_EDID_VRR_MAX_UPPER_MASK) << 2 | db[10]; ++ vrr->supported &= (vrr->vrr_max == 0 || vrr->vrr_max >= 100); ++} ++ + static void drm_parse_dsc_info(struct drm_hdmi_dsc_cap *hdmi_dsc, + const u8 *hf_scds) + { +@@ -6308,6 +6335,8 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + + drm_parse_ycbcr420_deep_color_info(connector, hf_scds); + ++ drm_parse_hdmi_gaming_info(&connector->display_info.hdmi, hf_scds); ++ + if (cea_db_payload_len(hf_scds) >= 11 && hf_scds[11]) { + drm_parse_dsc_info(hdmi_dsc, hf_scds); + dsc_support = true; +@@ -6317,6 +6346,19 @@ static void drm_parse_hdmi_forum_scds(struct drm_connector *connector, + "[CONNECTOR:%d:%s] HF-VSDB: max TMDS clock: %d KHz, HDMI 2.1 support: %s, DSC 1.2 support: %s\n", + connector->base.id, connector->name, + max_tmds_clock, str_yes_no(max_frl_rate), str_yes_no(dsc_support)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] FAPA in blanking: %s, ALLM support: %s, Fast Vactive support: %s\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->fapa_start_location), ++ str_yes_no(hdmi->allm), str_yes_no(hdmi->vrr_cap.fva)); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Negative M VRR support: %s, CinemaVRR support: %s, Mdelta: %d\n", ++ connector->base.id, connector->name, str_yes_no(hdmi->vrr_cap.cnmvrr), ++ str_yes_no(hdmi->vrr_cap.cinema_vrr), hdmi->vrr_cap.mdelta); ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] VRRmin: %u, VRRmax: %u, VRR supported: %s\n", ++ connector->base.id, connector->name, hdmi->vrr_cap.vrr_min, ++ hdmi->vrr_cap.vrr_max, str_yes_no(hdmi->vrr_cap.supported)); ++ + } + + static void drm_parse_hdmi_deep_color_info(struct drm_connector *connector, +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -254,6 +254,44 @@ struct drm_scdc { + struct drm_scrambling scrambling; + }; + ++/** ++ * struct drm_hdmi_vrr_cap - Information about VRR capabilities of a HDMI sink ++ * ++ * Describes the VRR support provided by HDMI 2.1 sink. The information is ++ * fetched fom additional HFVSDB blocks defined for HDMI 2.1. ++ */ ++struct drm_hdmi_vrr_cap { ++ /** @fva: flag for Fast VActive (Quick Frame Transport) support */ ++ bool fva; ++ ++ /** @mcnmvrr: flag for Negative M VRR support */ ++ bool cnmvrr; ++ ++ /** @mcinema_vrr: flag for Cinema VRR support */ ++ bool cinema_vrr; ++ ++ /** @mdelta: flag for limited frame-to-frame compensation support */ ++ bool mdelta; ++ ++ /** ++ * @vrr_min : minimum supported variable refresh rate in Hz. ++ * Valid values only inide 1 - 48 range ++ */ ++ u16 vrr_min; ++ ++ /** ++ * @vrr_max : maximum supported variable refresh rate in Hz (optional). ++ * Valid values are either 0 (max based on video mode) or >= 100 ++ */ ++ u16 vrr_max; ++ ++ /** ++ * @supported: flag for vrr support based on checking for VRRmin and ++ * VRRmax values having correct values. ++ */ ++ bool supported; ++}; ++ + /** + * struct drm_hdmi_dsc_cap - DSC capabilities of HDMI sink + * +@@ -330,6 +368,15 @@ struct drm_hdmi_info { + /** @max_lanes: supported by sink */ + u8 max_lanes; + ++ /** @fapa_start_location: flag for the FAPA in blanking support */ ++ bool fapa_start_location; ++ ++ /** @allm: flag for Auto Low Latency Mode support by sink */ ++ bool allm; ++ ++ /** @vrr_cap: VRR capabilities of the sink */ ++ struct drm_hdmi_vrr_cap vrr_cap; ++ + /** @dsc_cap: DSC capabilities of the sink */ + struct drm_hdmi_dsc_cap dsc_cap; + }; diff --git a/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch.sig b/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch.sig new file mode 100644 index 0000000..8e70633 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0450-amd-hdmi-vrr-allm.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch b/pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch new file mode 100644 index 0000000..5bc46ad --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -10089,7 +10089,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-muqss/0451-amd-vtem-tmds-links.patch.sig b/pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch.sig new file mode 100644 index 0000000..29e7f44 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0451-amd-vtem-tmds-links.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0452-amd-hdmi-frl-default.patch b/pkgbuilds/linux-omarchy-muqss/0452-amd-hdmi-frl-default.patch new file mode 100644 index 0000000..e392ccc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0452-amd-hdmi-frl-default.patch.sig b/pkgbuilds/linux-omarchy-muqss/0452-amd-hdmi-frl-default.patch.sig new file mode 100644 index 0000000..c507d82 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0452-amd-hdmi-frl-default.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch b/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch new file mode 100644 index 0000000..0a3c3d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch @@ -0,0 +1,14 @@ +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 +@@ -10090,8 +10090,8 @@ static void update_freesync_state_on_stream( + pack_sdp_v1_3); + + /* 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 && ++ if (new_stream->signal == SIGNAL_TYPE_HDMI_FRL || ++ (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A && + aconn && aconn->base.display_info.hdmi.vrr_cap.supported)) + mod_build_infopacket_vtem(new_stream, &vrr_params, 0, &vrr_infopacket); + diff --git a/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch.sig b/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch.sig new file mode 100644 index 0000000..795a770 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0453-amd-vtem-null-sink.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch b/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch new file mode 100644 index 0000000..62ada93 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -2025,7 +2025,7 @@ static void hdmi_frl_status_polling_work(struct work_struct *work) + if (!dc_is_hdmi_signal(dc_link->connector_signal)) + continue; + +- if (dc_link->connector_signal != SIGNAL_TYPE_HDMI_FRL) ++ if (dc_link->frl_link_settings.frl_link_rate == 0) + continue; + + link_update = dc_link_frl_poll_status_flag(dc_link); diff --git a/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch.sig b/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch.sig new file mode 100644 index 0000000..786879a Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0454-amd-hdmi-frl-status-polling.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch b/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch new file mode 100644 index 0000000..26db142 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +--- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c ++++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c +@@ -882,7 +882,7 @@ static void verify_link_capability_non_destructive(struct dc_link *link) + else + link->verified_link_cap = dp_get_max_link_cap(link); + } else if (dc_is_hdmi_signal(link->local_sink->sink_signal)) { +- link->verified_link_cap = link->reported_link_cap; ++ link->frl_verified_link_cap = link->frl_reported_link_cap; + + if (is_hdmi_frl_in_use(link)) { + link->local_sink->sink_signal = SIGNAL_TYPE_HDMI_TYPE_A; diff --git a/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig b/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig new file mode 100644 index 0000000..f225581 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch b/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch new file mode 100644 index 0000000..4531604 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch @@ -0,0 +1,26 @@ +diff --git a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +--- a/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c ++++ b/drivers/gpu/drm/amd/display/dc/dml/dml1_frl_cap_chk.c +@@ -630,17 +630,17 @@ enum frl_cap_chk_result dml1_frl_cap_chk(struct frl_cap_chk_params *params) + { + struct frl_cap_chk_intermediates inter; + +-#if defined (CONFIG_DRM_AMD_DC_FP) +- if (params->compressed) +- return dml1_frl_cap_chk_compressed(params, &inter); +-#endif +- + return dml1_frl_cap_chk_inter(params, &inter); + } + + enum frl_cap_chk_result dml1_frl_cap_chk_inter(struct frl_cap_chk_params *params, + struct frl_cap_chk_intermediates *inter) + { ++#if defined (CONFIG_DRM_AMD_DC_FP) ++ if (params->compressed) ++ return dml1_frl_cap_chk_compressed(params, inter); ++#endif ++ + return dml1_frl_cap_chk_uncompressed(params, inter); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch.sig b/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch.sig new file mode 100644 index 0000000..8297ca2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0456-amd-dml-frl-compressed-cap-check.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch b/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch new file mode 100644 index 0000000..7597723 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch @@ -0,0 +1,335 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7116,6 +7116,11 @@ static void fill_stream_properties_from_drm_display_mode( + + stream->output_color_space = get_output_color_space(timing_out, connector_state); + stream->content_type = get_output_content_type(connector_state); ++ ++ /* DisplayID Type VII pass-through timings. */ ++ if (mode_in->dsc_passthrough_timings_support && info->dp_dsc_bpp_x16 != 0) { ++ stream->timing.dsc_fixed_bits_per_pixel_x16 = info->dp_dsc_bpp_x16; ++ } + } + + static void fill_audio_info(struct audio_info *audio_info, +@@ -7636,6 +7641,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode mode; + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; ++ struct drm_display_mode *dsc_passthru_mode = NULL; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7727,6 +7733,16 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (dsc_passthru_mode->hdisplay == mode.hdisplay && ++ dsc_passthru_mode->vdisplay == mode.vdisplay && ++ drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + +diff --git a/drivers/gpu/drm/drm_displayid_internal.h b/drivers/gpu/drm/drm_displayid_internal.h +--- a/drivers/gpu/drm/drm_displayid_internal.h ++++ b/drivers/gpu/drm/drm_displayid_internal.h +@@ -98,6 +98,7 @@ struct displayid_header { + u8 ext_count; + } __packed; + ++#define DISPLAYID_BLOCK_REV GENMASK(2, 0) + struct displayid_block { + u8 tag; + u8 rev; +@@ -126,6 +127,7 @@ struct displayid_detailed_timings_1 { + __le16 vsw; + } __packed; + ++#define DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT BIT(3) + struct displayid_detailed_timing_block { + struct displayid_block base; + struct displayid_detailed_timings_1 timings[]; +@@ -138,19 +140,28 @@ struct displayid_formula_timings_9 { + u8 vrefresh; + } __packed; + ++#define DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES GENMASK(6, 4) + struct displayid_formula_timing_block { + struct displayid_block base; + struct displayid_formula_timings_9 timings[]; + } __packed; + ++#define DISPLAYID_VESA_DP_TYPE GENMASK(2, 0) + #define DISPLAYID_VESA_MSO_OVERLAP GENMASK(3, 0) + #define DISPLAYID_VESA_MSO_MODE GENMASK(6, 5) ++#define DISPLAYID_VESA_DSC_BPP_INT GENMASK(5, 0) ++#define DISPLAYID_VESA_DSC_BPP_FRACT GENMASK(3, 0) ++ ++#define DISPLAYID_VESA_DP_TYPE_EDP 0 ++#define DISPLAYID_VESA_DP_TYPE_DP 1 + + struct displayid_vesa_vendor_specific_block { + struct displayid_block base; + u8 oui[3]; + u8 data_structure_type; + u8 mso; ++ u8 dsc_bpp_int; ++ u8 dsc_bpp_fract; + } __packed; + + /* +diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c +--- a/drivers/gpu/drm/drm_edid.c ++++ b/drivers/gpu/drm/drm_edid.c +@@ -45,6 +45,7 @@ + #include + #include + #include ++#include + #include + + #include "drm_crtc_internal.h" +@@ -6683,12 +6684,13 @@ static bool drm_update_displayid_adaptive_sync_range(struct drm_connector *conne + return false; + } + +-static void drm_parse_vesa_mso_data(struct drm_connector *connector, +- const struct displayid_block *block) ++static void drm_parse_vesa_specific_block(struct drm_connector *connector, ++ const struct displayid_block *block) + { + struct displayid_vesa_vendor_specific_block *vesa = + (struct displayid_vesa_vendor_specific_block *)block; + struct drm_display_info *info = &connector->display_info; ++ int dp_type; + + if (block->num_bytes < 3) { + drm_dbg_kms(connector->dev, +@@ -6700,51 +6702,73 @@ static void drm_parse_vesa_mso_data(struct drm_connector *connector, + if (oui(vesa->oui[0], vesa->oui[1], vesa->oui[2]) != VESA_IEEE_OUI) + return; + +- if (sizeof(*vesa) != sizeof(*block) + block->num_bytes) { ++ if (block->num_bytes < 5) { + drm_dbg_kms(connector->dev, + "[CONNECTOR:%d:%s] Unexpected VESA vendor block size\n", + connector->base.id, connector->name); + return; + } + +- switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { +- default: +- drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ dp_type = FIELD_GET(DISPLAYID_VESA_DP_TYPE, vesa->data_structure_type); ++ if (dp_type > 1) { ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved dp type value\n", + connector->base.id, connector->name); +- fallthrough; +- case 0: +- info->mso_stream_count = 0; +- break; +- case 1: +- info->mso_stream_count = 2; /* 2 or 4 links */ +- break; +- case 2: +- info->mso_stream_count = 4; /* 4 links */ +- break; + } + +- if (!info->mso_stream_count) { ++ /* MSO is only supported for eDP */ ++ if (dp_type == DISPLAYID_VESA_DP_TYPE_EDP) { ++ switch (FIELD_GET(DISPLAYID_VESA_MSO_MODE, vesa->mso)) { ++ default: ++ drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Reserved MSO mode value\n", ++ connector->base.id, connector->name); ++ fallthrough; ++ case 0: ++ info->mso_stream_count = 0; ++ break; ++ case 1: ++ info->mso_stream_count = 2; /* 2 or 4 links */ ++ break; ++ case 2: ++ info->mso_stream_count = 4; /* 4 links */ ++ break; ++ } ++ } ++ ++ if (info->mso_stream_count) { ++ info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); ++ if (info->mso_pixel_overlap > 8) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", ++ connector->base.id, connector->name, ++ info->mso_pixel_overlap); ++ info->mso_pixel_overlap = 8; ++ } ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", ++ connector->base.id, connector->name, ++ info->mso_stream_count, info->mso_pixel_overlap); ++ } else { + info->mso_pixel_overlap = 0; ++ } ++ ++ if (block->num_bytes < 7) { ++ /* DSC bpp is optional */ + return; + } + +- info->mso_pixel_overlap = FIELD_GET(DISPLAYID_VESA_MSO_OVERLAP, vesa->mso); +- if (info->mso_pixel_overlap > 8) { +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] Reserved MSO pixel overlap value %u\n", +- connector->base.id, connector->name, +- info->mso_pixel_overlap); +- info->mso_pixel_overlap = 8; +- } ++ info->dp_dsc_bpp_x16 = FIELD_GET(DISPLAYID_VESA_DSC_BPP_INT, vesa->dsc_bpp_int) << 4 | ++ FIELD_GET(DISPLAYID_VESA_DSC_BPP_FRACT, vesa->dsc_bpp_fract); + +- drm_dbg_kms(connector->dev, +- "[CONNECTOR:%d:%s] MSO stream count %u, pixel overlap %u\n", +- connector->base.id, connector->name, +- info->mso_stream_count, info->mso_pixel_overlap); ++ if (info->dp_dsc_bpp_x16 > 0) { ++ drm_dbg_kms(connector->dev, ++ "[CONNECTOR:%d:%s] DSC bits per pixel " FXP_Q4_FMT "\n", ++ connector->base.id, connector->name, ++ FXP_Q4_ARGS(info->dp_dsc_bpp_x16)); ++ } + } + +-static void drm_update_mso(struct drm_connector *connector, +- const struct drm_edid *drm_edid) ++static void drm_update_vesa_specific_block(struct drm_connector *connector, ++ const struct drm_edid *drm_edid) + { + const struct displayid_block *block; + struct displayid_iter iter; +@@ -6752,7 +6776,7 @@ static void drm_update_mso(struct drm_connector *connector, + displayid_iter_edid_begin(drm_edid, &iter); + displayid_iter_for_each(block, &iter) { + if (block->tag == DATA_BLOCK_2_VENDOR_SPECIFIC) +- drm_parse_vesa_mso_data(connector, block); ++ drm_parse_vesa_specific_block(connector, block); + } + displayid_iter_end(&iter); + } +@@ -6789,6 +6813,7 @@ static void drm_reset_display_info(struct drm_connector *connector) + info->mso_stream_count = 0; + info->mso_pixel_overlap = 0; + info->max_dsc_bpp = 0; ++ info->dp_dsc_bpp_x16 = 0; + + kfree(info->vics); + info->vics = NULL; +@@ -6930,7 +6955,7 @@ static void update_display_info(struct drm_connector *connector, + if (edid->features & DRM_EDID_FEATURE_RGB_YCRCB422) + info->color_formats |= BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + +- drm_update_mso(connector, drm_edid); ++ drm_update_vesa_specific_block(connector, drm_edid); + + out: + if (drm_edid_has_internal_quirk(connector, EDID_QUIRK_NON_DESKTOP)) { +@@ -6960,8 +6985,8 @@ static void update_display_info(struct drm_connector *connector, + } + + static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *dev, +- const struct displayid_detailed_timings_1 *timings, +- bool type_7) ++ const struct displayid_block *block, ++ const struct displayid_detailed_timings_1 *timings) + { + struct drm_display_mode *mode; + unsigned int pixel_clock = (timings->pixel_clock[0] | +@@ -6977,11 +7002,16 @@ static struct drm_display_mode *drm_mode_displayid_detailed(struct drm_device *d + unsigned int vsync_width = le16_to_cpu(timings->vsw) + 1; + bool hsync_positive = le16_to_cpu(timings->hsync) & (1 << 15); + bool vsync_positive = le16_to_cpu(timings->vsync) & (1 << 15); ++ bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + + mode = drm_mode_create(dev); + if (!mode) + return NULL; + ++ if (type_7 && FIELD_GET(DISPLAYID_BLOCK_REV, block->rev) >= 1) ++ mode->dsc_passthrough_timings_support = ++ block->rev & DISPLAYID_BLOCK_PASSTHROUGH_TIMINGS_SUPPORT; ++ + /* resolution is kHz for type VII, and 10 kHz for type I */ + mode->clock = type_7 ? pixel_clock : pixel_clock * 10; + mode->hdisplay = hactive; +@@ -7014,7 +7044,6 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + int num_timings; + struct drm_display_mode *newmode; + int num_modes = 0; +- bool type_7 = block->tag == DATA_BLOCK_2_TYPE_7_DETAILED_TIMING; + /* blocks must be multiple of 20 bytes length */ + if (block->num_bytes % 20) + return 0; +@@ -7023,7 +7052,7 @@ static int add_displayid_detailed_1_modes(struct drm_connector *connector, + for (i = 0; i < num_timings; i++) { + struct displayid_detailed_timings_1 *timings = &det->timings[i]; + +- newmode = drm_mode_displayid_detailed(connector->dev, timings, type_7); ++ newmode = drm_mode_displayid_detailed(connector->dev, block, timings); + if (!newmode) + continue; + +@@ -7070,7 +7099,8 @@ static int add_displayid_formula_modes(struct drm_connector *connector, + struct drm_display_mode *newmode; + int num_modes = 0; + bool type_10 = block->tag == DATA_BLOCK_2_TYPE_10_FORMULA_TIMING; +- int timing_size = 6 + ((formula_block->base.rev & 0x70) >> 4); ++ int timing_size = 6 + ++ FIELD_GET(DISPLAYID_BLOCK_DESCRIPTOR_PAYLOAD_BYTES, formula_block->base.rev); + + /* extended blocks are not supported yet */ + if (timing_size != 6) +diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h +--- a/include/drm/drm_connector.h ++++ b/include/drm/drm_connector.h +@@ -939,6 +939,12 @@ struct drm_display_info { + */ + u32 max_dsc_bpp; + ++ /** ++ * @dp_dsc_bpp: DP Display-Stream-Compression (DSC) timing's target ++ * DSC bits per pixel in 6.4 fixed point format. 0 means undefined. ++ */ ++ u16 dp_dsc_bpp_x16; ++ + /** + * @vics: Array of vics_len VICs. Internal to EDID parsing. + */ +diff --git a/include/drm/drm_modes.h b/include/drm/drm_modes.h +--- a/include/drm/drm_modes.h ++++ b/include/drm/drm_modes.h +@@ -417,6 +417,16 @@ struct drm_display_mode { + */ + enum hdmi_picture_aspect picture_aspect_ratio; + ++ /** ++ * @dsc_passthrough_timing_support: ++ * ++ * Indicates whether this mode timing descriptor is supported ++ * with specific target DSC bits per pixel only. ++ * ++ * VESA vendor-specific data block shall exist with the relevant ++ * DSC bits per pixel declaration when this flag is set to true. ++ */ ++ bool dsc_passthrough_timings_support; + }; + + /** diff --git a/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch.sig b/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch.sig new file mode 100644 index 0000000..922eab7 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0460-vesa-displayid-dsc-bpp.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch b/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch new file mode 100644 index 0000000..b826d02 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch @@ -0,0 +1,51 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7642,6 +7642,7 @@ create_stream_for_sink(struct drm_connector *connector, + struct drm_display_mode saved_mode; + struct drm_display_mode *freesync_mode = NULL; + struct drm_display_mode *dsc_passthru_mode = NULL; ++ unsigned int dsc_passthru_match_flags; + bool native_mode_found = false; + bool recalculate_timing = false; + bool scale = dm_state->scaling != RMX_OFF; +@@ -7708,6 +7709,22 @@ create_stream_for_sink(struct drm_connector *connector, + + mode_refresh = drm_mode_vrefresh(&mode); + ++ dsc_passthru_match_flags = DRM_MODE_MATCH_TIMINGS | ++ DRM_MODE_MATCH_CLOCK | ++ DRM_MODE_MATCH_FLAGS | ++ DRM_MODE_MATCH_3D_FLAGS; ++ if (mode.picture_aspect_ratio) ++ dsc_passthru_match_flags |= DRM_MODE_MATCH_ASPECT_RATIO; ++ ++ list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { ++ if (drm_mode_match(dsc_passthru_mode, &mode, ++ dsc_passthru_match_flags)) { ++ mode.dsc_passthrough_timings_support = ++ dsc_passthru_mode->dsc_passthrough_timings_support; ++ break; ++ } ++ } ++ + if (preferred_mode == NULL) { + /* + * This may not be an error, the use case is when we have no +@@ -7733,16 +7750,6 @@ create_stream_for_sink(struct drm_connector *connector, + } + } + +- list_for_each_entry(dsc_passthru_mode, &connector->modes, head) { +- if (dsc_passthru_mode->hdisplay == mode.hdisplay && +- dsc_passthru_mode->vdisplay == mode.vdisplay && +- drm_mode_vrefresh(dsc_passthru_mode) == mode_refresh) { +- mode.dsc_passthrough_timings_support = +- dsc_passthru_mode->dsc_passthrough_timings_support; +- break; +- } +- } +- + if (recalculate_timing) + drm_mode_set_crtcinfo(&saved_mode, 0); + diff --git a/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch.sig b/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch.sig new file mode 100644 index 0000000..cb18d66 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0461-vesa-dsc-passthru-mode-match-fix.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch b/pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch new file mode 100644 index 0000000..942772c --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0472-amdgpu-userq-post-reset-error.patch.sig b/pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch.sig new file mode 100644 index 0000000..5de4d85 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0472-amdgpu-userq-post-reset-error.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch b/pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch new file mode 100644 index 0000000..035758b --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -4176,8 +4176,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-muqss/0474-amd-display-oled-vesa-backlight.patch.sig b/pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch.sig new file mode 100644 index 0000000..f64009c Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0474-amd-display-oled-vesa-backlight.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch b/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch new file mode 100644 index 0000000..a58a962 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c +--- a/drivers/gpu/drm/i915/display/intel_dp.c ++++ b/drivers/gpu/drm/i915/display/intel_dp.c +@@ -3219,11 +3219,7 @@ static bool intel_dp_needs_as_sdp(struct intel_dp *intel_dp, + if (drm_dp_is_branch(intel_dp->dpcd)) + return false; + +- if (intel_psr_needs_alpm_aux_less(intel_dp, crtc_state) && +- !intel_psr_pr_async_video_timing_supported(intel_dp)) +- return true; +- +- return intel_vrr_possible(crtc_state); ++ return crtc_state->vrr.enable; + } + + static void intel_dp_compute_as_sdp(struct intel_dp *intel_dp, diff --git a/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig b/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig new file mode 100644 index 0000000..3ddfad0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch b/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch new file mode 100644 index 0000000..8d6c8a4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch @@ -0,0 +1,25 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -7020,16 +7020,11 @@ static void fill_stream_properties_from_drm_display_mode( + timing_out->v_border_top = 0; + timing_out->v_border_bottom = 0; + /* TODO: un-hardcode */ +- if (drm_mode_is_420_only(info, mode_in) +- && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || +- stream->signal == SIGNAL_TYPE_HDMI_FRL) +- && aconnector +- && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) +- timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; +- else if (drm_mode_is_420_also(info, mode_in) +- && aconnector +- && (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 +- || aconnector->force_yuv420_output)) ++ if (drm_mode_is_420_only(info, mode_in) || ++ (aconnector && ++ (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420 || ++ aconnector->force_yuv420_output) && ++ drm_mode_is_420_also(info, mode_in))) + timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; + else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + && aconnector diff --git a/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch.sig new file mode 100644 index 0000000..2b5da94 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0476-amd-display-ycbcr420-only-modes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch b/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch new file mode 100644 index 0000000..1b0eb75 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch @@ -0,0 +1,303 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -3345,7 +3345,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + if (acrtc && state->stream_status[i].plane_count != 0 && + amdgpu_ip_version(adev, DCE_HWIP, 0) == 0) { + irq_source = IRQ_TYPE_PFLIP + acrtc->otg_inst; +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + if (rc) + drm_warn(adev_to_drm(adev), "Failed to %s pflip interrupts\n", + enable ? "enable" : "disable"); +@@ -3369,7 +3369,7 @@ static void dm_gpureset_toggle_interrupts(struct amdgpu_device *adev, + /* During gpu-reset we disable and then enable vblank irq, so + * don't use amdgpu_irq_get/put() to avoid refcount change. + */ +- if (!dc_interrupt_set(adev->dm.dc, irq_source, enable)) ++ if (!amdgpu_dm_irq_set(adev, irq_source, enable)) + drm_warn(adev_to_drm(adev), "Failed to %sable vblank interrupt\n", enable ? "en" : "dis"); + + } else if (acrtc && state->stream_status[i].plane_count != 0) { +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +@@ -550,6 +550,18 @@ struct amdgpu_display_manager { + struct common_irq_params + vupdate_params[DC_IRQ_SOURCE_VUPDATE6 - DC_IRQ_SOURCE_VUPDATE1 + 1]; + ++ /** ++ * @irq_reg_lock: ++ * ++ * Serializes the read-modify-writes of the HW interrupt control ++ * registers. Several interrupt sources share one register - e.g. the ++ * enable and clear bits of both VSTARTUP (vblank) and VUPDATE_NO_LOCK ++ * live in OTG_GLOBAL_SYNC_STATUS. Therefore, enabling one source must ++ * not race with acking another. Held only across amdgpu_dm_irq_set() ++ * and amdgpu_dm_irq_ack(). ++ */ ++ spinlock_t irq_reg_lock; ++ + /** + * @dmub_trace_params: + * +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +@@ -31,6 +31,7 @@ + #include "amdgpu_dm_psr.h" + #include "amdgpu_dm_replay.h" + #include "amdgpu_dm_crtc.h" ++#include "amdgpu_dm_irq.h" + #include "amdgpu_dm_plane.h" + #include "amdgpu_dm_trace.h" + #include "amdgpu_dm_debugfs.h" +@@ -87,7 +88,7 @@ int amdgpu_dm_crtc_set_vupdate_irq(struct drm_crtc *crtc, bool enable) + + irq_source = IRQ_TYPE_VUPDATE + acrtc->otg_inst; + +- rc = dc_interrupt_set(adev->dm.dc, irq_source, enable) ? 0 : -EBUSY; ++ rc = amdgpu_dm_irq_set(adev, irq_source, enable) ? 0 : -EBUSY; + + DRM_DEBUG_VBL("crtc %d - vupdate irq %sabling: r=%d\n", + acrtc->crtc_id, enable ? "en" : "dis", rc); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +@@ -1378,12 +1378,13 @@ void dm_helpers_free_gpu_mem( + + bool dm_helpers_dmub_outbox_interrupt_control(struct dc_context *ctx, bool enable) + { ++ struct amdgpu_device *adev = ctx->driver_context; + enum dc_irq_source irq_source; + bool ret; + + irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + +- ret = dc_interrupt_set(ctx->dc, irq_source, enable); ++ ret = amdgpu_dm_irq_set(adev, irq_source, enable); + + DRM_DEBUG_DRIVER("Dmub trace irq %sabling: r=%d\n", + enable ? "en" : "dis", ret); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c +@@ -424,6 +424,7 @@ int amdgpu_dm_irq_init(struct amdgpu_device *adev) + DRM_DEBUG_KMS("DM_IRQ\n"); + + spin_lock_init(&adev->dm.irq_handler_list_table_lock); ++ spin_lock_init(&adev->dm.irq_reg_lock); + + for (src = 0; src < DAL_IRQ_SOURCES_NUMBER; src++) { + /* low context handler list init */ +@@ -496,7 +497,7 @@ void amdgpu_dm_irq_suspend(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, false); ++ amdgpu_dm_irq_set(adev, src, false); + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + +@@ -533,7 +534,7 @@ void amdgpu_dm_irq_resume_early(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -558,7 +559,7 @@ void amdgpu_dm_irq_resume_late(struct amdgpu_device *adev) + hnd_list_l = &adev->dm.irq_handler_list_low_tab[src]; + hnd_list_h = &adev->dm.irq_handler_list_high_tab[src]; + if (!list_empty(hnd_list_l) || !list_empty(hnd_list_h)) +- dc_interrupt_set(adev->dm.dc, src, true); ++ amdgpu_dm_irq_set(adev, src, true); + } + + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); +@@ -645,6 +646,21 @@ static void amdgpu_dm_irq_immediate_work(struct amdgpu_device *adev, + DM_IRQ_TABLE_UNLOCK(adev, irq_table_flags); + } + ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ return dc_interrupt_set(adev->dm.dc, src, enable); ++} ++ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src) ++{ ++ guard(spinlock_irqsave)(&adev->dm.irq_reg_lock); ++ ++ dc_interrupt_ack(adev->dm.dc, src); ++} ++ + /** + * amdgpu_dm_irq_handler - Generic DM IRQ handler + * @adev: amdgpu base driver device containing the DM device +@@ -665,7 +681,7 @@ static int amdgpu_dm_irq_handler(struct amdgpu_device *adev, + entry->src_id, + entry->src_data[0]); + +- dc_interrupt_ack(adev->dm.dc, src); ++ amdgpu_dm_irq_ack(adev, src); + + /* Call high irq work immediately */ + amdgpu_dm_irq_immediate_work(adev, src); +@@ -703,7 +719,7 @@ static int amdgpu_dm_set_hpd_irq_state(struct amdgpu_device *adev, + enum dc_irq_source src = amdgpu_dm_hpd_to_dal_irq_source(type); + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, src, st); ++ amdgpu_dm_irq_set(adev, src, st); + return 0; + } + +@@ -737,7 +753,7 @@ static inline int dm_irq_state(struct amdgpu_device *adev, + if (dc && dc->caps.ips_support && dc->idle_optimizations_allowed) + dc_allow_idle_optimizations(dc, false); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -791,7 +807,7 @@ static int amdgpu_dm_set_dmub_outbox_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -817,7 +833,7 @@ static int amdgpu_dm_set_dmub_trace_irq_state(struct amdgpu_device *adev, + enum dc_irq_source irq_source = DC_IRQ_SOURCE_DMCUB_OUTBOX0; + bool st = (state == AMDGPU_IRQ_STATE_ENABLE); + +- dc_interrupt_set(adev->dm.dc, irq_source, st); ++ amdgpu_dm_irq_set(adev, irq_source, st); + return 0; + } + +@@ -881,9 +897,7 @@ void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev) + } + void amdgpu_dm_outbox_init(struct amdgpu_device *adev) + { +- dc_interrupt_set(adev->dm.dc, +- DC_IRQ_SOURCE_DMCUB_OUTBOX, +- true); ++ amdgpu_dm_irq_set(adev, DC_IRQ_SOURCE_DMCUB_OUTBOX, true); + } + + /** +@@ -905,7 +919,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + + /* First, clear all hpd and hpdrx interrupts */ + for (i = DC_IRQ_SOURCE_HPD1; i <= DC_IRQ_SOURCE_HPD6RX; i++) { +- if (!dc_interrupt_set(adev->dm.dc, i, false)) ++ if (!amdgpu_dm_irq_set(adev, i, false)) + drm_err(dev, "Failed to clear hpd(rx) source=%d on init\n", + i); + } +@@ -934,7 +948,7 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * of dm. Note that only hpd interrupt types are registered with + * base driver; hpd_rx types aren't. IOW, amdgpu_irq_get/put on + * hpd_rx isn't available. DM currently controls hpd_rx +- * explicitly with dc_interrupt_set() ++ * explicitly with amdgpu_dm_irq_set() + */ + if (dc_link->irq_source_hpd != DC_IRQ_SOURCE_INVALID) { + irq_type = dc_link->irq_source_hpd - DC_IRQ_SOURCE_HPD1; +@@ -943,23 +957,21 @@ void amdgpu_dm_hpd_init(struct amdgpu_device *adev) + * and what bios reports as the # of connectors with hpd + * sources. Since the # of hpd source types registered + * with base driver == mode_info.num_hpd, we have to +- * fallback to dc_interrupt_set for the remaining types. ++ * fallback to amdgpu_dm_irq_set for the remaining types. + */ + if (irq_type < adev->mode_info.num_hpd) { + if (amdgpu_irq_get(adev, &adev->hpd_irq, irq_type)) + drm_err(dev, "DM_IRQ: Failed get HPD for source=%d)!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ true); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- true); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ true); + } + } + drm_connector_list_iter_end(&iter); +@@ -1003,16 +1015,14 @@ void amdgpu_dm_hpd_fini(struct amdgpu_device *adev) + drm_err(dev, "DM_IRQ: Failed put HPD for source=%d!\n", + dc_link->irq_source_hpd); + } else { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd, ++ false); + } + } + + if (dc_link->irq_source_hpd_rx != DC_IRQ_SOURCE_INVALID) { +- dc_interrupt_set(adev->dm.dc, +- dc_link->irq_source_hpd_rx, +- false); ++ amdgpu_dm_irq_set(adev, dc_link->irq_source_hpd_rx, ++ false); + } + } + drm_connector_list_iter_end(&iter); +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.h +@@ -81,6 +81,34 @@ void amdgpu_dm_irq_unregister_interrupt(struct amdgpu_device *adev, + enum dc_irq_source irq_source, + void *ih_index); + ++/** ++ * amdgpu_dm_irq_set - enable or disable a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to toggle ++ * @enable: true to enable the source, false to disable it ++ * ++ * DM-wide replacement for dc_interrupt_set(). As locking is DM's ++ * responsibility, this is a thin wrapper serializes the underlying ++ * read-modify-write against the other interrupt sources sharing HW control ++ * registers with @src, so DM must never call dc_interrupt_set() directly. ++ * ++ * Returns: true if the source was toggled. ++ */ ++bool amdgpu_dm_irq_set(struct amdgpu_device *adev, enum dc_irq_source src, ++ bool enable); ++ ++/** ++ * amdgpu_dm_irq_ack - acknowledge a DC interrupt source. ++ * ++ * @adev: AMD DRM device ++ * @src: DC interrupt source to acknowledge ++ * ++ * DM-wide replacement for dc_interrupt_ack(), serialized the same way as ++ * amdgpu_dm_irq_set(). ++ */ ++void amdgpu_dm_irq_ack(struct amdgpu_device *adev, enum dc_irq_source src); ++ + void amdgpu_dm_set_irq_funcs(struct amdgpu_device *adev); + + void amdgpu_dm_outbox_init(struct amdgpu_device *adev); diff --git a/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch.sig b/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch.sig new file mode 100644 index 0000000..c0f963e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0477-amd-display-irq-atomic-rmw.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch b/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch new file mode 100644 index 0000000..dcdb282 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -14232,6 +14232,12 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + } + } + ++ /* HDMI/PCON without an EDID FreeSync VCP code is not FreeSync-capable. */ ++ if ((sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A || ++ as_type == FREESYNC_TYPE_PCON_IN_WHITELIST) && ++ !sink->edid_caps.freesync_vcp_code) ++ freesync_capable = false; ++ + /* Handle MCCS */ + if (do_mccs) { + dm_helpers_read_mccs_caps(adev->dm.dc->ctx, amdgpu_dm_connector->dc_link, sink); diff --git a/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig b/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig new file mode 100644 index 0000000..eed1a60 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0478-amd-freesync-vcp-code-check-hdmi-pcon.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch b/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch new file mode 100644 index 0000000..13b9043 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch @@ -0,0 +1,40 @@ +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -10135,20 +10135,19 @@ static void update_freesync_state_on_stream( + */ + if (new_stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || + new_stream->signal == SIGNAL_TYPE_HDMI_FRL) { +- struct dc_info_packet hf_vsif = {0}; ++ struct dc_info_packet vsp_infopacket = {0}; + bool sink_allm = aconn && aconn->base.display_info.hdmi.allm; + bool allm = sink_allm && + (vrr_params.state == VRR_STATE_ACTIVE_VARIABLE || + vrr_params.state == VRR_STATE_ACTIVE_FIXED); + bool allm_changed; + +- if (allm) +- mod_build_hf_vsif_infopacket(new_stream, &hf_vsif, allm, allm); ++ mod_build_hf_vsif_infopacket(new_stream, &vsp_infopacket, allm, allm); + +- allm_changed = memcmp(&new_stream->hfvsif_infopacket, &hf_vsif, +- sizeof(hf_vsif)) != 0; ++ allm_changed = memcmp(&new_stream->vsp_infopacket, &vsp_infopacket, ++ sizeof(vsp_infopacket)) != 0; + new_crtc_state->freesync_vrr_info_changed |= allm_changed; +- new_stream->hfvsif_infopacket = hf_vsif; ++ new_stream->vsp_infopacket = vsp_infopacket; + + if (allm_changed) + drm_dbg_driver(adev_to_drm(adev), +@@ -10727,8 +10726,8 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, + if (acrtc_state->freesync_vrr_info_changed) { + bundle->stream_update.vrr_infopacket = + &acrtc_state->stream->vrr_infopacket; +- bundle->stream_update.hfvsif_infopacket = +- &acrtc_state->stream->hfvsif_infopacket; ++ bundle->stream_update.vsp_infopacket = ++ &acrtc_state->stream->vsp_infopacket; + } + } + } diff --git a/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig b/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig new file mode 100644 index 0000000..37a9ec9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch b/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch new file mode 100644 index 0000000..1368f53 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch @@ -0,0 +1,12141 @@ +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -1757,6 +1757,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) + * by the drivers/platform/x86/serial-multi-instantiate.c driver, which + * knows which client device id to use for each resource. + */ ++ {"AWDZ8399", }, + {"BSG1160", }, + {"BSG2150", }, + {"CSC3551", }, +diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c +--- a/drivers/platform/x86/serial-multi-instantiate.c ++++ b/drivers/platform/x86/serial-multi-instantiate.c +@@ -307,6 +307,15 @@ static void smi_remove(struct platform_device *pdev) + smi_devs_unregister(smi); + } + ++static const struct smi_node aw88399_hda = { ++ .instances = { ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ { "aw88399-hda", IRQ_RESOURCE_AUTO, 0 }, ++ {} ++ }, ++ .bus_type = SMI_AUTO_DETECT, ++}; ++ + static const struct smi_node bsg1160_data = { + .instances = { + { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, +@@ -405,6 +414,7 @@ static const struct smi_node tas2781_hda = { + * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). + */ + static const struct acpi_device_id smi_acpi_ids[] = { ++ { "AWDZ8399", (unsigned long)&aw88399_hda }, + { "BSG1160", (unsigned long)&bsg1160_data }, + { "BSG2150", (unsigned long)&bsg2150_data }, + { "CSC3551", (unsigned long)&cs35l41_hda }, +diff --git a/include/sound/ac97/codec.h b/include/sound/ac97/codec.h +--- a/include/sound/ac97/codec.h ++++ b/include/sound/ac97/codec.h +@@ -33,7 +33,7 @@ struct ac97_id { + }; + + /** +- * ac97_codec_device - a ac97 codec ++ * struct ac97_codec_device - a ac97 codec + * @dev: the core device + * @vendor_id: the vendor_id of the codec, as sensed on the AC-link + * @num: the codec number, 0 is primary, 1 is first slave, etc ... +@@ -53,7 +53,7 @@ struct ac97_codec_device { + }; + + /** +- * ac97_codec_driver - a ac97 codec driver ++ * struct ac97_codec_driver - a ac97 codec driver + * @driver: the device driver structure + * @probe: the function called when a ac97_codec_device is matched + * @remove: the function called when the device is unbound/removed +diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h +--- a/include/sound/asequencer.h ++++ b/include/sound/asequencer.h +@@ -11,9 +11,6 @@ + #include + #include + +-/* helper macro */ +-#define snd_seq_event_bounce_ext_data(ev) ((void*)((char *)(ev)->data.ext.ptr + sizeof(struct snd_seq_event_bounce))) +- + /* + * type check macros + */ +diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h +new file mode 100644 +--- /dev/null ++++ b/include/sound/aw88399.h +@@ -0,0 +1,621 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * linux/sound/aw88399.h -- Platform data for AW88399 ++ * ++ * Copyright (c) 2023 AWINIC Technology CO., LTD ++ * ++ * Author: Weidong Wang ++ */ ++ ++#ifndef __SOUND_AW88399_H ++#define __SOUND_AW88399_H ++ ++#include ++#include ++ ++/* registers list */ ++#define AW88399_ID_REG (0x00) ++#define AW88399_SYSST_REG (0x01) ++#define AW88399_SYSINT_REG (0x02) ++#define AW88399_SYSINTM_REG (0x03) ++#define AW88399_SYSCTRL_REG (0x04) ++#define AW88399_SYSCTRL2_REG (0x05) ++#define AW88399_I2SCTRL1_REG (0x06) ++#define AW88399_I2SCTRL2_REG (0x07) ++#define AW88399_I2SCTRL3_REG (0x08) ++#define AW88399_DACCFG1_REG (0x09) ++#define AW88399_DACCFG2_REG (0x0A) ++#define AW88399_DACCFG3_REG (0x0B) ++#define AW88399_DACCFG4_REG (0x0C) ++#define AW88399_DACCFG5_REG (0x0D) ++#define AW88399_DACCFG6_REG (0x0E) ++#define AW88399_DACCFG7_REG (0x0F) ++#define AW88399_MPDCFG1_REG (0x10) ++#define AW88399_MPDCFG2_REG (0x11) ++#define AW88399_MPDCFG3_REG (0x12) ++#define AW88399_MPDCFG4_REG (0x13) ++#define AW88399_PWMCTRL1_REG (0x14) ++#define AW88399_PWMCTRL2_REG (0x15) ++#define AW88399_PWMCTRL3_REG (0x16) ++#define AW88399_I2SCFG1_REG (0x17) ++#define AW88399_DBGCTRL_REG (0x18) ++#define AW88399_HAGCST_REG (0x20) ++#define AW88399_VBAT_REG (0x21) ++#define AW88399_TEMP_REG (0x22) ++#define AW88399_PVDD_REG (0x23) ++#define AW88399_ISNDAT_REG (0x24) ++#define AW88399_VSNDAT_REG (0x25) ++#define AW88399_I2SINT_REG (0x26) ++#define AW88399_I2SCAPCNT_REG (0x27) ++#define AW88399_ANASTA1_REG (0x28) ++#define AW88399_ANASTA2_REG (0x29) ++#define AW88399_ANASTA3_REG (0x2A) ++#define AW88399_TESTDET_REG (0x2B) ++#define AW88399_DSMCFG1_REG (0x30) ++#define AW88399_DSMCFG2_REG (0x31) ++#define AW88399_DSMCFG3_REG (0x32) ++#define AW88399_DSMCFG4_REG (0x33) ++#define AW88399_DSMCFG5_REG (0x34) ++#define AW88399_DSMCFG6_REG (0x35) ++#define AW88399_DSMCFG7_REG (0x36) ++#define AW88399_DSMCFG8_REG (0x37) ++#define AW88399_TESTIN_REG (0x38) ++#define AW88399_TESTOUT_REG (0x39) ++#define AW88399_MEMTEST_REG (0x3A) ++#define AW88399_VSNCTRL1_REG (0x3B) ++#define AW88399_ISNCTRL1_REG (0x3C) ++#define AW88399_ISNCTRL2_REG (0x3D) ++#define AW88399_DSPMADD_REG (0x40) ++#define AW88399_DSPMDAT_REG (0x41) ++#define AW88399_WDT_REG (0x42) ++#define AW88399_ACR1_REG (0x43) ++#define AW88399_ACR2_REG (0x44) ++#define AW88399_ASR1_REG (0x45) ++#define AW88399_ASR2_REG (0x46) ++#define AW88399_DSPCFG_REG (0x47) ++#define AW88399_ASR3_REG (0x48) ++#define AW88399_ASR4_REG (0x49) ++#define AW88399_DSPVCALB_REG (0x4A) ++#define AW88399_CRCCTRL_REG (0x4B) ++#define AW88399_DSPDBG1_REG (0x4C) ++#define AW88399_DSPDBG2_REG (0x4D) ++#define AW88399_DSPDBG3_REG (0x4E) ++#define AW88399_PLLCTRL1_REG (0x50) ++#define AW88399_PLLCTRL2_REG (0x51) ++#define AW88399_PLLCTRL3_REG (0x52) ++#define AW88399_CDACTRL1_REG (0x53) ++#define AW88399_CDACTRL2_REG (0x54) ++#define AW88399_CDACTRL3_REG (0x55) ++#define AW88399_SADCCTRL1_REG (0x56) ++#define AW88399_SADCCTRL2_REG (0x57) ++#define AW88399_BOPCTRL1_REG (0x58) ++#define AW88399_BOPCTRL2_REG (0x5A) ++#define AW88399_BOPCTRL3_REG (0x5B) ++#define AW88399_BOPCTRL4_REG (0x5C) ++#define AW88399_BOPCTRL5_REG (0x5D) ++#define AW88399_BOPCTRL6_REG (0x5E) ++#define AW88399_BOPCTRL7_REG (0x5F) ++#define AW88399_BSTCTRL1_REG (0x60) ++#define AW88399_BSTCTRL2_REG (0x61) ++#define AW88399_BSTCTRL3_REG (0x62) ++#define AW88399_BSTCTRL4_REG (0x63) ++#define AW88399_BSTCTRL5_REG (0x64) ++#define AW88399_BSTCTRL6_REG (0x65) ++#define AW88399_BSTCTRL7_REG (0x66) ++#define AW88399_BSTCTRL8_REG (0x67) ++#define AW88399_BSTCTRL9_REG (0x68) ++#define AW88399_BSTCTRL10_REG (0x69) ++#define AW88399_CPCTRL_REG (0x6A) ++#define AW88399_EFWH_REG (0x6C) ++#define AW88399_EFWM2_REG (0x6D) ++#define AW88399_EFWM1_REG (0x6E) ++#define AW88399_EFWL_REG (0x6F) ++#define AW88399_TESTCTRL1_REG (0x70) ++#define AW88399_TESTCTRL2_REG (0x71) ++#define AW88399_EFCTRL1_REG (0x72) ++#define AW88399_EFCTRL2_REG (0x73) ++#define AW88399_EFRH4_REG (0x74) ++#define AW88399_EFRH3_REG (0x75) ++#define AW88399_EFRH2_REG (0x76) ++#define AW88399_EFRH1_REG (0x77) ++#define AW88399_EFRL4_REG (0x78) ++#define AW88399_EFRL3_REG (0x79) ++#define AW88399_EFRL2_REG (0x7A) ++#define AW88399_EFRL1_REG (0x7B) ++#define AW88399_TM_REG (0x7C) ++#define AW88399_TM2_REG (0x7D) ++ ++#define AW88399_REG_MAX (0x7E) ++#define AW88399_MUTE_VOL (1023) ++ ++#define AW88399_DSP_CFG_ADDR (0x9B00) ++#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) ++#define AW88399_DSP_FW_ADDR (0x8980) ++#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) ++#define AW88399_DSP_ROM_CHECK_DATA (0x4638) ++ ++#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) ++#define AW88399_CALI_RE_HBITS_SHIFT (16) ++ ++#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) ++#define AW88399_CALI_RE_LBITS_SHIFT (0) ++ ++#define AW88399_I2STXEN_START_BIT (9) ++#define AW88399_I2STXEN_BITS_LEN (1) ++#define AW88399_I2STXEN_MASK \ ++ (~(((1<> (shift)) ++#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) ++#define AW88399_CRC_CHECK_PASS_VAL (0x4) ++ ++#define AW88399_CRC_CFG_BASE_ADDR (0xD80) ++#define AW88399_CRC_FW_BASE_ADDR (0x4C0) ++#define AW88399_ACF_FILE "aw88399_acf.bin" ++#define AW88399_DEV_SYSST_CHECK_MAX (10) ++#define AW88399_CHIP_ID 0x2183 ++ ++#define AW88399_START_RETRIES (5) ++#define AW88399_START_WORK_DELAY_MS (0) ++ ++enum { ++ AW_EF_AND_CHECK = 0, ++ AW_EF_OR_CHECK, ++}; ++ ++enum { ++ AW88399_DEV_VDSEL_DAC = 0, ++ AW88399_DEV_VDSEL_VSENSE = 32, ++}; ++ ++enum { ++ AW88399_DSP_CRC_NA = 0, ++ AW88399_DSP_CRC_OK = 1, ++}; ++ ++enum { ++ AW88399_DSP_FW_UPDATE_OFF = 0, ++ AW88399_DSP_FW_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_FORCE_UPDATE_OFF = 0, ++ AW88399_FORCE_UPDATE_ON = 1, ++}; ++ ++enum { ++ AW88399_1000_US = 1000, ++ AW88399_2000_US = 2000, ++ AW88399_3000_US = 3000, ++ AW88399_4000_US = 4000, ++}; ++ ++enum AW88399_DEV_STATUS { ++ AW88399_DEV_PW_OFF = 0, ++ AW88399_DEV_PW_ON, ++}; ++ ++enum AW88399_DEV_FW_STATUS { ++ AW88399_DEV_FW_FAILED = 0, ++ AW88399_DEV_FW_OK, ++}; ++ ++enum AW88399_DEV_MEMCLK { ++ AW88399_DEV_MEMCLK_OSC = 0, ++ AW88399_DEV_MEMCLK_PLL = 1, ++}; ++ ++enum AW88399_DEV_DSP_CFG { ++ AW88399_DEV_DSP_WORK = 0, ++ AW88399_DEV_DSP_BYPASS = 1, ++}; ++ ++enum { ++ AW88399_NOT_RCV_MODE = 0, ++ AW88399_RCV_MODE = 1, ++}; ++ ++enum { ++ AW88399_SYNC_START = 0, ++ AW88399_ASYNC_START, ++}; ++ ++struct aw_device; ++struct aw_container; ++struct aw_cali_desc; ++struct gpio_desc; ++struct i2c_client; ++struct regmap; ++ ++struct aw88399 { ++ struct aw_device *aw_pa; ++ struct mutex lock; ++ struct gpio_desc *reset_gpio; ++ struct delayed_work start_work; ++ struct regmap *regmap; ++ struct aw_container *aw_cfg; ++ ++ unsigned int check_val; ++ unsigned int crc_init_val; ++ unsigned int vcalb_init_val; ++ unsigned int dither_st; ++ bool bsts_unreliable; ++ bool fw_needs_reload; ++}; ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev); ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable); ++int aw_dev_get_dsp_status(struct aw_device *aw_dev); ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value); ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc); ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name); ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute); ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel); ++void aw88399_hw_reset(struct aw88399 *aw88399); ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap); ++extern const struct regmap_config aw88399_remap_config; ++int aw88399_request_firmware_file(struct aw88399 *aw88399); ++void aw88399_start(struct aw88399 *aw88399, bool sync_start); ++void aw88399_startup_work(struct work_struct *work); ++int aw88399_stop(struct aw_device *aw_dev); ++ ++#endif /* __SOUND_AW88399_H */ +diff --git a/include/sound/control.h b/include/sound/control.h +--- a/include/sound/control.h ++++ b/include/sound/control.h +@@ -7,6 +7,7 @@ + * Copyright (c) by Jaroslav Kysela + */ + ++#include + #include + #include + #include +@@ -167,6 +168,8 @@ snd_ctl_find_id_mixer(struct snd_card *card, const char *name) + + int snd_ctl_create(struct snd_card *card); + ++extern struct rw_semaphore snd_ioctl_rwsem; ++ + int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn); + int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn); + #ifdef CONFIG_COMPAT +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -108,8 +108,8 @@ struct snd_card { + char longname[80]; /* name of this soundcard */ + char irq_descr[32]; /* Interrupt description */ + char mixername[80]; /* mixer name */ +- char components[128]; /* card components delimited with +- space */ ++ char *components; /* card components, space-delimited */ ++ unsigned int components_alloc_size; /* current allocation size of components */ + struct module *module; /* top-level module */ + + void *private_data; /* private data for soundcard */ +diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h +--- a/include/sound/hda_codec.h ++++ b/include/sound/hda_codec.h +@@ -256,6 +256,7 @@ struct hda_codec { + unsigned int link_down_at_suspend:1; /* link down at runtime suspend */ + unsigned int relaxed_resume:1; /* don't resume forcibly for jack */ + unsigned int forced_resume:1; /* forced resume for jack */ ++ unsigned int acomp_requested_resume:1; /* resume requested by acomp */ + unsigned int no_stream_clean_at_suspend:1; /* do not clean streams at suspend */ + unsigned int ctl_dev_id:1; /* old control element id build behaviour */ + unsigned int eld_jack_detect:1; /* Machine jack-detection by ELD */ +diff --git a/include/sound/hda_regmap.h b/include/sound/hda_regmap.h +--- a/include/sound/hda_regmap.h ++++ b/include/sound/hda_regmap.h +@@ -69,11 +69,14 @@ void snd_hdac_regmap_sync(struct hdac_device *codec); + + /** + * snd_hdac_regmap_write - Write a verb with caching ++ * @codec: HD-audio codec base device + * @nid: codec NID +- * @reg: verb to write ++ * @verb: verb to write + * @val: value to write + * + * For writing an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, +@@ -85,13 +88,16 @@ snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_update - Update a verb value with caching ++ * snd_hdac_regmap_update - Update a verb value with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to update + * @mask: bit mask to update + * @val: value to update + * + * For updating an amp value, use snd_hdac_regmap_update_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, +@@ -104,12 +110,15 @@ snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid, + } + + /** +- * snd_hda_regmap_read - Read a verb with caching ++ * snd_hdac_regmap_read - Read a verb with caching ++ * @codec: HD-audio codec + * @nid: codec NID + * @verb: verb to read + * @val: pointer to store the value + * + * For reading an amp value, use snd_hda_regmap_get_amp(). ++ * ++ * Returns: %0 if successful or a negative error code. + */ + static inline int + snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, +@@ -125,12 +134,12 @@ snd_hdac_regmap_read(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -148,13 +157,14 @@ snd_hdac_regmap_get_amp(struct hdac_device *codec, hda_nid_t nid, + * @codec: HD-audio codec + * @nid: NID to read the AMP value + * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the AMP value with a bit mask. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, +@@ -169,13 +179,12 @@ snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_get_amp_stereo - Read stereo AMP values + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @ch: channel (left=0 or right=1) +- * @direction: #HDA_INPUT or #HDA_OUTPUT +- * @index: the index value (only for input direction) +- * @val: the pointer to store the value ++ * @dir: #HDA_INPUT or #HDA_OUTPUT ++ * @idx: the index value (only for input direction) + * + * Read stereo AMP values. The lower byte is left, the upper byte is right. +- * Returns the value or a negative error. ++ * ++ * Returns: the value or a negative error. + */ + static inline int + snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +@@ -192,14 +201,15 @@ snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid, + * snd_hdac_regmap_update_amp_stereo - update the stereo AMP value + * @codec: HD-audio codec + * @nid: NID to read the AMP value +- * @direction: #HDA_INPUT or #HDA_OUTPUT ++ * @dir: #HDA_INPUT or #HDA_OUTPUT + * @idx: the index value (only for input direction) + * @mask: bit mask to set + * @val: the bits value to set + * + * Update the stereo AMP value with a bit mask. + * The lower byte is left, the upper byte is right. +- * Returns 0 if the value is unchanged, 1 if changed, or a negative error. ++ * ++ * Returns: 0 if the value is unchanged, 1 if changed, or a negative error. + */ + static inline int + snd_hdac_regmap_update_amp_stereo(struct hdac_device *codec, hda_nid_t nid, +diff --git a/include/sound/pcm.h b/include/sound/pcm.h +--- a/include/sound/pcm.h ++++ b/include/sound/pcm.h +@@ -145,61 +145,60 @@ struct snd_pcm_ops { + #define SNDRV_PCM_RATE_8000_768000 (SNDRV_PCM_RATE_8000_384000|\ + SNDRV_PCM_RATE_705600|\ + SNDRV_PCM_RATE_768000) +-#define _SNDRV_PCM_FMTBIT(fmt) (1ULL << (__force int)SNDRV_PCM_FORMAT_##fmt) +-#define SNDRV_PCM_FMTBIT_S8 _SNDRV_PCM_FMTBIT(S8) +-#define SNDRV_PCM_FMTBIT_U8 _SNDRV_PCM_FMTBIT(U8) +-#define SNDRV_PCM_FMTBIT_S16_LE _SNDRV_PCM_FMTBIT(S16_LE) +-#define SNDRV_PCM_FMTBIT_S16_BE _SNDRV_PCM_FMTBIT(S16_BE) +-#define SNDRV_PCM_FMTBIT_U16_LE _SNDRV_PCM_FMTBIT(U16_LE) +-#define SNDRV_PCM_FMTBIT_U16_BE _SNDRV_PCM_FMTBIT(U16_BE) +-#define SNDRV_PCM_FMTBIT_S24_LE _SNDRV_PCM_FMTBIT(S24_LE) +-#define SNDRV_PCM_FMTBIT_S24_BE _SNDRV_PCM_FMTBIT(S24_BE) +-#define SNDRV_PCM_FMTBIT_U24_LE _SNDRV_PCM_FMTBIT(U24_LE) +-#define SNDRV_PCM_FMTBIT_U24_BE _SNDRV_PCM_FMTBIT(U24_BE) ++#define SNDRV_PCM_FMTBIT_S8 (1ULL << SNDRV_PCM_FORMAT_S8) ++#define SNDRV_PCM_FMTBIT_U8 (1ULL << SNDRV_PCM_FORMAT_U8) ++#define SNDRV_PCM_FMTBIT_S16_LE (1ULL << SNDRV_PCM_FORMAT_S16_LE) ++#define SNDRV_PCM_FMTBIT_S16_BE (1ULL << SNDRV_PCM_FORMAT_S16_BE) ++#define SNDRV_PCM_FMTBIT_U16_LE (1ULL << SNDRV_PCM_FORMAT_U16_LE) ++#define SNDRV_PCM_FMTBIT_U16_BE (1ULL << SNDRV_PCM_FORMAT_U16_BE) ++#define SNDRV_PCM_FMTBIT_S24_LE (1ULL << SNDRV_PCM_FORMAT_S24_LE) ++#define SNDRV_PCM_FMTBIT_S24_BE (1ULL << SNDRV_PCM_FORMAT_S24_BE) ++#define SNDRV_PCM_FMTBIT_U24_LE (1ULL << SNDRV_PCM_FORMAT_U24_LE) ++#define SNDRV_PCM_FMTBIT_U24_BE (1ULL << SNDRV_PCM_FORMAT_U24_BE) + // For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + // available bit count in most significant bit. It's for the case of so-called 'left-justified' or + // `right-padding` sample which has less width than 32 bit. +-#define SNDRV_PCM_FMTBIT_S32_LE _SNDRV_PCM_FMTBIT(S32_LE) +-#define SNDRV_PCM_FMTBIT_S32_BE _SNDRV_PCM_FMTBIT(S32_BE) +-#define SNDRV_PCM_FMTBIT_U32_LE _SNDRV_PCM_FMTBIT(U32_LE) +-#define SNDRV_PCM_FMTBIT_U32_BE _SNDRV_PCM_FMTBIT(U32_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT_LE _SNDRV_PCM_FMTBIT(FLOAT_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT_BE _SNDRV_PCM_FMTBIT(FLOAT_BE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_LE _SNDRV_PCM_FMTBIT(FLOAT64_LE) +-#define SNDRV_PCM_FMTBIT_FLOAT64_BE _SNDRV_PCM_FMTBIT(FLOAT64_BE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_LE) +-#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE _SNDRV_PCM_FMTBIT(IEC958_SUBFRAME_BE) +-#define SNDRV_PCM_FMTBIT_MU_LAW _SNDRV_PCM_FMTBIT(MU_LAW) +-#define SNDRV_PCM_FMTBIT_A_LAW _SNDRV_PCM_FMTBIT(A_LAW) +-#define SNDRV_PCM_FMTBIT_IMA_ADPCM _SNDRV_PCM_FMTBIT(IMA_ADPCM) +-#define SNDRV_PCM_FMTBIT_MPEG _SNDRV_PCM_FMTBIT(MPEG) +-#define SNDRV_PCM_FMTBIT_GSM _SNDRV_PCM_FMTBIT(GSM) +-#define SNDRV_PCM_FMTBIT_S20_LE _SNDRV_PCM_FMTBIT(S20_LE) +-#define SNDRV_PCM_FMTBIT_U20_LE _SNDRV_PCM_FMTBIT(U20_LE) +-#define SNDRV_PCM_FMTBIT_S20_BE _SNDRV_PCM_FMTBIT(S20_BE) +-#define SNDRV_PCM_FMTBIT_U20_BE _SNDRV_PCM_FMTBIT(U20_BE) +-#define SNDRV_PCM_FMTBIT_SPECIAL _SNDRV_PCM_FMTBIT(SPECIAL) +-#define SNDRV_PCM_FMTBIT_S24_3LE _SNDRV_PCM_FMTBIT(S24_3LE) +-#define SNDRV_PCM_FMTBIT_U24_3LE _SNDRV_PCM_FMTBIT(U24_3LE) +-#define SNDRV_PCM_FMTBIT_S24_3BE _SNDRV_PCM_FMTBIT(S24_3BE) +-#define SNDRV_PCM_FMTBIT_U24_3BE _SNDRV_PCM_FMTBIT(U24_3BE) +-#define SNDRV_PCM_FMTBIT_S20_3LE _SNDRV_PCM_FMTBIT(S20_3LE) +-#define SNDRV_PCM_FMTBIT_U20_3LE _SNDRV_PCM_FMTBIT(U20_3LE) +-#define SNDRV_PCM_FMTBIT_S20_3BE _SNDRV_PCM_FMTBIT(S20_3BE) +-#define SNDRV_PCM_FMTBIT_U20_3BE _SNDRV_PCM_FMTBIT(U20_3BE) +-#define SNDRV_PCM_FMTBIT_S18_3LE _SNDRV_PCM_FMTBIT(S18_3LE) +-#define SNDRV_PCM_FMTBIT_U18_3LE _SNDRV_PCM_FMTBIT(U18_3LE) +-#define SNDRV_PCM_FMTBIT_S18_3BE _SNDRV_PCM_FMTBIT(S18_3BE) +-#define SNDRV_PCM_FMTBIT_U18_3BE _SNDRV_PCM_FMTBIT(U18_3BE) +-#define SNDRV_PCM_FMTBIT_G723_24 _SNDRV_PCM_FMTBIT(G723_24) +-#define SNDRV_PCM_FMTBIT_G723_24_1B _SNDRV_PCM_FMTBIT(G723_24_1B) +-#define SNDRV_PCM_FMTBIT_G723_40 _SNDRV_PCM_FMTBIT(G723_40) +-#define SNDRV_PCM_FMTBIT_G723_40_1B _SNDRV_PCM_FMTBIT(G723_40_1B) +-#define SNDRV_PCM_FMTBIT_DSD_U8 _SNDRV_PCM_FMTBIT(DSD_U8) +-#define SNDRV_PCM_FMTBIT_DSD_U16_LE _SNDRV_PCM_FMTBIT(DSD_U16_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_LE _SNDRV_PCM_FMTBIT(DSD_U32_LE) +-#define SNDRV_PCM_FMTBIT_DSD_U16_BE _SNDRV_PCM_FMTBIT(DSD_U16_BE) +-#define SNDRV_PCM_FMTBIT_DSD_U32_BE _SNDRV_PCM_FMTBIT(DSD_U32_BE) ++#define SNDRV_PCM_FMTBIT_S32_LE (1ULL << SNDRV_PCM_FORMAT_S32_LE) ++#define SNDRV_PCM_FMTBIT_S32_BE (1ULL << SNDRV_PCM_FORMAT_S32_BE) ++#define SNDRV_PCM_FMTBIT_U32_LE (1ULL << SNDRV_PCM_FORMAT_U32_LE) ++#define SNDRV_PCM_FMTBIT_U32_BE (1ULL << SNDRV_PCM_FORMAT_U32_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT_BE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_LE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_LE) ++#define SNDRV_PCM_FMTBIT_FLOAT64_BE (1ULL << SNDRV_PCM_FORMAT_FLOAT64_BE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) ++#define SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE (1ULL << SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE) ++#define SNDRV_PCM_FMTBIT_MU_LAW (1ULL << SNDRV_PCM_FORMAT_MU_LAW) ++#define SNDRV_PCM_FMTBIT_A_LAW (1ULL << SNDRV_PCM_FORMAT_A_LAW) ++#define SNDRV_PCM_FMTBIT_IMA_ADPCM (1ULL << SNDRV_PCM_FORMAT_IMA_ADPCM) ++#define SNDRV_PCM_FMTBIT_MPEG (1ULL << SNDRV_PCM_FORMAT_MPEG) ++#define SNDRV_PCM_FMTBIT_GSM (1ULL << SNDRV_PCM_FORMAT_GSM) ++#define SNDRV_PCM_FMTBIT_S20_LE (1ULL << SNDRV_PCM_FORMAT_S20_LE) ++#define SNDRV_PCM_FMTBIT_U20_LE (1ULL << SNDRV_PCM_FORMAT_U20_LE) ++#define SNDRV_PCM_FMTBIT_S20_BE (1ULL << SNDRV_PCM_FORMAT_S20_BE) ++#define SNDRV_PCM_FMTBIT_U20_BE (1ULL << SNDRV_PCM_FORMAT_U20_BE) ++#define SNDRV_PCM_FMTBIT_SPECIAL (1ULL << SNDRV_PCM_FORMAT_SPECIAL) ++#define SNDRV_PCM_FMTBIT_S24_3LE (1ULL << SNDRV_PCM_FORMAT_S24_3LE) ++#define SNDRV_PCM_FMTBIT_U24_3LE (1ULL << SNDRV_PCM_FORMAT_U24_3LE) ++#define SNDRV_PCM_FMTBIT_S24_3BE (1ULL << SNDRV_PCM_FORMAT_S24_3BE) ++#define SNDRV_PCM_FMTBIT_U24_3BE (1ULL << SNDRV_PCM_FORMAT_U24_3BE) ++#define SNDRV_PCM_FMTBIT_S20_3LE (1ULL << SNDRV_PCM_FORMAT_S20_3LE) ++#define SNDRV_PCM_FMTBIT_U20_3LE (1ULL << SNDRV_PCM_FORMAT_U20_3LE) ++#define SNDRV_PCM_FMTBIT_S20_3BE (1ULL << SNDRV_PCM_FORMAT_S20_3BE) ++#define SNDRV_PCM_FMTBIT_U20_3BE (1ULL << SNDRV_PCM_FORMAT_U20_3BE) ++#define SNDRV_PCM_FMTBIT_S18_3LE (1ULL << SNDRV_PCM_FORMAT_S18_3LE) ++#define SNDRV_PCM_FMTBIT_U18_3LE (1ULL << SNDRV_PCM_FORMAT_U18_3LE) ++#define SNDRV_PCM_FMTBIT_S18_3BE (1ULL << SNDRV_PCM_FORMAT_S18_3BE) ++#define SNDRV_PCM_FMTBIT_U18_3BE (1ULL << SNDRV_PCM_FORMAT_U18_3BE) ++#define SNDRV_PCM_FMTBIT_G723_24 (1ULL << SNDRV_PCM_FORMAT_G723_24) ++#define SNDRV_PCM_FMTBIT_G723_24_1B (1ULL << SNDRV_PCM_FORMAT_G723_24_1B) ++#define SNDRV_PCM_FMTBIT_G723_40 (1ULL << SNDRV_PCM_FORMAT_G723_40) ++#define SNDRV_PCM_FMTBIT_G723_40_1B (1ULL << SNDRV_PCM_FORMAT_G723_40_1B) ++#define SNDRV_PCM_FMTBIT_DSD_U8 (1ULL << SNDRV_PCM_FORMAT_DSD_U8) ++#define SNDRV_PCM_FMTBIT_DSD_U16_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_LE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_LE) ++#define SNDRV_PCM_FMTBIT_DSD_U16_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U16_BE) ++#define SNDRV_PCM_FMTBIT_DSD_U32_BE (1ULL << SNDRV_PCM_FORMAT_DSD_U32_BE) + + #ifdef SNDRV_LITTLE_ENDIAN + #define SNDRV_PCM_FMTBIT_S16 SNDRV_PCM_FMTBIT_S16_LE +@@ -228,11 +227,10 @@ struct snd_pcm_ops { + #define SNDRV_PCM_FMTBIT_U20 SNDRV_PCM_FMTBIT_U20_BE + #endif + +-#define _SNDRV_PCM_SUBFMTBIT(fmt) BIT((__force int)SNDRV_PCM_SUBFORMAT_##fmt) +-#define SNDRV_PCM_SUBFMTBIT_STD _SNDRV_PCM_SUBFMTBIT(STD) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX _SNDRV_PCM_SUBFMTBIT(MSBITS_MAX) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 _SNDRV_PCM_SUBFMTBIT(MSBITS_20) +-#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 _SNDRV_PCM_SUBFMTBIT(MSBITS_24) ++#define SNDRV_PCM_SUBFMTBIT_STD (1U << SNDRV_PCM_SUBFORMAT_STD) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_MAX (1U << SNDRV_PCM_SUBFORMAT_MSBITS_MAX) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_20 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_20) ++#define SNDRV_PCM_SUBFMTBIT_MSBITS_24 (1U << SNDRV_PCM_SUBFORMAT_MSBITS_24) + + struct snd_pcm_file { + struct snd_pcm_substream *substream; +@@ -1515,7 +1513,7 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, + */ + static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + { +- return 1ULL << (__force int) pcm_format; ++ return 1ULL << pcm_format; + } + + /** +@@ -1523,9 +1521,7 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format) + * @f: the iterator variable in snd_pcm_format_t type + */ + #define pcm_for_each_format(f) \ +- for ((f) = SNDRV_PCM_FORMAT_FIRST; \ +- (__force int)(f) <= (__force int)SNDRV_PCM_FORMAT_LAST; \ +- (f) = (__force snd_pcm_format_t)((__force int)(f) + 1)) ++ for ((f) = SNDRV_PCM_FORMAT_FIRST; (f) <= SNDRV_PCM_FORMAT_LAST; (f)++) + + /* printk helpers */ + #define pcm_err(pcm, fmt, args...) \ +diff --git a/include/sound/pcm_params.h b/include/sound/pcm_params.h +--- a/include/sound/pcm_params.h ++++ b/include/sound/pcm_params.h +@@ -71,7 +71,7 @@ static inline void snd_mask_set(struct snd_mask *mask, unsigned int val) + static inline void snd_mask_set_format(struct snd_mask *mask, + snd_pcm_format_t format) + { +- snd_mask_set(mask, (__force unsigned int)format); ++ snd_mask_set(mask, format); + } + + static inline void snd_mask_reset(struct snd_mask *mask, unsigned int val) +@@ -132,7 +132,7 @@ static inline int snd_mask_test(const struct snd_mask *mask, unsigned int val) + static inline int snd_mask_test_format(const struct snd_mask *mask, + snd_pcm_format_t format) + { +- return snd_mask_test(mask, (__force unsigned int)format); ++ return snd_mask_test(mask, format); + } + + static inline int snd_mask_single(const struct snd_mask *mask) +@@ -302,8 +302,7 @@ static inline int snd_interval_eq(const struct snd_interval *i1, const struct sn + */ + static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_access_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_ACCESS)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_ACCESS)); + } + + /** +@@ -312,8 +311,7 @@ static inline snd_pcm_access_t params_access(const struct snd_pcm_hw_params *p) + */ + static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_format_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_FORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_FORMAT)); + } + + /** +@@ -323,8 +321,7 @@ static inline snd_pcm_format_t params_format(const struct snd_pcm_hw_params *p) + static inline snd_pcm_subformat_t + params_subformat(const struct snd_pcm_hw_params *p) + { +- return (__force snd_pcm_subformat_t)snd_mask_min(hw_param_mask_c(p, +- SNDRV_PCM_HW_PARAM_SUBFORMAT)); ++ return snd_mask_min(hw_param_mask_c(p, SNDRV_PCM_HW_PARAM_SUBFORMAT)); + } + + /** +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -176,8 +176,9 @@ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream); + /* main midi functions */ + + int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info); +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); +@@ -191,6 +192,15 @@ long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, + long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, + const unsigned char *buf, long count); + ++/* non-nested version */ ++static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, ++ int subdevice, ++ int mode, ++ struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/include/sound/seq_virmidi.h b/include/sound/seq_virmidi.h +--- a/include/sound/seq_virmidi.h ++++ b/include/sound/seq_virmidi.h +@@ -46,7 +46,6 @@ struct snd_virmidi_dev { + int client; /* created/attached client */ + int port; /* created/attached port */ + unsigned int flags; /* SNDRV_VIRMIDI_* */ +- rwlock_t filelist_lock; + struct rw_semaphore filelist_sem; + struct list_head filelist; + }; +diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h +--- a/include/uapi/sound/asequencer.h ++++ b/include/uapi/sound/asequencer.h +@@ -308,16 +308,6 @@ struct snd_seq_ump_event { + }; + }; + +-/* +- * bounce event - stored as variable size data +- */ +-struct snd_seq_event_bounce { +- int err; +- struct snd_seq_event event; +- /* external data follows here. */ +-}; +- +- + /* system information */ + struct snd_seq_system_info { + int queues; /* maximum queues count */ +@@ -348,10 +338,10 @@ struct snd_seq_running_info { + + + /* client types */ +-typedef int __bitwise snd_seq_client_type_t; +-#define NO_CLIENT ((__force snd_seq_client_type_t) 0) +-#define USER_CLIENT ((__force snd_seq_client_type_t) 1) +-#define KERNEL_CLIENT ((__force snd_seq_client_type_t) 2) ++typedef int snd_seq_client_type_t; ++#define NO_CLIENT 0 ++#define USER_CLIENT 1 ++#define KERNEL_CLIENT 2 + + /* event filter flags */ + #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ +diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h +--- a/include/uapi/sound/asound.h ++++ b/include/uapi/sound/asound.h +@@ -169,72 +169,72 @@ enum { + SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, + }; + +-typedef int __bitwise snd_pcm_access_t; +-#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED ((__force snd_pcm_access_t) 0) /* interleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED ((__force snd_pcm_access_t) 1) /* noninterleaved mmap */ +-#define SNDRV_PCM_ACCESS_MMAP_COMPLEX ((__force snd_pcm_access_t) 2) /* complex mmap */ +-#define SNDRV_PCM_ACCESS_RW_INTERLEAVED ((__force snd_pcm_access_t) 3) /* readi/writei */ +-#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ((__force snd_pcm_access_t) 4) /* readn/writen */ ++typedef int snd_pcm_access_t; ++#define SNDRV_PCM_ACCESS_MMAP_INTERLEAVED 0 /* interleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED 1 /* noninterleaved mmap */ ++#define SNDRV_PCM_ACCESS_MMAP_COMPLEX 2 /* complex mmap */ ++#define SNDRV_PCM_ACCESS_RW_INTERLEAVED 3 /* readi/writei */ ++#define SNDRV_PCM_ACCESS_RW_NONINTERLEAVED 4 /* readn/writen */ + #define SNDRV_PCM_ACCESS_LAST SNDRV_PCM_ACCESS_RW_NONINTERLEAVED + +-typedef int __bitwise snd_pcm_format_t; +-#define SNDRV_PCM_FORMAT_S8 ((__force snd_pcm_format_t) 0) +-#define SNDRV_PCM_FORMAT_U8 ((__force snd_pcm_format_t) 1) +-#define SNDRV_PCM_FORMAT_S16_LE ((__force snd_pcm_format_t) 2) +-#define SNDRV_PCM_FORMAT_S16_BE ((__force snd_pcm_format_t) 3) +-#define SNDRV_PCM_FORMAT_U16_LE ((__force snd_pcm_format_t) 4) +-#define SNDRV_PCM_FORMAT_U16_BE ((__force snd_pcm_format_t) 5) +-#define SNDRV_PCM_FORMAT_S24_LE ((__force snd_pcm_format_t) 6) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_S24_BE ((__force snd_pcm_format_t) 7) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_LE ((__force snd_pcm_format_t) 8) /* low three bytes */ +-#define SNDRV_PCM_FORMAT_U24_BE ((__force snd_pcm_format_t) 9) /* low three bytes */ ++typedef int snd_pcm_format_t; ++#define SNDRV_PCM_FORMAT_S8 0 ++#define SNDRV_PCM_FORMAT_U8 1 ++#define SNDRV_PCM_FORMAT_S16_LE 2 ++#define SNDRV_PCM_FORMAT_S16_BE 3 ++#define SNDRV_PCM_FORMAT_U16_LE 4 ++#define SNDRV_PCM_FORMAT_U16_BE 5 ++#define SNDRV_PCM_FORMAT_S24_LE 6 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_S24_BE 7 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_LE 8 /* low three bytes */ ++#define SNDRV_PCM_FORMAT_U24_BE 9 /* low three bytes */ + /* + * For S32/U32 formats, 'msbits' hardware parameter is often used to deliver information about the + * available bit count in most significant bit. It's for the case of so-called 'left-justified' or + * `right-padding` sample which has less width than 32 bit. + */ +-#define SNDRV_PCM_FORMAT_S32_LE ((__force snd_pcm_format_t) 10) +-#define SNDRV_PCM_FORMAT_S32_BE ((__force snd_pcm_format_t) 11) +-#define SNDRV_PCM_FORMAT_U32_LE ((__force snd_pcm_format_t) 12) +-#define SNDRV_PCM_FORMAT_U32_BE ((__force snd_pcm_format_t) 13) +-#define SNDRV_PCM_FORMAT_FLOAT_LE ((__force snd_pcm_format_t) 14) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT_BE ((__force snd_pcm_format_t) 15) /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_LE ((__force snd_pcm_format_t) 16) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_FLOAT64_BE ((__force snd_pcm_format_t) 17) /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((__force snd_pcm_format_t) 18) /* IEC-958 subframe, Little Endian */ +-#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((__force snd_pcm_format_t) 19) /* IEC-958 subframe, Big Endian */ +-#define SNDRV_PCM_FORMAT_MU_LAW ((__force snd_pcm_format_t) 20) +-#define SNDRV_PCM_FORMAT_A_LAW ((__force snd_pcm_format_t) 21) +-#define SNDRV_PCM_FORMAT_IMA_ADPCM ((__force snd_pcm_format_t) 22) +-#define SNDRV_PCM_FORMAT_MPEG ((__force snd_pcm_format_t) 23) +-#define SNDRV_PCM_FORMAT_GSM ((__force snd_pcm_format_t) 24) +-#define SNDRV_PCM_FORMAT_S20_LE ((__force snd_pcm_format_t) 25) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_S20_BE ((__force snd_pcm_format_t) 26) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_LE ((__force snd_pcm_format_t) 27) /* in four bytes, LSB justified */ +-#define SNDRV_PCM_FORMAT_U20_BE ((__force snd_pcm_format_t) 28) /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S32_LE 10 ++#define SNDRV_PCM_FORMAT_S32_BE 11 ++#define SNDRV_PCM_FORMAT_U32_LE 12 ++#define SNDRV_PCM_FORMAT_U32_BE 13 ++#define SNDRV_PCM_FORMAT_FLOAT_LE 14 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT_BE 15 /* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_LE 16 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_FLOAT64_BE 17 /* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE 18 /* IEC-958 subframe, Little Endian */ ++#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE 19 /* IEC-958 subframe, Big Endian */ ++#define SNDRV_PCM_FORMAT_MU_LAW 20 ++#define SNDRV_PCM_FORMAT_A_LAW 21 ++#define SNDRV_PCM_FORMAT_IMA_ADPCM 22 ++#define SNDRV_PCM_FORMAT_MPEG 23 ++#define SNDRV_PCM_FORMAT_GSM 24 ++#define SNDRV_PCM_FORMAT_S20_LE 25 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_S20_BE 26 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_LE 27 /* in four bytes, LSB justified */ ++#define SNDRV_PCM_FORMAT_U20_BE 28 /* in four bytes, LSB justified */ + /* gap in the numbering for a future standard linear format */ +-#define SNDRV_PCM_FORMAT_SPECIAL ((__force snd_pcm_format_t) 31) +-#define SNDRV_PCM_FORMAT_S24_3LE ((__force snd_pcm_format_t) 32) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S24_3BE ((__force snd_pcm_format_t) 33) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3LE ((__force snd_pcm_format_t) 34) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U24_3BE ((__force snd_pcm_format_t) 35) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3LE ((__force snd_pcm_format_t) 36) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S20_3BE ((__force snd_pcm_format_t) 37) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3LE ((__force snd_pcm_format_t) 38) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U20_3BE ((__force snd_pcm_format_t) 39) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3LE ((__force snd_pcm_format_t) 40) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_S18_3BE ((__force snd_pcm_format_t) 41) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3LE ((__force snd_pcm_format_t) 42) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_U18_3BE ((__force snd_pcm_format_t) 43) /* in three bytes */ +-#define SNDRV_PCM_FORMAT_G723_24 ((__force snd_pcm_format_t) 44) /* 8 samples in 3 bytes */ +-#define SNDRV_PCM_FORMAT_G723_24_1B ((__force snd_pcm_format_t) 45) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_G723_40 ((__force snd_pcm_format_t) 46) /* 8 Samples in 5 bytes */ +-#define SNDRV_PCM_FORMAT_G723_40_1B ((__force snd_pcm_format_t) 47) /* 1 sample in 1 byte */ +-#define SNDRV_PCM_FORMAT_DSD_U8 ((__force snd_pcm_format_t) 48) /* DSD, 1-byte samples DSD (x8) */ +-#define SNDRV_PCM_FORMAT_DSD_U16_LE ((__force snd_pcm_format_t) 49) /* DSD, 2-byte samples DSD (x16), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_LE ((__force snd_pcm_format_t) 50) /* DSD, 4-byte samples DSD (x32), little endian */ +-#define SNDRV_PCM_FORMAT_DSD_U16_BE ((__force snd_pcm_format_t) 51) /* DSD, 2-byte samples DSD (x16), big endian */ +-#define SNDRV_PCM_FORMAT_DSD_U32_BE ((__force snd_pcm_format_t) 52) /* DSD, 4-byte samples DSD (x32), big endian */ ++#define SNDRV_PCM_FORMAT_SPECIAL 31 ++#define SNDRV_PCM_FORMAT_S24_3LE 32 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S24_3BE 33 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3LE 34 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U24_3BE 35 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3LE 36 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S20_3BE 37 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3LE 38 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U20_3BE 39 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3LE 40 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_S18_3BE 41 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3LE 42 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_U18_3BE 43 /* in three bytes */ ++#define SNDRV_PCM_FORMAT_G723_24 44 /* 8 samples in 3 bytes */ ++#define SNDRV_PCM_FORMAT_G723_24_1B 45 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_G723_40 46 /* 8 Samples in 5 bytes */ ++#define SNDRV_PCM_FORMAT_G723_40_1B 47 /* 1 sample in 1 byte */ ++#define SNDRV_PCM_FORMAT_DSD_U8 48 /* DSD, 1-byte samples DSD (x8) */ ++#define SNDRV_PCM_FORMAT_DSD_U16_LE 49 /* DSD, 2-byte samples DSD (x16), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_LE 50 /* DSD, 4-byte samples DSD (x32), little endian */ ++#define SNDRV_PCM_FORMAT_DSD_U16_BE 51 /* DSD, 2-byte samples DSD (x16), big endian */ ++#define SNDRV_PCM_FORMAT_DSD_U32_BE 52 /* DSD, 4-byte samples DSD (x32), big endian */ + #define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE + #define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 + +@@ -265,11 +265,11 @@ typedef int __bitwise snd_pcm_format_t; + #define SNDRV_PCM_FORMAT_U20 SNDRV_PCM_FORMAT_U20_BE + #endif + +-typedef int __bitwise snd_pcm_subformat_t; +-#define SNDRV_PCM_SUBFORMAT_STD ((__force snd_pcm_subformat_t) 0) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX ((__force snd_pcm_subformat_t) 1) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_20 ((__force snd_pcm_subformat_t) 2) +-#define SNDRV_PCM_SUBFORMAT_MSBITS_24 ((__force snd_pcm_subformat_t) 3) ++typedef int snd_pcm_subformat_t; ++#define SNDRV_PCM_SUBFORMAT_STD 0 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_MAX 1 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_20 2 ++#define SNDRV_PCM_SUBFORMAT_MSBITS_24 3 + #define SNDRV_PCM_SUBFORMAT_LAST SNDRV_PCM_SUBFORMAT_MSBITS_24 + + #define SNDRV_PCM_INFO_MMAP 0x00000001 /* hardware supports mmap */ +@@ -303,16 +303,16 @@ typedef int __bitwise snd_pcm_subformat_t; + #define __SND_STRUCT_TIME64 + #endif + +-typedef int __bitwise snd_pcm_state_t; +-#define SNDRV_PCM_STATE_OPEN ((__force snd_pcm_state_t) 0) /* stream is open */ +-#define SNDRV_PCM_STATE_SETUP ((__force snd_pcm_state_t) 1) /* stream has a setup */ +-#define SNDRV_PCM_STATE_PREPARED ((__force snd_pcm_state_t) 2) /* stream is ready to start */ +-#define SNDRV_PCM_STATE_RUNNING ((__force snd_pcm_state_t) 3) /* stream is running */ +-#define SNDRV_PCM_STATE_XRUN ((__force snd_pcm_state_t) 4) /* stream reached an xrun */ +-#define SNDRV_PCM_STATE_DRAINING ((__force snd_pcm_state_t) 5) /* stream is draining */ +-#define SNDRV_PCM_STATE_PAUSED ((__force snd_pcm_state_t) 6) /* stream is paused */ +-#define SNDRV_PCM_STATE_SUSPENDED ((__force snd_pcm_state_t) 7) /* hardware is suspended */ +-#define SNDRV_PCM_STATE_DISCONNECTED ((__force snd_pcm_state_t) 8) /* hardware is disconnected */ ++typedef int snd_pcm_state_t; ++#define SNDRV_PCM_STATE_OPEN 0 /* stream is open */ ++#define SNDRV_PCM_STATE_SETUP 1 /* stream has a setup */ ++#define SNDRV_PCM_STATE_PREPARED 2 /* stream is ready to start */ ++#define SNDRV_PCM_STATE_RUNNING 3 /* stream is running */ ++#define SNDRV_PCM_STATE_XRUN 4 /* stream reached an xrun */ ++#define SNDRV_PCM_STATE_DRAINING 5 /* stream is draining */ ++#define SNDRV_PCM_STATE_PAUSED 6 /* stream is paused */ ++#define SNDRV_PCM_STATE_SUSPENDED 7 /* hardware is suspended */ ++#define SNDRV_PCM_STATE_DISCONNECTED 8 /* hardware is disconnected */ + #define SNDRV_PCM_STATE_LAST SNDRV_PCM_STATE_DISCONNECTED + + enum { +@@ -1058,7 +1058,7 @@ struct snd_timer_tread { + * * + ****************************************************************************/ + +-#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) ++#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 10) + + struct snd_ctl_card_info { + int card; /* card number */ +@@ -1072,24 +1072,43 @@ struct snd_ctl_card_info { + unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ + }; + +-typedef int __bitwise snd_ctl_elem_type_t; +-#define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */ +-#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) /* integer type */ +-#define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) /* enumerated type */ +-#define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) /* byte array */ +-#define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) /* IEC958 (S/PDIF) setup */ +-#define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) /* 64-bit integer type */ ++/* ++ * Card components can exceed the fixed 128 bytes in snd_ctl_card_info. ++ * Use SNDRV_CTL_IOCTL_CARD_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS ++ * to retrieve the full string. ++ */ ++ ++/* Type values for struct snd_ctl_card_bytes::type */ ++enum { ++ SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */ ++}; ++ ++struct snd_ctl_card_bytes { ++ __u32 type; /* SND_CTL_CARD_BTYPE_* */ ++ __u32 data_allocated; /* size of @data buffer in bytes */ ++ __u32 data_len; /* in/out: actual data length in bytes */ ++ __u32 reserved; /* explicit pad */ ++ __u64 data; /* user buffer (pointer stored as __u64) */ ++}; ++ ++typedef int snd_ctl_elem_type_t; ++#define SNDRV_CTL_ELEM_TYPE_NONE 0 /* invalid */ ++#define SNDRV_CTL_ELEM_TYPE_BOOLEAN 1 /* boolean type */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER 2 /* integer type */ ++#define SNDRV_CTL_ELEM_TYPE_ENUMERATED 3 /* enumerated type */ ++#define SNDRV_CTL_ELEM_TYPE_BYTES 4 /* byte array */ ++#define SNDRV_CTL_ELEM_TYPE_IEC958 5 /* IEC958 (S/PDIF) setup */ ++#define SNDRV_CTL_ELEM_TYPE_INTEGER64 6 /* 64-bit integer type */ + #define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64 + +-typedef int __bitwise snd_ctl_elem_iface_t; +-#define SNDRV_CTL_ELEM_IFACE_CARD ((__force snd_ctl_elem_iface_t) 0) /* global control */ +-#define SNDRV_CTL_ELEM_IFACE_HWDEP ((__force snd_ctl_elem_iface_t) 1) /* hardware dependent device */ +-#define SNDRV_CTL_ELEM_IFACE_MIXER ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */ +-#define SNDRV_CTL_ELEM_IFACE_PCM ((__force snd_ctl_elem_iface_t) 3) /* PCM device */ +-#define SNDRV_CTL_ELEM_IFACE_RAWMIDI ((__force snd_ctl_elem_iface_t) 4) /* RawMidi device */ +-#define SNDRV_CTL_ELEM_IFACE_TIMER ((__force snd_ctl_elem_iface_t) 5) /* timer device */ +-#define SNDRV_CTL_ELEM_IFACE_SEQUENCER ((__force snd_ctl_elem_iface_t) 6) /* sequencer client */ ++typedef int snd_ctl_elem_iface_t; ++#define SNDRV_CTL_ELEM_IFACE_CARD 0 /* global control */ ++#define SNDRV_CTL_ELEM_IFACE_HWDEP 1 /* hardware dependent device */ ++#define SNDRV_CTL_ELEM_IFACE_MIXER 2 /* virtual mixer device */ ++#define SNDRV_CTL_ELEM_IFACE_PCM 3 /* PCM device */ ++#define SNDRV_CTL_ELEM_IFACE_RAWMIDI 4 /* RawMidi device */ ++#define SNDRV_CTL_ELEM_IFACE_TIMER 5 /* timer device */ ++#define SNDRV_CTL_ELEM_IFACE_SEQUENCER 6 /* sequencer client */ + #define SNDRV_CTL_ELEM_IFACE_LAST SNDRV_CTL_ELEM_IFACE_SEQUENCER + + #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) +@@ -1198,6 +1217,7 @@ struct snd_ctl_tlv { + + #define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) + #define SNDRV_CTL_IOCTL_CARD_INFO _IOR('U', 0x01, struct snd_ctl_card_info) ++#define SNDRV_CTL_IOCTL_CARD_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes) + #define SNDRV_CTL_IOCTL_ELEM_LIST _IOWR('U', 0x10, struct snd_ctl_elem_list) + #define SNDRV_CTL_IOCTL_ELEM_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info) + #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) +diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h +--- a/include/uapi/sound/firewire.h ++++ b/include/uapi/sound/firewire.h +@@ -79,11 +79,12 @@ struct snd_firewire_event_motu_register_dsp_change { + * + * @type: Fixed to SNDRV_FIREWIRE_EVENT_FF400_MESSAGE. + * @message_count: The number of messages. ++ * @messages: Array of @message_count messages + * @messages.message: The messages expressing hardware knob operation. + * @messages.tstamp: The isochronous cycle at which the request subaction of asynchronous +- * transaction was sent to deliver the message. It has 16 bit unsigned integer ++ * transaction was sent to deliver the message. It has 16-bit unsigned integer + * value. The higher 3 bits of value expresses the lower three bits of second +- * field in the format of CYCLE_TIME, up to 7. The rest 13 bits expresses cycle ++ * field in the format of CYCLE_TIME, up to 7. The remaining 13 bits express cycle + * field up to 7999. + * + * The structure expresses message transmitted by Fireface 400 when operating hardware knob. +@@ -191,8 +192,9 @@ struct snd_firewire_motu_register_dsp_meter { + #define SNDRV_FIREWIRE_MOTU_REGISTER_DSP_ALIGNED_INPUT_COUNT (SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT + 2) + + /** +- * snd_firewire_motu_register_dsp_parameter - the container for parameters of DSP controlled +- * by register access. ++ * struct snd_firewire_motu_register_dsp_parameter - the container for parameters ++ * of DSP controlled by register access. ++ * @mixer: aggregate of @mixer.source and @mixer.output + * @mixer.source.gain: The gain of source to mixer. + * @mixer.source.pan: The L/R balance of source to mixer. + * @mixer.source.flag: The flag of source to mixer, including mute, solo. +@@ -200,21 +202,25 @@ struct snd_firewire_motu_register_dsp_meter { + * Audio Express. + * @mixer.source.paired_width: The width of paired source to mixer, only for 4 pre and + * Audio Express. ++ * @mixer.output: FIXME + * @mixer.output.paired_volume: The volume of paired output from mixer. + * @mixer.output.paired_flag: The flag of paired output from mixer. ++ * @output: output parameters + * @output.main_paired_volume: The volume of paired main output. + * @output.hp_paired_volume: The volume of paired hp output. + * @output.hp_paired_assignment: The source assigned to paired hp output. +- * @output.reserved: Padding for 32 bit alignment for future extension. ++ * @output.reserved: Padding for 32-bit alignment for future extension. ++ * @line_input: line input parameters + * @line_input.boost_flag: The flags of boost for line inputs, only for 828mk2 and Traveler. + * @line_input.nominal_level_flag: The flags of nominal level for line inputs, only for 828mk2 and + * Traveler. +- * @line_input.reserved: Padding for 32 bit alignment for future extension. ++ * @line_input.reserved: Padding for 32-bit alignment for future extension. ++ * @input: input parameters + * @input.gain_and_invert: The value including gain and invert for input, only for Ultralite, 4 pre + * and Audio Express. + * @input.flag: The flag of input; e.g. jack detection, phantom power, and pad, only for Ultralite, + * 4 pre and Audio express. +- * @reserved: Padding so that the size of structure is kept to 512 byte, but for future extension. ++ * @reserved: Padding so that the size of structure is kept to 512 bytes, but for future extension. + * + * The structure expresses the set of parameters for DSP controlled by register access. + */ +@@ -272,8 +278,8 @@ struct snd_firewire_motu_register_dsp_parameter { + * controlled by command + * @data: Signal level meters. The mapping between position and signal channel is model-dependent. + * +- * The structure expresses the part of DSP status for hardware meter. The 32 bit storage is +- * estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is ++ * The structure expresses the part of DSP status for hardware meter. The 32-bit storage is ++ * estimated to include IEEE 764 32-bit single precision floating point (binary32) value. It is + * expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0. + */ + struct snd_firewire_motu_command_dsp_meter { +diff --git a/sound/atmel/ac97c.c b/sound/atmel/ac97c.c +--- a/sound/atmel/ac97c.c ++++ b/sound/atmel/ac97c.c +@@ -693,7 +693,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct snd_card *card; + struct atmel_ac97c *chip; +- struct resource *regs; ++ void __iomem *regs; + struct clk *pclk; + static const struct snd_ac97_bus_ops ops = { + .write = atmel_ac97c_write, +@@ -702,42 +702,34 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + int retval; + int irq; + +- regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- if (!regs) { +- dev_dbg(&pdev->dev, "no memory resource\n"); +- return -ENXIO; +- } ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- dev_dbg(&pdev->dev, "could not get irq: %d\n", irq); ++ if (irq < 0) + return irq; +- } + +- pclk = clk_get(&pdev->dev, "ac97_clk"); ++ pclk = devm_clk_get_enabled(&pdev->dev, "ac97_clk"); + if (IS_ERR(pclk)) { + dev_dbg(&pdev->dev, "no peripheral clock\n"); + return PTR_ERR(pclk); + } +- retval = clk_prepare_enable(pclk); +- if (retval) +- goto err_prepare_enable; + +- retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, ++ retval = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct atmel_ac97c), &card); + if (retval) { + dev_dbg(&pdev->dev, "could not create sound card device\n"); +- goto err_snd_card_new; ++ return retval; + } + + chip = get_chip(card); + +- retval = request_irq(irq, atmel_ac97c_interrupt, 0, "AC97C", chip); +- if (retval) { +- dev_dbg(&pdev->dev, "unable to request irq %d\n", irq); +- goto err_request_irq; +- } ++ retval = devm_request_irq(&pdev->dev, irq, atmel_ac97c_interrupt, 0, "AC97C", chip); ++ if (retval) ++ return retval; ++ + chip->irq = irq; + + spin_lock_init(&chip->lock); +@@ -749,13 +741,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->card = card; + chip->pclk = pclk; + chip->pdev = pdev; +- chip->regs = ioremap(regs->start, resource_size(regs)); +- +- if (!chip->regs) { +- dev_dbg(&pdev->dev, "could not remap register memory\n"); +- retval = -ENOMEM; +- goto err_ioremap; +- } ++ chip->regs = regs; + + chip->reset_pin = devm_gpiod_get_index(dev, "ac97", 2, GPIOD_OUT_HIGH); + if (IS_ERR(chip->reset_pin)) +@@ -770,25 +756,25 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + retval = snd_ac97_bus(card, 0, &ops, chip, &chip->ac97_bus); + if (retval) { + dev_dbg(&pdev->dev, "could not register on ac97 bus\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_mixer_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 mixer\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = atmel_ac97c_pcm_new(chip); + if (retval) { + dev_dbg(&pdev->dev, "could not register ac97 pcm device\n"); +- goto err_ac97_bus; ++ return retval; + } + + retval = snd_card_register(card); + if (retval) { + dev_dbg(&pdev->dev, "could not register sound card\n"); +- goto err_ac97_bus; ++ return retval; + } + + platform_set_drvdata(pdev, card); +@@ -797,18 +783,6 @@ static int atmel_ac97c_probe(struct platform_device *pdev) + chip->regs, irq); + + return 0; +- +-err_ac97_bus: +- iounmap(chip->regs); +-err_ioremap: +- free_irq(irq, chip); +-err_request_irq: +- snd_card_free(card); +-err_snd_card_new: +- clk_disable_unprepare(pclk); +-err_prepare_enable: +- clk_put(pclk); +- return retval; + } + + static int atmel_ac97c_suspend(struct device *pdev) +@@ -839,13 +813,6 @@ static void atmel_ac97c_remove(struct platform_device *pdev) + ac97c_writel(chip, CAMR, 0); + ac97c_writel(chip, COMR, 0); + ac97c_writel(chip, MR, 0); +- +- clk_disable_unprepare(chip->pclk); +- clk_put(chip->pclk); +- iounmap(chip->regs); +- free_irq(chip->irq, chip); +- +- snd_card_free(card); + } + + static struct platform_driver atmel_ac97c_driver = { +diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c +--- a/sound/core/compress_offload.c ++++ b/sound/core/compress_offload.c +@@ -1393,17 +1393,17 @@ static long snd_compr_ioctl_compat(struct file *file, unsigned int cmd, + #endif + + static const struct file_operations snd_compr_file_ops = { +- .owner = THIS_MODULE, +- .open = snd_compr_open, +- .release = snd_compr_free, +- .write = snd_compr_write, +- .read = snd_compr_read, +- .unlocked_ioctl = snd_compr_ioctl, ++ .owner = THIS_MODULE, ++ .open = snd_compr_open, ++ .release = snd_compr_free, ++ .write = snd_compr_write, ++ .read = snd_compr_read, ++ .unlocked_ioctl = snd_compr_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_compr_ioctl_compat, ++ .compat_ioctl = snd_compr_ioctl_compat, + #endif +- .mmap = snd_compr_mmap, +- .poll = snd_compr_poll, ++ .mmap = snd_compr_mmap, ++ .poll = snd_compr_poll, + }; + + static int snd_compress_dev_register(struct snd_device *device) +diff --git a/sound/core/control.c b/sound/core/control.c +--- a/sound/core/control.c ++++ b/sound/core/control.c +@@ -38,7 +38,7 @@ struct snd_kctl_ioctl { + snd_kctl_ioctl_func_t fioctl; + }; + +-static DECLARE_RWSEM(snd_ioctl_rwsem); ++DECLARE_RWSEM(snd_ioctl_rwsem); + static DECLARE_RWSEM(snd_ctl_layer_rwsem); + static LIST_HEAD(snd_control_ioctls); + #ifdef CONFIG_COMPAT +@@ -55,9 +55,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + struct snd_ctl_file *ctl; + int i, err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); + if (!card) { +@@ -65,12 +63,10 @@ static int snd_ctl_open(struct inode *inode, struct file *file) + goto __error1; + } + err = snd_card_file_add(card, file); +- if (err < 0) { +- err = -ENODEV; ++ if (err < 0) + goto __error1; +- } + if (!try_module_get(card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); +@@ -876,23 +872,81 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, + { + struct snd_ctl_card_info *info __free(kfree) = + kzalloc(sizeof(*info), GFP_KERNEL); ++ ssize_t n; + + if (! info) + return -ENOMEM; ++ ++ static_assert(sizeof(info->components) >= 2); ++ + scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ + info->card = card->number; + strscpy(info->id, card->id, sizeof(info->id)); + strscpy(info->driver, card->driver, sizeof(info->driver)); + strscpy(info->name, card->shortname, sizeof(info->name)); + strscpy(info->longname, card->longname, sizeof(info->longname)); + strscpy(info->mixername, card->mixername, sizeof(info->mixername)); +- strscpy(info->components, card->components, sizeof(info->components)); ++ n = strscpy(info->components, components, sizeof(info->components)); ++ if (n < 0) // mark the truncation with '>' before NULL terminator ++ info->components[sizeof(info->components) - 2] = '>'; + } + if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) + return -EFAULT; + return 0; + } + ++static int snd_ctl_card_bytes(struct snd_card *card, ++ struct snd_ctl_card_bytes *info, ++ unsigned int __user *data_len_out) ++{ ++ unsigned int data_len; ++ ++ switch (info->type) { ++ case SND_CTL_CARD_BTYPE_COMPONENTS: ++ scoped_guard(rwsem_read, &snd_ioctl_rwsem) { ++ const char *components = card->components; ++ ++ if (!components) ++ components = ""; ++ ++ data_len = strlen(components) + 1; ++ ++ if (!info->data || info->data_allocated == 0) ++ break; ++ ++ if (info->data_allocated < data_len) ++ return -ENOMEM; ++ ++ if (copy_to_user(u64_to_user_ptr(info->data), components, data_len)) ++ return -EFAULT; ++ } ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (put_user(data_len, data_len_out)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int snd_ctl_card_bytes_user(struct snd_card *card, ++ struct snd_ctl_card_bytes __user *_info) ++{ ++ struct snd_ctl_card_bytes info; ++ ++ if (copy_from_user(&info, _info, sizeof(info))) ++ return -EFAULT; ++ ++ return snd_ctl_card_bytes(card, &info, &_info->data_len); ++} ++ + static int snd_ctl_elem_list(struct snd_card *card, + struct snd_ctl_elem_list *list) + { +@@ -1990,6 +2044,8 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); ++ case SNDRV_CTL_IOCTL_CARD_BYTES: ++ return snd_ctl_card_bytes_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: +@@ -2339,16 +2395,15 @@ EXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer); + * INIT PART + */ + +-static const struct file_operations snd_ctl_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_ctl_read, +- .open = snd_ctl_open, +- .release = snd_ctl_release, +- .poll = snd_ctl_poll, +- .unlocked_ioctl = snd_ctl_ioctl, +- .compat_ioctl = snd_ctl_ioctl_compat, +- .fasync = snd_ctl_fasync, ++static const struct file_operations snd_ctl_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_ctl_read, ++ .open = snd_ctl_open, ++ .release = snd_ctl_release, ++ .poll = snd_ctl_poll, ++ .unlocked_ioctl = snd_ctl_ioctl, ++ .compat_ioctl = snd_ctl_ioctl_compat, ++ .fasync = snd_ctl_fasync, + }; + + /* call lops under rwsems; called from snd_ctl_dev_*() below() */ +diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c +--- a/sound/core/control_compat.c ++++ b/sound/core/control_compat.c +@@ -226,8 +226,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, + if (type < 0) + return type; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -236,7 +236,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, + data->value.integer.value[i] = val; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (size < 0) { + dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; +@@ -259,8 +259,8 @@ static int copy_ctl_value_to_user(void __user *userdata, + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, size; + +- if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || +- type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { ++ if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || ++ type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; +@@ -269,7 +269,7 @@ static int copy_ctl_value_to_user(void __user *userdata, + return -EFAULT; + } + } else { +- size = get_elem_size((__force snd_ctl_elem_type_t)type, count); ++ size = get_elem_size(type, count); + if (copy_to_user(valuep, data->value.bytes.data, size)) + return -EFAULT; + } +@@ -417,7 +417,7 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, + break; + } + return snd_ctl_elem_add(file, data, replace); +-} ++} + + enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), +@@ -446,6 +446,7 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: ++ case SNDRV_CTL_IOCTL_CARD_BYTES: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: +diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c +--- a/sound/core/hwdep.c ++++ b/sound/core/hwdep.c +@@ -87,7 +87,7 @@ static int snd_hwdep_open(struct inode *inode, struct file * file) + + if (!try_module_get(hw->card->module)) { + snd_card_unref(hw->card); +- return -EFAULT; ++ return -ENODEV; + } + + init_waitqueue_entry(&wait, current); +@@ -323,18 +323,17 @@ static int snd_hwdep_control_ioctl(struct snd_card *card, + + */ + +-static const struct file_operations snd_hwdep_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_hwdep_llseek, +- .read = snd_hwdep_read, +- .write = snd_hwdep_write, +- .open = snd_hwdep_open, +- .release = snd_hwdep_release, +- .poll = snd_hwdep_poll, +- .unlocked_ioctl = snd_hwdep_ioctl, +- .compat_ioctl = snd_hwdep_ioctl_compat, +- .mmap = snd_hwdep_mmap, ++static const struct file_operations snd_hwdep_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_hwdep_llseek, ++ .read = snd_hwdep_read, ++ .write = snd_hwdep_write, ++ .open = snd_hwdep_open, ++ .release = snd_hwdep_release, ++ .poll = snd_hwdep_poll, ++ .unlocked_ioctl = snd_hwdep_ioctl, ++ .compat_ioctl = snd_hwdep_ioctl_compat, ++ .mmap = snd_hwdep_mmap, + }; + + static void snd_hwdep_free(struct snd_hwdep *hwdep) +diff --git a/sound/core/info.c b/sound/core/info.c +--- a/sound/core/info.c ++++ b/sound/core/info.c +@@ -78,7 +78,7 @@ static int alloc_info_private(struct snd_info_entry *entry, + if (!entry || !entry->p) + return -ENODEV; + if (!try_module_get(entry->module)) +- return -EFAULT; ++ return -ENODEV; + data = kzalloc_obj(*data); + if (!data) { + module_put(entry->module); +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -466,20 +466,19 @@ static int snd_disconnect_fasync(int fd, struct file *file, int on) + return -ENODEV; + } + +-static const struct file_operations snd_shutdown_f_ops = +-{ +- .owner = THIS_MODULE, +- .llseek = snd_disconnect_llseek, +- .read = snd_disconnect_read, +- .write = snd_disconnect_write, +- .release = snd_disconnect_release, +- .poll = snd_disconnect_poll, +- .unlocked_ioctl = snd_disconnect_ioctl, ++static const struct file_operations snd_shutdown_f_ops = { ++ .owner = THIS_MODULE, ++ .llseek = snd_disconnect_llseek, ++ .read = snd_disconnect_read, ++ .write = snd_disconnect_write, ++ .release = snd_disconnect_release, ++ .poll = snd_disconnect_poll, ++ .unlocked_ioctl = snd_disconnect_ioctl, + #ifdef CONFIG_COMPAT +- .compat_ioctl = snd_disconnect_ioctl, ++ .compat_ioctl = snd_disconnect_ioctl, + #endif +- .mmap = snd_disconnect_mmap, +- .fasync = snd_disconnect_fasync ++ .mmap = snd_disconnect_mmap, ++ .fasync = snd_disconnect_fasync + }; + + /** +@@ -592,6 +591,9 @@ static int snd_card_do_free(struct snd_card *card) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + #endif + snd_device_free_all(card); ++ kfree(card->components); ++ card->components = NULL; ++ card->components_alloc_size = 0; + if (card->private_free) + card->private_free(card); + #ifdef CONFIG_SND_CTL_DEBUG +@@ -725,7 +727,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, + int len, loops; + bool is_default = false; + char *id; +- ++ + copy_valid_id_string(card, src, nid); + id = card->id; + +@@ -1034,21 +1036,44 @@ int __init snd_card_info_init(void) + * + * Return: Zero otherwise a negative error code. + */ +- ++ + int snd_component_add(struct snd_card *card, const char *component) + { + char *ptr; + int len = strlen(component); ++ unsigned int cur_len, need_len; + +- ptr = strstr(card->components, component); +- if (ptr != NULL) { +- if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ +- return 1; ++ guard(rwsem_write)(&snd_ioctl_rwsem); ++ ++ if (card->components) { ++ ptr = strstr(card->components, component); ++ if (ptr) { ++ if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ ++ return 1; ++ } ++ cur_len = strlen(card->components) + 1; ++ } else { ++ cur_len = 0; + } +- if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { ++ ++ need_len = cur_len + len + 1; ++ if (need_len > 512) { + snd_BUG(); + return -ENOMEM; + } ++ ++ if (need_len > card->components_alloc_size) { ++ unsigned int new_alloc = roundup(need_len, 32); ++ ++ ptr = krealloc(card->components, new_alloc, GFP_KERNEL); ++ if (!ptr) ++ return -ENOMEM; ++ if (!card->components) ++ ptr[0] = '\0'; ++ card->components = ptr; ++ card->components_alloc_size = new_alloc; ++ } ++ + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); +diff --git a/sound/core/jack.c b/sound/core/jack.c +--- a/sound/core/jack.c ++++ b/sound/core/jack.c +@@ -303,41 +303,41 @@ static ssize_t jack_type_read(struct file *file, + } + + static const struct file_operations jack_type_fops = { +- .open = simple_open, +- .read = jack_type_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_type_read, ++ .llseek = default_llseek, + }; + #endif + + static const struct file_operations sw_inject_enable_fops = { +- .open = simple_open, +- .read = sw_inject_enable_read, +- .write = sw_inject_enable_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = sw_inject_enable_read, ++ .write = sw_inject_enable_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jackin_inject_fops = { +- .open = simple_open, +- .write = jackin_inject_write, +- .llseek = default_llseek, ++ .open = simple_open, ++ .write = jackin_inject_write, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_id_fops = { +- .open = simple_open, +- .read = jack_kctl_id_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_id_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_mask_bits_fops = { +- .open = simple_open, +- .read = jack_kctl_mask_bits_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_mask_bits_read, ++ .llseek = default_llseek, + }; + + static const struct file_operations jack_kctl_status_fops = { +- .open = simple_open, +- .read = jack_kctl_status_read, +- .llseek = default_llseek, ++ .open = simple_open, ++ .read = jack_kctl_status_read, ++ .llseek = default_llseek, + }; + + static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, +diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c +--- a/sound/core/oss/mixer_oss.c ++++ b/sound/core/oss/mixer_oss.c +@@ -30,9 +30,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + struct snd_mixer_oss_file *fmixer; + int err; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + card = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIXER); +@@ -60,7 +58,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file) + kfree(fmixer); + snd_card_file_remove(card, file); + snd_card_unref(card); +- return -EFAULT; ++ return -ENODEV; + } + snd_card_unref(card); + return 0; +@@ -407,13 +405,12 @@ static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, + * REGISTRATION PART + */ + +-static const struct file_operations snd_mixer_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .open = snd_mixer_oss_open, +- .release = snd_mixer_oss_release, +- .unlocked_ioctl = snd_mixer_oss_ioctl, +- .compat_ioctl = snd_mixer_oss_ioctl_compat, ++static const struct file_operations snd_mixer_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .open = snd_mixer_oss_open, ++ .release = snd_mixer_oss_release, ++ .unlocked_ioctl = snd_mixer_oss_ioctl, ++ .compat_ioctl = snd_mixer_oss_ioctl_compat, + }; + + /* +diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c +--- a/sound/core/oss/pcm_oss.c ++++ b/sound/core/oss/pcm_oss.c +@@ -879,11 +879,11 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&substream->mmap_count)) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) +- snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); ++ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { +@@ -909,7 +909,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + else + sformat = snd_pcm_plug_slave_format(format, sformat_mask); + +- if ((__force int)sformat < 0 || ++ if (sformat < 0 || + !snd_mask_test_format(sformat_mask, sformat)) { + pcm_for_each_format(sformat) { + if (snd_mask_test_format(sformat_mask, sformat) && +@@ -921,7 +921,7 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + goto failure; + } + format_found: +- err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); ++ err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0); + if (err < 0) + goto failure; + +@@ -930,9 +930,9 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, +- (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); ++ SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, +- (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); ++ snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, +@@ -1875,7 +1875,7 @@ static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) + format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(format_mask, fmt)) { +- int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); ++ int f = snd_pcm_oss_format_to(fmt); + if (f >= 0) + formats |= f; + } +@@ -2495,9 +2495,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + int nonblock; + wait_queue_entry_t wait; + +- err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ nonseekable_open(inode, file); + + pcm = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM); +@@ -2509,7 +2507,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { +@@ -3129,17 +3127,16 @@ static inline void snd_pcm_oss_proc_done(struct snd_pcm *pcm) + * ENTRY functions + */ + +-static const struct file_operations snd_pcm_oss_f_reg = +-{ +- .owner = THIS_MODULE, +- .read = snd_pcm_oss_read, +- .write = snd_pcm_oss_write, +- .open = snd_pcm_oss_open, +- .release = snd_pcm_oss_release, +- .poll = snd_pcm_oss_poll, +- .unlocked_ioctl = snd_pcm_oss_ioctl, +- .compat_ioctl = snd_pcm_oss_ioctl_compat, +- .mmap = snd_pcm_oss_mmap, ++static const struct file_operations snd_pcm_oss_f_reg = { ++ .owner = THIS_MODULE, ++ .read = snd_pcm_oss_read, ++ .write = snd_pcm_oss_write, ++ .open = snd_pcm_oss_open, ++ .release = snd_pcm_oss_release, ++ .poll = snd_pcm_oss_poll, ++ .unlocked_ioctl = snd_pcm_oss_ioctl, ++ .compat_ioctl = snd_pcm_oss_ioctl_compat, ++ .mmap = snd_pcm_oss_mmap, + }; + + static void register_oss_dsp(struct snd_pcm *pcm, int index) +diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c +--- a/sound/core/oss/pcm_plugin.c ++++ b/sound/core/oss/pcm_plugin.c +@@ -272,13 +272,13 @@ static int snd_pcm_plug_formats(const struct snd_mask *mask, + SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); +- snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); ++ snd_mask_set(&formats, SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & lower_32_bits(linfmts)) + formats.bits[0] |= lower_32_bits(linfmts); + if (formats.bits[1] & upper_32_bits(linfmts)) + formats.bits[1] |= upper_32_bits(linfmts); +- return snd_mask_test(&formats, (__force int)format); ++ return snd_mask_test(&formats, format); + } + + static const snd_pcm_format_t preferred_formats[] = { +@@ -307,20 +307,20 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + { + int i; + +- if (snd_mask_test(format_mask, (__force int)format)) ++ if (snd_mask_test(format_mask, format)) + return format; + if (!snd_pcm_plug_formats(format_mask, format)) +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + if (snd_pcm_format_linear(format)) { + unsigned int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format) > 0; + int big = snd_pcm_format_big_endian(format) > 0; + unsigned int badness, best = -1; +- snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; ++ snd_pcm_format_t best_format = -1; + for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { + snd_pcm_format_t f = preferred_formats[i]; + unsigned int w; +- if (!snd_mask_test(format_mask, (__force int)f)) ++ if (!snd_mask_test(format_mask, f)) + continue; + w = snd_pcm_format_width(f); + if (w >= width) +@@ -334,21 +334,21 @@ snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + best = badness; + } + } +- if ((__force int)best_format >= 0) ++ if (best_format >= 0) + return best_format; + else +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } else { + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + snd_pcm_format_t format1 = preferred_formats[i]; +- if (snd_mask_test(format_mask, (__force int)format1)) ++ if (snd_mask_test(format_mask, format1)) + return format1; + } + fallthrough; + default: +- return (__force snd_pcm_format_t)-EINVAL; ++ return -EINVAL; + } + } + } +diff --git a/sound/core/pcm.c b/sound/core/pcm.c +--- a/sound/core/pcm.c ++++ b/sound/core/pcm.c +@@ -211,11 +211,9 @@ static const char * const snd_pcm_format_names[] = { + */ + const char *snd_pcm_format_name(snd_pcm_format_t format) + { +- unsigned int format_num = (__force unsigned int)format; +- +- if (format_num >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format_num]) ++ if (format >= ARRAY_SIZE(snd_pcm_format_names) || !snd_pcm_format_names[format]) + return "Unknown"; +- return snd_pcm_format_names[format_num]; ++ return snd_pcm_format_names[format]; + } + EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +@@ -275,12 +273,12 @@ static const char *snd_pcm_stream_name(int stream) + + static const char *snd_pcm_access_name(snd_pcm_access_t access) + { +- return snd_pcm_access_names[(__force int)access]; ++ return snd_pcm_access_names[access]; + } + + static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) + { +- return snd_pcm_subformat_names[(__force int)subformat]; ++ return snd_pcm_subformat_names[subformat]; + } + + static const char *snd_pcm_tstamp_mode_name(int mode) +@@ -290,7 +288,7 @@ static const char *snd_pcm_tstamp_mode_name(int mode) + + static const char *snd_pcm_state_name(snd_pcm_state_t state) + { +- return snd_pcm_state_names[(__force int)state]; ++ return snd_pcm_state_names[state]; + } + + #if IS_ENABLED(CONFIG_SND_PCM_OSS) +diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c +--- a/sound/core/pcm_misc.c ++++ b/sound/core/pcm_misc.c +@@ -24,15 +24,12 @@ struct pcm_format_data { + unsigned char silence[8]; /* silence data to fill */ + }; + +-/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ +-#define INT __force int +- + static bool valid_format(snd_pcm_format_t format) + { +- return (INT)format >= 0 && (INT)format <= (INT)SNDRV_PCM_FORMAT_LAST; ++ return format >= 0 && format <= SNDRV_PCM_FORMAT_LAST; + } + +-static const struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { ++static const struct pcm_format_data pcm_formats[SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, +@@ -251,7 +248,7 @@ int snd_pcm_format_signed(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].signd; ++ val = pcm_formats[format].signd; + if (val < 0) + return -EINVAL; + return val; +@@ -300,7 +297,7 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].le; ++ val = pcm_formats[format].le; + if (val < 0) + return -EINVAL; + return val; +@@ -337,7 +334,7 @@ int snd_pcm_format_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].width; ++ val = pcm_formats[format].width; + if (!val) + return -EINVAL; + return val; +@@ -356,7 +353,7 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format) + int val; + if (!valid_format(format)) + return -EINVAL; +- val = pcm_formats[(INT)format].phys; ++ val = pcm_formats[format].phys; + if (!val) + return -EINVAL; + return val; +@@ -390,9 +387,9 @@ const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) + { + if (!valid_format(format)) + return NULL; +- if (! pcm_formats[(INT)format].phys) ++ if (! pcm_formats[format].phys) + return NULL; +- return pcm_formats[(INT)format].silence; ++ return pcm_formats[format].silence; + } + EXPORT_SYMBOL(snd_pcm_format_silence_64); + +@@ -416,12 +413,12 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int + return -EINVAL; + if (samples == 0) + return 0; +- width = pcm_formats[(INT)format].phys; /* physical width */ ++ width = pcm_formats[format].phys; /* physical width */ + if (!width) + return -EINVAL; +- pat = pcm_formats[(INT)format].silence; ++ pat = pcm_formats[format].silence; + /* signed or 1 byte data */ +- if (pcm_formats[(INT)format].signd == 1 || width <= 8) { ++ if (pcm_formats[format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; +diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c +--- a/sound/core/pcm_native.c ++++ b/sound/core/pcm_native.c +@@ -257,7 +257,7 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream, + } + + /* macro for simplified cast */ +-#define PARAM_MASK_BIT(b) (1U << (__force int)(b)) ++#define PARAM_MASK_BIT(b) (1U << (b)) + + static bool hw_support_mmap(struct snd_pcm_substream *substream) + { +@@ -489,7 +489,7 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + params->msbits = snd_interval_value(i); + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + params->msbits = snd_pcm_format_width(format); + } + } +@@ -497,13 +497,13 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + if (params->msbits) { + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + if (snd_mask_single(m)) { +- snd_pcm_format_t format = (__force snd_pcm_format_t)snd_mask_min(m); ++ snd_pcm_format_t format = snd_mask_min(m); + + if (snd_pcm_format_linear(format) && + snd_pcm_format_width(format) != params->msbits) { + m_rw = hw_param_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT); + snd_mask_reset(m_rw, +- (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + if (snd_mask_empty(m_rw)) + return -EINVAL; + } +@@ -1252,7 +1252,7 @@ static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) + runtime->trigger_master = NULL; + } + +-#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0 ++#define ACTION_ARG_IGNORE 0 + + struct action_ops { + int (*pre_action)(struct snd_pcm_substream *substream, +@@ -1637,7 +1637,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); + /* + * pause callbacks: pass boolean (to start pause or resume) as state argument + */ +-#define pause_pushed(state) (__force bool)(state) ++#define pause_pushed(state) (bool)(state) + + static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +@@ -1709,14 +1709,14 @@ static const struct action_ops snd_pcm_action_pause = { + static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push) + { + return snd_pcm_action(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream, + bool push) + { + return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream, +- (__force snd_pcm_state_t)push); ++ (snd_pcm_state_t)push); + } + + #ifdef CONFIG_PM +@@ -1984,7 +1984,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) + { + snd_pcm_state_t cur_state = snd_pcm_get_state(substream); +- int f_flags = (__force int)state; ++ int f_flags = state; + + if (cur_state == SNDRV_PCM_STATE_OPEN || + cur_state == SNDRV_PCM_STATE_DISCONNECTED) +@@ -2052,7 +2052,7 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, + + return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, + substream, +- (__force snd_pcm_state_t)f_flags); ++ (snd_pcm_state_t)f_flags); + } + + /* +@@ -2463,7 +2463,7 @@ static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) +- snd_mask_reset(&m, (__force unsigned)k); ++ snd_mask_reset(&m, k); + } + return snd_mask_refine(mask, &m); + } +@@ -2545,16 +2545,16 @@ static int snd_pcm_hw_rule_subformats(struct snd_pcm_hw_params *params, + + snd_mask_none(&m); + /* All PCMs support at least the default STD subformat. */ +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_STD); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_STD); + + pcm_for_each_format(f) { +- if (!snd_mask_test(fmask, (__force unsigned)f)) ++ if (!snd_mask_test(fmask, f)) + continue; + + if (f == SNDRV_PCM_FORMAT_S32_LE && *subformats) + m.bits[0] |= *subformats; + else if (snd_pcm_format_linear(f)) +- snd_mask_set(&m, (__force unsigned)SNDRV_PCM_SUBFORMAT_MSBITS_MAX); ++ snd_mask_set(&m, SNDRV_PCM_SUBFORMAT_MSBITS_MAX); + } + + return snd_mask_refine(sfmask, &m); +@@ -2882,9 +2882,10 @@ static int snd_pcm_open_file(struct file *file, + static int snd_pcm_playback_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); +@@ -2896,9 +2897,10 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file) + static int snd_pcm_capture_open(struct inode *inode, struct file *file) + { + struct snd_pcm *pcm; +- int err = nonseekable_open(inode, file); +- if (err < 0) +- return err; ++ int err; ++ ++ nonseekable_open(inode, file); ++ + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); +@@ -2920,7 +2922,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { +- err = -EFAULT; ++ err = -ENODEV; + goto __error2; + } + init_waitqueue_entry(&wait, current); +@@ -3916,6 +3918,8 @@ static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf) + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; ++ if (runtime->state == SNDRV_PCM_STATE_DISCONNECTED) ++ return VM_FAULT_SIGBUS; + offset = vmf->pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) +@@ -4256,29 +4260,29 @@ static unsigned long snd_pcm_get_unmapped_area(struct file *file, + + const struct file_operations snd_pcm_f_ops[2] = { + { +- .owner = THIS_MODULE, +- .write = snd_pcm_write, +- .write_iter = snd_pcm_writev, +- .open = snd_pcm_playback_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .write = snd_pcm_write, ++ .write_iter = snd_pcm_writev, ++ .open = snd_pcm_playback_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + }, + { +- .owner = THIS_MODULE, +- .read = snd_pcm_read, +- .read_iter = snd_pcm_readv, +- .open = snd_pcm_capture_open, +- .release = snd_pcm_release, +- .poll = snd_pcm_poll, +- .unlocked_ioctl = snd_pcm_ioctl, +- .compat_ioctl = snd_pcm_ioctl_compat, +- .mmap = snd_pcm_mmap, +- .fasync = snd_pcm_fasync, +- .get_unmapped_area = snd_pcm_get_unmapped_area, ++ .owner = THIS_MODULE, ++ .read = snd_pcm_read, ++ .read_iter = snd_pcm_readv, ++ .open = snd_pcm_capture_open, ++ .release = snd_pcm_release, ++ .poll = snd_pcm_poll, ++ .unlocked_ioctl = snd_pcm_ioctl, ++ .compat_ioctl = snd_pcm_ioctl_compat, ++ .mmap = snd_pcm_mmap, ++ .fasync = snd_pcm_fasync, ++ .get_unmapped_area = snd_pcm_get_unmapped_area, + } + }; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -408,9 +408,10 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, + return 0; + } + +-/* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, +- int mode, struct snd_rawmidi_file *rfile) ++/* called from sound/core/seq/seq_midi.c and sound/core/ump.c */ ++int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, ++ int mode, struct snd_rawmidi_file *rfile, ++ int depth) + { + int err; + +@@ -419,13 +420,14 @@ int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, + if (!try_module_get(rmidi->card->module)) + return -ENXIO; + +- guard(mutex)(&rmidi->open_mutex); ++ mutex_lock_nested(&rmidi->open_mutex, depth); + err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); + if (err < 0) + module_put(rmidi->card->module); ++ mutex_unlock(&rmidi->open_mutex); + return err; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_open); ++EXPORT_SYMBOL(snd_rawmidi_kernel_open_nested); + + static int snd_rawmidi_open(struct inode *inode, struct file *file) + { +@@ -441,9 +443,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file) + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + if (maj == snd_major) { + rmidi = snd_lookup_minor_data(iminor(inode), +@@ -1784,14 +1784,14 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, + */ + + static const struct file_operations snd_rawmidi_f_ops = { +- .owner = THIS_MODULE, +- .read = snd_rawmidi_read, +- .write = snd_rawmidi_write, +- .open = snd_rawmidi_open, +- .release = snd_rawmidi_release, +- .poll = snd_rawmidi_poll, +- .unlocked_ioctl = snd_rawmidi_ioctl, +- .compat_ioctl = snd_rawmidi_ioctl_compat, ++ .owner = THIS_MODULE, ++ .read = snd_rawmidi_read, ++ .write = snd_rawmidi_write, ++ .open = snd_rawmidi_open, ++ .release = snd_rawmidi_release, ++ .poll = snd_rawmidi_poll, ++ .unlocked_ioctl = snd_rawmidi_ioctl, ++ .compat_ioctl = snd_rawmidi_ioctl_compat, + }; + + static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, +diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c +--- a/sound/core/seq/oss/seq_oss.c ++++ b/sound/core/seq/oss/seq_oss.c +@@ -206,17 +206,16 @@ odev_poll(struct file *file, poll_table * wait) + * registration of sequencer minor device + */ + +-static const struct file_operations seq_oss_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = odev_read, +- .write = odev_write, +- .open = odev_open, +- .release = odev_release, +- .poll = odev_poll, +- .unlocked_ioctl = odev_ioctl, +- .compat_ioctl = odev_ioctl_compat, +- .llseek = noop_llseek, ++static const struct file_operations seq_oss_f_ops = { ++ .owner = THIS_MODULE, ++ .read = odev_read, ++ .write = odev_write, ++ .open = odev_open, ++ .release = odev_release, ++ .poll = odev_poll, ++ .unlocked_ioctl = odev_ioctl, ++ .compat_ioctl = odev_ioctl_compat, ++ .llseek = noop_llseek, + }; + + static int __init +diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c +--- a/sound/core/seq/seq_clientmgr.c ++++ b/sound/core/seq/seq_clientmgr.c +@@ -59,7 +59,7 @@ static DEFINE_MUTEX(register_mutex); + * client table + */ + static char clienttablock[SNDRV_SEQ_MAX_CLIENTS]; +-static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS]; ++static struct snd_seq_client __rcu *clienttab[SNDRV_SEQ_MAX_CLIENTS]; + static struct snd_seq_usage client_usage; + + /* +@@ -95,15 +95,23 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) + return snd_seq_total_cells(client->pool) > 0; + } + +-/* return pointer to client structure for specified id */ +-static struct snd_seq_client *clientptr(int clientid) ++/* return pointer to client structure for specified id; call under RCU read-lock */ ++static struct snd_seq_client *__clientptr(int clientid) + { + if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { + pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", + clientid); + return NULL; + } +- return clienttab[clientid]; ++ return rcu_dereference_check(clienttab[clientid], ++ lockdep_is_held(&clients_lock)); ++} ++ ++/* return pointer to client structure for specified id */ ++static struct snd_seq_client *clientptr(int clientid) ++{ ++ guard(rcu)(); ++ return __clientptr(clientid); + } + + static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) +@@ -115,8 +123,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + clientid); + return NULL; + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + if (clienttablock[clientid]) +@@ -150,8 +158,8 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) + snd_seq_device_load_drivers(); + } + } +- scoped_guard(spinlock_irqsave, &clients_lock) { +- client = clientptr(clientid); ++ scoped_guard(rcu) { ++ client = __clientptr(clientid); + if (client) + return snd_seq_client_ref(client); + } +@@ -212,7 +220,6 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + } + client->type = NO_CLIENT; + snd_use_lock_init(&client->use_lock); +- rwlock_init(&client->ports_lock); + mutex_init(&client->ports_mutex); + INIT_LIST_HEAD(&client->ports_list_head); + mutex_init(&client->ioctl_mutex); +@@ -224,14 +231,17 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { +- if (clienttab[c] || clienttablock[c]) ++ if (rcu_access_pointer(clienttab[c]) || clienttablock[c]) + continue; +- clienttab[client->number = c] = client; ++ client->number = c; ++ rcu_assign_pointer(clienttab[c], client); + return client; + } + } else { +- if (clienttab[client_index] == NULL && !clienttablock[client_index]) { +- clienttab[client->number = client_index] = client; ++ if (rcu_access_pointer(clienttab[client_index]) == NULL && ++ !clienttablock[client_index]) { ++ client->number = client_index; ++ rcu_assign_pointer(clienttab[client_index], client); + return client; + } + } +@@ -249,10 +259,16 @@ static int seq_free_client1(struct snd_seq_client *client) + return 0; + scoped_guard(spinlock_irq, &clients_lock) { + clienttablock[client->number] = 1; +- clienttab[client->number] = NULL; ++ rcu_assign_pointer(clienttab[client->number], NULL); + } + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); ++ /* the client has been unpublished from the table; wait for a grace ++ * period so that lockless readers (snd_seq_client_use_ptr()) that ++ * observed the old pointer can no longer take a new use_lock ++ * reference, then drain the outstanding references before freeing ++ */ ++ synchronize_rcu(); + snd_use_lock_sync(&client->use_lock); + if (client->pool) + snd_seq_pool_delete(&client->pool); +@@ -296,11 +312,8 @@ static int snd_seq_open(struct inode *inode, struct file *file) + int c, mode; /* client id */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + scoped_guard(mutex, ®ister_mutex) { + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); +@@ -517,9 +530,8 @@ static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event) + * Return the error event. + * + * If the receiver client is a user client, the original event is +- * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. If +- * the original event is also variable length, the external data is +- * copied after the event record. ++ * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event. The ++ * external data of a variable length event is not copied along. + * If the receiver client is a kernel client, the original event is + * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra + * kmalloc. +@@ -724,10 +736,11 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + + /* lock list */ + if (atomic) +- read_lock(&grp->list_lock); ++ rcu_read_lock(); + else + down_read_nested(&grp->list_mutex, hop); +- list_for_each_entry(subs, &grp->list_head, src_list) { ++ hlist_for_each_entry_rcu(subs, &grp->list_head, src_list, ++ lockdep_is_held(&grp->list_mutex)) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; +@@ -748,7 +761,7 @@ static int __deliver_to_subscribers(struct snd_seq_client *client, + memcpy(event, &event_saved, saved_size); + } + if (atomic) +- read_unlock(&grp->list_lock); ++ rcu_read_unlock(); + else + up_read(&grp->list_mutex); + memcpy(event, &event_saved, saved_size); +@@ -1945,7 +1958,7 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + { + struct snd_seq_query_subs *subs = arg; + struct snd_seq_port_subs_info *group; +- struct list_head *p; ++ struct hlist_node *p; + int i; + + struct snd_seq_client *cptr __free(snd_seq_client) = +@@ -1972,15 +1985,15 @@ static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; +@@ -2536,19 +2549,19 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) + { +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int count = 0; + + guard(rwsem_read)(&group->list_mutex); +- if (list_empty(&group->list_head)) ++ if (hlist_empty(&group->list_head)) + return; + snd_iprintf(buffer, msg); +- list_for_each(p, &group->list_head) { ++ hlist_for_each(p, &group->list_head) { + if (is_src) +- s = list_entry(p, struct snd_seq_subscribers, src_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- s = list_entry(p, struct snd_seq_subscribers, dest_list); ++ s = hlist_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", +@@ -2670,16 +2683,15 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, + * REGISTRATION PART + */ + +-static const struct file_operations snd_seq_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_seq_read, +- .write = snd_seq_write, +- .open = snd_seq_open, +- .release = snd_seq_release, +- .poll = snd_seq_poll, +- .unlocked_ioctl = snd_seq_ioctl, +- .compat_ioctl = snd_seq_ioctl_compat, ++static const struct file_operations snd_seq_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_seq_read, ++ .write = snd_seq_write, ++ .open = snd_seq_open, ++ .release = snd_seq_release, ++ .poll = snd_seq_poll, ++ .unlocked_ioctl = snd_seq_ioctl, ++ .compat_ioctl = snd_seq_ioctl_compat, + }; + + static struct device *seq_dev; +diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h +--- a/sound/core/seq/seq_clientmgr.h ++++ b/sound/core/seq/seq_clientmgr.h +@@ -49,7 +49,6 @@ struct snd_seq_client { + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; +- rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ +diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c +--- a/sound/core/seq/seq_ports.c ++++ b/sound/core/seq/seq_ports.c +@@ -48,8 +48,8 @@ struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + + if (client == NULL) + return NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ +@@ -71,8 +71,8 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + + num = pinfo->addr.port; + found = NULL; +- guard(read_lock)(&client->ports_lock); +- list_for_each_entry(port, &client->ports_list_head, list) { ++ guard(rcu)(); ++ list_for_each_entry_rcu(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ +@@ -98,10 +98,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl + /* initialize snd_seq_port_subs_info */ + static void port_subs_info_init(struct snd_seq_port_subs_info *grp) + { +- INIT_LIST_HEAD(&grp->list_head); ++ INIT_HLIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; +- rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +@@ -154,7 +153,6 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + + num = max(port, 0); + guard(mutex)(&client->ports_mutex); +- guard(write_lock_irq)(&client->ports_lock); + struct list_head *insert_before = &client->ports_list_head; + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) +@@ -166,12 +164,13 @@ int snd_seq_insert_port(struct snd_seq_client *client, int port, + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } +- /* insert the new port */ +- list_add_tail(&new_port->list, insert_before); +- client->num_ports++; ++ /* finish initializing the port before publishing it to RCU readers */ + new_port->addr.port = num; /* store the port number in the port */ + if (!new_port->name[0]) + sprintf(new_port->name, "port-%d", num); ++ /* insert the new port */ ++ list_add_tail_rcu(&new_port->list, insert_before); ++ client->num_ports++; + + return num; + } +@@ -202,12 +201,12 @@ static void delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack); + + static inline struct snd_seq_subscribers * +-get_subscriber(struct list_head *p, bool is_src) ++get_subscriber(struct hlist_node *p, bool is_src) + { + if (is_src) +- return list_entry(p, struct snd_seq_subscribers, src_list); ++ return hlist_entry(p, struct snd_seq_subscribers, src_list); + else +- return list_entry(p, struct snd_seq_subscribers, dest_list); ++ return hlist_entry(p, struct snd_seq_subscribers, dest_list); + } + + /* +@@ -219,9 +218,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_port_subs_info *grp, + int is_src) + { +- struct list_head *p, *n; ++ struct hlist_node *p, *n; + +- list_for_each_safe(p, n, &grp->list_head) { ++ hlist_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + + subs = get_subscriber(p, is_src); +@@ -238,13 +237,13 @@ static void clear_subscriber_list(struct snd_seq_client *client, + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) +- kfree(subs); ++ kfree_rcu(subs, rcu); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); +- kfree(subs); ++ kfree_rcu(subs, rcu); + } + } + +@@ -254,7 +253,13 @@ static int port_delete(struct snd_seq_client *client, + { + /* set closing flag and wait for all port access are gone */ + port->closing = 1; +- snd_use_lock_sync(&port->use_lock); ++ /* the port has already been unlinked from the client's port list; ++ * wait for a grace period so that RCU readers still traversing the ++ * list can no longer take a new use_lock reference, then drain the ++ * outstanding references before freeing ++ */ ++ synchronize_rcu(); ++ snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); +@@ -277,11 +282,10 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + struct snd_seq_client_port *found = NULL, *p; + + scoped_guard(mutex, &client->ports_mutex) { +- guard(write_lock_irq)(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ +- list_del(&p->list); ++ list_del_rcu(&p->list); + client->num_ports--; + found = p; + break; +@@ -297,26 +301,16 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) + /* delete the all ports belonging to the given client */ + int snd_seq_delete_all_ports(struct snd_seq_client *client) + { +- struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; +- +- /* move the port list to deleted_list, and +- * clear the port list in the client data. ++ ++ /* unlink and delete each port; port_delete() waits for an RCU grace ++ * period before draining the port, so concurrent lockless readers can ++ * no longer take a new use_lock reference on it + */ + guard(mutex)(&client->ports_mutex); +- scoped_guard(write_lock_irq, &client->ports_lock) { +- if (!list_empty(&client->ports_list_head)) { +- list_add(&deleted_list, &client->ports_list_head); +- list_del_init(&client->ports_list_head); +- } else { +- INIT_LIST_HEAD(&deleted_list); +- } +- client->num_ports = 0; +- } +- +- /* remove each port in deleted_list */ +- list_for_each_entry_safe(port, tmp, &deleted_list, list) { +- list_del(&port->list); ++ list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { ++ list_del_rcu(&port->list); ++ client->num_ports--; + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } +@@ -499,20 +493,20 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + bool is_src, bool exclusive, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *p; ++ struct hlist_node *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + guard(rwsem_write)(&grp->list_mutex); + if (exclusive) { +- if (!list_empty(&grp->list_head)) ++ if (!hlist_empty(&grp->list_head)) + return -EBUSY; + } else { + if (grp->exclusive) + return -EBUSY; + /* check whether already exists */ +- list_for_each(p, &grp->list_head) { ++ hlist_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + return -EBUSY; +@@ -526,11 +520,10 @@ static int check_and_subscribe_port(struct snd_seq_client *client, + } + + /* add to list */ +- guard(write_lock_irq)(&grp->list_lock); + if (is_src) +- list_add_tail(&subs->src_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->src_list, &grp->list_head); + else +- list_add_tail(&subs->dest_list, &grp->list_head); ++ hlist_add_tail_rcu(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + +@@ -544,17 +537,15 @@ static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + bool is_src, bool ack) + { + struct snd_seq_port_subs_info *grp; +- struct list_head *list; ++ struct hlist_node *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; +- scoped_guard(write_lock_irq, &grp->list_lock) { +- empty = list_empty(list); +- if (!empty) +- list_del_init(list); +- grp->exclusive = 0; +- } ++ empty = hlist_unhashed(list); ++ if (!empty) ++ hlist_del_init_rcu(list); ++ grp->exclusive = 0; + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +@@ -590,8 +581,8 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + + subs->info = *info; + atomic_set(&subs->ref_count, 0); +- INIT_LIST_HEAD(&subs->src_list); +- INIT_LIST_HEAD(&subs->dest_list); ++ INIT_HLIST_NODE(&subs->src_list); ++ INIT_HLIST_NODE(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + +@@ -612,7 +603,7 @@ int snd_seq_port_connect(struct snd_seq_client *connector, + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: +- kfree(subs); ++ kfree_rcu(subs, rcu); + return err; + } + +@@ -633,7 +624,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + */ + scoped_guard(rwsem_write, &dest->list_mutex) { + /* look for the connection */ +- list_for_each_entry(subs, &dest->list_head, dest_list) { ++ hlist_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, +@@ -648,7 +639,7 @@ int snd_seq_port_disconnect(struct snd_seq_client *connector, + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); +- kfree(subs); ++ kfree_rcu(subs, rcu); + return 0; + } + +@@ -662,7 +653,7 @@ int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + int err = -ENOENT; + + guard(rwsem_read)(&src_grp->list_mutex); +- list_for_each_entry(s, &src_grp->list_head, src_list) { ++ hlist_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; +diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h +--- a/sound/core/seq/seq_ports.h ++++ b/sound/core/seq/seq_ports.h +@@ -28,17 +28,17 @@ + + struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ +- struct list_head src_list; /* link of sources */ +- struct list_head dest_list; /* link of destinations */ ++ struct hlist_node src_list; /* link of sources */ ++ struct hlist_node dest_list; /* link of destinations */ + atomic_t ref_count; ++ struct rcu_head rcu; /* for deferred free */ + }; + + struct snd_seq_port_subs_info { +- struct list_head list_head; /* list of subscribed ports */ ++ struct hlist_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; +- rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); + }; +diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c +--- a/sound/core/seq/seq_ump_client.c ++++ b/sound/core/seq/seq_ump_client.c +@@ -37,8 +37,11 @@ struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ +- rwlock_t output_lock; /* protects out_rfile output access */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ ++ /* RCU-protected shadow of out_rfile.output for the delivery hot path; ++ * out_rfile itself is only touched by open/close under open_mutex ++ */ ++ struct snd_rawmidi_substream __rcu *out_substream; + struct seq_ump_input_buffer input; /* input parser context */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ +@@ -89,8 +92,8 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + unsigned char type; + int len; + +- guard(read_lock_irqsave)(&client->output_lock); +- substream = client->out_rfile.output; ++ guard(rcu)(); ++ substream = rcu_dereference(client->out_substream); + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) +@@ -108,19 +111,22 @@ static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + static int seq_ump_client_open(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + int err; + + guard(mutex)(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { ++ /* out_rfile is only accessed under open_mutex; the delivery ++ * path reads out_substream via RCU, so open into out_rfile ++ * directly and publish the substream afterwards ++ */ + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, +- &rfile); ++ &client->out_rfile); + if (err < 0) + return err; +- scoped_guard(write_lock_irqsave, &client->output_lock) +- client->out_rfile = rfile; ++ rcu_assign_pointer(client->out_substream, ++ client->out_rfile.output); + } + client->opened[dir]++; + return 0; +@@ -130,17 +136,18 @@ static int seq_ump_client_open(struct seq_ump_client *client, int dir) + static int seq_ump_client_close(struct seq_ump_client *client, int dir) + { + struct snd_ump_endpoint *ump = client->ump; +- struct snd_rawmidi_file rfile = {}; + + guard(mutex)(&ump->open_mutex); + if (!--client->opened[dir]) { +- if (dir == STR_OUT) { +- scoped_guard(write_lock_irqsave, &client->output_lock) { +- rfile = client->out_rfile; +- client->out_rfile = (struct snd_rawmidi_file){}; +- } +- if (rfile.rmidi) +- snd_rawmidi_kernel_release(&rfile); ++ if (dir == STR_OUT && client->out_rfile.rmidi) { ++ rcu_assign_pointer(client->out_substream, NULL); ++ /* wait for a grace period so that no reader in the ++ * delivery path is still writing to the substream ++ * before it is released ++ */ ++ synchronize_rcu(); ++ snd_rawmidi_kernel_release(&client->out_rfile); ++ client->out_rfile = (struct snd_rawmidi_file){}; + } + } + return 0; +@@ -480,7 +487,6 @@ static int snd_seq_ump_probe(struct snd_seq_device *dev) + + INIT_WORK(&client->group_notify_work, handle_group_notify); + client->ump = ump; +- rwlock_init(&client->output_lock); + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, +diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c +--- a/sound/core/seq/seq_virmidi.c ++++ b/sound/core/seq/seq_virmidi.c +@@ -78,10 +78,11 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + int len; + + if (atomic) +- read_lock(&rdev->filelist_lock); ++ rcu_read_lock(); + else + down_read(&rdev->filelist_sem); +- list_for_each_entry(vmidi, &rdev->filelist, list) { ++ list_for_each_entry_rcu(vmidi, &rdev->filelist, list, ++ lockdep_is_held(&rdev->filelist_sem)) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { +@@ -96,7 +97,7 @@ static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + } + } + if (atomic) +- read_unlock(&rdev->filelist_lock); ++ rcu_read_unlock(); + else + up_read(&rdev->filelist_sem); + +@@ -200,8 +201,7 @@ static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) + vmidi->port = rdev->port; + runtime->private_data = vmidi; + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_add_tail(&vmidi->list, &rdev->filelist); ++ list_add_tail_rcu(&vmidi->list, &rdev->filelist); + } + vmidi->rdev = rdev; + return 0; +@@ -243,9 +243,13 @@ static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) + struct snd_virmidi *vmidi = substream->runtime->private_data; + + scoped_guard(rwsem_write, &rdev->filelist_sem) { +- guard(write_lock_irq)(&rdev->filelist_lock); +- list_del(&vmidi->list); ++ list_del_rcu(&vmidi->list); + } ++ /* wait for a grace period so that lockless readers in the atomic ++ * delivery path (snd_virmidi_dev_receive_event()) are no longer ++ * traversing this entry before its parser and memory are freed ++ */ ++ synchronize_rcu(); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); +@@ -508,7 +512,6 @@ int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmi + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); +- rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; +diff --git a/sound/core/sound.c b/sound/core/sound.c +--- a/sound/core/sound.c ++++ b/sound/core/sound.c +@@ -167,11 +167,10 @@ static int snd_open(struct inode *inode, struct file *file) + return err; + } + +-static const struct file_operations snd_fops = +-{ +- .owner = THIS_MODULE, +- .open = snd_open, +- .llseek = noop_llseek, ++static const struct file_operations snd_fops = { ++ .owner = THIS_MODULE, ++ .open = snd_open, ++ .llseek = noop_llseek, + }; + + #ifdef CONFIG_SND_DYNAMIC_MINORS +@@ -358,7 +357,7 @@ static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_bu + struct snd_minor *mptr; + + guard(mutex)(&sound_mutex); +- for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) { ++ for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + mptr = snd_minors[minor]; + if (!mptr) + continue; +@@ -395,15 +394,21 @@ int __init snd_minor_info_init(void) + + static int __init alsa_sound_init(void) + { ++ int err; ++ + snd_major = major; + snd_ecards_limit = cards_limit; +- if (register_chrdev(major, "alsa", &snd_fops)) { ++ ++ err = register_chrdev(major, "alsa", &snd_fops); ++ if (err < 0) { + pr_err("ALSA core: unable to register native major device number %d\n", major); +- return -EIO; ++ return err; + } +- if (snd_info_init() < 0) { ++ ++ err = snd_info_init(); ++ if (err < 0) { + unregister_chrdev(major, "alsa"); +- return -ENOMEM; ++ return err; + } + + #ifdef CONFIG_SND_DEBUG +diff --git a/sound/core/sound_kunit.c b/sound/core/sound_kunit.c +--- a/sound/core/sound_kunit.c ++++ b/sound/core/sound_kunit.c +@@ -17,8 +17,8 @@ + .name = #fmt, \ + } + +-#define WRONG_FORMAT_1 (__force snd_pcm_format_t)((__force int)SNDRV_PCM_FORMAT_LAST + 1) +-#define WRONG_FORMAT_2 (__force snd_pcm_format_t)-1 ++#define WRONG_FORMAT_1 (SNDRV_PCM_FORMAT_LAST + 1) ++#define WRONG_FORMAT_2 -1 + + #define VALID_NAME "ValidName" + #define NAME_W_SPEC_CHARS "In%v@1id name" +diff --git a/sound/core/timer.c b/sound/core/timer.c +--- a/sound/core/timer.c ++++ b/sound/core/timer.c +@@ -1546,11 +1546,8 @@ static int realloc_user_queue(struct snd_timer_user *tu, int size) + static int snd_timer_user_open(struct inode *inode, struct file *file) + { + struct snd_timer_user *tu; +- int err; + +- err = stream_open(inode, file); +- if (err < 0) +- return err; ++ stream_open(inode, file); + + tu = kzalloc_obj(*tu); + if (tu == NULL) +@@ -2176,9 +2173,9 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon + } + + static const struct file_operations snd_utimer_fops = { +- .llseek = noop_llseek, +- .release = snd_utimer_release, +- .unlocked_ioctl = snd_utimer_ioctl, ++ .llseek = noop_llseek, ++ .release = snd_utimer_release, ++ .unlocked_ioctl = snd_utimer_ioctl, + }; + + static int snd_utimer_start(struct snd_timer *t) +@@ -2530,16 +2527,15 @@ static __poll_t snd_timer_user_poll(struct file *file, poll_table * wait) + #define snd_timer_user_ioctl_compat NULL + #endif + +-static const struct file_operations snd_timer_f_ops = +-{ +- .owner = THIS_MODULE, +- .read = snd_timer_user_read, +- .open = snd_timer_user_open, +- .release = snd_timer_user_release, +- .poll = snd_timer_user_poll, +- .unlocked_ioctl = snd_timer_user_ioctl, +- .compat_ioctl = snd_timer_user_ioctl_compat, +- .fasync = snd_timer_user_fasync, ++static const struct file_operations snd_timer_f_ops = { ++ .owner = THIS_MODULE, ++ .read = snd_timer_user_read, ++ .open = snd_timer_user_open, ++ .release = snd_timer_user_release, ++ .poll = snd_timer_user_poll, ++ .unlocked_ioctl = snd_timer_user_ioctl, ++ .compat_ioctl = snd_timer_user_ioctl_compat, ++ .fasync = snd_timer_user_fasync, + }; + + /* unregister the system timer */ +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1157,10 +1157,11 @@ static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) + return -ENODEV; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { +- err = snd_rawmidi_kernel_open(&ump->core, 0, +- SNDRV_RAWMIDI_LFLG_OUTPUT | +- SNDRV_RAWMIDI_LFLG_APPEND, +- &ump->legacy_out_rfile); ++ err = snd_rawmidi_kernel_open_nested(&ump->core, 0, ++ SNDRV_RAWMIDI_LFLG_OUTPUT | ++ SNDRV_RAWMIDI_LFLG_APPEND, ++ &ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + if (err < 0) + return err; + } +diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c +--- a/sound/drivers/aloop.c ++++ b/sound/drivers/aloop.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -55,7 +56,7 @@ MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver."); + module_param_array(pcm_notify, int, NULL, 0444); + MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes."); + module_param_array(timer_source, charp, NULL, 0444); +-MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default]."); ++MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default], 'hrtimer' for high-resolution timer."); + + #define NO_PITCH 100000 + +@@ -161,8 +162,11 @@ struct loopback_pcm { + unsigned int period_size_frac; /* period size in jiffies ticks */ + unsigned int last_drift; + unsigned long last_jiffies; +- /* If jiffies timer is used */ ++ /* If jiffies / hrtimer is used */ + struct timer_list timer; ++#ifdef CONFIG_HIGH_RES_TIMERS ++ struct hrtimer hrtimer; ++#endif + + /* size of per channel buffer in case of non-interleaved access */ + unsigned int channel_buf_n; +@@ -232,6 +236,31 @@ static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static int loopback_hrtimer_start(struct loopback_pcm *dpcm) ++{ ++ unsigned long tick; ++ unsigned int rate_shift = get_rate_shift(dpcm); ++ ++ if (rate_shift != dpcm->pcm_rate_shift) { ++ dpcm->pcm_rate_shift = rate_shift; ++ dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size); ++ } ++ if (dpcm->period_size_frac <= dpcm->irq_pos) { ++ dpcm->irq_pos %= dpcm->period_size_frac; ++ dpcm->period_update_pending = 1; ++ } ++ tick = dpcm->period_size_frac - dpcm->irq_pos; ++ tick = DIV_ROUND_UP(tick, dpcm->pcm_bps); ++ hrtimer_start(&dpcm->hrtimer, ++ ns_to_ktime(div_u64((u64)tick * NSEC_PER_SEC, HZ)), ++ HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_start(struct loopback_pcm *dpcm) + { +@@ -270,6 +299,16 @@ static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++/* call in cable->lock */ ++static inline int loopback_hrtimer_stop(struct loopback_pcm *dpcm) ++{ ++ hrtimer_try_to_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_stop(struct loopback_pcm *dpcm) + { +@@ -300,6 +339,15 @@ static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm) + return 0; + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static inline int loopback_hrtimer_stop_sync(struct loopback_pcm *dpcm) ++{ ++ hrtimer_cancel(&dpcm->hrtimer); ++ ++ return 0; ++} ++#endif ++ + /* call in loopback->cable_lock */ + static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) + { +@@ -734,6 +782,30 @@ static void loopback_jiffies_timer_function(struct timer_list *t) + snd_pcm_period_elapsed(dpcm->substream); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static enum hrtimer_restart loopback_hrtimer_function(struct hrtimer *t) ++{ ++ struct loopback_pcm *dpcm = container_of(t, struct loopback_pcm, hrtimer); ++ bool period_elapsed = false; ++ ++ scoped_guard(spinlock_irqsave, &dpcm->cable->lock) { ++ if (loopback_jiffies_timer_pos_update(dpcm->cable) & ++ (1 << dpcm->substream->stream)) { ++ loopback_hrtimer_start(dpcm); ++ if (dpcm->period_update_pending) { ++ dpcm->period_update_pending = 0; ++ period_elapsed = true; ++ } ++ } ++ } ++ ++ if (period_elapsed) ++ snd_pcm_period_elapsed(dpcm->substream); ++ ++ return HRTIMER_NORESTART; ++} ++#endif ++ + /* call in cable->lock */ + static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime, + unsigned long resolution) +@@ -907,6 +979,21 @@ static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm, + snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires); + } + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static void loopback_hrtimer_dpcm_info(struct loopback_pcm *dpcm, ++ struct snd_info_buffer *buffer) ++{ ++ snd_iprintf(buffer, " update_pending:\t%u\n", ++ dpcm->period_update_pending); ++ snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos); ++ snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac); ++ snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n", ++ dpcm->last_jiffies, jiffies); ++ snd_iprintf(buffer, " timer_expires:\t%llu\n", ++ ktime_to_ns(hrtimer_get_expires(&dpcm->hrtimer))); ++} ++#endif ++ + static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer) + { +@@ -1097,6 +1184,26 @@ static const struct loopback_ops loopback_jiffies_timer_ops = { + .dpcm_info = loopback_jiffies_timer_dpcm_info, + }; + ++#ifdef CONFIG_HIGH_RES_TIMERS ++static int loopback_hrtimer_open(struct loopback_pcm *dpcm) ++{ ++ hrtimer_setup(&dpcm->hrtimer, loopback_hrtimer_function, ++ CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); ++ ++ return 0; ++} ++ ++static const struct loopback_ops loopback_hrtimer_ops = { ++ .open = loopback_hrtimer_open, ++ .start = loopback_hrtimer_start, ++ .stop = loopback_hrtimer_stop, ++ .stop_sync = loopback_hrtimer_stop_sync, ++ .close_substream = loopback_hrtimer_stop_sync, ++ .pos_update = loopback_jiffies_timer_pos_update, ++ .dpcm_info = loopback_hrtimer_dpcm_info, ++}; ++#endif ++ + static int loopback_parse_timer_id(const char *str, + struct snd_timer_id *tid) + { +@@ -1274,7 +1381,12 @@ static int loopback_open(struct snd_pcm_substream *substream) + spin_lock_init(&cable->lock); + snd_refcount_init(&cable->stop_count); + cable->hw = loopback_pcm_hardware; +- if (loopback->timer_source) ++#ifdef CONFIG_HIGH_RES_TIMERS ++ if (loopback->timer_source && !strcmp(loopback->timer_source, "hrtimer")) ++ cable->ops = &loopback_hrtimer_ops; ++ else ++#endif ++ if (loopback->timer_source && loopback->timer_source[0]) + cable->ops = &loopback_snd_timer_ops; + else + cable->ops = &loopback_jiffies_timer_ops; +@@ -1491,7 +1603,7 @@ static int loopback_format_info(struct snd_kcontrol *kcontrol, + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; +- uinfo->value.integer.max = (__force int)SNDRV_PCM_FORMAT_LAST; ++ uinfo->value.integer.max = SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.step = 1; + return 0; + } +@@ -1502,7 +1614,7 @@ static int loopback_format_get(struct snd_kcontrol *kcontrol, + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = +- (__force int)loopback->setup[kcontrol->id.subdevice] ++ loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].format; + return 0; + } +@@ -1556,7 +1668,7 @@ static int loopback_channels_get(struct snd_kcontrol *kcontrol, + static int loopback_access_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +- const char * const texts[] = {"Interleaved", "Non-interleaved"}; ++ static const char * const texts[] = {"Interleaved", "Non-interleaved"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); + } +diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c +--- a/sound/drivers/dummy.c ++++ b/sound/drivers/dummy.c +@@ -100,6 +100,7 @@ struct dummy_timer_ops { + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); ++ int (*sync_stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); + }; + +@@ -285,6 +286,14 @@ static int dummy_systimer_stop(struct snd_pcm_substream *substream) + return 0; + } + ++static int dummy_systimer_sync_stop(struct snd_pcm_substream *substream) ++{ ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_delete_sync(&dpcm->timer); ++ return 0; ++} ++ + static int dummy_systimer_prepare(struct snd_pcm_substream *substream) + { + struct snd_pcm_runtime *runtime = substream->runtime; +@@ -341,7 +350,10 @@ static int dummy_systimer_create(struct snd_pcm_substream *substream) + + static void dummy_systimer_free(struct snd_pcm_substream *substream) + { +- kfree(substream->runtime->private_data); ++ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; ++ ++ timer_shutdown_sync(&dpcm->timer); ++ kfree(dpcm); + } + + static const struct dummy_timer_ops dummy_systimer_ops = { +@@ -350,6 +362,7 @@ static const struct dummy_timer_ops dummy_systimer_ops = { + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, ++ .sync_stop = dummy_systimer_sync_stop, + .pointer = dummy_systimer_pointer, + }; + +@@ -407,9 +420,12 @@ static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) + return 0; + } + +-static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) ++static int dummy_hrtimer_sync_stop(struct snd_pcm_substream *substream) + { ++ struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; ++ + hrtimer_cancel(&dpcm->timer); ++ return 0; + } + + static snd_pcm_uframes_t +@@ -435,7 +451,6 @@ static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) + long sec; + unsigned long nsecs; + +- dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; +@@ -463,7 +478,7 @@ static int dummy_hrtimer_create(struct snd_pcm_substream *substream) + static void dummy_hrtimer_free(struct snd_pcm_substream *substream) + { + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; +- dummy_hrtimer_sync(dpcm); ++ + kfree(dpcm); + } + +@@ -473,6 +488,7 @@ static const struct dummy_timer_ops dummy_hrtimer_ops = { + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, ++ .sync_stop = dummy_hrtimer_sync_stop, + .pointer = dummy_hrtimer_pointer, + }; + +@@ -495,6 +511,13 @@ static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) + return -EINVAL; + } + ++static int dummy_pcm_sync_stop(struct snd_pcm_substream *substream) ++{ ++ if (get_dummy_ops(substream)->sync_stop) ++ return get_dummy_ops(substream)->sync_stop(substream); ++ return 0; ++} ++ + static int dummy_pcm_prepare(struct snd_pcm_substream *substream) + { + return get_dummy_ops(substream)->prepare(substream); +@@ -646,6 +669,7 @@ static const struct snd_pcm_ops dummy_pcm_ops = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + }; + +@@ -655,6 +679,7 @@ static const struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, ++ .sync_stop = dummy_pcm_sync_stop, + .pointer = dummy_pcm_pointer, + .copy = dummy_pcm_copy, + .fill_silence = dummy_pcm_silence, +diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c +--- a/sound/hda/codecs/ca0132.c ++++ b/sound/hda/codecs/ca0132.c +@@ -5788,7 +5788,8 @@ static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; +- sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); ++ snprintf(namestr, sizeof(namestr), "%d %s", ++ (uinfo->value.enumerated.item * 10), sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5840,9 +5841,9 @@ static int ae5_headphone_gain_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_HEADPHONE_GAIN_MAX; + if (uinfo->value.enumerated.item >= AE5_HEADPHONE_GAIN_MAX) + uinfo->value.enumerated.item = AE5_HEADPHONE_GAIN_MAX - 1; +- sprintf(namestr, "%s %s", +- ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, +- sfx); ++ snprintf(namestr, sizeof(namestr), "%s %s", ++ ae5_headphone_gain_presets[uinfo->value.enumerated.item].name, ++ sfx); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -5894,8 +5895,8 @@ static int ae5_sound_filter_info(struct snd_kcontrol *kcontrol, + uinfo->value.enumerated.items = AE5_SOUND_FILTER_MAX; + if (uinfo->value.enumerated.item >= AE5_SOUND_FILTER_MAX) + uinfo->value.enumerated.item = AE5_SOUND_FILTER_MAX - 1; +- sprintf(namestr, "%s", +- ae5_filter_presets[uinfo->value.enumerated.item].name); ++ snprintf(namestr, sizeof(namestr), "%s", ++ ae5_filter_presets[uinfo->value.enumerated.item].name); + strscpy(uinfo->value.enumerated.name, namestr); + return 0; + } +@@ -6632,7 +6633,7 @@ static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + +- sprintf(namestr, "FX: %s %s Volume", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Volume", pfx, dirstr[dir]); + + knew.tlv.c = NULL; + +@@ -6671,9 +6672,9 @@ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, + * prefix to OutFX or InFX enable controls. + */ + if (ca0132_use_alt_controls(spec) && (nid <= IN_EFFECT_END_NID)) +- sprintf(namestr, "FX: %s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "FX: %s %s Switch", pfx, dirstr[dir]); + else +- sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); ++ snprintf(namestr, sizeof(namestr), "%s %s Switch", pfx, dirstr[dir]); + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + } +@@ -8530,10 +8531,9 @@ static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) + + static bool ca0132_download_dsp_images(struct hda_codec *codec) + { +- bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; + const struct dsp_image_seg *dsp_os_image; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case +@@ -8573,15 +8573,10 @@ static bool ca0132_download_dsp_images(struct hda_codec *codec) + dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); + if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { + codec_err(codec, "ca0132 DSP load image failed\n"); +- goto exit_download; ++ return false; + } + +- dsp_loaded = dspload_wait_loaded(codec); +- +-exit_download: +- release_firmware(fw_entry); +- +- return dsp_loaded; ++ return dspload_wait_loaded(codec); + } + + static void ca0132_download_dsp(struct hda_codec *codec) +@@ -9634,6 +9629,7 @@ static void ca0132_free(struct hda_codec *codec) + #endif + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void dbpro_free(struct hda_codec *codec) +@@ -9644,6 +9640,7 @@ static void dbpro_free(struct hda_codec *codec) + + kfree(spec->spec_init_verbs); + kfree(codec->spec); ++ codec->spec = NULL; + } + + static void ca0132_config(struct hda_codec *codec) +diff --git a/sound/hda/codecs/cm9825.c b/sound/hda/codecs/cm9825.c +--- a/sound/hda/codecs/cm9825.c ++++ b/sound/hda/codecs/cm9825.c +@@ -15,7 +15,8 @@ + + enum { + QUIRK_CM_STD = 0x0, +- QUIRK_GENE_TWL7_SSID = 0x160dc000 ++ QUIRK_GENE_TWL7_SSID = 0x160dc000, ++ QUIRK_IBP_SSID = 0x15bd3275 + }; + + /* CM9825 Offset Definitions */ +@@ -40,13 +41,19 @@ enum { + #define CM9825_VERB_SET_GAD 0x7b4 + #define CM9825_VERB_SET_TMOD 0x7b5 + #define CM9825_VERB_SET_SNR 0x7b6 ++#define CM9825_VERB_SET_OMTP 0x7ef ++#define CM9825_VERB_READ_OMTP 0xfec + + struct cmi_spec { + struct hda_gen_spec gen; + const struct hda_verb *chip_d0_verbs; + const struct hda_verb *chip_d3_verbs; ++ const struct hda_verb *chip_playback_start_verbs; ++ const struct hda_verb *chip_playback_stop_verbs; + const struct hda_verb *chip_hp_present_verbs; + const struct hda_verb *chip_hp_remove_verbs; ++ const struct hda_verb *chip_lineout_present_verbs; ++ const struct hda_verb *chip_lineout_remove_verbs; + struct hda_codec *codec; + struct delayed_work unsol_inputs_work; + struct delayed_work unsol_lineout_work; +@@ -193,6 +200,94 @@ static const struct hda_verb cm9825_gene_twl7_playback_stop_verbs[] = { + {} + }; + ++/* ++ * To save power, AD/CLK is turned off. ++ */ ++static const struct hda_verb cm9825_ibp_d3_verbs[] = { ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_PLL, 0x01}, ++ {0x43, CM9825_VERB_SET_NEG, 0xc2}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x50}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x04}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf6}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {} ++}; ++ ++/* ++ * D0 configuration: enable PLL/CLK/ADC/DAC and optimize performance ++ */ ++static const struct hda_verb cm9825_ibp_d0_verbs[] = { ++ {0x34, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {0x43, CM9825_VERB_SET_PLL, 0x00}, ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {0x43, CM9825_VERB_SET_VNEG, 0x56}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_DACTRL, 0x00}, ++ {0x43, CM9825_VERB_SET_PDNEG, 0x0c}, ++ {0x43, CM9825_VERB_SET_CDALR, 0xf4}, ++ {0x43, CM9825_VERB_SET_OTP, 0xcd}, ++ {0x43, CM9825_VERB_SET_MTCBA, 0x61}, ++ {0x43, CM9825_VERB_SET_OCP, 0x33}, ++ {0x43, CM9825_VERB_SET_GAD, 0x07}, ++ {0x43, CM9825_VERB_SET_TMOD, 0x26}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0xa0, 0x2d}, ++ {0x3c, AC_VERB_SET_AMP_GAIN_MUTE | 0x90, 0x2d}, ++ {0x43, CM9825_VERB_SET_HPF_1, 0x40}, ++ {0x43, CM9825_VERB_SET_HPF_2, 0x40}, ++ {} ++}; ++ ++/* ++ * Enable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_present_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x8c}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x10}, ++ {} ++}; ++ ++/* ++ * Disable mbias, ADC. ++ */ ++static const struct hda_verb cm9825_ibp_lineout_remove_verbs[] = { ++ {0x43, CM9825_VERB_SET_ADCL, 0x00}, ++ {0x43, CM9825_VERB_SET_MBIAS, 0x00}, ++ {} ++}; ++ ++/* ++ * Turn on the DAC (widget NID 0x43) in the playback path by writing ++ * a sequence of vendor-specific verbs. ++ */ ++static const struct hda_verb cm9825_ibp_playback_start_verbs[] = { ++ {0x43, CM9825_VERB_SET_DACL, 0xaa}, ++ {0x43, CM9825_VERB_SET_D2S, 0xf2}, ++ {0x43, CM9825_VERB_SET_VDO, 0xc4}, ++ {0x43, CM9825_VERB_SET_SNR, 0x30}, ++ {} ++}; ++ ++/* ++ * Shut down the playback path. The order differs slightly from the ++ * enable sequence, likely to avoid audible pop noise when powering ++ * down the output stage. ++ */ ++static const struct hda_verb cm9825_ibp_playback_stop_verbs[] = { ++ {0x43, CM9825_VERB_SET_VDO, 0xc0}, ++ {0x43, CM9825_VERB_SET_DACL, 0x02}, ++ {0x43, CM9825_VERB_SET_D2S, 0x62}, ++ {0x43, CM9825_VERB_SET_VDO, 0x80}, ++ {0x43, CM9825_VERB_SET_SNR, 0x38}, ++ {} ++}; ++ + static void cm9825_update_jk_plug_status(struct hda_codec *codec, hda_nid_t nid) + { + struct cmi_spec *spec = codec->spec; +@@ -232,6 +327,23 @@ static void cm9825_unsol_lineout_delayed(struct work_struct *work) + struct cmi_spec *spec = + container_of(to_delayed_work(work), struct cmi_spec, + unsol_lineout_work); ++ hda_nid_t line_out_pin = spec->gen.autocfg.line_out_pins[0]; ++ bool line_out_jack_plugin = false; ++ ++ line_out_jack_plugin = snd_hda_jack_detect(spec->codec, line_out_pin); ++ ++ codec_dbg(spec->codec, "lineout_jack_plugin %d, lineout_pin 0x%X\n", ++ (int)line_out_jack_plugin, line_out_pin); ++ ++ if (!line_out_jack_plugin) { ++ /* Jack plugout */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_remove_verbs); ++ } else { ++ /* Jack plugin */ ++ snd_hda_sequence_write(spec->codec, ++ spec->chip_lineout_present_verbs); ++ } + + cm9825_update_jk_plug_status(spec->codec, + spec->gen.autocfg.line_out_pins[0]); +@@ -362,12 +474,10 @@ static void cm9825_playback_pcm_hook(struct hda_pcm_stream *hinfo, + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_start_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_start_verbs); + break; + case HDA_GEN_PCM_ACT_CLEANUP: +- snd_hda_sequence_write(spec->codec, +- cm9825_gene_twl7_playback_stop_verbs); ++ snd_hda_sequence_write(codec, spec->chip_playback_stop_verbs); + break; + default: + return; +@@ -471,7 +581,8 @@ static int cm9825_resume(struct hda_codec *codec) + + if (codec->core.subsystem_id == QUIRK_CM_STD) + cm9825_cm_std_resume(codec); +- else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID) { ++ else if (codec->core.subsystem_id == QUIRK_GENE_TWL7_SSID || ++ codec->core.subsystem_id == QUIRK_IBP_SSID) { + snd_hda_codec_init(codec); + snd_hda_sequence_write(codec, spec->chip_d0_verbs); + } +@@ -487,6 +598,7 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + struct cmi_spec *spec; + struct auto_pin_cfg *cfg; + int err = 0; ++ unsigned int val; + + spec = kzalloc_obj(*spec); + if (spec == NULL) +@@ -523,9 +635,33 @@ static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->chip_d0_verbs = cm9825_gene_twl7_d0_verbs; + spec->chip_d3_verbs = cm9825_gene_twl7_d3_verbs; + spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_playback_start_verbs = ++ cm9825_gene_twl7_playback_start_verbs; ++ spec->chip_playback_stop_verbs = ++ cm9825_gene_twl7_playback_stop_verbs; + /* Internal fixed device, Rear, Mic-in, 3.5mm */ + snd_hda_codec_set_pincfg(codec, 0x37, 0x24A70100); + break; ++ case QUIRK_IBP_SSID: ++ snd_hda_codec_set_name(codec, "CM9825 IBP"); ++ spec->chip_d0_verbs = cm9825_ibp_d0_verbs; ++ spec->chip_d3_verbs = cm9825_ibp_d3_verbs; ++ spec->gen.pcm_playback_hook = cm9825_playback_pcm_hook; ++ spec->chip_lineout_present_verbs = ++ cm9825_ibp_lineout_present_verbs; ++ spec->chip_lineout_remove_verbs = ++ cm9825_ibp_lineout_remove_verbs; ++ spec->chip_playback_start_verbs = ++ cm9825_ibp_playback_start_verbs; ++ spec->chip_playback_stop_verbs = cm9825_ibp_playback_stop_verbs; ++ ++ /* OMTP */ ++ val = ++ snd_hda_codec_read(codec, 0x46, 0, CM9825_VERB_READ_OMTP, ++ 0x0); ++ snd_hda_codec_write(codec, 0x46, 0, CM9825_VERB_SET_OMTP, ++ (val >> 24) & 0x7f); ++ break; + default: + err = -ENXIO; + break; +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -292,6 +292,7 @@ enum { + CXT_PINCFG_TOP_SPEAKER, + CXT_FIXUP_HP_A_U, + CXT_FIXUP_ACER_SWIFT_HP, ++ CXT_FIXUP_HUAWEI_MATEBOOK_HP, + }; + + /* for hda_fixup_thinkpad_acpi() */ +@@ -451,7 +452,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + if (!spec->dc_enable) { + /* disable DC bias path and pin for port F */ + update_mic_pin(codec, 0x1e, 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, false, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, false, false); + + /* update port B (ext mic) and C (int mic) */ + /* OLPC defers mic widget control until when capture is +@@ -487,7 +489,8 @@ static void olpc_xo_update_mic_pins(struct hda_codec *codec) + update_mic_pin(codec, 0x1b, 0); + /* enable DC bias path and pin */ + update_mic_pin(codec, 0x1e, spec->recording ? PIN_IN : 0); +- snd_hda_activate_path(codec, spec->dc_mode_path, true, false); ++ if (spec->dc_mode_path) ++ snd_hda_activate_path(codec, spec->dc_mode_path, true, false); + } + } + +@@ -1033,6 +1036,13 @@ static const struct hda_fixup cxt_fixups[] = { + { } + }, + }, ++ [CXT_FIXUP_HUAWEI_MATEBOOK_HP] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x18, 0x03211020 }, /* Headphone */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk cxt5045_fixups[] = { +@@ -1097,6 +1107,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), ++ SND_PCI_QUIRK(0x103c, 0x837b, "HP ProBook 440 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), + SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x83b3, "HP EliteBook 830 G5", CXT_FIXUP_HP_DOCK), +@@ -1133,6 +1144,7 @@ static const struct hda_quirk cxt5066_fixups[] = { + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), ++ SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), + SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), + SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), +diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c +--- a/sound/hda/codecs/generic.c ++++ b/sound/hda/codecs/generic.c +@@ -2695,30 +2695,13 @@ static const struct snd_kcontrol_new out_jack_mode_enum = { + .put = out_jack_mode_put, + }; + +-static bool find_kctl_name(struct hda_codec *codec, const char *name, int idx) +-{ +- struct hda_gen_spec *spec = codec->spec; +- const struct snd_kcontrol_new *kctl; +- int i; +- +- snd_array_for_each(&spec->kctls, i, kctl) { +- if (!strcmp(kctl->name, name) && kctl->index == idx) +- return true; +- } +- return false; +-} +- + static void get_jack_mode_name(struct hda_codec *codec, hda_nid_t pin, + char *name, size_t name_len) + { + struct hda_gen_spec *spec = codec->spec; +- int idx = 0; + +- snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len, &idx); +- strlcat(name, " Jack Mode", name_len); +- +- for (; find_kctl_name(codec, name, idx); idx++) +- ; ++ snd_hda_get_pin_label(codec, pin, &spec->autocfg, name, name_len); ++ hda_append_suffix(name, " Jack Mode", name_len); + } + + static int get_out_jack_num_items(struct hda_codec *codec, hda_nid_t pin) +@@ -5695,7 +5678,7 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx, + break; + } + } +- strlcat(str, sfx, len); ++ hda_append_suffix(str, sfx, len); + } + + /* copy PCM stream info from @default_str, and override non-NULL entries +diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c +--- a/sound/hda/codecs/hdmi/hdmi.c ++++ b/sound/hda/codecs/hdmi/hdmi.c +@@ -2236,8 +2236,10 @@ void snd_hda_hdmi_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) + /* skip notification during system suspend (but not in runtime PM); + * the state will be updated at resume + */ +- if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) ++ if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) { ++ codec->acomp_requested_resume = 1; + return; ++ } + + snd_hda_hdmi_check_presence_and_report(codec, pin_nid, dev_id); + } +@@ -2346,6 +2348,7 @@ static const struct hda_device_id snd_hda_id_generichdmi[] = { + HDA_CODEC_ID_MODEL(0x1d179f8e, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f8f, "KX-7000 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x1d179f90, "KX-7000 HDMI/DP", MODEL_GF), ++ HDA_CODEC_ID_MODEL(0x4c545020, "Lisuan HDMI/DP", MODEL_GENERIC), + HDA_CODEC_ID_MODEL(0x67663d82, "Arise 82 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d83, "Arise 83 HDMI/DP", MODEL_GF), + HDA_CODEC_ID_MODEL(0x67663d84, "Arise 84 HDMI/DP", MODEL_GF), +diff --git a/sound/hda/codecs/helpers/razer_blade16_2025.c b/sound/hda/codecs/helpers/razer_blade16_2025.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/helpers/razer_blade16_2025.c +@@ -0,0 +1,820 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Razer Blade 16 (2025) external smart-amplifier programming and fixup, ++ * to be included from the codec driver. ++ * ++ * The internal speakers are driven by an external smart amplifier whose ++ * crossover, voicing and protection live in a DSP reached over a vendor ++ * coef mailbox on NID 0x20. These tables hold the programming captured ++ * from the Windows driver, reduced to the NID-0x20 traffic this machine ++ * needs (everything else the HDA parser rebuilds). ++ * ++ * Each entry is either a plain codec coef write or a mailbox write: ++ * strobe == 0: coef[addr] = lo ++ * strobe != 0: amp[addr] = (hi << 16) | lo, with the bank selected on ++ * port 0x89 and the write committed by the strobe opcode. ++ */ ++ ++struct alc298_razer_blade16_2025_op { ++ u16 bank; /* mailbox bank (port 0x89); unused for a plain coef */ ++ u16 addr; /* mailbox register, or plain coef index */ ++ u16 hi; /* mailbox high word; unused for a plain coef */ ++ u16 lo; /* mailbox low word, or plain coef value */ ++ u16 strobe; /* mailbox commit opcode; 0 marks a plain coef write */ ++}; ++ ++/* program the external DSP at init; the amp is woken/parked separately */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_init[] = { ++ { 0x0000, 0xf102, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf103, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc000, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xf20d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xf212, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xc001, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc003, 0x0000, 0x0022, 0xb031 }, ++ { 0x0000, 0xc004, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc005, 0x0000, 0x0044, 0xb031 }, ++ { 0x0000, 0xc007, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xc00e, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xf223, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xf224, 0x0000, 0x00db, 0xb031 }, ++ { 0x0000, 0xf225, 0x0000, 0x00ee, 0xb031 }, ++ { 0x0000, 0xf226, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xf227, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xf21a, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf242, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc120, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc200, 0x0000, 0x00d8, 0xb031 }, ++ { 0x0000, 0xc201, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xc202, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xc400, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc401, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc402, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xc403, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc404, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xc406, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc407, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc408, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xc300, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc125, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdf5f, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xdf60, 0x0000, 0x00a7, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x0084, 0xb031 }, ++ { 0x0000, 0xc206, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xf10a, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f4, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0065, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xf104, 0x0000, 0x00f7, 0xb031 }, ++ { 0x0000, 0xf105, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xf10b, 0x0000, 0x005c, 0xb031 }, ++ { 0x0000, 0xe706, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe707, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xe806, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xe807, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xce04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xce05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xce07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xcf04, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xcf05, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xcf06, 0x0000, 0x0031, 0xb031 }, ++ { 0x0000, 0xcf07, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xc130, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xe000, 0x0000, 0x00a8, 0xb031 }, ++ { 0x4100, 0x1888, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xea00, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xed00, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xcc2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcc2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcc2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd2c, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd2d, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xcd2e, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xcd2f, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcc26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcc27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcd24, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd25, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xcd26, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xcd27, 0x0000, 0x0085, 0xb031 }, ++ { 0x0000, 0xcc20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcd20, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd21, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd22, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xcc16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcc17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd16, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xcd17, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcc2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcd29, 0x0000, 0x005d, 0xb031 }, ++ { 0x0000, 0xcd2a, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xcc31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcc32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd31, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xcd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd33, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd34, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcc37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd36, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xcd37, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd38, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcd39, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xcc09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcc0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcc0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcd09, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0a, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xcd0b, 0x0000, 0x007c, 0xb031 }, ++ { 0x0000, 0xcd0c, 0x0000, 0x005b, 0xb031 }, ++ { 0x0000, 0xcc0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcc0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcc10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcc11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xcd0e, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcd0f, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xcd10, 0x0000, 0x003e, 0xb031 }, ++ { 0x0000, 0xcd11, 0x0000, 0x002d, 0xb031 }, ++ { 0x0000, 0xccd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd6, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd7, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdd8, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcdd9, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xccda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xccdb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xcdda, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xcddb, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xc320, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xc321, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xe604, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdd00, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xdc19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xdc1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xdc1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xdc1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdc1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xdc1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xdc20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xde19, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1a, 0x0000, 0x006a, 0xb031 }, ++ { 0x0000, 0xde1b, 0x0000, 0x00aa, 0xb031 }, ++ { 0x0000, 0xde1c, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xde1d, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xde1e, 0x0000, 0x0027, 0xb031 }, ++ { 0x0000, 0xde1f, 0x0000, 0x0062, 0xb031 }, ++ { 0x0000, 0xde20, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xdb32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdd32, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdb33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdd33, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xdb34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdd34, 0x0000, 0x001a, 0xb031 }, ++ { 0x0000, 0xdb15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdd15, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdd17, 0x0000, 0x003f, 0xb031 }, ++ { 0x0000, 0xdb94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdd94, 0x0000, 0x0070, 0xb031 }, ++ { 0x0000, 0xdb19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd19, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x001c, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xdbbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xdbbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xdbbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xdbbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xddbb, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xddbc, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xddbd, 0x0000, 0x00f3, 0xb031 }, ++ { 0x0000, 0xddbe, 0x0000, 0x00cf, 0xb031 }, ++ { 0x0000, 0xdb01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdd01, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xdc52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xde52, 0x0000, 0x00ef, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xdf0a, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xdf0b, 0x0000, 0x007f, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdf01, 0x0000, 0x0073, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xebcb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xeb13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xeb14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xeb16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xeb21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xeb22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xeb23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xeb27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xeb2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xeb2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xeb30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xeb36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xeb37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xeb40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xeb43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xeb44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xeb46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xeb47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xeb4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xeb4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeb50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xeb52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xeb53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xeb56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xeb57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xeb60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xeb63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xeb64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xeb65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xeb66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xeb67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xeb68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xeb69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xeb6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xeb6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xebb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xeb75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xeb77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xeb80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xeb85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xeb86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xeb87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xeb88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xeb89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xeb8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xeb8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xebb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeb90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xeb92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xeb93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xeb94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xeb95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xeb96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xeb97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeba0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeba2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeba3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeba4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeba5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeba6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeba7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeba8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeba9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xebaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xebab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xeb00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xeb01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xeb02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xeb03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xeb05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xeb06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xeb07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd0cb, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xd0b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd010, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd011, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd012, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd013, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd014, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd015, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd016, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd017, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd020, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd021, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd022, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd023, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd024, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd025, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd026, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd027, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd028, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd029, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd02a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd02b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce6a, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdb04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdb05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xcc00, 0x0000, 0x00c1, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca62, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca65, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca68, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca75, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca7b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca00, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xca02, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc860, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc861, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc862, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc863, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc864, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc865, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc867, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc868, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc869, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc86a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc86b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc875, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc87b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc836, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc833, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc834, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc83f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc83e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc83a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc83b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc830, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc826, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc890, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc823, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc824, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc82f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc82e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc820, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc816, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc880, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc813, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc814, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc81f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc81e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc81a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc81b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc810, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc846, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc8d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc844, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc84f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc84e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc84a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc84b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xc851, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc500, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc501, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc502, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc503, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc504, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc505, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc506, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc507, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc510, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc511, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc512, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc513, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc514, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc515, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc516, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc517, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc518, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd251, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0xf800, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xecb0, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec10, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec11, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec12, 0x0000, 0x00ab, 0xb031 }, ++ { 0x0000, 0xec13, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xec14, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec15, 0x0000, 0x004c, 0xb031 }, ++ { 0x0000, 0xec16, 0x0000, 0x0069, 0xb031 }, ++ { 0x0000, 0xec21, 0x0000, 0x0011, 0xb031 }, ++ { 0x0000, 0xec22, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xec23, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xec24, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec25, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec26, 0x0000, 0x0055, 0xb031 }, ++ { 0x0000, 0xec27, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec28, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec29, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xec2a, 0x0000, 0x0051, 0xb031 }, ++ { 0x0000, 0xec2b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb2, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec30, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec31, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec32, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec33, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec34, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec35, 0x0000, 0x00bb, 0xb031 }, ++ { 0x0000, 0xec36, 0x0000, 0x0024, 0xb031 }, ++ { 0x0000, 0xec37, 0x0000, 0x0068, 0xb031 }, ++ { 0x0000, 0xec40, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec41, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec42, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xec43, 0x0000, 0x00e8, 0xb031 }, ++ { 0x0000, 0xec44, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec45, 0x0000, 0x004f, 0xb031 }, ++ { 0x0000, 0xec46, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xec47, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec48, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec49, 0x0000, 0x00c5, 0xb031 }, ++ { 0x0000, 0xec4a, 0x0000, 0x0032, 0xb031 }, ++ { 0x0000, 0xec4b, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xecb4, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec50, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec51, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xec52, 0x0000, 0x00b4, 0xb031 }, ++ { 0x0000, 0xec53, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec54, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec55, 0x0000, 0x007d, 0xb031 }, ++ { 0x0000, 0xec56, 0x0000, 0x00d9, 0xb031 }, ++ { 0x0000, 0xec57, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xec60, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec61, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec62, 0x0000, 0x00b5, 0xb031 }, ++ { 0x0000, 0xec63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xec64, 0x0000, 0x00fa, 0xb031 }, ++ { 0x0000, 0xec65, 0x0000, 0x0086, 0xb031 }, ++ { 0x0000, 0xec66, 0x0000, 0x00a8, 0xb031 }, ++ { 0x0000, 0xec67, 0x0000, 0x0040, 0xb031 }, ++ { 0x0000, 0xec68, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xec69, 0x0000, 0x009a, 0xb031 }, ++ { 0x0000, 0xec6a, 0x0000, 0x002f, 0xb031 }, ++ { 0x0000, 0xec6b, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xecb6, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec70, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec71, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec72, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec73, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec74, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xec75, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec76, 0x0000, 0x002c, 0xb031 }, ++ { 0x0000, 0xec77, 0x0000, 0x0050, 0xb031 }, ++ { 0x0000, 0xec80, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec81, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec82, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec83, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec84, 0x0000, 0x00f5, 0xb031 }, ++ { 0x0000, 0xec85, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xec86, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xec87, 0x0000, 0x00d0, 0xb031 }, ++ { 0x0000, 0xec88, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xec89, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xec8a, 0x0000, 0x0016, 0xb031 }, ++ { 0x0000, 0xec8b, 0x0000, 0x0028, 0xb031 }, ++ { 0x0000, 0xecb8, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xec90, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec91, 0x0000, 0x008b, 0xb031 }, ++ { 0x0000, 0xec92, 0x0000, 0x00bd, 0xb031 }, ++ { 0x0000, 0xec93, 0x0000, 0x0064, 0xb031 }, ++ { 0x0000, 0xec94, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xec95, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xec96, 0x0000, 0x00e7, 0xb031 }, ++ { 0x0000, 0xec97, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xeca0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca1, 0x0000, 0x0091, 0xb031 }, ++ { 0x0000, 0xeca2, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xeca3, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xeca4, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xeca5, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xeca6, 0x0000, 0x0093, 0xb031 }, ++ { 0x0000, 0xeca7, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xeca8, 0x0000, 0x0003, 0xb031 }, ++ { 0x0000, 0xeca9, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xecaa, 0x0000, 0x0099, 0xb031 }, ++ { 0x0000, 0xecab, 0x0000, 0x006c, 0xb031 }, ++ { 0x0000, 0xebc3, 0x0000, 0x00ff, 0xb031 }, ++ { 0x0000, 0xebc4, 0x0000, 0x00fc, 0xb031 }, ++ { 0x0000, 0xec00, 0x0000, 0x0005, 0xb031 }, ++ { 0x0000, 0xec01, 0x0000, 0x00a9, 0xb031 }, ++ { 0x0000, 0xec02, 0x0000, 0x00df, 0xb031 }, ++ { 0x0000, 0xec03, 0x0000, 0x007b, 0xb031 }, ++ { 0x0000, 0xec05, 0x0000, 0x0079, 0xb031 }, ++ { 0x0000, 0xec06, 0x0000, 0x005a, 0xb031 }, ++ { 0x0000, 0xec07, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xebc0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xd1b0, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd110, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd111, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd112, 0x0000, 0x0092, 0xb031 }, ++ { 0x0000, 0xd113, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd114, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd115, 0x0000, 0x00f6, 0xb031 }, ++ { 0x0000, 0xd116, 0x0000, 0x0072, 0xb031 }, ++ { 0x0000, 0xd117, 0x0000, 0x00b0, 0xb031 }, ++ { 0x0000, 0xd120, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd121, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd122, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd123, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd124, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd125, 0x0000, 0x0009, 0xb031 }, ++ { 0x0000, 0xd126, 0x0000, 0x0090, 0xb031 }, ++ { 0x0000, 0xd127, 0x0000, 0x0020, 0xb031 }, ++ { 0x0000, 0xd128, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xd129, 0x0000, 0x00fb, 0xb031 }, ++ { 0x0000, 0xd12a, 0x0000, 0x0037, 0xb031 }, ++ { 0x0000, 0xd12b, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c3, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd0c4, 0x0000, 0x000c, 0xb031 }, ++ { 0x0000, 0xd0c0, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xce63, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xce64, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xdd04, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xdd05, 0x0000, 0x0006, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x000f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca82, 0x0000, 0x0077, 0xb031 }, ++ { 0x0000, 0xca85, 0x0000, 0x0082, 0xb031 }, ++ { 0x0000, 0xca88, 0x0000, 0x00c2, 0xb031 }, ++ { 0x0000, 0xca95, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xca9b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xca10, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xca12, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc960, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc961, 0x0000, 0x00e5, 0xb031 }, ++ { 0x0000, 0xc962, 0x0000, 0x00eb, 0xb031 }, ++ { 0x0000, 0xc963, 0x0000, 0x00c6, 0xb031 }, ++ { 0x0000, 0xc964, 0x0000, 0x00ba, 0xb031 }, ++ { 0x0000, 0xc965, 0x0000, 0x0061, 0xb031 }, ++ { 0x0000, 0xc967, 0x0000, 0x0017, 0xb031 }, ++ { 0x0000, 0xc968, 0x0000, 0x00ed, 0xb031 }, ++ { 0x0000, 0xc969, 0x0000, 0x003c, 0xb031 }, ++ { 0x0000, 0xc96a, 0x0000, 0x008a, 0xb031 }, ++ { 0x0000, 0xc96b, 0x0000, 0x00e2, 0xb031 }, ++ { 0x0000, 0xc975, 0x0000, 0x003a, 0xb031 }, ++ { 0x0000, 0xc97b, 0x0000, 0x006f, 0xb031 }, ++ { 0x0000, 0xc936, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9a0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc933, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xc934, 0x0000, 0x0002, 0xb031 }, ++ { 0x0000, 0xc93f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc93e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc93a, 0x0000, 0x0076, 0xb031 }, ++ { 0x0000, 0xc93b, 0x0000, 0x0089, 0xb031 }, ++ { 0x0000, 0xc930, 0x0000, 0x0013, 0xb031 }, ++ { 0x0000, 0xc926, 0x0000, 0x00d1, 0xb031 }, ++ { 0x0000, 0xc990, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc923, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc924, 0x0000, 0x0004, 0xb031 }, ++ { 0x0000, 0xc92f, 0x0000, 0x0018, 0xb031 }, ++ { 0x0000, 0xc92e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc920, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc916, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc980, 0x0000, 0x00e1, 0xb031 }, ++ { 0x0000, 0xc913, 0x0000, 0x0000, 0xb031 }, ++ { 0x0000, 0xc914, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc91f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc91e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc91a, 0x0000, 0x0066, 0xb031 }, ++ { 0x0000, 0xc91b, 0x0000, 0x0078, 0xb031 }, ++ { 0x0000, 0xc910, 0x0000, 0x000d, 0xb031 }, ++ { 0x0000, 0xc946, 0x0000, 0x00f9, 0xb031 }, ++ { 0x0000, 0xc9d0, 0x0000, 0x00f1, 0xb031 }, ++ { 0x0000, 0xc944, 0x0000, 0x0001, 0xb031 }, ++ { 0x0000, 0xc94f, 0x0000, 0x0014, 0xb031 }, ++ { 0x0000, 0xc94e, 0x0000, 0x0094, 0xb031 }, ++ { 0x0000, 0xc94a, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc94b, 0x0000, 0x0056, 0xb031 }, ++ { 0x0000, 0xc951, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xc600, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc601, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc602, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc603, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc604, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc605, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc606, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc607, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc610, 0x0000, 0x0007, 0xb031 }, ++ { 0x0000, 0xc611, 0x0000, 0x00da, 0xb031 }, ++ { 0x0000, 0xc612, 0x0000, 0x000e, 0xb031 }, ++ { 0x0000, 0xc613, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc614, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xc615, 0x0000, 0x0026, 0xb031 }, ++ { 0x0000, 0xc616, 0x0000, 0x004a, 0xb031 }, ++ { 0x0000, 0xc617, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xc618, 0x0000, 0x0008, 0xb031 }, ++ { 0x0000, 0xc541, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xc802, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc902, 0x0000, 0x00d2, 0xb031 }, ++ { 0x0000, 0xc800, 0x0000, 0x00f0, 0xb031 }, ++ { 0x0000, 0xd250, 0x0000, 0x0087, 0xb031 }, ++ { 0x0000, 0xd252, 0x0000, 0x0030, 0xb031 }, ++ { 0x0000, 0xd264, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xce60, 0x0000, 0x00e3, 0xb031 }, ++ { 0x0000, 0xdf00, 0x0000, 0x0010, 0xb031 }, ++ { 0x0000, 0xdbb5, 0x0041, 0xd27f, 0xb037 }, ++ { 0x0000, 0xddb5, 0x0040, 0x6e3b, 0xb037 }, ++ { 0x0000, 0xdb93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdd93, 0x0000, 0x009e, 0xb033 }, ++ { 0x0000, 0xdb12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdd12, 0x0000, 0x00c0, 0xb031 }, ++ { 0x0000, 0xdb08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdd08, 0x0000, 0x0080, 0xb031 }, ++ { 0x0000, 0xdb00, 0x0000, 0x00cc, 0xb031 }, ++ { 0x0000, 0xc203, 0x0000, 0x009c, 0xb031 }, ++ { 0x0000, 0x0010, 0x0000, 0x0f21, 0x0000 }, ++}; ++ ++/* wake the amp (ea00=0x43, c121=0x0b, f109=0xe0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_wake[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0043, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000b, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00e0, 0xb031 }, ++}; ++ ++/* park the amp (ea00=0x47, c121=0x0a, f109=0xa0) */ ++static const struct alc298_razer_blade16_2025_op alc298_razer_blade16_2025_amp_sleep[] = { ++ { 0x0000, 0xea00, 0x0000, 0x0047, 0xb031 }, ++ { 0x0000, 0xc121, 0x0000, 0x000a, 0xb031 }, ++ { 0x0000, 0xf109, 0x0000, 0x00a0, 0xb031 }, ++}; ++ ++/* ++ * Razer Blade 16 (2025): Realtek ALC298 driving an external smart amp. ++ * Crossover, voicing and speaker protection live in an external DSP ++ * reached over a vendor-coef mailbox on NID 0x20; the captured DSP ++ * programming is in the tables above. NID 0x14, the BIOS-disabled ++ * tweeter pin, is re-exposed via a pin-config override so the parser ++ * drives it as a normal internal speaker (shared volume/mute, auto-mute ++ * on headphone insertion). ++ * ++ * alc298_razer_blade16_2025_apply() runs one op-table: a plain coef write ++ * (strobe == 0) goes through alc_write_coef_idx(), while a mailbox op ++ * selects a bank and streams four PROC_COEF words (addr/hi/lo/commit) ++ * into a single coef index (0x23) on NID 0x20 to reach the external DSP ++ * -- that one-index/many-words form is why it cannot be expressed as an ++ * alc_process_coef_fw() table (one value per index). ++ */ ++static void alc298_razer_blade16_2025_apply(struct hda_codec *codec, ++ const struct alc298_razer_blade16_2025_op *seq, ++ int num) ++{ ++ int i; ++ ++ for (i = 0; i < num; i++) { ++ if (!seq[i].strobe) { ++ alc_write_coef_idx(codec, seq[i].addr, seq[i].lo); ++ continue; ++ } ++ alc_write_coef_idx(codec, 0x89, seq[i].bank); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0x23); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].addr); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].hi); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].lo); ++ snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_PROC_COEF, seq[i].strobe); ++ } ++} ++ ++static void alc298_razer_blade16_2025_pcm_hook(struct hda_pcm_stream *hinfo, ++ struct hda_codec *codec, ++ struct snd_pcm_substream *substream, ++ int action) ++{ ++ /* power the external amp only while a stream is running */ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_PREPARE: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} ++ ++static void alc298_fixup_razer_blade16_2025(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Pin every output to a fixed converter so the routing does not ++ * rely on the generic parser's DAC-allocation heuristics: without ++ * a fixed assignment the parser brings up only one speaker pin and ++ * leaves the other silent, and the allocation could change between ++ * kernel versions. Both speaker pins share DAC 0x03 through mixer ++ * 0x0d: that is the only converter that drives the woofer (NID ++ * 0x17) on this machine -- feeding it from DAC 0x02 or the digital ++ * converter 0x06 leaves the woofer silent. Both pins carry the ++ * same stereo stream and the external amp does the crossover. ++ * Headphone NID 0x21 keeps DAC 0x02. ++ */ ++ static const hda_nid_t preferred_pairs[] = { ++ 0x21, 0x02, ++ 0x14, 0x03, ++ 0x17, 0x03, ++ 0 ++ }; ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.preferred_dacs = preferred_pairs; ++ break; ++ case HDA_FIXUP_ACT_PROBE: ++ spec->gen.pcm_playback_hook = alc298_razer_blade16_2025_pcm_hook; ++ break; ++ case HDA_FIXUP_ACT_INIT: ++ /* ++ * Wake the amp, program its DSP at boot and on resume, then ++ * park it. It is woken again for playback and parked ++ * afterwards from the pcm_playback_hook. ++ */ ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_wake, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_wake)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_init, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_init)); ++ alc298_razer_blade16_2025_apply(codec, alc298_razer_blade16_2025_amp_sleep, ++ ARRAY_SIZE(alc298_razer_blade16_2025_amp_sleep)); ++ break; ++ } ++} +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -296,7 +296,8 @@ static void alc282_init(struct hda_codec *codec) + msleep(100); + + /* Headphone capless set to normal mode */ +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static void alc282_shutup(struct hda_codec *codec) +@@ -333,7 +334,8 @@ static void alc282_shutup(struct hda_codec *codec) + + alc_auto_setup_eapd(codec, false); + alc_shutup_pins(codec); +- alc_write_coef_idx(codec, 0x78, coef78); ++ if (coef78 != -1) ++ alc_write_coef_idx(codec, 0x78, coef78); + } + + static const struct coef_fw alc283_coefs[] = { +@@ -585,15 +587,19 @@ static void alc285_hp_init(struct hda_codec *codec) + + alc_write_coefex_idx(codec, 0x58, 0x00, 0xf888); /* HP depop procedure start */ + val = alc_read_coefex_idx(codec, 0x58, 0x00); +- for (i = 0; i < 20 && val & 0x8000; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x8000; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x00); + } /* Wait for depop procedure finish */ + +- alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ +- alc_update_coef_idx(codec, 0x38, 1<<4, coef38); +- alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); +- alc_update_coef_idx(codec, 0x36, 3<<13, coef36); ++ if (val != -1) ++ alc_write_coefex_idx(codec, 0x58, 0x00, val); /* write back the result */ ++ if (coef38 != -1) ++ alc_update_coef_idx(codec, 0x38, 1<<4, coef38); ++ if (coef0d != -1) ++ alc_update_coef_idx(codec, 0x0d, 0x110, coef0d); ++ if (coef36 != -1) ++ alc_update_coef_idx(codec, 0x36, 3<<13, coef36); + + msleep(50); + alc_update_coef_idx(codec, 0x4a, 1<<15, 0); +@@ -858,7 +864,7 @@ static void alc294_hp_init(struct hda_codec *codec) + + /* Wait for depop procedure finish */ + val = alc_read_coefex_idx(codec, 0x58, 0x01); +- for (i = 0; i < 20 && val & 0x0080; i++) { ++ for (i = 0; i < 20 && val != -1 && val & 0x0080; i++) { + msleep(50); + val = alc_read_coefex_idx(codec, 0x58, 0x01); + } +@@ -1392,6 +1398,15 @@ static void alc245_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0, 0x04); + } + ++static void alc245_fixup_minisforum_jack_detect(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ if (action == HDA_FIXUP_ACT_INIT) ++ /* Clear Reset HP JD while preserving the other coefficient bits. */ ++ alc_update_coef_idx(codec, 0x4a, BIT(15), 0); ++} ++ + /* turn on/off mic-mute LED per capture hook via VREF change */ + static int vref_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +@@ -2557,6 +2572,13 @@ static void alc282_fixup_asus_tx300(struct hda_codec *codec, + } + } + ++static void alc285_lenovo_dac_rename(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ if (action == HDA_FIXUP_ACT_BUILD) ++ rename_ctl(codec, "Line Out Playback Volume", ++ "Headphone Playback Volume"); ++} + static void alc290_fixup_mono_speakers(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -3262,6 +3284,36 @@ static void find_cirrus_companion_amps(struct hda_codec *cdc) + comp_generic_fixup(cdc, HDA_FIXUP_ACT_PRE_PROBE, bus, acpi_ids[i].hid, match, count); + } + ++static void aw88399_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) ++{ ++ comp_generic_fixup(cdc, action, "i2c", "AWDZ8399", "-%s:00-aw88399-hda.%d", 2); ++} ++ ++static void alc287_fixup_legion_16iax10h_aw88399(struct hda_codec *codec, ++ const struct hda_fixup *fix, int action) ++{ ++ /* ++ * Force DAC 0x02 for the bass speaker 0x17, as the default 0x06 lacks volume controls. ++ */ ++ static const hda_nid_t conn[] = { 0x02 }; ++ struct alc_spec *spec = codec->spec; ++ ++ alc269_fixup_limit_int_mic_boost(codec, fix, action); ++ alc_fixup_headset_mode_no_hp_mic(codec, fix, action); ++ alc_fixup_headset_jack(codec, fix, action); ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->gen.suppress_auto_mic = 1; ++ snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); ++ break; ++ case HDA_FIXUP_ACT_BUILD: ++ spec->gen.multiout.max_channels = 2; ++ spec->gen.pcm_rec[0]->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 2; ++ break; ++ } ++} ++ + static void cs35l41_fixup_i2c_two(struct hda_codec *cdc, const struct hda_fixup *fix, int action) + { + comp_generic_fixup(cdc, action, "i2c", "CSC3551", "-%s:00-cs35l41-hda.%d", 2); +@@ -3357,6 +3409,9 @@ static void alc287_fixup_acer_micmute_led(struct hda_codec *codec, + /* for alc285_fixup_ideapad_s740_coef() */ + #include "../helpers/ideapad_s740.c" + ++/* for alc298_fixup_razer_blade16_2025() */ ++#include "../helpers/razer_blade16_2025.c" ++ + static const struct coef_fw alc256_fixup_set_coef_defaults_coefs[] = { + WRITE_COEF(0x10, 0x0020), WRITE_COEF(0x24, 0x0000), + WRITE_COEF(0x26, 0x0000), WRITE_COEF(0x29, 0x3000), +@@ -3939,6 +3994,7 @@ enum { + ALC271_FIXUP_HP_GATE_MIC_JACK_E1_572, + ALC269_FIXUP_ACER_AC700, + ALC269_FIXUP_LIMIT_INT_MIC_BOOST, ++ ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST, + ALC269VB_FIXUP_ASUS_ZENBOOK, + ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, + ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4007,6 +4063,7 @@ enum { + ALC225_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC295_FIXUP_DISABLE_DAC3, + ALC285_FIXUP_SPEAKER2_TO_DAC1, ++ ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1, + ALC285_FIXUP_ASUS_HEADSET_MIC, + ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS, +@@ -4076,7 +4133,6 @@ enum { + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, +- ALC294_FIXUP_ASUS_ALLY_SPEAKER, + ALC294_FIXUP_ASUS_HPE, + ALC294_FIXUP_ASUS_COEF_1B, + ALC294_FIXUP_ASUS_GX502_HP, +@@ -4101,6 +4157,8 @@ enum { + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, + ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, + ALC298_FIXUP_LG_GRAM_STYLE_14, ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS, ++ ALC298_FIXUP_RAZER_BLADE16_2025, + ALC298_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, + ALC295_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4118,7 +4176,6 @@ enum { + ALC269_FIXUP_LEMOTE_A190X, + ALC256_FIXUP_INTEL_NUC8_RUGGED, + ALC233_FIXUP_INTEL_NUC8_DMIC, +- ALC233_FIXUP_INTEL_NUC8_BOOST, + ALC256_FIXUP_INTEL_NUC10, + ALC255_FIXUP_XIAOMI_HEADSET_MIC, + ALC274_FIXUP_HP_MIC, +@@ -4149,6 +4206,7 @@ enum { + ALC298_FIXUP_LENOVO_C940_DUET7, + ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, + ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK, + ALC287_FIXUP_13S_GEN2_SPEAKERS, + ALC256_FIXUP_SET_COEF_DEFAULTS, + ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, +@@ -4193,7 +4251,6 @@ enum { + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC2XX_FIXUP_HEADSET_MIC, + ALC289_FIXUP_DELL_CS35L41_SPI_2, +- ALC294_FIXUP_CS35L41_I2C_2, + ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_HEADPHONE_AMP_VOL, + ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, +@@ -4207,6 +4264,7 @@ enum { + ALC287_FIXUP_LENOVO_THKPAD_WH_ALC1318, + ALC256_FIXUP_CHROME_BOOK, + ALC245_FIXUP_CLEVO_NOISY_MIC, ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT, + ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE, + ALC233_FIXUP_MEDION_MTL_SPK, + ALC233_FIXUP_STARLABS_STARFIGHTER, +@@ -4216,7 +4274,6 @@ enum { + ALC274_FIXUP_HP_AIO_BIND_DACS, + ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2, + ALC285_FIXUP_ASUS_GA605K_HEADSET_MIC, +- ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1, + ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC, + ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK, + ALC256_FIXUP_VAIO_RPL_MIC_NO_PRESENCE, +@@ -4236,6 +4293,10 @@ enum { + ALC236_FIXUP_DELL_HP_POP_NOISE, + ALC274_FIXUP_HP_89E9_GPIO, + ALC274_FIXUP_HP_VERBS, ++ ALC287_FIXUP_AW88399_I2C_2, ++ ALC287_FIXUP_LENOVO_LEGION_AW88399, ++ ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ++ ALC285_LENOVO_DAC_RENAME, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4273,6 +4334,20 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + } + + static const struct hda_fixup alc269_fixups[] = { ++ [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170121 }, /* tweeter as internal speaker, seq 1 */ ++ { 0x17, 0x90170120 }, /* woofer as internal speaker, seq 0 */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC298_FIXUP_RAZER_BLADE16_2025, ++ }, ++ [ALC298_FIXUP_RAZER_BLADE16_2025] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc298_fixup_razer_blade16_2025, ++ }, + [ALC233_FIXUP_WUJIE_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -4713,6 +4788,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chain_id = ALC271_FIXUP_DMIC, + }, + [ALC269_FIXUP_LIMIT_INT_MIC_BOOST] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc269_fixup_limit_int_mic_boost ++ }, ++ [ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_limit_int_mic_boost, + .chained = true, +@@ -5088,11 +5167,7 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, + .chained = true, +- .chain_id = ALC233_FIXUP_INTEL_NUC8_BOOST, +- }, +- [ALC233_FIXUP_INTEL_NUC8_BOOST] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc269_fixup_limit_int_mic_boost ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC255_FIXUP_DELL_SPK_NOISE] = { + .type = HDA_FIXUP_FUNC, +@@ -5153,6 +5228,10 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC285_FIXUP_SPEAKER2_TO_DAC1] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, ++ }, ++ [ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, + .chained = true, + .chain_id = ALC269_FIXUP_THINKPAD_ACPI + }, +@@ -5672,11 +5751,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER +- }, +- [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC285_FIXUP_THINKPAD_X1_GEN7] = { + .type = HDA_FIXUP_FUNC, +@@ -6223,6 +6298,16 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_LENOVO_XPAD_ACPI, + }, ++ [ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ /* Same as ALC285_FIXUP_THINKPAD_HEADSET_JACK, except that the ++ * mute LEDs are driven through ideapad_laptop rather than ++ * thinkpad_acpi. ++ */ ++ .chain_id = ALC287_FIXUP_LENOVO_YOGA_PRO7, ++ }, + [ALC623_FIXUP_LENOVO_THINKSTATION_P340] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_no_shutup, +@@ -6662,10 +6747,6 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC289_FIXUP_DUAL_SPK + }, +- [ALC294_FIXUP_CS35L41_I2C_2] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = cs35l41_fixup_i2c_two, +- }, + [ALC256_FIXUP_ACER_SFG16_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, +@@ -6738,6 +6819,10 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, + }, ++ [ALC245_FIXUP_MINISFORUM_JACK_DETECT] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc245_fixup_minisforum_jack_detect, ++ }, + [ALC269_FIXUP_VAIO_VJFH52_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { +@@ -6758,6 +6843,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC233_FIXUP_STARLABS_STARFIGHTER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_starlabs_starfighter, ++ .chained = true, ++ .chain_id = ALC269_FIXUP_LIMIT_INT_MIC_BOOST, + }, + [ALC294_FIXUP_BASS_SPEAKER_15] = { + .type = HDA_FIXUP_FUNC, +@@ -6785,11 +6872,7 @@ static const struct hda_fixup alc269_fixups[] = { + { } + }, + .chained = true, +- .chain_id = ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1 +- }, +- [ALC285_FIXUP_ASUS_GA605K_I2C_SPEAKER2_TO_DAC1] = { +- .type = HDA_FIXUP_FUNC, +- .v.func = alc285_fixup_speaker2_to_dac1, ++ .chain_id = ALC285_FIXUP_SPEAKER2_TO_DAC1 + }, + [ALC269_FIXUP_POSITIVO_P15X_HEADSET_MIC] = { + .type = HDA_FIXUP_FUNC, +@@ -6907,6 +6990,26 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC274_FIXUP_HP_89E9_GPIO, + }, ++ [ALC287_FIXUP_AW88399_I2C_2] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = aw88399_fixup_i2c_two, ++ }, ++ [ALC287_FIXUP_LENOVO_LEGION_AW88399] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc287_fixup_legion_16iax10h_aw88399, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_AW88399_I2C_2, ++ }, ++ [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_headset_jack, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ++ }, ++ [ALC285_LENOVO_DAC_RENAME] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_lenovo_dac_rename, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -6960,8 +7063,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1597, "Acer Nitro 5 AN517-55", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159e, "Acer Nitro 5 AN515-46", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), +@@ -6970,6 +7075,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x1844, "Acer Helios ZPS", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1025, 0x1909, "Acer Nitro ANV16-42", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X), + SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS), +@@ -7154,10 +7260,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x860f, "HP ZBook 15 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), ++ SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), +@@ -7268,6 +7376,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8a0e, "HP Pavilion Laptop 15-eh2xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a0f, "HP Pavilion 14-ec1xxx", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8a1b, "HP 255 15.6 inch G9 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8a1f, "HP Laptop 14s-dr5xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7359,10 +7468,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c15, "HP Spectre x360 2-in-1 Laptop 14-eu0xxx", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), + SND_PCI_QUIRK(0x103c, 0x8c16, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), +- SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre 16", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c47, "HP EliteBook 840 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8c48, "HP EliteBook 860 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7511,6 +7621,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x8f7e, "HP Messi", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f94, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f95, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x1043, 0x1024, "ASUS Zephyrus G14 2025", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), +@@ -7518,6 +7629,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1034, "ASUS GU605C", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), ++ SND_PCI_QUIRK(0x1043, 0x1044, "ASUS GA403UM", ALC285_FIXUP_ASUS_GA403U_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1054, "ASUS G614FH/FM/FP", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x106f, "ASUS VivoBook X515UA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), +@@ -7525,16 +7637,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), ++ SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), + SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x1043, 0x115d, "Asus 1015E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1043, 0x1194, "ASUS UM3406KA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x11c0, "ASUS X556UR", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), +- HDA_CODEC_QUIRK(0x1043, 0x1204, "ASUS Strix G16 G615JMR", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TAS2781_I2C), +- HDA_CODEC_QUIRK(0x1043, 0x1214, "ASUS ROG Strix G615LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), +- SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TAS2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x1204, "ASUS Strix G615JHR_JMR_JPR", ALC287_FIXUP_TXNW2781_I2C_ASUS), ++ SND_PCI_QUIRK(0x1043, 0x1214, "ASUS Strix G615LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x125e, "ASUS Q524UQK", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1271, "ASUS X430UN", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1290, "ASUS X441SA", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), +@@ -7583,7 +7694,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1863, "ASUS UX6404VI/VV", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), + SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x18d3, "ASUS UM3504DA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), +@@ -7594,7 +7705,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x19f4, "ASUS UM3405GA", ALC294_FIXUP_ASUS_I2C_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1a13, "Asus G73Jw", ALC269_FIXUP_ASUS_G73JW), + SND_PCI_QUIRK(0x1043, 0x1a63, "ASUS UX3405MA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), +- SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC294_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1a83, "ASUS UM5302LA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1a8e, "ASUS G712LWS", ALC294_FIXUP_LENOVO_MIC_LOCATION), + SND_PCI_QUIRK(0x1043, 0x1a8f, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1b11, "ASUS UX431DA", ALC294_FIXUP_ASUS_COEF_1B), +@@ -7623,6 +7734,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1264, "ASUS UM5606KA", ALC294_FIXUP_BASS_SPEAKER_15), + SND_PCI_QUIRK(0x1043, 0x1e02, "ASUS UX3402ZA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x1e10, "ASUS VivoBook X507UAR", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), ++ SND_PCI_QUIRK(0x1043, 0x1e3e, "ASUS VivoBook M515DA/X515DAP", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1e11, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA502), + SND_PCI_QUIRK(0x1043, 0x1e12, "ASUS UM3402", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1e1f, "ASUS Vivobook 15 X1504VAP", ALC2XX_FIXUP_HEADSET_MIC), +@@ -7634,8 +7746,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1e93, "ASUS ExpertBook B9403CVAR", ALC294_FIXUP_ASUS_HPE), + SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RC72LA", ALC287_FIXUP_ASUS_ALLY_X), + SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2), +- HDA_CODEC_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), +- SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x1043, 0x1ee2, "ASUS UM6702RA/RC", ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x1043, 0x1c52, "ASUS Zephyrus G15 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f11, "ASUS Zephyrus G14", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1f12, "ASUS UM5302", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7846,7 +7957,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x2211, "Thinkpad W541", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2212, "Thinkpad T440", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2214, "Thinkpad X240", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x2215, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2218, "Thinkpad X1 Carbon 2nd", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2223, "ThinkPad T550", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x2226, "ThinkPad X250", ALC292_FIXUP_TPT440_DOCK), +@@ -7862,7 +7973,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x224b, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224c, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x224d, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x225c, "Lenovo ThinkPad X1 Carbon 6th Gen", ALC285_LENOVO_DAC_RENAME), ++ SND_PCI_QUIRK(0x17aa, 0x225d, "Thinkpad T480", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x2288, "Thinkpad X390", ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x2292, "Thinkpad X1 Carbon 7th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), + SND_PCI_QUIRK(0x17aa, 0x22be, "Thinkpad X1 Carbon 8th", ALC285_FIXUP_THINKPAD_HEADSET_JACK), +@@ -7937,7 +8049,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_SPEAKER2_TO_DAC1), ++ SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7972,6 +8084,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38b8, "Yoga S780-14.5 proX AMD YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38b9, "Yoga S780-14.5 proX AMD LX Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38ba, "Yoga S780-14.5 Air AMD quad YC", ALC287_FIXUP_TAS2781_I2C), ++ HDA_CODEC_QUIRK(0x17aa, 0x3926, "Legion Pro 5 16ADR10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ /* Legion R9000P ADR10 shares PCI SSID 17aa:38bb with Yoga S780-14.5 Air AMD quad AAC; ++ * use codec SSID to distinguish them ++ */ ++ HDA_CODEC_QUIRK(0x17aa, 0x3927, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3928, "Legion R9000P ADR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), + SND_PCI_QUIRK(0x17aa, 0x38bb, "Yoga S780-14.5 Air AMD quad AAC", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38be, "Yoga S980-14.5 proX YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38bf, "Yoga S980-14.5 proX LX Dual", ALC287_FIXUP_TAS2781_I2C), +@@ -7989,7 +8107,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C), + /* Legion 7 15ASH11 shares PCI SSID 17aa:38f9 with Thinkbook 16P Gen5; + * use codec SSID to distinguish them + */ +@@ -7999,7 +8116,9 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38fc, "Lenovo Yoga Pro 7 15ASH11", ALC287_FIXUP_LENOVO_YOGA_PRO7), + SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), +- SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), ++ HDA_CODEC_QUIRK(0x17aa, 0x3906, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), + SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), +@@ -8007,14 +8126,19 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), ++ HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3937, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ HDA_CODEC_QUIRK(0x17aa, 0x3939, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), ++ SND_PCI_QUIRK(0x17aa, 0x393e, "Lenovo ThinkBook 14 G8+ IPH", ALC287_FIXUP_LENOVO_XPAD_HEADSET_JACK), + HDA_CODEC_QUIRK(0x17aa, 0x394c, "Lenovo Yoga Slim 7 14AGP11", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI), + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_FIXUP_PCM_44K), +- SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5013, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x501a, "Thinkpad", ALC283_FIXUP_INT_MIC), + SND_PCI_QUIRK(0x17aa, 0x501e, "Thinkpad L440", ALC292_FIXUP_TPT440_DOCK), +- SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5026, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x5034, "Thinkpad T450", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5036, "Thinkpad T450s", ALC292_FIXUP_TPT440_DOCK), + SND_PCI_QUIRK(0x17aa, 0x503c, "Thinkpad L450", ALC292_FIXUP_TPT440_DOCK), +@@ -8027,7 +8151,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x505f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x5062, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x508b, "Thinkpad X12 Gen 1", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), +- SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x17aa, 0x5109, "Thinkpad", ALC269_FIXUP_THINKPAD_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x511e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK), + SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD), +@@ -8041,9 +8165,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1854, 0x0489, "LG gram 16 (16Z90R-A)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x048a, "LG gram 17 (17ZD90R)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), + SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), ++ SND_PCI_QUIRK(0x1854, 0x0554, "LG gram 16 (16Z90TR)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), + SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), ++ SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", ++ ALC298_FIXUP_RAZER_BLADE16_2025_PINS), + SND_PCI_QUIRK(0x1b35, 0x1235, "CZC B20", ALC269_FIXUP_CZC_B20), + SND_PCI_QUIRK(0x1b35, 0x1236, "CZC TMI", ALC269_FIXUP_CZC_TMI), + SND_PCI_QUIRK(0x1b35, 0x1237, "CZC L101", ALC269_FIXUP_CZC_L101), +@@ -8078,13 +8207,17 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), + SND_PCI_QUIRK(0x1e39, 0xca14, "MEDION NM14LNL", ALC233_FIXUP_MEDION_MTL_SPK), + SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), ++ SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), + SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), ++ SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ++ ALC245_FIXUP_MINISFORUM_JACK_DETECT), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), ++ SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +@@ -8095,6 +8228,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), + SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), ++ SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), +@@ -8168,6 +8302,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = { + SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO), + SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI), + SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED), ++ SND_PCI_QUIRK_VENDOR(0x2145, "Star Labs", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + {} + }; + +@@ -8307,6 +8442,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { + {.id = ALC2XX_FIXUP_HEADSET_MIC, .name = "alc2xx-fixup-headset-mic"}, + {.id = ALC245_FIXUP_BASS_HP_DAC, .name = "alc245-fixup-bass-hp-dac"}, + {.id = ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, .name = "alc256-honor-mrb-xxx-m1020-audio"}, ++ {.id = ALC287_FIXUP_LENOVO_LEGION_AW88399, .name = "alc287-lenovo-legion-aw88399"}, + {} + }; + #define ALC225_STANDARD_PINS \ +@@ -8912,6 +9048,7 @@ static int alc269_probe(struct hda_codec *codec, const struct hda_device_id *id) + spec->init_hook = alc256_init; + spec->gen.mixer_nid = 0; /* ALC256 does not have any loopback mixer path */ + if (codec->core.vendor_id == 0x10ec0236 && ++ codec->bus->pci && + codec->bus->pci->vendor != PCI_VENDOR_ID_AMD) + spec->en_3kpull_low = false; + break; +diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c +--- a/sound/hda/codecs/realtek/alc662.c ++++ b/sound/hda/codecs/realtek/alc662.c +@@ -325,6 +325,7 @@ enum { + ALC897_FIXUP_UNIS_H3C_X500S, + ALC897_FIXUP_HEADSET_MIC_PIN3, + ALC662_FIXUP_CSL_GPIO, ++ ALC897_FIXUP_BEELINK_SER6_AMP, + }; + + static const struct hda_fixup alc662_fixups[] = { +@@ -782,6 +783,10 @@ static const struct hda_fixup alc662_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_csl_amp, + }, ++ [ALC897_FIXUP_BEELINK_SER6_AMP] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc_fixup_gpio4, ++ }, + }; + + static const struct hda_quirk alc662_fixup_tbl[] = { +@@ -853,6 +858,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { + SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), + SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), ++ SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), + + #if 0 + /* Below is a quirk table taken from the old code. +diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig +--- a/sound/hda/codecs/side-codecs/Kconfig ++++ b/sound/hda/codecs/side-codecs/Kconfig +@@ -13,6 +13,24 @@ config SND_HDA_CIRRUS_SCODEC_KUNIT_TEST + Documentation/dev-tools/kunit/. + If in doubt, say "N". + ++config SND_HDA_SCODEC_AW88399 ++ tristate ++ select SND_HDA_GENERIC ++ ++config SND_HDA_SCODEC_AW88399_I2C ++ tristate "Build AW88399 HD-audio side codec support for I2C Bus" ++ depends on I2C ++ depends on ACPI ++ depends on SND_SOC ++ select SND_HDA_SCODEC_AW88399 ++ select SND_SOC_AW88399_LIB ++ help ++ Say Y or M here to include AW88399 I2C HD-audio side codec support ++ in snd-hda-intel driver, such as ALC287. ++ ++comment "Set to Y if you want auto-loading the side codec driver" ++ depends on SND_HDA=y && SND_HDA_SCODEC_AW88399_I2C=m ++ + config SND_HDA_SCODEC_CS35L41 + tristate + select SND_HDA_GENERIC +diff --git a/sound/hda/codecs/side-codecs/Makefile b/sound/hda/codecs/side-codecs/Makefile +--- a/sound/hda/codecs/side-codecs/Makefile ++++ b/sound/hda/codecs/side-codecs/Makefile +@@ -3,6 +3,8 @@ subdir-ccflags-y += -I$(src)/../../common + + snd-hda-cirrus-scodec-y := cirrus_scodec.o + snd-hda-cirrus-scodec-test-y := cirrus_scodec_test.o ++snd-hda-scodec-aw88399-y := aw88399_hda.o ++snd-hda-scodec-aw88399-i2c-y := aw88399_hda_i2c.o + snd-hda-scodec-cs35l41-y := cs35l41_hda.o cs35l41_hda_property.o + snd-hda-scodec-cs35l41-i2c-y := cs35l41_hda_i2c.o + snd-hda-scodec-cs35l41-spi-y := cs35l41_hda_spi.o +@@ -16,6 +18,8 @@ snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o + + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC) += snd-hda-cirrus-scodec.o + obj-$(CONFIG_SND_HDA_CIRRUS_SCODEC_KUNIT_TEST) += snd-hda-cirrus-scodec-test.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399) += snd-hda-scodec-aw88399.o ++obj-$(CONFIG_SND_HDA_SCODEC_AW88399_I2C) += snd-hda-scodec-aw88399-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o + obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.c +@@ -0,0 +1,373 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA side codec driver ++// ++// Based on cs35l41_hda.c and aw88399.c ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../generic.h" ++#include "hda_component.h" ++#include "aw88399_hda.h" ++ ++#define AW88399_HDA_I2C_BASE_ADDR 0x34 ++ ++static void aw88399_hda_playback_hook(struct device *dev, int action) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct aw88399 *core = aw88399->core; ++ int ret = 0; ++ ++ dev_dbg(aw88399->dev, "Playback action: %d\n", action); ++ ++ switch (action) { ++ case HDA_GEN_PCM_ACT_OPEN: ++ pm_runtime_get_sync(dev); ++ aw88399->playing = true; ++ break; ++ case HDA_GEN_PCM_ACT_PREPARE: ++ if (core) ++ aw88399_start(core, AW88399_SYNC_START); ++ break; ++ case HDA_GEN_PCM_ACT_CLEANUP: ++ if (aw88399->aw_dev) ++ ret = aw88399_stop(aw88399->aw_dev); ++ if (ret) ++ dev_err(aw88399->dev, "Failed to stop amplifier: %d\n", ret); ++ break; ++ case HDA_GEN_PCM_ACT_CLOSE: ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ aw88399->playing = false; ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_put_autosuspend(dev); ++ break; ++ default: ++ dev_warn(aw88399->dev, "Unsupported action: %d\n", action); ++ break; ++ } ++} ++ ++static int aw88399_hda_bind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (!comp) ++ return -EINVAL; ++ ++ if (comp->dev) ++ return -EBUSY; ++ ++ comp->dev = dev; ++ ++ strscpy(comp->name, dev_name(dev), sizeof(comp->name)); ++ ++ comp->playback_hook = aw88399_hda_playback_hook; ++ ++ dev_info(aw88399->dev, ++ "AW88399 Bound - SSID: %s, channel: %d\n", ++ aw88399->acpi_subsystem_id, aw88399->channel); ++ ++ return 0; ++} ++ ++static void aw88399_hda_unbind(struct device *dev, struct device *master, void *master_data) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ struct hda_component_parent *parent = master_data; ++ struct hda_component *comp; ++ ++ comp = hda_component_from_index(parent, aw88399->index); ++ if (comp && (comp->dev == dev)) ++ memset(comp, 0, sizeof(*comp)); ++ ++ dev_dbg(aw88399->dev, "Unbound from HDA codec\n"); ++} ++ ++static const struct component_ops aw88399_hda_comp_ops = { ++ .bind = aw88399_hda_bind, ++ .unbind = aw88399_hda_unbind, ++}; ++ ++static int aw88399_hda_index_from_i2c(struct aw88399_hda *aw88399) ++{ ++ return to_i2c_client(aw88399->dev)->addr - AW88399_HDA_I2C_BASE_ADDR; ++} ++ ++static int aw88399_hda_init(struct aw88399_hda *aw88399) ++{ ++ struct device *dev = aw88399->dev; ++ struct i2c_client *i2c = to_i2c_client(dev); ++ struct aw88399 *core; ++ int ret; ++ ++ core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); ++ if (!core) ++ return -ENOMEM; ++ ++ mutex_init(&core->lock); ++ core->reset_gpio = aw88399->reset_gpio; ++ core->regmap = aw88399->regmap; ++ core->bsts_unreliable = aw88399->bsts_unreliable; ++ ++ aw88399_hw_reset(core); ++ ++ ret = aw88399_init(core, i2c, aw88399->regmap); ++ if (ret) ++ return ret; ++ ++ /* Set channel BEFORE loading firmware so ACF parser sees correct value */ ++ if (core->aw_pa) ++ aw88399_dev_set_channel(core, aw88399->channel); ++ ++ ret = aw88399_request_firmware_file(core); ++ if (ret) ++ return ret; ++ ++ aw88399->core = core; ++ aw88399->aw_dev = core->aw_pa; ++ ++ return 0; ++} ++ ++static int aw88399_swap_channels(struct aw88399_hda *aw88399) ++{ ++ /* ++ * Certain Lenovo Legion laptops have their ++ * I2C wiring reversed: 0x34 is physically the right speaker, ++ * 0x35 is the left. Swap channels to correct L/R assignment. ++ * This is a model-specific hardware wiring issue, not a driver bug. ++ */ ++ aw88399->channel = 1 - aw88399->channel; ++ dev_dbg(aw88399->dev, ++ "Channel swap applied: index %d -> channel %d\n", ++ aw88399->index, aw88399->channel); ++ return 0; ++} ++ ++static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) ++{ ++ /* ++ * BSTS (boost-finished) status bit does not reliably report on ++ * some hardware. On certain Lenovo Legion laptops, both amps ++ * report BSTS=0 (boost not finished) during normal playback ++ * despite clean audio output. Skip BSTS in the startup status ++ * check to avoid false init failures. ++ */ ++ aw88399->bsts_unreliable = true; ++ dev_dbg(aw88399->dev, "BSTS status check disabled\n"); ++ return 0; ++} ++ ++static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) ++{ ++ aw88399_swap_channels(aw88399); ++ aw88399_skip_bsts_check(aw88399); ++ return 0; ++} ++ ++struct aw88399_prop_model { ++ const char *ssid; ++ int (*apply_prop)(struct aw88399_hda *aw88399); ++}; ++ ++static const struct aw88399_prop_model aw88399_prop_model_table[] = { ++ { "17AA3906", aw88399_apply_legion_quirks }, ++ { "17AA3907", aw88399_apply_legion_quirks }, ++ { "17AA3927", aw88399_apply_legion_quirks }, ++ { "17AA3928", aw88399_apply_legion_quirks }, ++ { "17AA3936", aw88399_apply_legion_quirks }, ++ { "17AA3937", aw88399_apply_legion_quirks }, ++ { "17AA3938", aw88399_apply_legion_quirks }, ++ { "17AA3939", aw88399_apply_legion_quirks }, ++ { } ++}; ++ ++static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) ++{ ++ struct acpi_device *adev; ++ const char *sub; ++ const struct aw88399_prop_model *model; ++ ++ aw88399->index = aw88399_hda_index_from_i2c(aw88399); ++ aw88399->channel = aw88399->index; ++ aw88399->acpi_subsystem_id = NULL; ++ ++ adev = acpi_dev_get_first_match_dev("AWDZ8399", NULL, -1); ++ if (!adev) { ++ dev_err(aw88399->dev, "Failed to find an ACPI device for AWDZ8399\n"); ++ return -ENODEV; ++ } ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); ++ acpi_dev_put(adev); ++ if (!physdev) ++ return -ENODEV; ++ ++ sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); ++ if (IS_ERR_OR_NULL(sub)) ++ return 0; ++ ++ aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); ++ kfree(sub); ++ if (!aw88399->acpi_subsystem_id) ++ return -ENOMEM; ++ ++ for (model = aw88399_prop_model_table; model->ssid; model++) { ++ if (!strcasecmp(model->ssid, aw88399->acpi_subsystem_id)) { ++ dev_info(aw88399->dev, ++ "Applying properties for SSID %s\n", ++ aw88399->acpi_subsystem_id); ++ return model->apply_prop(aw88399); ++ } ++ } ++ ++ return 0; ++} ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap) ++{ ++ struct aw88399_hda *aw88399; ++ int ret; ++ ++ aw88399 = devm_kzalloc(dev, sizeof(*aw88399), GFP_KERNEL); ++ if (!aw88399) ++ return -ENOMEM; ++ ++ if (IS_ERR(regmap)) ++ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to obtain regmap\n"); ++ ++ aw88399->dev = dev; ++ aw88399->regmap = regmap; ++ dev_set_drvdata(dev, aw88399); ++ ++ aw88399->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); ++ if (IS_ERR(aw88399->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(aw88399->reset_gpio), ++ "Failed to get reset GPIO\n"); ++ ++ ret = aw88399_hda_acpi_probe(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "ACPI probe failed\n"); ++ ++ ret = aw88399_hda_init(aw88399); ++ if (ret) ++ return dev_err_probe(dev, ret, "Chip initialization failed\n"); ++ ++ /* Enable runtime PM */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ pm_runtime_mark_last_busy(dev); ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ ret = component_add(dev, &aw88399_hda_comp_ops); ++ if (ret) { ++ pm_runtime_disable(dev); ++ return dev_err_probe(dev, ret, "Failed to register component\n"); ++ } ++ ++ dev_info(dev, "AW88399 HDA side codec registered successfully\n"); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_probe, "SND_HDA_SCODEC_AW88399"); ++ ++void aw88399_hda_remove(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ pm_runtime_disable(dev); ++ ++ if (aw88399->aw_dev) ++ aw88399_stop(aw88399->aw_dev); ++ ++ component_del(dev, &aw88399_hda_comp_ops); ++ ++ dev_dbg(aw88399->dev, "AW88399 HDA side codec removed\n"); ++} ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_remove, "SND_HDA_SCODEC_AW88399"); ++ ++static int aw88399_hda_runtime_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ return 0; ++} ++ ++static int aw88399_hda_runtime_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ ++ dev_dbg(aw88399->dev, "Runtime resume\n"); ++ ++ if (aw88399->core && aw88399->aw_dev && aw88399->playing) ++ aw88399_start(aw88399->core, AW88399_SYNC_START); ++ ++ return 0; ++} ++ ++static int aw88399_hda_system_suspend(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System suspend\n"); ++ ++ if (aw88399->aw_dev && aw88399->playing) ++ aw88399_stop(aw88399->aw_dev); ++ ++ if (aw88399->core) ++ aw88399->core->fw_needs_reload = true; ++ ++ ret = pm_runtime_force_suspend(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force suspend failed: %d\n", ret); ++ ++ return ret; ++} ++ ++static int aw88399_hda_system_resume(struct device *dev) ++{ ++ struct aw88399_hda *aw88399 = dev_get_drvdata(dev); ++ int ret; ++ ++ dev_dbg(aw88399->dev, "System resume\n"); ++ ++ if (aw88399->aw_dev) ++ aw88399_hw_reset(aw88399->core); ++ ++ ret = pm_runtime_force_resume(dev); ++ if (ret) ++ dev_err(aw88399->dev, "Runtime force resume failed: %d\n", ret); ++ ++ return ret; ++} ++ ++const struct dev_pm_ops aw88399_hda_pm_ops = { ++ RUNTIME_PM_OPS(aw88399_hda_runtime_suspend, aw88399_hda_runtime_resume, NULL) ++ SYSTEM_SLEEP_PM_OPS(aw88399_hda_system_suspend, aw88399_hda_system_resume) ++}; ++EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); ++ ++MODULE_DESCRIPTION("AW88399 HDA driver"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_AUTHOR("Marco Giunta "); ++MODULE_LICENSE("GPL"); ++MODULE_FIRMWARE("aw88399_acf.bin"); +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda.h +@@ -0,0 +1,36 @@ ++/* SPDX-License-Identifier: GPL-2.0-only ++ * ++ * AW88399 HDA side codec driver ++ */ ++ ++#ifndef __AW88399_HDA_H__ ++#define __AW88399_HDA_H__ ++ ++#include ++#include ++#include ++ ++struct aw88399; ++struct aw_device; ++ ++struct aw88399_hda { ++ struct device *dev; ++ struct regmap *regmap; ++ struct gpio_desc *reset_gpio; ++ struct aw_device *aw_dev; ++ struct aw88399 *core; ++ bool bsts_unreliable; ++ ++ const char *acpi_subsystem_id; ++ int index; ++ int channel; ++ ++ bool playing; ++}; ++ ++int aw88399_hda_probe(struct device *dev, struct regmap *regmap); ++void aw88399_hda_remove(struct device *dev); ++ ++extern const struct dev_pm_ops aw88399_hda_pm_ops; ++ ++#endif /* __AW88399_HDA_H__ */ +diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +new file mode 100644 +--- /dev/null ++++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +@@ -0,0 +1,54 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// AW88399 HDA I2C driver ++// ++// Based on cs35l41_hda_i2c.c ++// ++ ++#include ++#include ++#include ++ ++#include "aw88399_hda.h" ++ ++static int aw88399_hda_i2c_probe(struct i2c_client *clt) ++{ ++ if (!strstr(dev_name(&clt->dev), "AWDZ8399")) ++ return -ENODEV; ++ ++ return aw88399_hda_probe(&clt->dev, ++ devm_regmap_init_i2c(clt, &aw88399_remap_config)); ++} ++ ++static void aw88399_hda_i2c_remove(struct i2c_client *clt) ++{ ++ aw88399_hda_remove(&clt->dev); ++} ++ ++static const struct i2c_device_id aw88399_hda_i2c_id[] = { ++ { .name = "aw88399-hda" }, ++ { } ++}; ++ ++static const struct acpi_device_id aw88399_acpi_hda_match[] = { ++ { "AWDZ8399", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, aw88399_acpi_hda_match); ++ ++static struct i2c_driver aw88399_hda_i2c_driver = { ++ .driver = { ++ .name = "aw88399-hda", ++ .acpi_match_table = aw88399_acpi_hda_match, ++ .pm = &aw88399_hda_pm_ops, ++ }, ++ .probe = aw88399_hda_i2c_probe, ++ .remove = aw88399_hda_i2c_remove, ++ .id_table = aw88399_hda_i2c_id, ++}; ++module_i2c_driver(aw88399_hda_i2c_driver); ++ ++MODULE_DESCRIPTION("HDA AW88399 I2C driver"); ++MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); ++MODULE_AUTHOR("Yakov Till "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l41_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c +@@ -170,6 +170,7 @@ static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + char *s, c; + int ret = 0; + ++ *firmware = NULL; + if (spkid > -1 && ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "cirrus/%s-%s-%s-%s-spkid%d-%s.%s", CS35L41_PART, + dsp_name, cs35l41_hda_fw_ids[cs35l41->firmware_type], +@@ -528,8 +529,8 @@ static int cs35l41_read_tuning_params(struct cs35l41_hda *cs35l41, const struct + + static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_filename) + { +- const struct firmware *tuning_param_file = NULL; +- char *tuning_param_filename = NULL; ++ const struct firmware *tuning_param_file __free(firmware) = NULL; ++ char *tuning_param_filename __free(kfree) = NULL; + int ret; + + ret = cs35l41_request_tuning_param_file(cs35l41, tuning_filename, &tuning_param_file, +@@ -548,19 +549,16 @@ static int cs35l41_load_tuning_params(struct cs35l41_hda *cs35l41, char *tuning_ + cs35l41_set_default_tuning_params(cs35l41); + } + +- release_firmware(tuning_param_file); +- kfree(tuning_param_filename); +- + return ret; + } + + static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + int ret; + + if (!cs35l41->halo_initialized) { +@@ -592,20 +590,13 @@ static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) + + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, + cs35l41_hda_fw_ids[cs35l41->firmware_type]); +- if (ret) +- goto err; ++ if (ret) { ++ cs35l41_set_default_tuning_params(cs35l41); ++ return ret; ++ } + + cs35l41_hda_apply_calibration(cs35l41); +- +-err: +- if (ret) +- cs35l41_set_default_tuning_params(cs35l41); +- release_firmware(wmfw_firmware); +- release_firmware(coeff_firmware); +- kfree(wmfw_filename); +- kfree(coeff_filename); +- +- return ret; ++ return 0; + } + + static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) +@@ -1924,7 +1915,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) + { + struct acpi_device *adev; +- struct device *physdev; + struct spi_device *spi; + const char *sub; + int ret; +@@ -1936,7 +1926,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + cs35l41->dacpi = adev; +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + if (!physdev) { + acpi_dev_put(adev); + return -ENODEV; +@@ -1954,13 +1945,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i + } + + ret = cs35l41_hda_parse_acpi(cs35l41, physdev, id); +- if (ret) { +- put_device(physdev); ++ if (ret) + return ret; +- } + out: +- put_device(physdev); +- + cs35l41->bypass_fw = false; + if (cs35l41->control_bus == SPI) { + spi = to_spi_device(cs35l41->dev); +diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c +--- a/sound/hda/codecs/side-codecs/cs35l56_hda.c ++++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c +@@ -527,18 +527,6 @@ static void cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56, + base_name, NULL, NULL, "bin"); + } + +-static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmware, +- char *wmfw_filename, +- const struct firmware *coeff_firmware, +- char *coeff_filename) +-{ +- release_firmware(wmfw_firmware); +- kfree(wmfw_filename); +- +- release_firmware(coeff_firmware); +- kfree(coeff_filename); +-} +- + static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + { + int ret; +@@ -561,10 +549,10 @@ static int cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) + + static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + { +- const struct firmware *coeff_firmware = NULL; +- const struct firmware *wmfw_firmware = NULL; +- char *coeff_filename = NULL; +- char *wmfw_filename = NULL; ++ const struct firmware *coeff_firmware __free(firmware) = NULL; ++ const struct firmware *wmfw_firmware __free(firmware) = NULL; ++ char *coeff_filename __free(kfree) = NULL; ++ char *wmfw_filename __free(kfree) = NULL; + unsigned int preloaded_fw_ver; + bool firmware_missing; + int ret; +@@ -606,14 +594,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing) { + if (!wmfw_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "wmfw"); +- goto err_fw_release; ++ return; + } else if (!coeff_firmware) { + dev_err(cs35l56->base.dev, ".%s file required but not found\n", "bin"); +- goto err_fw_release; ++ return; + } + } + +- mutex_lock(&cs35l56->base.irq_lock); ++ guard(mutex)(&cs35l56->base.irq_lock); + + /* + * If the firmware hasn't been patched it must be shutdown before +@@ -624,14 +612,14 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + if (firmware_missing && (wmfw_firmware || coeff_firmware)) { + ret = cs35l56_firmware_shutdown(&cs35l56->base); + if (ret) +- goto err; ++ return; + } + + ret = cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, "misc"); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret); +- goto err; ++ return; + } + + if (wmfw_filename) +@@ -679,11 +667,6 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) + err_powered_up: + if (!cs35l56->base.fw_patched) + cs_dsp_power_down(&cs35l56->cs_dsp); +-err: +- mutex_unlock(&cs35l56->base.irq_lock); +-err_fw_release: +- cs35l56_hda_release_firmware_files(wmfw_firmware, wmfw_filename, +- coeff_firmware, coeff_filename); + } + + static void cs35l56_hda_dsp_work(struct work_struct *work) +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -88,7 +88,6 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + { + struct gpio_desc *speaker_id; + struct acpi_device *adev; +- struct device *physdev; + LIST_HEAD(resources); + const char *sub; + uint32_t subid; +@@ -101,7 +100,8 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + return -ENODEV; + } + +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); + if (ret < 0) { + dev_err(p->dev, "Failed to get ACPI resource.\n"); +@@ -151,14 +151,12 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) + end_2563: + acpi_dev_free_resource_list(&resources); + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- put_device(physdev); + acpi_dev_put(adev); + + return 0; + + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); + acpi_dev_put(adev); + + return ret; +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -328,7 +328,6 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + { + struct tasdevice_priv *p = tas_hda->priv; + struct acpi_device *adev; +- struct device *physdev; + u32 values[HDA_MAX_COMPONENTS]; + const char *property; + size_t nval; +@@ -341,7 +340,9 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + } + + strscpy(p->dev_name, hid, sizeof(p->dev_name)); +- physdev = get_device(acpi_get_first_physical_node(adev)); ++ ++ struct device *physdev __free(put_device) = ++ get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + if (!physdev) + return -ENODEV; +@@ -381,13 +382,11 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, + goto err; + } + } +- put_device(physdev); + + return 0; ++ + err: + dev_err(p->dev, "read acpi error, ret: %d\n", ret); +- put_device(physdev); +- + return ret; + } + +diff --git a/sound/hda/common/auto_parser.c b/sound/hda/common/auto_parser.c +--- a/sound/hda/common/auto_parser.c ++++ b/sound/hda/common/auto_parser.c +@@ -601,9 +601,9 @@ static int find_idx_in_nid_list(hda_nid_t nid, const hda_nid_t *list, int nums) + return -1; + } + +-/* get a unique suffix or an index number */ ++/* get a unique suffix */ + static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, +- int num_pins, int *indexp) ++ int num_pins) + { + static const char * const channel_sfx[] = { + " Front", " Surround", " CLFE", " Side" +@@ -615,11 +615,8 @@ static const char *check_output_sfx(hda_nid_t nid, const hda_nid_t *pins, + return NULL; + if (num_pins == 1) + return ""; +- if (num_pins > ARRAY_SIZE(channel_sfx)) { +- if (indexp) +- *indexp = i; ++ if (num_pins > ARRAY_SIZE(channel_sfx)) + return ""; +- } + return channel_sfx[i]; + } + +@@ -638,27 +635,9 @@ static const char *check_output_pfx(struct hda_codec *codec, hda_nid_t nid) + return ""; + } + +-static int get_hp_label_index(struct hda_codec *codec, hda_nid_t nid, +- const hda_nid_t *pins, int num_pins) +-{ +- int i, j, idx = 0; +- +- const char *pfx = check_output_pfx(codec, nid); +- +- i = find_idx_in_nid_list(nid, pins, num_pins); +- if (i < 0) +- return -1; +- for (j = 0; j < i; j++) +- if (pfx == check_output_pfx(codec, pins[j])) +- idx++; +- +- return idx; +-} +- + static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- const char *name, char *label, int maxlen, +- int *indexp) ++ const char *name, char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + int attr = snd_hda_get_input_pin_attr(def_conf); +@@ -671,19 +650,11 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + + if (cfg) { + /* try to give a unique suffix if needed */ +- sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs, +- indexp); ++ sfx = check_output_sfx(nid, cfg->line_out_pins, cfg->line_outs); ++ if (!sfx) ++ sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs); + if (!sfx) +- sfx = check_output_sfx(nid, cfg->speaker_pins, cfg->speaker_outs, +- indexp); +- if (!sfx) { +- /* don't add channel suffix for Headphone controls */ +- int idx = get_hp_label_index(codec, nid, cfg->hp_pins, +- cfg->hp_outs); +- if (idx >= 0 && indexp) +- *indexp = idx; + sfx = ""; +- } + } + snprintf(label, maxlen, "%s%s%s", pfx, name, sfx); + return 1; +@@ -699,7 +670,6 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * @cfg: the parsed pin configuration + * @label: the string buffer to store + * @maxlen: the max length of string buffer (including termination) +- * @indexp: the pointer to return the index number (for multiple ctls) + * + * Get a label for the given pin. This function works for both input and + * output pins. When @cfg is given as non-NULL, the function tries to get +@@ -708,47 +678,33 @@ static int fill_audio_out_name(struct hda_codec *codec, hda_nid_t nid, + * This function tries to give a unique label string for the pin as much as + * possible. For example, when the multiple line-outs are present, it adds + * the channel suffix like "Front", "Surround", etc (only when @cfg is given). +- * If no unique name with a suffix is available and @indexp is non-NULL, the +- * index number is stored in the pointer. + */ + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp) ++ char *label, int maxlen) + { + unsigned int def_conf = snd_hda_codec_get_pincfg(codec, nid); + const char *name = NULL; + int i; + bool hdmi; + +- if (indexp) +- *indexp = 0; + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + return 0; + + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + return fill_audio_out_name(codec, nid, cfg, "Line Out", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPEAKER: + return fill_audio_out_name(codec, nid, cfg, "Speaker", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_HP_OUT: + return fill_audio_out_name(codec, nid, cfg, "Headphone", +- label, maxlen, indexp); ++ label, maxlen); + case AC_JACK_SPDIF_OUT: + case AC_JACK_DIG_OTHER_OUT: + hdmi = is_hdmi_cfg(def_conf); + name = hdmi ? "HDMI" : "SPDIF"; +- if (cfg && indexp) +- for (i = 0; i < cfg->dig_outs; i++) { +- hda_nid_t pin = cfg->dig_out_pins[i]; +- unsigned int c; +- if (pin == nid) +- break; +- c = snd_hda_codec_get_pincfg(codec, pin); +- if (hdmi == is_hdmi_cfg(c)) +- (*indexp)++; +- } + break; + default: + if (cfg) { +diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c +--- a/sound/hda/common/codec.c ++++ b/sound/hda/common/codec.c +@@ -39,6 +39,12 @@ static int call_exec_verb(struct hda_bus *bus, struct hda_codec *codec, + int err; + + CLASS(snd_hda_power_pm, pm)(codec); ++ if (pm.err < 0 && !bus->core.chip_init) { ++ codec_warn(codec, ++ "Failed to send cmd 0x%x ret=[%d], hda control stopped\n", ++ cmd, pm.err); ++ return pm.err; ++ } + guard(mutex)(&bus->core.cmd_mutex); + if (flags & HDA_RW_NO_RESPONSE_FALLBACK) + bus->no_response_fallback = 1; +@@ -2969,8 +2975,11 @@ static void hda_codec_pm_complete(struct device *dev) + dev->power.power_state = PMSG_RESUME; + + if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || +- hda_codec_need_resume(codec) || codec->forced_resume)) ++ hda_codec_need_resume(codec) || codec->forced_resume || ++ codec->acomp_requested_resume)) { ++ codec->acomp_requested_resume = 0; + pm_request_resume(dev); ++ } + } + + static int hda_codec_pm_suspend(struct device *dev) +@@ -3372,7 +3381,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, + for (; knew->name; knew++) { + struct snd_kcontrol *kctl; + int addr = 0, idx = 0; +- if (knew->iface == (__force snd_ctl_elem_iface_t)-1) ++ if (knew->iface == -1) + continue; /* skip this codec private value */ + for (;;) { + kctl = snd_ctl_new1(knew, codec); +diff --git a/sound/hda/common/hda_auto_parser.h b/sound/hda/common/hda_auto_parser.h +--- a/sound/hda/common/hda_auto_parser.h ++++ b/sound/hda/common/hda_auto_parser.h +@@ -46,7 +46,7 @@ const char *hda_get_autocfg_input_label(struct hda_codec *codec, + int input); + int snd_hda_get_pin_label(struct hda_codec *codec, hda_nid_t nid, + const struct auto_pin_cfg *cfg, +- char *label, int maxlen, int *indexp); ++ char *label, int maxlen); + + enum { + INPUT_PIN_ATTR_UNUSED, /* pin not connected */ +diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h +--- a/sound/hda/common/hda_local.h ++++ b/sound/hda/common/hda_local.h +@@ -723,4 +723,11 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable); + #define codec_dbg(codec, fmt, args...) \ + dev_dbg(hda_codec_dev(codec), fmt, ##args) + ++/* append a suffix string safely; equivalent with strlcat() */ ++static inline void hda_append_suffix(char *str, const char *suffix, size_t size) ++{ ++ size_t len = strnlen(str, size); ++ strscpy(str + len, suffix, size - len); ++} ++ + #endif /* __SOUND_HDA_LOCAL_H */ +diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c +--- a/sound/hda/common/jack.c ++++ b/sound/hda/common/jack.c +@@ -612,10 +612,10 @@ static int add_jack_kctl(struct hda_codec *codec, hda_nid_t nid, + if (base_name) + strscpy(name, base_name, sizeof(name)); + else +- snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name), NULL); ++ snd_hda_get_pin_label(codec, nid, cfg, name, sizeof(name)); + if (phantom_jack) + /* Example final name: "Internal Mic Phantom Jack" */ +- strncat(name, " Phantom", sizeof(name) - strlen(name) - 1); ++ hda_append_suffix(name, " Phantom", sizeof(name)); + err = snd_hda_jack_add_kctl(codec, nid, name, phantom_jack, 0, NULL); + if (err < 0) + return err; +diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c +--- a/sound/hda/controllers/intel.c ++++ b/sound/hda/controllers/intel.c +@@ -100,6 +100,8 @@ enum { + #define ATIHDMI_NUM_CAPTURE 0 + #define ATIHDMI_NUM_PLAYBACK 8 + ++/* Hygon HD Audio controller */ ++#define PCI_DEVICE_ID_HYGON_18H_M05H_HDA 0x14a9 + + static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; + static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +@@ -241,6 +243,7 @@ enum { + AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_ZHAOXINHDMI, + AZX_DRIVER_LOONGSON, ++ AZX_DRIVER_HYGON, + AZX_DRIVER_GENERIC, + AZX_NUM_DRIVERS, /* keep this as last entry */ + }; +@@ -360,6 +363,7 @@ static const char * const driver_short_names[] = { + [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_ZHAOXINHDMI] = "HDA Zhaoxin HDMI", + [AZX_DRIVER_LOONGSON] = "HDA Loongson", ++ [AZX_DRIVER_HYGON] = "HDA Hygon", + [AZX_DRIVER_GENERIC] = "HD-Audio Generic", + }; + +@@ -1926,6 +1930,10 @@ static int azx_first_init(struct azx *chip) + if (chip->driver_type == AZX_DRIVER_ZHAOXINHDMI) + bus->polling_mode = 1; + ++ if (chip->driver_type == AZX_DRIVER_HYGON && ++ chip->pci->device == PCI_DEVICE_ID_HYGON_18H_M05H_HDA) ++ bus->access_sdnctl_in_dword = 1; ++ + bus->remap_addr = pcim_iomap_region(pci, 0, "ICH HD audio"); + if (IS_ERR(bus->remap_addr)) + return PTR_ERR(bus->remap_addr); +@@ -2189,6 +2197,15 @@ static int azx_probe(struct pci_dev *pci, + dev_warn(&pci->dev, "dmic_detect option is deprecated, pass snd-intel-dspcfg.dsp_driver=1 option instead\n"); + } + ++ /* A sanity check against wild device binding; ++ * here the range 0x200 is enough for the registers used at probe, ++ * but it doesn't mean covering all HD-audio registers ++ */ ++ if (pci_resource_len(pci, 0) < 0x200) { ++ dev_err(&pci->dev, "Too small PCI BAR0\n"); ++ return -EINVAL; ++ } ++ + err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { +@@ -2381,7 +2398,7 @@ static int azx_probe_continue(struct azx *chip) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (patch[dev] && *patch[dev]) { +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&pci->dev, "Applying patch firmware '%s'\n", + patch[dev]); +@@ -2390,7 +2407,6 @@ static int azx_probe_continue(struct azx *chip) + "Cannot load firmware, continue without patching\n"); + } else { + err = snd_hda_load_patch(&chip->bus, fw->size, fw->data); +- release_firmware(fw); + if (err < 0) + goto out_free; + } +@@ -2838,6 +2854,11 @@ static const struct pci_device_id azx_ids[] = { + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, + { PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), + .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL }, ++ /* Hygon HDAudio */ ++ { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_HYGON_18H_M05H_HDA), ++ .driver_data = AZX_DRIVER_HYGON | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_NO_MSI }, ++ /* Lisuan HD-audio */ ++ { PCI_DEVICE(0x4c54, 0x5010), .driver_data = AZX_DRIVER_GENERIC }, + { 0, } + }; + MODULE_DEVICE_TABLE(pci, azx_ids); +diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c +--- a/sound/hda/core/controller.c ++++ b/sound/hda/core/controller.c +@@ -511,7 +511,10 @@ void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus) + { + unsigned long timeout; + +- snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); ++ else ++ snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET); + + timeout = jiffies + msecs_to_jiffies(100); + while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout)) +@@ -576,7 +579,10 @@ static void azx_int_disable(struct hdac_bus *bus) + + /* disable interrupts in stream descriptor */ + list_for_each_entry(azx_dev, &bus->stream_list, list) +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_INT_MASK, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); + + /* disable SIE for all streams & disable controller CIE and GIE */ + snd_hdac_chip_writel(bus, INTCTL, 0); +@@ -682,6 +688,11 @@ int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status, + sd_status = snd_hdac_stream_readb(azx_dev, SD_STS); + snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); + handled |= 1 << azx_dev->index; ++ if (sd_status & (SD_INT_FIFO_ERR | SD_INT_DESC_ERR)) { ++ dev_warn_ratelimited(bus->dev, ++ "stream %u dma error: 0x%02x\n", ++ azx_dev->index, sd_status); ++ } + if ((!azx_dev->substream && !azx_dev->cstream) || + !azx_dev->running || !(sd_status & SD_INT_COMPLETE)) + continue; +@@ -713,7 +724,7 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + BDL_SIZE, &s->bdl); + num_streams++; + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + } + + if (WARN_ON(!num_streams)) +@@ -722,12 +733,24 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) + err = snd_dma_alloc_pages(dma_type, bus->dev, + num_streams * 8, &bus->posbuf); + if (err < 0) +- return -ENOMEM; ++ goto error_bdl; + list_for_each_entry(s, &bus->stream_list, list) + s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8); + + /* single page (at least 4096 bytes) must suffice for both ringbuffes */ +- return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ err = snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb); ++ if (err < 0) ++ goto error_posbuf; ++ return 0; ++ ++error_posbuf: ++ snd_dma_free_pages(&bus->posbuf); ++error_bdl: ++ list_for_each_entry(s, &bus->stream_list, list) { ++ if (s->bdl.area) ++ snd_dma_free_pages(&s->bdl); ++ } ++ return -ENOMEM; + } + EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages); + +diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c +--- a/sound/hda/core/device.c ++++ b/sound/hda/core/device.c +@@ -664,6 +664,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, + { 0x434d, "C-Media" }, ++ { 0x4c54, "Lisuan" }, + { 0x8086, "Intel" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +@@ -766,7 +767,7 @@ unsigned int snd_hdac_stream_format_bits(snd_pcm_format_t format, snd_pcm_subfor + + params_set_format(¶ms, snd_hdac_format_normalize(format)); + snd_mask_set(hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT), +- (__force unsigned int)subformat); ++ subformat); + + bits = snd_pcm_hw_params_bits(¶ms); + if (maxbits) +diff --git a/sound/hda/core/hda_bus_type.c b/sound/hda/core/hda_bus_type.c +--- a/sound/hda/core/hda_bus_type.c ++++ b/sound/hda/core/hda_bus_type.c +@@ -39,10 +39,7 @@ EXPORT_SYMBOL_GPL(hdac_get_device_id); + + static int hdac_codec_match(struct hdac_device *dev, const struct hdac_driver *drv) + { +- if (hdac_get_device_id(dev, drv)) +- return 1; +- else +- return 0; ++ return !!hdac_get_device_id(dev, drv); + } + + static int hda_bus_match(struct device *dev, const struct device_driver *drv) +@@ -59,9 +56,7 @@ static int hda_bus_match(struct device *dev, const struct device_driver *drv) + */ + if (hdrv->match) + return hdrv->match(hdev, hdrv); +- else +- return hdac_codec_match(hdev, hdrv); +- return 1; ++ return hdac_codec_match(hdev, hdrv); + } + + static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env) +diff --git a/sound/hda/core/stream.c b/sound/hda/core/stream.c +--- a/sound/hda/core/stream.c ++++ b/sound/hda/core/stream.c +@@ -146,8 +146,12 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) + stripe_ctl = snd_hdac_get_stream_stripe_ctl(bus, azx_dev->substream); + else + stripe_ctl = 0; +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, +- stripe_ctl); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, ++ stripe_ctl); + } + /* set DMA start and interrupt mask */ + if (bus->access_sdnctl_in_dword) +@@ -166,11 +170,22 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_start); + */ + static void snd_hdac_stream_clear(struct hdac_stream *azx_dev) + { +- snd_hdac_stream_updateb(azx_dev, SD_CTL, +- SD_CTL_DMA_START | SD_INT_MASK, 0); +- snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ +- if (azx_dev->stripe) +- snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ struct hdac_bus *bus = azx_dev->bus; ++ ++ if (bus->access_sdnctl_in_dword) { ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } else { ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, ++ SD_CTL_DMA_START | SD_INT_MASK, 0); ++ snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */ ++ if (azx_dev->stripe) ++ snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0); ++ } ++ + azx_dev->running = false; + } + +@@ -225,12 +240,16 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + { + unsigned char val; + int dma_run_state; ++ struct hdac_bus *bus = azx_dev->bus; + + snd_hdac_stream_clear(azx_dev); + + dma_run_state = snd_hdac_stream_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START; + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_STREAM_RESET); + + /* wait for hardware to report that the stream entered reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, (val & SD_CTL_STREAM_RESET), 3, 300); +@@ -238,7 +257,10 @@ void snd_hdac_stream_reset(struct hdac_stream *azx_dev) + if (azx_dev->bus->dma_stop_delay && dma_run_state) + udelay(azx_dev->bus->dma_stop_delay); + +- snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ if (bus->access_sdnctl_in_dword) ++ snd_hdac_stream_updatel(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); ++ else ++ snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_CTL_STREAM_RESET, 0); + + /* wait for hardware to report that the stream is out of reset */ + snd_hdac_stream_readb_poll(azx_dev, SD_CTL, val, !(val & SD_CTL_STREAM_RESET), 3, 300); +diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c +--- a/sound/isa/msnd/msnd_pinnacle.c ++++ b/sound/isa/msnd/msnd_pinnacle.c +@@ -367,7 +367,8 @@ static int snd_msnd_init_sma(struct snd_msnd *chip) + static int upload_dsp_code(struct snd_card *card) + { + struct snd_msnd *chip = card->private_data; +- const struct firmware *init_fw = NULL, *perm_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; ++ const struct firmware *perm_fw __free(firmware) = NULL; + int err; + + outb(HPBLKSEL_0, chip->io + HP_BLKS); +@@ -375,28 +376,21 @@ static int upload_dsp_code(struct snd_card *card) + err = request_firmware(&init_fw, INITCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " INITCODEFILE); +- goto cleanup1; ++ return err; + } + err = request_firmware(&perm_fw, PERMCODEFILE, card->dev); + if (err < 0) { + dev_err(card->dev, LOGNAME ": Error loading " PERMCODEFILE); +- goto cleanup; ++ return err; + } + + memcpy_toio(chip->mappedbase, perm_fw->data, perm_fw->size); + if (snd_msnd_upload_host(chip, init_fw->data, init_fw->size) < 0) { + dev_warn(card->dev, LOGNAME ": Error uploading to DSP\n"); +- err = -ENODEV; +- goto cleanup; ++ return -ENODEV; + } + dev_info(card->dev, LOGNAME ": DSP firmware uploaded\n"); +- err = 0; +- +-cleanup: +- release_firmware(perm_fw); +-cleanup1: +- release_firmware(init_fw); +- return err; ++ return 0; + } + + #ifdef MSND_CLASSIC +diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c +--- a/sound/isa/sscape.c ++++ b/sound/isa/sscape.c +@@ -526,7 +526,7 @@ static int upload_dma_data(struct soundscape *s, const unsigned char *data, + static int sscape_upload_bootblock(struct snd_card *card) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + int data = 0; + int ret; + +@@ -537,8 +537,6 @@ static int sscape_upload_bootblock(struct snd_card *card) + } + ret = upload_dma_data(sscape, init_fw->data, init_fw->size); + +- release_firmware(init_fw); +- + guard(spinlock_irqsave)(&sscape->lock); + if (ret == 0) + data = host_read_ctrl_unsafe(sscape->io_base, 100); +@@ -562,7 +560,7 @@ static int sscape_upload_bootblock(struct snd_card *card) + static int sscape_upload_microcode(struct snd_card *card, int version) + { + struct soundscape *sscape = get_card_soundscape(card); +- const struct firmware *init_fw = NULL; ++ const struct firmware *init_fw __free(firmware) = NULL; + char name[14]; + int err; + +@@ -579,8 +577,6 @@ static int sscape_upload_microcode(struct snd_card *card, int version) + dev_info(card->dev, "sscape: MIDI firmware loaded %zu KBs\n", + init_fw->size >> 10); + +- release_firmware(init_fw); +- + return err; + } + +diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c +--- a/sound/isa/wavefront/wavefront_fx.c ++++ b/sound/isa/wavefront/wavefront_fx.c +@@ -232,41 +232,32 @@ snd_wavefront_fx_start (snd_wavefront_t *dev) + { + unsigned int i; + int err; +- const struct firmware *firmware = NULL; ++ const struct firmware *firmware __free(firmware) = NULL; + + if (dev->fx_initialized) + return 0; + + err = request_firmware(&firmware, "yamaha/yss225_registers.bin", + dev->card->dev); +- if (err < 0) { +- err = -1; +- goto out; +- } ++ if (err < 0) ++ return -1; + + for (i = 0; i + 1 < firmware->size; i += 2) { + if (firmware->data[i] >= 8 && firmware->data[i] < 16) { + outb(firmware->data[i + 1], + dev->base + firmware->data[i]); + } else if (firmware->data[i] == WAIT_IDLE) { +- if (!wavefront_fx_idle(dev)) { +- err = -1; +- goto out; +- } ++ if (!wavefront_fx_idle(dev)) ++ return -1; + } else { + dev_err(dev->card->dev, + "invalid address in register data\n"); +- err = -1; +- goto out; ++ return -1; + } + } + + dev->fx_initialized = 1; +- err = 0; +- +-out: +- release_firmware(firmware); +- return err; ++ return 0; + } + + MODULE_FIRMWARE("yamaha/yss225_registers.bin"); +diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c +--- a/sound/isa/wavefront/wavefront_synth.c ++++ b/sound/isa/wavefront/wavefront_synth.c +@@ -2053,7 +2053,7 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + const unsigned char *buf; + int len, err; + int section_cnt_downloaded = 0; +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + + err = request_firmware(&firmware, path, dev->card->dev); + if (err < 0) { +@@ -2108,11 +2108,9 @@ wavefront_download_firmware (snd_wavefront_t *dev, char *path) + section_cnt_downloaded++; + } + +- release_firmware(firmware); + return 0; + + failure: +- release_firmware(firmware); + dev_err(dev->card->dev, "firmware download failed!!!\n"); + return 1; + } +diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c +--- a/sound/pci/asihpi/asihpi.c ++++ b/sound/pci/asihpi/asihpi.c +@@ -280,7 +280,7 @@ static void print_hwparams(struct snd_pcm_substream *substream, + snd_pcm_format_width(params_format(p)) / 8); + } + +-#define INVALID_FORMAT (__force snd_pcm_format_t)(-1) ++#define INVALID_FORMAT -1 + + static const snd_pcm_format_t hpi_to_alsa_formats[] = { + INVALID_FORMAT, /* INVALID */ +diff --git a/sound/pci/asihpi/hpidspcd.c b/sound/pci/asihpi/hpidspcd.c +--- a/sound/pci/asihpi/hpidspcd.c ++++ b/sound/pci/asihpi/hpidspcd.c +@@ -23,7 +23,7 @@ struct dsp_code_private { + short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + u32 *os_error_code) + { +- const struct firmware *firmware; ++ const struct firmware *firmware __free(firmware) = NULL; + struct pci_dev *dev = os_data; + struct code_header header; + char fw_name[20]; +@@ -37,11 +37,11 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + if (err || !firmware) { + dev_err(&dev->dev, "%d, request_firmware failed for %s\n", + err, fw_name); +- goto error1; ++ goto error; + } + if (firmware->size < sizeof(header)) { + dev_err(&dev->dev, "Header size too small %s\n", fw_name); +- goto error2; ++ goto error; + } + memcpy(&header, firmware->data, sizeof(header)); + +@@ -51,7 +51,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Invalid firmware header size %d != file %zd\n", + header.size, firmware->size); +- goto error2; ++ goto error; + } + + if (HPI_VER_MAJOR(header.version) != HPI_VER_MAJOR(HPI_VER)) { +@@ -59,7 +59,7 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dev_err(&dev->dev, + "Incompatible firmware version DSP image %X != Driver %X\n", + header.version, HPI_VER); +- goto error2; ++ goto error; + } + + if (header.version != HPI_VER) { +@@ -72,19 +72,17 @@ short hpi_dsp_code_open(u32 adapter, void *os_data, struct dsp_code *dsp_code, + dsp_code->pvt = kmalloc_obj(*dsp_code->pvt); + if (!dsp_code->pvt) { + err_ret = HPI_ERROR_MEMORY_ALLOC; +- goto error2; ++ goto error; + } + + dsp_code->pvt->dev = dev; +- dsp_code->pvt->firmware = firmware; ++ dsp_code->pvt->firmware = no_free_ptr(firmware); + dsp_code->header = header; + dsp_code->block_length = header.size / sizeof(u32); + dsp_code->word_count = sizeof(header) / sizeof(u32); + return 0; + +-error2: +- release_firmware(firmware); +-error1: ++error: + dsp_code->block_length = 0; + return err_ret; + } +diff --git a/sound/pci/asihpi/hpioctl.c b/sound/pci/asihpi/hpioctl.c +--- a/sound/pci/asihpi/hpioctl.c ++++ b/sound/pci/asihpi/hpioctl.c +@@ -385,8 +385,7 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + if (pci_resource_flags(pci_dev, idx) & IORESOURCE_MEM) { + memlen = pci_resource_len(pci_dev, idx); + pci.ap_mem_base[idx] = +- ioremap(pci_resource_start(pci_dev, idx), +- memlen); ++ pcim_iomap(pci_dev, idx, memlen); + if (!pci.ap_mem_base[idx]) { + HPI_DEBUG_LOG(ERROR, + "ioremap failed, aborting\n"); +@@ -509,13 +508,6 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + return 0; + + err: +- while (--idx >= 0) { +- if (pci.ap_mem_base[idx]) { +- iounmap(pci.ap_mem_base[idx]); +- pci.ap_mem_base[idx] = NULL; +- } +- } +- + if (adapter.p_buffer) { + adapter.buffer_size = 0; + vfree(adapter.p_buffer); +@@ -527,14 +519,11 @@ int asihpi_adapter_probe(struct pci_dev *pci_dev, + + void asihpi_adapter_remove(struct pci_dev *pci_dev) + { +- int idx; + struct hpi_message hm; + struct hpi_response hr; + struct hpi_adapter *pa; +- struct hpi_pci pci; + + pa = pci_get_drvdata(pci_dev); +- pci = pa->adapter->pci; + + /* Disable IRQ generation on DSP side */ + hpi_init_message_response(&hm, &hr, HPI_OBJ_ADAPTER, +@@ -550,10 +539,6 @@ void asihpi_adapter_remove(struct pci_dev *pci_dev) + hm.adapter_index = pa->adapter->index; + hpi_send_recv_ex(&hm, &hr, HOWNER_KERNEL); + +- /* unmap PCI memory space, mapped during device init. */ +- for (idx = 0; idx < HPI_MAX_ADAPTER_MEM_SPACES; ++idx) +- iounmap(pci.ap_mem_base[idx]); +- + if (pa->irq) + free_irq(pa->irq, pa); + +diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c +--- a/sound/pci/cs46xx/cs46xx_lib.c ++++ b/sound/pci/cs46xx/cs46xx_lib.c +@@ -387,7 +387,7 @@ static int load_firmware(struct snd_cs46xx *chip, + unsigned int nums, fwlen, fwsize; + const __le32 *fwdat; + struct dsp_module_desc *module = NULL; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + char fw_path[32]; + + sprintf(fw_path, "cs46xx/%s", fw_name); +@@ -395,10 +395,8 @@ static int load_firmware(struct snd_cs46xx *chip, + if (err < 0) + return err; + fwsize = fw->size / 4; +- if (fwsize < 2) { +- err = -EINVAL; +- goto error; +- } ++ if (fwsize < 2) ++ return -EINVAL; + + err = -ENOMEM; + module = kzalloc_obj(*module); +@@ -454,14 +452,12 @@ static int load_firmware(struct snd_cs46xx *chip, + } + + *module_ret = module; +- release_firmware(fw); + return 0; + + error_inval: + err = -EINVAL; + error: + free_module_desc(module); +- release_firmware(fw); + return err; + } + +@@ -500,22 +496,18 @@ MODULE_FIRMWARE("cs46xx/ba1"); + + static int load_firmware(struct snd_cs46xx *chip) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int i, size, err; + + err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev); + if (err < 0) + return err; +- if (fw->size != sizeof(*chip->ba1)) { +- err = -EINVAL; +- goto error; +- } ++ if (fw->size != sizeof(*chip->ba1)) ++ return -EINVAL; + + chip->ba1 = vmalloc(sizeof(*chip->ba1)); +- if (!chip->ba1) { +- err = -ENOMEM; +- goto error; +- } ++ if (!chip->ba1) ++ return -ENOMEM; + + memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1)); + +@@ -524,11 +516,9 @@ static int load_firmware(struct snd_cs46xx *chip) + for (i = 0; i < BA1_MEMORY_COUNT; i++) + size += chip->ba1->memory[i].size; + if (size > BA1_DWORD_SIZE * 4) +- err = -EINVAL; ++ return -EINVAL; + +- error: +- release_firmware(fw); +- return err; ++ return 0; + } + + static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip) +diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c +--- a/sound/pci/emu10k1/emufx.c ++++ b/sound/pci/emu10k1/emufx.c +@@ -990,7 +990,7 @@ static int snd_emu10k1_list_controls(struct snd_emu10k1 *emu, + i < icode->gpr_list_control_count) { + memset(gctl, 0, sizeof(*gctl)); + id = &ctl->kcontrol->id; +- gctl->id.iface = (__force int)id->iface; ++ gctl->id.iface = id->iface; + strscpy(gctl->id.name, id->name, sizeof(gctl->id.name)); + gctl->id.index = id->index; + gctl->id.device = id->device; +@@ -1156,7 +1156,7 @@ static void + snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + if (high_res_gpr_volume) { +@@ -1180,7 +1180,7 @@ static void + snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + if (high_res_gpr_volume) { +@@ -1205,7 +1205,7 @@ static void + snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 1; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1218,7 +1218,7 @@ static void + snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval) + { +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, name); + ctl->vcount = ctl->count = 2; + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; +@@ -1542,7 +1542,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[nctl + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -1551,7 +1551,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) + ctl->value[0] = ctl->value[1] = 20; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[nctl + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2137,7 +2137,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + * Process tone control + */ + ctl = &controls[i + 0]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Bass"); + ctl->vcount = 2; + ctl->count = 10; +@@ -2147,7 +2147,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) + ctl->tlv = snd_emu10k1_bass_treble_db_scale; + ctl->translation = EMU10K1_GPR_TRANSLATION_BASS; + ctl = &controls[i + 1]; +- ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; ++ ctl->id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(ctl->id.name, "Tone Control - Treble"); + ctl->vcount = 2; + ctl->count = 10; +diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c +--- a/sound/pci/korg1212/korg1212.c ++++ b/sound/pci/korg1212/korg1212.c +@@ -1980,7 +1980,7 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + __maybe_unused unsigned ioport_size; + __maybe_unused unsigned iomem2_size; + struct snd_korg1212 *korg1212 = card->private_data; +- const struct firmware *dsp_code; ++ const struct firmware *dsp_code __free(firmware) = NULL; + + err = pcim_enable_device(pci); + if (err < 0) +@@ -2147,10 +2147,8 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + korg1212->dma_dsp = snd_devm_alloc_pages(&pci->dev, SNDRV_DMA_TYPE_DEV, + dsp_code->size); +- if (!korg1212->dma_dsp) { +- release_firmware(dsp_code); ++ if (!korg1212->dma_dsp) + return -ENOMEM; +- } + + K1212_DEBUG_PRINTK("K1212_DEBUG: DSP Code area = 0x%p (0x%08x) %d bytes [%s]\n", + korg1212->dma_dsp->area, korg1212->dma_dsp->addr, dsp_code->size, +@@ -2158,8 +2156,6 @@ static int snd_korg1212_create(struct snd_card *card, struct pci_dev *pci) + + memcpy(korg1212->dma_dsp->area, dsp_code->data, dsp_code->size); + +- release_firmware(dsp_code); +- + rc = snd_korg1212_Send1212Command(korg1212, K1212_DB_RebootCard, 0, 0, 0, 0); + + if (rc) +diff --git a/sound/pci/mixart/mixart_hwdep.c b/sound/pci/mixart/mixart_hwdep.c +--- a/sound/pci/mixart/mixart_hwdep.c ++++ b/sound/pci/mixart/mixart_hwdep.c +@@ -560,12 +560,11 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + "miXart8.xlx", "miXart8.elf", "miXart8AES.xlx" + }; + char path[32]; +- +- const struct firmware *fw_entry; + int i, err; + + for (i = 0; i < 3; i++) { + sprintf(path, "mixart/%s", fw_files[i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "miXart: can't load firmware %s\n", path); +@@ -573,7 +572,6 @@ int snd_mixart_setup_firmware(struct mixart_mgr *mgr) + } + /* fake hwdep dsp record */ + err = mixart_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/pcxhr/pcxhr_hwdep.c b/sound/pci/pcxhr/pcxhr_hwdep.c +--- a/sound/pci/pcxhr/pcxhr_hwdep.c ++++ b/sound/pci/pcxhr/pcxhr_hwdep.c +@@ -367,7 +367,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + }; + char path[32]; + +- const struct firmware *fw_entry; + int i, err; + int fw_set = mgr->fw_file_set; + +@@ -375,6 +374,7 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + if (!fw_files[fw_set][i]) + continue; + sprintf(path, "pcxhr/%s", fw_files[fw_set][i]); ++ const struct firmware *fw_entry __free(firmware) = NULL; + if (request_firmware(&fw_entry, path, &mgr->pci->dev)) { + dev_err(&mgr->pci->dev, + "pcxhr: can't load firmware %s\n", +@@ -383,7 +383,6 @@ int pcxhr_setup_firmware(struct pcxhr_mgr *mgr) + } + /* fake hwdep dsp record */ + err = pcxhr_dsp_load(mgr, i, fw_entry); +- release_firmware(fw_entry); + if (err < 0) + return err; + mgr->dsp_loaded |= 1 << i; +diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c +--- a/sound/pci/sis7019.c ++++ b/sound/pci/sis7019.c +@@ -1260,16 +1260,16 @@ static int sis_chip_create(struct snd_card *card, + sis->irq = -1; + sis->ioport = pci_resource_start(pci, 0); + +- rc = pcim_request_all_regions(pci, "SiS7019"); ++ rc = pcim_request_region(pci, 0, "SiS7019"); + if (rc) { +- dev_err(&pci->dev, "unable request regions\n"); ++ dev_err(&pci->dev, "unable request I/O region\n"); + return rc; + } + +- sis->ioaddr = devm_ioremap(&pci->dev, pci_resource_start(pci, 1), 0x4000); +- if (!sis->ioaddr) { ++ sis->ioaddr = pcim_iomap_region(pci, 1, "SiS7019"); ++ if (IS_ERR(sis->ioaddr)) { + dev_err(&pci->dev, "unable to remap MMIO, aborting\n"); +- return -EIO; ++ return PTR_ERR(sis->ioaddr); + } + + rc = sis_alloc_suspend(sis); +diff --git a/sound/sh/aica.c b/sound/sh/aica.c +--- a/sound/sh/aica.c ++++ b/sound/sh/aica.c +@@ -518,8 +518,9 @@ static const struct snd_kcontrol_new snd_aica_pcmvolume_control = { + static int load_aica_firmware(void) + { + int err; +- const struct firmware *fw_entry; + spu_reset(); ++ ++ const struct firmware *fw_entry __free(firmware) = NULL; + err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); + if (unlikely(err)) + return err; +@@ -527,7 +528,6 @@ static int load_aica_firmware(void) + spu_disable(); + spu_memload(0, fw_entry->data, fw_entry->size); + spu_enable(); +- release_firmware(fw_entry); + return err; + } + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -726,6 +726,10 @@ config SND_SOC_AW87390 + sound quality, which is a new high efficiency, low + noise, constant large volume, 6th Smart K audio amplifier. + ++config SND_SOC_AW88399_LIB ++ tristate ++ select SND_SOC_AW88395_LIB ++ + config SND_SOC_AW88399 + tristate "Soc Audio for awinic aw88399" + depends on I2C +@@ -733,6 +737,7 @@ config SND_SOC_AW88399 + select REGMAP_I2C + select GPIOLIB + select SND_SOC_AW88395_LIB ++ select SND_SOC_AW88399_LIB + help + This option enables support for aw88399 Smart PA. + The awinic AW88399 is an I2S/TDM input, high efficiency +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -55,6 +55,7 @@ snd-soc-aw88395-y := aw88395/aw88395.o + snd-soc-aw88166-y := aw88166.o + snd-soc-aw88261-y := aw88261.o + snd-soc-aw88399-y := aw88399.o ++snd-soc-aw88399-lib-y := aw88399-lib.o + snd-soc-bd28623-y := bd28623.o + snd-soc-bt-sco-y := bt-sco.o + snd-soc-chv3-codec-y := chv3-codec.o +@@ -496,6 +497,7 @@ obj-$(CONFIG_SND_SOC_AW88395) +=snd-soc-aw88395.o + obj-$(CONFIG_SND_SOC_AW88166) +=snd-soc-aw88166.o + obj-$(CONFIG_SND_SOC_AW88261) +=snd-soc-aw88261.o + obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o ++obj-$(CONFIG_SND_SOC_AW88399_LIB) += snd-soc-aw88399-lib.o + obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o + obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o + obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -0,0 +1,1411 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// aw88399-lib.c -- AW88399 Common functions for ASoC and HDA audio drivers ++// ++// Copyright (c) 2023 AWINIC Technology CO., LTD ++// ++// Author: Weidong Wang ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "aw88395/aw88395_device.h" ++ ++const struct regmap_config aw88399_remap_config = { ++ .val_bits = 16, ++ .reg_bits = 8, ++ .max_register = AW88399_REG_MAX, ++ .reg_format_endian = REGMAP_ENDIAN_LITTLE, ++ .val_format_endian = REGMAP_ENDIAN_BIG, ++}; ++EXPORT_SYMBOL_GPL(aw88399_remap_config); ++ ++static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) ++{ ++ int ret; ++ ++ if (pwd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); ++ if (ret) ++ dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); ++ else ++ *int_status = reg_val; ++ ++ dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); ++} ++ ++static void aw_dev_clear_int_status(struct aw_device *aw_dev) ++{ ++ u16 int_status; ++ ++ /* read int status and clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ /* make sure int status is clear */ ++ aw_dev_get_int_status(aw_dev, &int_status); ++ if (int_status) ++ dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); ++} ++ ++static int aw_dev_get_iis_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { ++ dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode1 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ reg_val &= (~AW88399_CCO_MUX_MASK); ++ if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { ++ dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); ++ return -EPERM; ++ } ++ ++ /* change mode2 */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ ++ /* change mode1 */ ++ regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, ++ ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); ++ if (ret == 0) { ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = aw_dev_get_iis_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ break; ++ } ++ } ++ } ++ ++ return ret; ++} ++ ++int aw_dev_check_syspll(struct aw_device *aw_dev) ++{ ++ int ret; ++ ++ ret = aw_dev_check_mode1_pll(aw_dev); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); ++ ret = aw_dev_check_mode2_pll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "mode2 check iis failed"); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_check_syspll); ++ ++static int aw_dev_check_sysst(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int check_val; ++ unsigned int reg_val; ++ int ret, i; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) ++ check_val = AW88399_BIT_SYSST_NOSWS_CHECK; ++ else ++ check_val = AW88399_BIT_SYSST_SWS_CHECK; ++ ++ /* ++ * On some hardware the BSTS (boost-finished) status bit does not ++ * reliably assert even when audio output is working normally. ++ * Allow per-instance bypass when flagged by the side-codec driver. ++ */ ++ if (aw88399->bsts_unreliable) ++ check_val &= ~AW88399_BSTS_FINISHED_VALUE; ++ ++ for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { ++ dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", ++ i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } else { ++ return 0; ++ } ++ } ++ ++ return -EPERM; ++} ++ ++static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) ++{ ++ int ret; ++ ++ if (amppd) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) ++{ ++ int ret; ++ ++ if (is_enable) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed\n", __func__); ++} ++EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); ++ ++static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) ++{ ++ uint16_t icalkh_val, icalkl_val, icalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); ++ if (ret) ++ return ret; ++ icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ icalk_val = icalkh_val & icalkl_val; ++ else ++ icalk_val = icalkh_val | icalkl_val; ++ ++ if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) ++ icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; ++ *icalk = (int16_t)icalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = vcalkh_val & vcalkl_val; ++ else ++ vcalk_val = vcalkh_val | vcalkl_val; ++ ++ if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) ++ vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) ++{ ++ uint16_t vcalkh_val, vcalkl_val, vcalk_val; ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); ++ if (ret) ++ return ret; ++ vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); ++ ++ if (aw88399->check_val == AW_EF_AND_CHECK) ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ else ++ vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | ++ (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); ++ ++ if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) ++ vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; ++ ++ *vcalk = (int16_t)vcalk_val; ++ ++ return 0; ++} ++ ++static int aw_dev_set_vcalb(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ unsigned int vsense_select, vsense_value; ++ int32_t ical_k, vcal_k, vcalb; ++ int16_t icalk, vcalk; ++ uint16_t reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); ++ if (ret) ++ return ret; ++ ++ vsense_select = vsense_value & (~AW88399_VDSEL_MASK); ++ ++ ret = aw88399_dev_get_icalk(aw88399, &icalk); ++ if (ret) { ++ dev_err(aw_dev->dev, "get icalk failed\n"); ++ return ret; ++ } ++ ++ ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ ++ switch (vsense_select) { ++ case AW88399_DEV_VDSEL_VSENSE: ++ ret = aw88399_dev_get_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * ++ ical_k / vcal_k * aw88399->vcalb_init_val; ++ break; ++ case AW88399_DEV_VDSEL_DAC: ++ ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); ++ vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; ++ vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / ++ AW88399_ISCAL_DAC_FACTOR * ical_k / ++ vcal_k * aw88399->vcalb_init_val; ++ break; ++ default: ++ dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); ++ ret = -EINVAL; ++ break; ++ } ++ if (ret) ++ return ret; ++ ++ vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; ++ reg_val = (uint32_t)vcalb; ++ ++ regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); ++ ++ return 0; ++} ++ ++int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ uint16_t re_lbits, re_hbits; ++ u32 cali_re; ++ int ret; ++ ++ if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || ++ (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) ++ return -EINVAL; ++ ++ cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + ++ aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); ++ ++ re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; ++ re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); ++ if (ret) { ++ dev_err(aw_dev->dev, "set cali re error"); ++ return ret; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); ++ if (ret) ++ dev_err(aw_dev->dev, "set cali re error"); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); ++ ++static int aw_dev_fw_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, fw_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate fw_end_addr */ ++ fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; ++ ++ /* write fw_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, fw_len_val); ++ if (ret) ++ return ret; ++ /* enable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); ++ ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ /* read crc check result */ ++ regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable fw crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", ++ __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) ++{ ++ uint16_t check_val, cfg_len_val; ++ unsigned int reg_val; ++ int ret; ++ ++ /* calculate cfg end addr */ ++ cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; ++ ++ /* write cfg_end_addr to crc_end_addr */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); ++ if (ret) ++ return ret; ++ ++ /* enable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ /* read crc check result */ ++ ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); ++ if (ret) ++ return ret; ++ ++ check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; ++ ++ /* disable cfg crc check */ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, ++ ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); ++ if (ret) ++ return ret; ++ ++ if (check_val != AW88399_CRC_CHECK_PASS_VAL) { ++ dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", ++ check_val, AW88399_CRC_CHECK_PASS_VAL); ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aw_dev_hw_crc_check(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); ++ if (ret) ++ return ret; ++ ++ ret = aw_dev_fw_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = aw_dev_cfg_crc_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "cfg_crc_check failed\n"); ++ goto crc_check_failed; ++ } ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); ++ if (ret) ++ return ret; ++ ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ ++ return ret; ++ ++crc_check_failed: ++ regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); ++ return ret; ++} ++ ++static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) ++{ ++ int ret; ++ ++ if (flag) ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); ++ else ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, ++ ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); ++ ++ if (ret) ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++} ++ ++int aw_dev_get_dsp_status(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); ++ if (ret) ++ return ret; ++ if (!(reg_val & (~AW88399_WDT_CNT_MASK))) ++ return -EPERM; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); ++ ++static int aw_dev_dsp_check(struct aw_device *aw_dev) ++{ ++ int ret, i; ++ ++ switch (aw_dev->dsp_cfg) { ++ case AW88399_DEV_DSP_BYPASS: ++ dev_dbg(aw_dev->dev, "dsp bypass"); ++ ret = 0; ++ break; ++ case AW88399_DEV_DSP_WORK: ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_dsp_enable(aw_dev, true); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { ++ ret = aw_dev_get_dsp_status(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ } ++ } ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) ++{ ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ unsigned int reg_value; ++ u16 real_value; ++ int ret; ++ ++ real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); ++ ++ real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw_dev_set_volume); ++ ++static void aw_dev_fade_in(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ u16 fade_in_vol = desc->ctl_volume; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_in_time == 0) { ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++ return; ++ } ++ ++ for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); ++ } ++ ++ if (i != fade_in_vol) ++ aw_dev_set_volume(aw_dev, fade_in_vol); ++} ++ ++static void aw_dev_fade_out(struct aw_device *aw_dev) ++{ ++ struct aw_volume_desc *desc = &aw_dev->volume_desc; ++ int fade_step = aw_dev->fade_step; ++ int i; ++ ++ if (fade_step == 0 || aw_dev->fade_out_time == 0) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ return; ++ } ++ ++ for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { ++ aw_dev_set_volume(aw_dev, i); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++ ++ if (i != AW88399_MUTE_VOL) { ++ aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); ++ usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); ++ } ++} ++ ++void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) ++{ ++ if (is_mute) { ++ aw_dev_fade_out(aw_dev); ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); ++ } else { ++ regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, ++ ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); ++ aw_dev_fade_in(aw_dev); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_mute); ++ ++static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ ++ if (dither) ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); ++ else ++ regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); ++} ++ ++static int aw88399_dev_start(struct aw88399 *aw88399) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ if (aw_dev->status == AW88399_DEV_PW_ON) { ++ dev_dbg(aw_dev->dev, "already power on"); ++ return 0; ++ } ++ ++ aw88399_dev_set_dither(aw88399, false); ++ ++ /* power on */ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_2000_US, AW88399_2000_US + 10); ++ ++ ret = aw_dev_check_syspll(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "pll check failed cannot start"); ++ goto pll_check_fail; ++ } ++ ++ /* amppd on */ ++ aw_dev_amppd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 50); ++ ++ /* check i2s status */ ++ ret = aw_dev_check_sysst(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "sysst check failed"); ++ goto sysst_check_fail; ++ } ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { ++ ret = aw_dev_hw_crc_check(aw88399); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp crc check failed"); ++ goto crc_check_fail; ++ } ++ aw_dev_dsp_enable(aw_dev, false); ++ aw_dev_set_vcalb(aw88399); ++ aw_dev_update_cali_re(&aw_dev->cali_desc); ++ ++ ret = aw_dev_dsp_check(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "dsp status check failed"); ++ goto dsp_check_fail; ++ } ++ } else { ++ dev_dbg(aw_dev->dev, "start pa with dsp bypass"); ++ } ++ ++ /* enable tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, true); ++ ++ if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) ++ aw88399_dev_set_dither(aw88399, true); ++ ++ /* close mute */ ++ aw88399_dev_mute(aw_dev, false); ++ /* clear inturrupt */ ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev->status = AW88399_DEV_PW_ON; ++ ++ return 0; ++ ++dsp_check_fail: ++crc_check_fail: ++ aw_dev_dsp_enable(aw_dev, false); ++sysst_check_fail: ++ aw_dev_clear_int_status(aw_dev); ++ aw_dev_amppd(aw_dev, true); ++pll_check_fail: ++ aw_dev_pwd(aw_dev, true); ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_container(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len, unsigned short base) ++{ ++ u32 tmp_len; ++ int i, ret; ++ ++ ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); ++ if (ret) ++ return ret; ++ ++ for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { ++ tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); ++ ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, ++ &data[i], tmp_len); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) ++{ ++ struct aw_device *aw_dev = ++ container_of(cali_desc, struct aw_device, cali_desc); ++ u32 dsp_ra; ++ int ret; ++ ++ ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, ++ &dsp_ra, AW_DSP_32_DATA); ++ if (ret) { ++ dev_err(aw_dev->dev, "read ra error"); ++ return ret; ++ } ++ ++ cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, ++ AW88399_DSP_RE_SHIFT); ++ ++ return 0; ++} ++ ++static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp config len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp config data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); ++ if (ret) ++ return ret; ++ ++ aw_dev->dsp_cfg_len = len; ++ ++ ret = aw_dev_get_ra(&aw_dev->cali_desc); ++ ++ return ret; ++} ++ ++static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); ++ ++ if (!len || !data) { ++ dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ aw_dev->dsp_fw_len = len; ++ ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); ++ ++ return ret; ++} ++ ++static int aw_dev_check_sram(struct aw_device *aw_dev) ++{ ++ unsigned int reg_val; ++ ++ /* read dsp_rom_check_reg */ ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { ++ dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", ++ reg_val, AW88399_DSP_ROM_CHECK_DATA); ++ return -EPERM; ++ } ++ ++ /* check dsp_cfg_base_addr */ ++ aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, ++ AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); ++ aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); ++ if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { ++ dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", ++ reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); ++ return -EPERM; ++ } ++ ++ return 0; ++} ++ ++static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) ++{ ++ int ret; ++ ++ switch (flag) { ++ case AW88399_DEV_MEMCLK_PLL: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_DAPHCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select pll failed"); ++ break; ++ case AW88399_DEV_MEMCLK_OSC: ++ ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, ++ ~AW88399_MEM_CLKSEL_MASK, ++ AW88399_MEM_CLKSEL_OSCCLK_VALUE); ++ if (ret) ++ dev_err(aw_dev->dev, "memclk select OSC failed"); ++ break; ++ default: ++ dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); ++ break; ++ } ++} ++ ++static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) ++{ ++ struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; ++ unsigned int reg_val; ++ int ret; ++ ++ ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); ++ if (ret) { ++ dev_dbg(aw_dev->dev, "%s failed", __func__); ++ return; ++ } ++ if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) ++ profctrl_desc->cur_mode = AW88399_RCV_MODE; ++ else ++ profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; ++} ++ ++static int aw_dev_update_reg_container(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; ++ u16 read_vol, reg_val; ++ int data_len, i, ret; ++ int16_t *reg_data; ++ u8 reg_addr; ++ ++ reg_data = (int16_t *)data; ++ data_len = len >> 1; ++ ++ if (data_len & 0x1) { ++ dev_err(aw_dev->dev, "data len:%d unsupported", data_len); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < data_len; i += 2) { ++ reg_addr = reg_data[i]; ++ reg_val = reg_data[i + 1]; ++ ++ if (reg_addr == AW88399_DSPVCALB_REG) { ++ aw88399->vcalb_init_val = reg_val; ++ continue; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL_REG) { ++ if (reg_val & (~AW88399_DSPBY_MASK)) ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; ++ else ++ aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; ++ ++ reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | ++ AW88399_DSPBY_MASK); ++ reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | ++ AW88399_DSPBY_BYPASS_VALUE); ++ } ++ ++ if (reg_addr == AW88399_I2SCTRL3_REG) { ++ reg_val &= AW88399_I2STXEN_MASK; ++ reg_val |= AW88399_I2STXEN_DISABLE_VALUE; ++ } ++ ++ if (reg_addr == AW88399_SYSCTRL2_REG) { ++ read_vol = (reg_val & (~AW88399_VOL_MASK)) >> ++ AW88399_VOL_START_BIT; ++ aw_dev->volume_desc.init_volume = read_vol; ++ } ++ ++ if (reg_addr == AW88399_DBGCTRL_REG) { ++ if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) ++ aw88399->check_val = AW_EF_OR_CHECK; ++ else ++ aw88399->check_val = AW_EF_AND_CHECK; ++ ++ aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); ++ } ++ ++ if (reg_addr == AW88399_CRCCTRL_REG) ++ aw88399->crc_init_val = reg_val; ++ ++ ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); ++ if (ret) ++ return ret; ++ } ++ ++ aw_dev_pwd(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ ++ aw_dev_get_cur_mode_st(aw_dev); ++ ++ if (aw_dev->prof_cur != aw_dev->prof_index) ++ vol_desc->ctl_volume = 0; ++ else ++ aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); ++ ++ return 0; ++} ++ ++static int aw_dev_reg_update(struct aw88399 *aw88399, ++ unsigned char *data, unsigned int len) ++{ ++ int ret; ++ ++ if (!len || !data) { ++ dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); ++ return -EINVAL; ++ } ++ ++ ret = aw_dev_update_reg_container(aw88399, data, len); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "reg update failed"); ++ ++ return ret; ++} ++ ++int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) ++{ ++ struct aw_prof_info *prof_info = &aw_dev->prof_info; ++ struct aw_prof_desc *prof_desc; ++ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "index[%d] overflow count[%d]", ++ index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ *prof_name = prof_info->prof_name_list[prof_desc->id]; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); ++ ++static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, ++ struct aw_prof_desc **prof_desc) ++{ ++ if ((index >= aw_dev->prof_info.count) || (index < 0)) { ++ dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", ++ __func__, index, aw_dev->prof_info.count); ++ return -EINVAL; ++ } ++ ++ *prof_desc = &aw_dev->prof_info.prof_desc[index]; ++ ++ return 0; ++} ++ ++static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ struct aw_prof_desc *prof_index_desc; ++ struct aw_sec_data_desc *sec_desc; ++ char *prof_name; ++ int ret; ++ ++ if ((aw_dev->prof_cur == aw_dev->prof_index) && ++ (force_up_en == AW88399_FORCE_UPDATE_OFF)) { ++ dev_dbg(aw_dev->dev, "scene no change, not update"); ++ return 0; ++ } ++ ++ if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { ++ dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); ++ return -EPERM; ++ } ++ ++ ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); ++ if (ret) ++ return ret; ++ ++ dev_dbg(aw_dev->dev, "start update %s", prof_name); ++ ++ ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); ++ if (ret) ++ return ret; ++ ++ /* update reg */ ++ sec_desc = prof_index_desc->sec_desc; ++ ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, ++ sec_desc[AW88395_DATA_TYPE_REG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update reg failed"); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ ++ ret = aw_dev_check_sram(aw_dev); ++ if (ret) { ++ dev_err(aw_dev->dev, "check sram failed"); ++ goto error; ++ } ++ ++ if (up_dsp_fw_en) { ++ dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); ++ ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_FW].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp fw failed"); ++ goto error; ++ } ++ } ++ ++ /* update dsp config */ ++ ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, ++ sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); ++ if (ret) { ++ dev_err(aw_dev->dev, "update dsp cfg failed"); ++ goto error; ++ } ++ ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ ++ aw_dev->prof_cur = aw_dev->prof_index; ++ ++ return 0; ++ ++error: ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ return ret; ++} ++ ++static void aw88399_start_pa(struct aw88399 *aw88399) ++{ ++ int ret, i; ++ ++ for (i = 0; i < AW88399_START_RETRIES; i++) { ++ ret = aw88399_dev_start(aw88399); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); ++ ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed"); ++ continue; ++ } ++ } else { ++ dev_dbg(aw88399->aw_pa->dev, "start success\n"); ++ break; ++ } ++ } ++} ++ ++void aw88399_startup_work(struct work_struct *work) ++{ ++ struct aw88399 *aw88399 = ++ container_of(work, struct aw88399, start_work.work); ++ ++ mutex_lock(&aw88399->lock); ++ aw88399_start_pa(aw88399); ++ mutex_unlock(&aw88399->lock); ++} ++EXPORT_SYMBOL_GPL(aw88399_startup_work); ++ ++void aw88399_start(struct aw88399 *aw88399, bool sync_start) ++{ ++ int ret; ++ ++ if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) ++ return; ++ ++ if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) ++ return; ++ ++ ret = aw88399_dev_fw_update(aw88399, aw88399->fw_needs_reload ? ++ AW88399_DSP_FW_UPDATE_ON : AW88399_DSP_FW_UPDATE_OFF, true); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "fw update failed."); ++ return; ++ } ++ ++ aw88399->fw_needs_reload = false; ++ ++ if (sync_start == AW88399_SYNC_START) ++ aw88399_start_pa(aw88399); ++ else ++ queue_delayed_work(system_dfl_wq, ++ &aw88399->start_work, ++ AW88399_START_WORK_DELAY_MS); ++} ++EXPORT_SYMBOL_GPL(aw88399_start); ++ ++static int aw_dev_check_sysint(struct aw_device *aw_dev) ++{ ++ u16 reg_val; ++ ++ aw_dev_get_int_status(aw_dev, ®_val); ++ if (reg_val & AW88399_BIT_SYSINT_CHECK) { ++ dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++int aw88399_stop(struct aw_device *aw_dev) ++{ ++ struct aw_sec_data_desc *dsp_cfg = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; ++ struct aw_sec_data_desc *dsp_fw = ++ &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; ++ int int_st; ++ ++ if (aw_dev->status == AW88399_DEV_PW_OFF) { ++ dev_dbg(aw_dev->dev, "already power off"); ++ return 0; ++ } ++ ++ aw_dev->status = AW88399_DEV_PW_OFF; ++ ++ aw88399_dev_mute(aw_dev, true); ++ usleep_range(AW88399_4000_US, AW88399_4000_US + 100); ++ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ int_st = aw_dev_check_sysint(aw_dev); ++ ++ aw_dev_dsp_enable(aw_dev, false); ++ ++ aw_dev_amppd(aw_dev, true); ++ ++ if (int_st) { ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); ++ aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); ++ aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); ++ aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); ++ } ++ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_stop); ++ ++static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) ++{ ++ struct aw_device *aw_dev = aw88399->aw_pa; ++ int ret; ++ ++ ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); ++ if (ret) { ++ dev_err(aw_dev->dev, "aw_dev acf parse failed"); ++ return -EINVAL; ++ } ++ aw_dev->fade_in_time = AW88399_1000_US / 10; ++ aw_dev->fade_out_time = AW88399_1000_US >> 1; ++ aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; ++ aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; ++ ++ ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); ++ if (ret) { ++ dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); ++ return ret; ++ } ++ ++ aw88399_dev_mute(aw_dev, true); ++ ++ /* close tx feedback */ ++ aw_dev_i2s_tx_enable(aw_dev, false); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 100); ++ ++ /* enable amppd */ ++ aw_dev_amppd(aw_dev, true); ++ ++ /* close dsp */ ++ aw_dev_dsp_enable(aw_dev, false); ++ /* set power down */ ++ aw_dev_pwd(aw_dev, true); ++ ++ return 0; ++} ++ ++int aw88399_request_firmware_file(struct aw88399 *aw88399) ++{ ++ const struct firmware *cont = NULL; ++ int ret; ++ ++ aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; ++ ++ ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", ++ AW88399_ACF_FILE, cont ? cont->size : 0); ++ ++ aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, ++ struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); ++ if (!aw88399->aw_cfg) { ++ release_firmware(cont); ++ return -ENOMEM; ++ } ++ aw88399->aw_cfg->len = (int)cont->size; ++ memcpy(aw88399->aw_cfg->data, cont->data, cont->size); ++ release_firmware(cont); ++ ++ ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); ++ if (ret) { ++ dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); ++ return ret; ++ } ++ ++ mutex_lock(&aw88399->lock); ++ /* aw device init */ ++ ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); ++ if (ret) ++ dev_err(aw88399->aw_pa->dev, "dev init failed"); ++ mutex_unlock(&aw88399->lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); ++ ++void aw88399_hw_reset(struct aw88399 *aw88399) ++{ ++ if (aw88399->reset_gpio) { ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 0); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ gpiod_set_value_cansleep(aw88399->reset_gpio, 1); ++ usleep_range(AW88399_1000_US, AW88399_1000_US + 10); ++ } ++} ++EXPORT_SYMBOL_GPL(aw88399_hw_reset); ++ ++static void aw88399_parse_channel_dt(struct aw_device *aw_dev) ++{ ++ struct device_node *np = aw_dev->dev->of_node; ++ u32 channel_value; ++ int ret; ++ ++ ret = of_property_read_u32(np, "awinic,audio-channel", &channel_value); ++ if (ret) { ++ /* ++ * On ACPI systems, DT properties don't exist. Derive channel ++ * from I2C address: 0x34 -> channel 0 (left), 0x35 -> channel 1 (right) ++ */ ++ aw_dev->channel = aw_dev->i2c->addr - 0x34; ++ dev_dbg(aw_dev->dev, ++ "DT channel property not found, using I2C address-based channel %d (addr 0x%02x)\n", ++ aw_dev->channel, aw_dev->i2c->addr); ++ return; ++ } ++ aw_dev->channel = channel_value; ++} ++ ++int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) ++{ ++ struct aw_device *aw_dev; ++ unsigned int chip_id; ++ int ret; ++ ++ ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); ++ if (ret) { ++ dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); ++ return ret; ++ } ++ if (chip_id != AW88399_CHIP_ID) { ++ dev_err(&i2c->dev, "unsupported device"); ++ return -ENXIO; ++ } ++ dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); ++ ++ aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); ++ if (!aw_dev) ++ return -ENOMEM; ++ aw88399->aw_pa = aw_dev; ++ ++ aw_dev->i2c = i2c; ++ aw_dev->dev = &i2c->dev; ++ aw_dev->regmap = regmap; ++ mutex_init(&aw_dev->dsp_lock); ++ ++ aw_dev->chip_id = chip_id; ++ aw_dev->acf = NULL; ++ aw_dev->prof_info.prof_desc = NULL; ++ aw_dev->prof_info.count = 0; ++ aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; ++ aw_dev->channel = AW88399_DEV_DEFAULT_CH; ++ aw_dev->fw_status = AW88399_DEV_FW_FAILED; ++ ++ aw_dev->fade_step = AW88399_VOLUME_STEP_DB; ++ aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; ++ ++ aw88399_parse_channel_dt(aw_dev); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(aw88399_init); ++ ++void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) ++{ ++ aw88399->aw_pa->channel = channel; ++} ++EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); ++ ++MODULE_DESCRIPTION("AW88399 common device library"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -10,1217 +10,12 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include + #include "aw88399.h" + #include "aw88395/aw88395_device.h" + +-static const struct regmap_config aw88399_remap_config = { +- .val_bits = 16, +- .reg_bits = 8, +- .max_register = AW88399_REG_MAX, +- .reg_format_endian = REGMAP_ENDIAN_LITTLE, +- .val_format_endian = REGMAP_ENDIAN_BIG, +-}; +- +-static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) +-{ +- int ret; +- +- if (pwd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_PWDN_MASK, AW88399_PWDN_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_get_int_status(struct aw_device *aw_dev, unsigned short *int_status) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSINT_REG, ®_val); +- if (ret) +- dev_err(aw_dev->dev, "read interrupt reg fail, ret=%d", ret); +- else +- *int_status = reg_val; +- +- dev_dbg(aw_dev->dev, "read interrupt reg=0x%04x", *int_status); +-} +- +-static void aw_dev_clear_int_status(struct aw_device *aw_dev) +-{ +- u16 int_status; +- +- /* read int status and clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- /* make sure int status is clear */ +- aw_dev_get_int_status(aw_dev, &int_status); +- if (int_status) +- dev_dbg(aw_dev->dev, "int status(%d) is not cleaned.\n", int_status); +-} +- +-static int aw_dev_get_iis_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- if ((reg_val & AW88399_BIT_PLL_CHECK) != AW88399_BIT_PLL_CHECK) { +- dev_err(aw_dev->dev, "check pll lock fail, reg_val:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw_dev_check_mode1_pll(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode1 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static int aw_dev_check_mode2_pll(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PLLCTRL2_REG, ®_val); +- if (ret) +- return ret; +- +- reg_val &= (~AW88399_CCO_MUX_MASK); +- if (reg_val == AW88399_CCO_MUX_DIVIDED_VALUE) { +- dev_dbg(aw_dev->dev, "CCO_MUX is already divider"); +- return -EPERM; +- } +- +- /* change mode2 */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_DIVIDED_VALUE); +- if (ret) +- return ret; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- +- /* change mode1 */ +- regmap_update_bits(aw_dev->regmap, AW88399_PLLCTRL2_REG, +- ~AW88399_CCO_MUX_MASK, AW88399_CCO_MUX_BYPASS_VALUE); +- if (ret == 0) { +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = aw_dev_get_iis_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 switch to mode1, iis signal check error"); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- break; +- } +- } +- } +- +- return ret; +-} +- +-static int aw_dev_check_syspll(struct aw_device *aw_dev) +-{ +- int ret; +- +- ret = aw_dev_check_mode1_pll(aw_dev); +- if (ret) { +- dev_dbg(aw_dev->dev, "mode1 check iis failed try switch to mode2 check"); +- ret = aw_dev_check_mode2_pll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "mode2 check iis failed"); +- return ret; +- } +- } +- +- return 0; +-} +- +-static int aw_dev_check_sysst(struct aw_device *aw_dev) +-{ +- unsigned int check_val; +- unsigned int reg_val; +- int ret, i; +- +- ret = regmap_read(aw_dev->regmap, AW88399_PWMCTRL3_REG, ®_val); +- if (ret) +- return ret; +- +- if (reg_val & (~AW88399_NOISE_GATE_EN_MASK)) +- check_val = AW88399_BIT_SYSST_NOSWS_CHECK; +- else +- check_val = AW88399_BIT_SYSST_SWS_CHECK; +- +- for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { +- ret = regmap_read(aw_dev->regmap, AW88399_SYSST_REG, ®_val); +- if (ret) +- return ret; +- +- if ((reg_val & (~AW88399_BIT_SYSST_CHECK_MASK) & check_val) != check_val) { +- dev_err(aw_dev->dev, "check sysst fail, cnt=%d, reg_val=0x%04x, check:0x%x", +- i, reg_val, AW88399_BIT_SYSST_NOSWS_CHECK); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } else { +- return 0; +- } +- } +- +- return -EPERM; +-} +- +-static void aw_dev_amppd(struct aw_device *aw_dev, bool amppd) +-{ +- int ret; +- +- if (amppd) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_POWER_DOWN_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_AMPPD_MASK, AW88399_AMPPD_WORKING_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) +-{ +- int ret; +- +- if (is_enable) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_WORKING_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_DSPBY_MASK, AW88399_DSPBY_BYPASS_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed\n", __func__); +-} +- +-static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) +-{ +- uint16_t icalkh_val, icalkl_val, icalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH4_REG, ®_val); +- if (ret) +- return ret; +- icalkh_val = reg_val & (~AW88399_EF_ISN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL4_REG, ®_val); +- if (ret) +- return ret; +- icalkl_val = reg_val & (~AW88399_EF_ISN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- icalk_val = icalkh_val & icalkl_val; +- else +- icalk_val = icalkh_val | icalkl_val; +- +- if (icalk_val & (~AW88399_EF_ISN_GESLP_SIGN_MASK)) +- icalk_val = icalk_val | AW88399_EF_ISN_GESLP_SIGN_NEG; +- *icalk = (int16_t)icalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkh_val = reg_val & (~AW88399_EF_VSN_GESLP_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL3_REG, ®_val); +- if (ret) +- return ret; +- +- vcalkl_val = reg_val & (~AW88399_EF_VSN_GESLP_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = vcalkh_val & vcalkl_val; +- else +- vcalk_val = vcalkh_val | vcalkl_val; +- +- if (vcalk_val & AW88399_EF_VSN_GESLP_SIGN_MASK) +- vcalk_val = vcalk_val | AW88399_EF_VSN_GESLP_SIGN_NEG; +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw88399_dev_get_internal_vcalk(struct aw88399 *aw88399, int16_t *vcalk) +-{ +- uint16_t vcalkh_val, vcalkl_val, vcalk_val; +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRH2_REG, ®_val); +- if (ret) +- return ret; +- vcalkh_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_H_MASK); +- +- ret = regmap_read(aw_dev->regmap, AW88399_EFRL2_REG, ®_val); +- if (ret) +- return ret; +- vcalkl_val = reg_val & (~AW88399_INTERNAL_VSN_TRIM_L_MASK); +- +- if (aw88399->check_val == AW_EF_AND_CHECK) +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) & +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- else +- vcalk_val = (vcalkh_val >> AW88399_INTERNAL_VSN_TRIM_H_START_BIT) | +- (vcalkl_val >> AW88399_INTERNAL_VSN_TRIM_L_START_BIT); +- +- if (vcalk_val & (~AW88399_TEM4_SIGN_MASK)) +- vcalk_val = vcalk_val | AW88399_TEM4_SIGN_NEG; +- +- *vcalk = (int16_t)vcalk_val; +- +- return 0; +-} +- +-static int aw_dev_set_vcalb(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- unsigned int vsense_select, vsense_value; +- int32_t ical_k, vcal_k, vcalb; +- int16_t icalk, vcalk; +- uint16_t reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_VSNCTRL1_REG, &vsense_value); +- if (ret) +- return ret; +- +- vsense_select = vsense_value & (~AW88399_VDSEL_MASK); +- +- ret = aw88399_dev_get_icalk(aw88399, &icalk); +- if (ret) { +- dev_err(aw_dev->dev, "get icalk failed\n"); +- return ret; +- } +- +- ical_k = icalk * AW88399_ICABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- +- switch (vsense_select) { +- case AW88399_DEV_VDSEL_VSENSE: +- ret = aw88399_dev_get_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_FACTOR / AW88399_ISCAL_FACTOR * +- ical_k / vcal_k * aw88399->vcalb_init_val; +- break; +- case AW88399_DEV_VDSEL_DAC: +- ret = aw88399_dev_get_internal_vcalk(aw88399, &vcalk); +- vcal_k = vcalk * AW88399_VCABLK_DAC_FACTOR + AW88399_CABL_BASE_VALUE; +- vcalb = AW88399_VCALB_ACCURACY * AW88399_VSCAL_DAC_FACTOR / +- AW88399_ISCAL_DAC_FACTOR * ical_k / +- vcal_k * aw88399->vcalb_init_val; +- break; +- default: +- dev_err(aw_dev->dev, "%s: unsupported vsense\n", __func__); +- ret = -EINVAL; +- break; +- } +- if (ret) +- return ret; +- +- vcalb = vcalb >> AW88399_VCALB_ADJ_FACTOR; +- reg_val = (uint32_t)vcalb; +- +- regmap_write(aw_dev->regmap, AW88399_DSPVCALB_REG, reg_val); +- +- return 0; +-} +- +-static int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- uint16_t re_lbits, re_hbits; +- u32 cali_re; +- int ret; +- +- if ((aw_dev->cali_desc.cali_re >= AW88399_CALI_RE_MAX) || +- (aw_dev->cali_desc.cali_re <= AW88399_CALI_RE_MIN)) +- return -EINVAL; +- +- cali_re = AW88399_SHOW_RE_TO_DSP_RE((aw_dev->cali_desc.cali_re + +- aw_dev->cali_desc.ra), AW88399_DSP_RE_SHIFT); +- +- re_hbits = (cali_re & (~AW88399_CALI_RE_HBITS_MASK)) >> AW88399_CALI_RE_HBITS_SHIFT; +- re_lbits = (cali_re & (~AW88399_CALI_RE_LBITS_MASK)) >> AW88399_CALI_RE_LBITS_SHIFT; +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR1_REG, re_hbits); +- if (ret) { +- dev_err(aw_dev->dev, "set cali re error"); +- return ret; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_ACR2_REG, re_lbits); +- if (ret) +- dev_err(aw_dev->dev, "set cali re error"); +- +- return ret; +-} +- +-static int aw_dev_fw_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, fw_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate fw_end_addr */ +- fw_len_val = ((aw_dev->dsp_fw_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_FW_BASE_ADDR; +- +- /* write fw_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, fw_len_val); +- if (ret) +- return ret; +- /* enable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_ENABLE_VALUE); +- +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- /* read crc check result */ +- regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable fw crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CODE_EN_MASK, AW88399_CRC_CODE_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "%s failed, check_val 0x%x != 0x%x", +- __func__, check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_cfg_crc_check(struct aw_device *aw_dev) +-{ +- uint16_t check_val, cfg_len_val; +- unsigned int reg_val; +- int ret; +- +- /* calculate cfg end addr */ +- cfg_len_val = ((aw_dev->dsp_cfg_len / AW_FW_ADDR_LEN) - 1) + AW88399_CRC_CFG_BASE_ADDR; +- +- /* write cfg_end_addr to crc_end_addr */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_END_ADDR_MASK, cfg_len_val); +- if (ret) +- return ret; +- +- /* enable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_ENABLE_VALUE); +- if (ret) +- return ret; +- +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- /* read crc check result */ +- ret = regmap_read(aw_dev->regmap, AW88399_HAGCST_REG, ®_val); +- if (ret) +- return ret; +- +- check_val = (reg_val & (~AW88399_CRC_CHECK_BITS_MASK)) >> AW88399_CRC_CHECK_START_BIT; +- +- /* disable cfg crc check */ +- ret = regmap_update_bits(aw_dev->regmap, AW88399_CRCCTRL_REG, +- ~AW88399_CRC_CFG_EN_MASK, AW88399_CRC_CFG_EN_DISABLE_VALUE); +- if (ret) +- return ret; +- +- if (check_val != AW88399_CRC_CHECK_PASS_VAL) { +- dev_err(aw_dev->dev, "crc_check failed, check val 0x%x != 0x%x", +- check_val, AW88399_CRC_CHECK_PASS_VAL); +- ret = -EINVAL; +- } +- +- return ret; +-} +- +-static int aw_dev_hw_crc_check(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_BYPASS_VALUE); +- if (ret) +- return ret; +- +- ret = aw_dev_fw_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "fw_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = aw_dev_cfg_crc_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "cfg_crc_check failed\n"); +- goto crc_check_failed; +- } +- +- ret = regmap_write(aw_dev->regmap, AW88399_CRCCTRL_REG, aw88399->crc_init_val); +- if (ret) +- return ret; +- +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- +- return ret; +- +-crc_check_failed: +- regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_RAM_CG_BYP_MASK, AW88399_RAM_CG_BYP_WORK_VALUE); +- return ret; +-} +- +-static void aw_dev_i2s_tx_enable(struct aw_device *aw_dev, bool flag) +-{ +- int ret; +- +- if (flag) +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCTRL3_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_ENABLE_VALUE); +- else +- ret = regmap_update_bits(aw_dev->regmap, AW88399_I2SCFG1_REG, +- ~AW88399_I2STXEN_MASK, AW88399_I2STXEN_DISABLE_VALUE); +- +- if (ret) +- dev_dbg(aw_dev->dev, "%s failed", __func__); +-} +- +-static int aw_dev_get_dsp_status(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_WDT_REG, ®_val); +- if (ret) +- return ret; +- if (!(reg_val & (~AW88399_WDT_CNT_MASK))) +- return -EPERM; +- +- return 0; +-} +- +-static int aw_dev_dsp_check(struct aw_device *aw_dev) +-{ +- int ret, i; +- +- switch (aw_dev->dsp_cfg) { +- case AW88399_DEV_DSP_BYPASS: +- dev_dbg(aw_dev->dev, "dsp bypass"); +- ret = 0; +- break; +- case AW88399_DEV_DSP_WORK: +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_dsp_enable(aw_dev, true); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- for (i = 0; i < AW88399_DEV_DSP_CHECK_MAX; i++) { +- ret = aw_dev_get_dsp_status(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp wdt status error=%d", ret); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- } +- } +- break; +- default: +- dev_err(aw_dev->dev, "unknown dsp cfg=%d", aw_dev->dsp_cfg); +- ret = -EINVAL; +- break; +- } +- +- return ret; +-} +- +-static int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) +-{ +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- unsigned int reg_value; +- u16 real_value; +- int ret; +- +- real_value = min((value + vol_desc->init_volume), (unsigned int)AW88399_MUTE_VOL); +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL2_REG, ®_value); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "value 0x%x , reg:0x%x", value, real_value); +- +- real_value = (real_value << AW88399_VOL_START_BIT) | (reg_value & AW88399_VOL_MASK); +- +- ret = regmap_write(aw_dev->regmap, AW88399_SYSCTRL2_REG, real_value); +- +- return ret; +-} +- +-static void aw_dev_fade_in(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- u16 fade_in_vol = desc->ctl_volume; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_in_time == 0) { +- aw_dev_set_volume(aw_dev, fade_in_vol); +- return; +- } +- +- for (i = AW88399_MUTE_VOL; i >= fade_in_vol; i -= fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_in_time, aw_dev->fade_in_time + 10); +- } +- +- if (i != fade_in_vol) +- aw_dev_set_volume(aw_dev, fade_in_vol); +-} +- +-static void aw_dev_fade_out(struct aw_device *aw_dev) +-{ +- struct aw_volume_desc *desc = &aw_dev->volume_desc; +- int fade_step = aw_dev->fade_step; +- int i; +- +- if (fade_step == 0 || aw_dev->fade_out_time == 0) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- return; +- } +- +- for (i = desc->ctl_volume; i <= AW88399_MUTE_VOL; i += fade_step) { +- aw_dev_set_volume(aw_dev, i); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +- +- if (i != AW88399_MUTE_VOL) { +- aw_dev_set_volume(aw_dev, AW88399_MUTE_VOL); +- usleep_range(aw_dev->fade_out_time, aw_dev->fade_out_time + 10); +- } +-} +- +-static void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) +-{ +- if (is_mute) { +- aw_dev_fade_out(aw_dev); +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_ENABLE_VALUE); +- } else { +- regmap_update_bits(aw_dev->regmap, AW88399_SYSCTRL_REG, +- ~AW88399_HMUTE_MASK, AW88399_HMUTE_DISABLE_VALUE); +- aw_dev_fade_in(aw_dev); +- } +-} +- +-static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- +- if (dither) +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_ENABLE_VALUE); +- else +- regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_DITHER_EN_MASK, AW88399_DITHER_EN_DISABLE_VALUE); +-} +- +-static int aw88399_dev_start(struct aw88399 *aw88399) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- if (aw_dev->status == AW88399_DEV_PW_ON) { +- dev_dbg(aw_dev->dev, "already power on"); +- return 0; +- } +- +- aw88399_dev_set_dither(aw88399, false); +- +- /* power on */ +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_2000_US, AW88399_2000_US + 10); +- +- ret = aw_dev_check_syspll(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "pll check failed cannot start"); +- goto pll_check_fail; +- } +- +- /* amppd on */ +- aw_dev_amppd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 50); +- +- /* check i2s status */ +- ret = aw_dev_check_sysst(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "sysst check failed"); +- goto sysst_check_fail; +- } +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) { +- ret = aw_dev_hw_crc_check(aw88399); +- if (ret) { +- dev_err(aw_dev->dev, "dsp crc check failed"); +- goto crc_check_fail; +- } +- aw_dev_dsp_enable(aw_dev, false); +- aw_dev_set_vcalb(aw88399); +- aw_dev_update_cali_re(&aw_dev->cali_desc); +- +- ret = aw_dev_dsp_check(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "dsp status check failed"); +- goto dsp_check_fail; +- } +- } else { +- dev_dbg(aw_dev->dev, "start pa with dsp bypass"); +- } +- +- /* enable tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, true); +- +- if (aw88399->dither_st == AW88399_DITHER_EN_ENABLE_VALUE) +- aw88399_dev_set_dither(aw88399, true); +- +- /* close mute */ +- aw88399_dev_mute(aw_dev, false); +- /* clear inturrupt */ +- aw_dev_clear_int_status(aw_dev); +- aw_dev->status = AW88399_DEV_PW_ON; +- +- return 0; +- +-dsp_check_fail: +-crc_check_fail: +- aw_dev_dsp_enable(aw_dev, false); +-sysst_check_fail: +- aw_dev_clear_int_status(aw_dev); +- aw_dev_amppd(aw_dev, true); +-pll_check_fail: +- aw_dev_pwd(aw_dev, true); +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- return ret; +-} +- +-static int aw_dev_dsp_update_container(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len, unsigned short base) +-{ +- u32 tmp_len; +- int i, ret; +- +- ret = regmap_write(aw_dev->regmap, AW88399_DSPMADD_REG, base); +- if (ret) +- return ret; +- +- for (i = 0; i < len; i += AW88399_MAX_RAM_WRITE_BYTE_SIZE) { +- tmp_len = min(len - i, AW88399_MAX_RAM_WRITE_BYTE_SIZE); +- ret = regmap_raw_write(aw_dev->regmap, AW88399_DSPMDAT_REG, +- &data[i], tmp_len); +- if (ret) +- return ret; +- } +- +- return 0; +-} +- +-static int aw_dev_get_ra(struct aw_cali_desc *cali_desc) +-{ +- struct aw_device *aw_dev = +- container_of(cali_desc, struct aw_device, cali_desc); +- u32 dsp_ra; +- int ret; +- +- ret = aw_dev_dsp_read(aw_dev, AW88399_DSP_REG_CFG_ADPZ_RA, +- &dsp_ra, AW_DSP_32_DATA); +- if (ret) { +- dev_err(aw_dev->dev, "read ra error"); +- return ret; +- } +- +- cali_desc->ra = AW88399_DSP_RE_TO_SHOW_RE(dsp_ra, +- AW88399_DSP_RE_SHIFT); +- +- return 0; +-} +- +-static int aw_dev_dsp_update_cfg(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp config len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp config data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_CFG_ADDR); +- if (ret) +- return ret; +- +- aw_dev->dsp_cfg_len = len; +- +- ret = aw_dev_get_ra(&aw_dev->cali_desc); +- +- return ret; +-} +- +-static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- dev_dbg(aw_dev->dev, "dsp firmware len:%d", len); +- +- if (!len || !data) { +- dev_err(aw_dev->dev, "dsp firmware data is null or len is 0"); +- return -EINVAL; +- } +- +- aw_dev->dsp_fw_len = len; +- ret = aw_dev_dsp_update_container(aw_dev, data, len, AW88399_DSP_FW_ADDR); +- +- return ret; +-} +- +-static int aw_dev_check_sram(struct aw_device *aw_dev) +-{ +- unsigned int reg_val; +- +- /* read dsp_rom_check_reg */ +- aw_dev_dsp_read(aw_dev, AW88399_DSP_ROM_CHECK_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ROM_CHECK_DATA) { +- dev_err(aw_dev->dev, "check dsp rom failed, read[0x%x] != check[0x%x]", +- reg_val, AW88399_DSP_ROM_CHECK_DATA); +- return -EPERM; +- } +- +- /* check dsp_cfg_base_addr */ +- aw_dev_dsp_write(aw_dev, AW88399_DSP_CFG_ADDR, +- AW88399_DSP_ODD_NUM_BIT_TEST, AW_DSP_16_DATA); +- aw_dev_dsp_read(aw_dev, AW88399_DSP_CFG_ADDR, ®_val, AW_DSP_16_DATA); +- if (reg_val != AW88399_DSP_ODD_NUM_BIT_TEST) { +- dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", +- reg_val, AW88399_DSP_ODD_NUM_BIT_TEST); +- return -EPERM; +- } +- +- return 0; +-} +- +-static void aw_dev_select_memclk(struct aw_device *aw_dev, unsigned char flag) +-{ +- int ret; +- +- switch (flag) { +- case AW88399_DEV_MEMCLK_PLL: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_DAPHCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select pll failed"); +- break; +- case AW88399_DEV_MEMCLK_OSC: +- ret = regmap_update_bits(aw_dev->regmap, AW88399_DBGCTRL_REG, +- ~AW88399_MEM_CLKSEL_MASK, +- AW88399_MEM_CLKSEL_OSCCLK_VALUE); +- if (ret) +- dev_err(aw_dev->dev, "memclk select OSC failed"); +- break; +- default: +- dev_err(aw_dev->dev, "unknown memclk config, flag=0x%x", flag); +- break; +- } +-} +- +-static void aw_dev_get_cur_mode_st(struct aw_device *aw_dev) +-{ +- struct aw_profctrl_desc *profctrl_desc = &aw_dev->profctrl_desc; +- unsigned int reg_val; +- int ret; +- +- ret = regmap_read(aw_dev->regmap, AW88399_SYSCTRL_REG, ®_val); +- if (ret) { +- dev_dbg(aw_dev->dev, "%s failed", __func__); +- return; +- } +- if ((reg_val & (~AW88399_RCV_MODE_MASK)) == AW88399_RCV_MODE_RECEIVER_VALUE) +- profctrl_desc->cur_mode = AW88399_RCV_MODE; +- else +- profctrl_desc->cur_mode = AW88399_NOT_RCV_MODE; +-} +- +-static int aw_dev_update_reg_container(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_volume_desc *vol_desc = &aw_dev->volume_desc; +- u16 read_vol, reg_val; +- int data_len, i, ret; +- int16_t *reg_data; +- u8 reg_addr; +- +- reg_data = (int16_t *)data; +- data_len = len >> 1; +- +- if (data_len & 0x1) { +- dev_err(aw_dev->dev, "data len:%d unsupported", data_len); +- return -EINVAL; +- } +- +- for (i = 0; i < data_len; i += 2) { +- reg_addr = reg_data[i]; +- reg_val = reg_data[i + 1]; +- +- if (reg_addr == AW88399_DSPVCALB_REG) { +- aw88399->vcalb_init_val = reg_val; +- continue; +- } +- +- if (reg_addr == AW88399_SYSCTRL_REG) { +- if (reg_val & (~AW88399_DSPBY_MASK)) +- aw_dev->dsp_cfg = AW88399_DEV_DSP_BYPASS; +- else +- aw_dev->dsp_cfg = AW88399_DEV_DSP_WORK; +- +- reg_val &= (AW88399_HMUTE_MASK | AW88399_PWDN_MASK | +- AW88399_DSPBY_MASK); +- reg_val |= (AW88399_HMUTE_ENABLE_VALUE | AW88399_PWDN_POWER_DOWN_VALUE | +- AW88399_DSPBY_BYPASS_VALUE); +- } +- +- if (reg_addr == AW88399_I2SCTRL3_REG) { +- reg_val &= AW88399_I2STXEN_MASK; +- reg_val |= AW88399_I2STXEN_DISABLE_VALUE; +- } +- +- if (reg_addr == AW88399_SYSCTRL2_REG) { +- read_vol = (reg_val & (~AW88399_VOL_MASK)) >> +- AW88399_VOL_START_BIT; +- aw_dev->volume_desc.init_volume = read_vol; +- } +- +- if (reg_addr == AW88399_DBGCTRL_REG) { +- if ((reg_val & (~AW88399_EF_DBMD_MASK)) == AW88399_EF_DBMD_OR_VALUE) +- aw88399->check_val = AW_EF_OR_CHECK; +- else +- aw88399->check_val = AW_EF_AND_CHECK; +- +- aw88399->dither_st = reg_val & (~AW88399_DITHER_EN_MASK); +- } +- +- if (reg_addr == AW88399_CRCCTRL_REG) +- aw88399->crc_init_val = reg_val; +- +- ret = regmap_write(aw_dev->regmap, reg_addr, reg_val); +- if (ret) +- return ret; +- } +- +- aw_dev_pwd(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- +- aw_dev_get_cur_mode_st(aw_dev); +- +- if (aw_dev->prof_cur != aw_dev->prof_index) +- vol_desc->ctl_volume = 0; +- else +- aw_dev_set_volume(aw_dev, vol_desc->ctl_volume); +- +- return 0; +-} +- +-static int aw_dev_reg_update(struct aw88399 *aw88399, +- unsigned char *data, unsigned int len) +-{ +- int ret; +- +- if (!len || !data) { +- dev_err(aw88399->aw_pa->dev, "reg data is null or len is 0"); +- return -EINVAL; +- } +- +- ret = aw_dev_update_reg_container(aw88399, data, len); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "reg update failed"); +- +- return ret; +-} +- +-static int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) +-{ +- struct aw_prof_info *prof_info = &aw_dev->prof_info; +- struct aw_prof_desc *prof_desc; +- +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "index[%d] overflow count[%d]", +- index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- *prof_name = prof_info->prof_name_list[prof_desc->id]; +- +- return 0; +-} +- +-static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, +- struct aw_prof_desc **prof_desc) +-{ +- if ((index >= aw_dev->prof_info.count) || (index < 0)) { +- dev_err(aw_dev->dev, "%s: index[%d] overflow count[%d]\n", +- __func__, index, aw_dev->prof_info.count); +- return -EINVAL; +- } +- +- *prof_desc = &aw_dev->prof_info.prof_desc[index]; +- +- return 0; +-} +- +-static int aw88399_dev_fw_update(struct aw88399 *aw88399, bool up_dsp_fw_en, bool force_up_en) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- struct aw_prof_desc *prof_index_desc; +- struct aw_sec_data_desc *sec_desc; +- char *prof_name; +- int ret; +- +- if ((aw_dev->prof_cur == aw_dev->prof_index) && +- (force_up_en == AW88399_FORCE_UPDATE_OFF)) { +- dev_dbg(aw_dev->dev, "scene no change, not update"); +- return 0; +- } +- +- if (aw_dev->fw_status == AW88399_DEV_FW_FAILED) { +- dev_err(aw_dev->dev, "fw status[%d] error", aw_dev->fw_status); +- return -EPERM; +- } +- +- ret = aw88399_dev_get_prof_name(aw_dev, aw_dev->prof_index, &prof_name); +- if (ret) +- return ret; +- +- dev_dbg(aw_dev->dev, "start update %s", prof_name); +- +- ret = aw88399_dev_get_prof_data(aw_dev, aw_dev->prof_index, &prof_index_desc); +- if (ret) +- return ret; +- +- /* update reg */ +- sec_desc = prof_index_desc->sec_desc; +- ret = aw_dev_reg_update(aw88399, sec_desc[AW88395_DATA_TYPE_REG].data, +- sec_desc[AW88395_DATA_TYPE_REG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update reg failed"); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- if (aw_dev->dsp_cfg == AW88399_DEV_DSP_WORK) +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- +- ret = aw_dev_check_sram(aw_dev); +- if (ret) { +- dev_err(aw_dev->dev, "check sram failed"); +- goto error; +- } +- +- if (up_dsp_fw_en) { +- dev_dbg(aw_dev->dev, "fw_ver: [%x]", prof_index_desc->fw_ver); +- ret = aw_dev_dsp_update_fw(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_FW].data, +- sec_desc[AW88395_DATA_TYPE_DSP_FW].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp fw failed"); +- goto error; +- } +- } +- +- /* update dsp config */ +- ret = aw_dev_dsp_update_cfg(aw_dev, sec_desc[AW88395_DATA_TYPE_DSP_CFG].data, +- sec_desc[AW88395_DATA_TYPE_DSP_CFG].len); +- if (ret) { +- dev_err(aw_dev->dev, "update dsp cfg failed"); +- goto error; +- } +- +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- +- aw_dev->prof_cur = aw_dev->prof_index; +- +- return 0; +- +-error: +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- return ret; +-} +- +-static void aw88399_start_pa(struct aw88399 *aw88399) +-{ +- int ret, i; +- +- for (i = 0; i < AW88399_START_RETRIES; i++) { +- ret = aw88399_dev_start(aw88399); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "aw88399 device start failed. retry = %d", i); +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_ON, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed"); +- continue; +- } +- } else { +- dev_dbg(aw88399->aw_pa->dev, "start success\n"); +- break; +- } +- } +-} +- +-static void aw88399_startup_work(struct work_struct *work) +-{ +- struct aw88399 *aw88399 = +- container_of(work, struct aw88399, start_work.work); +- +- mutex_lock(&aw88399->lock); +- aw88399_start_pa(aw88399); +- mutex_unlock(&aw88399->lock); +-} +- +-static void aw88399_start(struct aw88399 *aw88399, bool sync_start) +-{ +- int ret; +- +- if (aw88399->aw_pa->fw_status != AW88399_DEV_FW_OK) +- return; +- +- if (aw88399->aw_pa->status == AW88399_DEV_PW_ON) +- return; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_DSP_FW_UPDATE_OFF, true); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "fw update failed."); +- return; +- } +- +- if (sync_start == AW88399_SYNC_START) +- aw88399_start_pa(aw88399); +- else +- queue_delayed_work(system_dfl_wq, +- &aw88399->start_work, +- AW88399_START_WORK_DELAY_MS); +-} +- +-static int aw_dev_check_sysint(struct aw_device *aw_dev) +-{ +- u16 reg_val; +- +- aw_dev_get_int_status(aw_dev, ®_val); +- if (reg_val & AW88399_BIT_SYSINT_CHECK) { +- dev_err(aw_dev->dev, "pa stop check fail:0x%04x", reg_val); +- return -EINVAL; +- } +- +- return 0; +-} +- +-static int aw88399_stop(struct aw_device *aw_dev) +-{ +- struct aw_sec_data_desc *dsp_cfg = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_CFG]; +- struct aw_sec_data_desc *dsp_fw = +- &aw_dev->prof_info.prof_desc[aw_dev->prof_cur].sec_desc[AW88395_DATA_TYPE_DSP_FW]; +- int int_st; +- +- if (aw_dev->status == AW88399_DEV_PW_OFF) { +- dev_dbg(aw_dev->dev, "already power off"); +- return 0; +- } +- +- aw_dev->status = AW88399_DEV_PW_OFF; +- +- aw88399_dev_mute(aw_dev, true); +- usleep_range(AW88399_4000_US, AW88399_4000_US + 100); +- +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- int_st = aw_dev_check_sysint(aw_dev); +- +- aw_dev_dsp_enable(aw_dev, false); +- +- aw_dev_amppd(aw_dev, true); +- +- if (int_st) { +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_OSC); +- aw_dev_dsp_update_fw(aw_dev, dsp_fw->data, dsp_fw->len); +- aw_dev_dsp_update_cfg(aw_dev, dsp_cfg->data, dsp_cfg->len); +- aw_dev_select_memclk(aw_dev, AW88399_DEV_MEMCLK_PLL); +- } +- +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- + static struct snd_soc_dai_driver aw88399_dai[] = { + { + .name = "aw88399-aif", +@@ -1869,86 +664,6 @@ static int aw88399_calib_set(struct snd_kcontrol *kcontrol, + return 0; + } + +-static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) +-{ +- struct aw_device *aw_dev = aw88399->aw_pa; +- int ret; +- +- ret = aw88395_dev_cfg_load(aw_dev, aw_cfg); +- if (ret) { +- dev_err(aw_dev->dev, "aw_dev acf parse failed"); +- return -EINVAL; +- } +- aw_dev->fade_in_time = AW88399_1000_US / 10; +- aw_dev->fade_out_time = AW88399_1000_US >> 1; +- aw_dev->prof_cur = aw_dev->prof_info.prof_desc[0].id; +- aw_dev->prof_index = aw_dev->prof_info.prof_desc[0].id; +- +- ret = aw88399_dev_fw_update(aw88399, AW88399_FORCE_UPDATE_ON, AW88399_DSP_FW_UPDATE_ON); +- if (ret) { +- dev_err(aw_dev->dev, "fw update failed ret = %d\n", ret); +- return ret; +- } +- +- aw88399_dev_mute(aw_dev, true); +- +- /* close tx feedback */ +- aw_dev_i2s_tx_enable(aw_dev, false); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 100); +- +- /* enable amppd */ +- aw_dev_amppd(aw_dev, true); +- +- /* close dsp */ +- aw_dev_dsp_enable(aw_dev, false); +- /* set power down */ +- aw_dev_pwd(aw_dev, true); +- +- return 0; +-} +- +-static int aw88399_request_firmware_file(struct aw88399 *aw88399) +-{ +- const struct firmware *cont = NULL; +- int ret; +- +- aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +- +- ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", +- AW88399_ACF_FILE, cont ? cont->size : 0); +- +- aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, +- struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); +- return -ENOMEM; +- } +- aw88399->aw_cfg->len = (int)cont->size; +- memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); +- +- ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); +- if (ret) { +- dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); +- return ret; +- } +- +- mutex_lock(&aw88399->lock); +- /* aw device init */ +- ret = aw88399_dev_init(aw88399, aw88399->aw_cfg); +- if (ret) +- dev_err(aw88399->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88399->lock); +- +- return ret; +-} +- + static const struct snd_kcontrol_new aw88399_controls[] = { + SOC_SINGLE_EXT("PCM Playback Volume", AW88399_SYSCTRL2_REG, + 6, AW88399_MUTE_VOL, 0, aw88399_volume_get, +@@ -2040,70 +755,6 @@ static const struct snd_soc_component_driver soc_codec_dev_aw88399 = { + .num_controls = ARRAY_SIZE(aw88399_controls), + }; + +-static void aw88399_hw_reset(struct aw88399 *aw88399) +-{ +- if (aw88399->reset_gpio) { +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 0); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- gpiod_set_value_cansleep(aw88399->reset_gpio, 1); +- usleep_range(AW88399_1000_US, AW88399_1000_US + 10); +- } +-} +- +-static void aw88399_parse_channel_dt(struct aw_device *aw_dev) +-{ +- struct device_node *np = aw_dev->dev->of_node; +- u32 channel_value; +- +- of_property_read_u32(np, "awinic,audio-channel", &channel_value); +- aw_dev->channel = channel_value; +-} +- +-static int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap *regmap) +-{ +- struct aw_device *aw_dev; +- unsigned int chip_id; +- int ret; +- +- ret = regmap_read(regmap, AW88399_ID_REG, &chip_id); +- if (ret) { +- dev_err(&i2c->dev, "%s read chipid error. ret = %d", __func__, ret); +- return ret; +- } +- if (chip_id != AW88399_CHIP_ID) { +- dev_err(&i2c->dev, "unsupported device"); +- return -ENXIO; +- } +- dev_dbg(&i2c->dev, "chip id = %x\n", chip_id); +- +- aw_dev = devm_kzalloc(&i2c->dev, sizeof(*aw_dev), GFP_KERNEL); +- if (!aw_dev) +- return -ENOMEM; +- aw88399->aw_pa = aw_dev; +- +- aw_dev->i2c = i2c; +- aw_dev->dev = &i2c->dev; +- aw_dev->regmap = regmap; +- mutex_init(&aw_dev->dsp_lock); +- +- aw_dev->chip_id = chip_id; +- aw_dev->acf = NULL; +- aw_dev->prof_info.prof_desc = NULL; +- aw_dev->prof_info.count = 0; +- aw_dev->prof_info.prof_type = AW88395_DEV_NONE_TYPE_ID; +- aw_dev->channel = AW88399_DEV_DEFAULT_CH; +- aw_dev->fw_status = AW88399_DEV_FW_FAILED; +- +- aw_dev->fade_step = AW88399_VOLUME_STEP_DB; +- aw_dev->volume_desc.ctl_volume = AW88399_VOL_DEFAULT_VALUE; +- +- aw88399_parse_channel_dt(aw_dev); +- +- return 0; +-} +- + static int aw88399_i2c_probe(struct i2c_client *i2c) + { + struct aw88399 *aw88399; +diff --git a/sound/soc/codecs/aw88399.h b/sound/soc/codecs/aw88399.h +--- a/sound/soc/codecs/aw88399.h ++++ b/sound/soc/codecs/aw88399.h +@@ -10,512 +10,10 @@ + #ifndef __AW88399_H__ + #define __AW88399_H__ + +-/* registers list */ +-#define AW88399_ID_REG (0x00) +-#define AW88399_SYSST_REG (0x01) +-#define AW88399_SYSINT_REG (0x02) +-#define AW88399_SYSINTM_REG (0x03) +-#define AW88399_SYSCTRL_REG (0x04) +-#define AW88399_SYSCTRL2_REG (0x05) +-#define AW88399_I2SCTRL1_REG (0x06) +-#define AW88399_I2SCTRL2_REG (0x07) +-#define AW88399_I2SCTRL3_REG (0x08) +-#define AW88399_DACCFG1_REG (0x09) +-#define AW88399_DACCFG2_REG (0x0A) +-#define AW88399_DACCFG3_REG (0x0B) +-#define AW88399_DACCFG4_REG (0x0C) +-#define AW88399_DACCFG5_REG (0x0D) +-#define AW88399_DACCFG6_REG (0x0E) +-#define AW88399_DACCFG7_REG (0x0F) +-#define AW88399_MPDCFG1_REG (0x10) +-#define AW88399_MPDCFG2_REG (0x11) +-#define AW88399_MPDCFG3_REG (0x12) +-#define AW88399_MPDCFG4_REG (0x13) +-#define AW88399_PWMCTRL1_REG (0x14) +-#define AW88399_PWMCTRL2_REG (0x15) +-#define AW88399_PWMCTRL3_REG (0x16) +-#define AW88399_I2SCFG1_REG (0x17) +-#define AW88399_DBGCTRL_REG (0x18) +-#define AW88399_HAGCST_REG (0x20) +-#define AW88399_VBAT_REG (0x21) +-#define AW88399_TEMP_REG (0x22) +-#define AW88399_PVDD_REG (0x23) +-#define AW88399_ISNDAT_REG (0x24) +-#define AW88399_VSNDAT_REG (0x25) +-#define AW88399_I2SINT_REG (0x26) +-#define AW88399_I2SCAPCNT_REG (0x27) +-#define AW88399_ANASTA1_REG (0x28) +-#define AW88399_ANASTA2_REG (0x29) +-#define AW88399_ANASTA3_REG (0x2A) +-#define AW88399_TESTDET_REG (0x2B) +-#define AW88399_DSMCFG1_REG (0x30) +-#define AW88399_DSMCFG2_REG (0x31) +-#define AW88399_DSMCFG3_REG (0x32) +-#define AW88399_DSMCFG4_REG (0x33) +-#define AW88399_DSMCFG5_REG (0x34) +-#define AW88399_DSMCFG6_REG (0x35) +-#define AW88399_DSMCFG7_REG (0x36) +-#define AW88399_DSMCFG8_REG (0x37) +-#define AW88399_TESTIN_REG (0x38) +-#define AW88399_TESTOUT_REG (0x39) +-#define AW88399_MEMTEST_REG (0x3A) +-#define AW88399_VSNCTRL1_REG (0x3B) +-#define AW88399_ISNCTRL1_REG (0x3C) +-#define AW88399_ISNCTRL2_REG (0x3D) +-#define AW88399_DSPMADD_REG (0x40) +-#define AW88399_DSPMDAT_REG (0x41) +-#define AW88399_WDT_REG (0x42) +-#define AW88399_ACR1_REG (0x43) +-#define AW88399_ACR2_REG (0x44) +-#define AW88399_ASR1_REG (0x45) +-#define AW88399_ASR2_REG (0x46) +-#define AW88399_DSPCFG_REG (0x47) +-#define AW88399_ASR3_REG (0x48) +-#define AW88399_ASR4_REG (0x49) +-#define AW88399_DSPVCALB_REG (0x4A) +-#define AW88399_CRCCTRL_REG (0x4B) +-#define AW88399_DSPDBG1_REG (0x4C) +-#define AW88399_DSPDBG2_REG (0x4D) +-#define AW88399_DSPDBG3_REG (0x4E) +-#define AW88399_PLLCTRL1_REG (0x50) +-#define AW88399_PLLCTRL2_REG (0x51) +-#define AW88399_PLLCTRL3_REG (0x52) +-#define AW88399_CDACTRL1_REG (0x53) +-#define AW88399_CDACTRL2_REG (0x54) +-#define AW88399_CDACTRL3_REG (0x55) +-#define AW88399_SADCCTRL1_REG (0x56) +-#define AW88399_SADCCTRL2_REG (0x57) +-#define AW88399_BOPCTRL1_REG (0x58) +-#define AW88399_BOPCTRL2_REG (0x5A) +-#define AW88399_BOPCTRL3_REG (0x5B) +-#define AW88399_BOPCTRL4_REG (0x5C) +-#define AW88399_BOPCTRL5_REG (0x5D) +-#define AW88399_BOPCTRL6_REG (0x5E) +-#define AW88399_BOPCTRL7_REG (0x5F) +-#define AW88399_BSTCTRL1_REG (0x60) +-#define AW88399_BSTCTRL2_REG (0x61) +-#define AW88399_BSTCTRL3_REG (0x62) +-#define AW88399_BSTCTRL4_REG (0x63) +-#define AW88399_BSTCTRL5_REG (0x64) +-#define AW88399_BSTCTRL6_REG (0x65) +-#define AW88399_BSTCTRL7_REG (0x66) +-#define AW88399_BSTCTRL8_REG (0x67) +-#define AW88399_BSTCTRL9_REG (0x68) +-#define AW88399_BSTCTRL10_REG (0x69) +-#define AW88399_CPCTRL_REG (0x6A) +-#define AW88399_EFWH_REG (0x6C) +-#define AW88399_EFWM2_REG (0x6D) +-#define AW88399_EFWM1_REG (0x6E) +-#define AW88399_EFWL_REG (0x6F) +-#define AW88399_TESTCTRL1_REG (0x70) +-#define AW88399_TESTCTRL2_REG (0x71) +-#define AW88399_EFCTRL1_REG (0x72) +-#define AW88399_EFCTRL2_REG (0x73) +-#define AW88399_EFRH4_REG (0x74) +-#define AW88399_EFRH3_REG (0x75) +-#define AW88399_EFRH2_REG (0x76) +-#define AW88399_EFRH1_REG (0x77) +-#define AW88399_EFRL4_REG (0x78) +-#define AW88399_EFRL3_REG (0x79) +-#define AW88399_EFRL2_REG (0x7A) +-#define AW88399_EFRL1_REG (0x7B) +-#define AW88399_TM_REG (0x7C) +-#define AW88399_TM2_REG (0x7D) +- +-#define AW88399_REG_MAX (0x7E) +-#define AW88399_MUTE_VOL (1023) +- +-#define AW88399_DSP_CFG_ADDR (0x9B00) +-#define AW88399_DSP_REG_CFG_ADPZ_RA (0x9B68) +-#define AW88399_DSP_FW_ADDR (0x8980) +-#define AW88399_DSP_ROM_CHECK_ADDR (0x1F40) +-#define AW88399_DSP_ROM_CHECK_DATA (0x4638) +- +-#define AW88399_CALI_RE_HBITS_MASK (~(0xFFFF0000)) +-#define AW88399_CALI_RE_HBITS_SHIFT (16) +- +-#define AW88399_CALI_RE_LBITS_MASK (~(0xFFFF)) +-#define AW88399_CALI_RE_LBITS_SHIFT (0) +- +-#define AW88399_I2STXEN_START_BIT (9) +-#define AW88399_I2STXEN_BITS_LEN (1) +-#define AW88399_I2STXEN_MASK \ +- (~(((1<> (shift)) +-#define AW88399_SHOW_RE_TO_DSP_RE(re, shift) (((re) << shift) / (1000)) +-#define AW88399_CRC_CHECK_PASS_VAL (0x4) +- +-#define AW88399_CRC_CFG_BASE_ADDR (0xD80) +-#define AW88399_CRC_FW_BASE_ADDR (0x4C0) +-#define AW88399_ACF_FILE "aw88399_acf.bin" +-#define AW88399_DEV_SYSST_CHECK_MAX (10) +-#define AW88399_CHIP_ID 0x2183 ++#include + + #define AW88399_I2C_NAME "aw88399" + +-#define AW88399_START_RETRIES (5) +-#define AW88399_START_WORK_DELAY_MS (0) +- + #define AW88399_RATES (SNDRV_PCM_RATE_8000_48000 | \ + SNDRV_PCM_RATE_96000) + #define AW88399_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ +@@ -550,80 +48,4 @@ + .put = profile_set, \ + } + +-enum { +- AW_EF_AND_CHECK = 0, +- AW_EF_OR_CHECK, +-}; +- +-enum { +- AW88399_DEV_VDSEL_DAC = 0, +- AW88399_DEV_VDSEL_VSENSE = 32, +-}; +- +-enum { +- AW88399_DSP_CRC_NA = 0, +- AW88399_DSP_CRC_OK = 1, +-}; +- +-enum { +- AW88399_DSP_FW_UPDATE_OFF = 0, +- AW88399_DSP_FW_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_FORCE_UPDATE_OFF = 0, +- AW88399_FORCE_UPDATE_ON = 1, +-}; +- +-enum { +- AW88399_1000_US = 1000, +- AW88399_2000_US = 2000, +- AW88399_3000_US = 3000, +- AW88399_4000_US = 4000, +-}; +- +-enum AW88399_DEV_STATUS { +- AW88399_DEV_PW_OFF = 0, +- AW88399_DEV_PW_ON, +-}; +- +-enum AW88399_DEV_FW_STATUS { +- AW88399_DEV_FW_FAILED = 0, +- AW88399_DEV_FW_OK, +-}; +- +-enum AW88399_DEV_MEMCLK { +- AW88399_DEV_MEMCLK_OSC = 0, +- AW88399_DEV_MEMCLK_PLL = 1, +-}; +- +-enum AW88399_DEV_DSP_CFG { +- AW88399_DEV_DSP_WORK = 0, +- AW88399_DEV_DSP_BYPASS = 1, +-}; +- +-enum { +- AW88399_NOT_RCV_MODE = 0, +- AW88399_RCV_MODE = 1, +-}; +- +-enum { +- AW88399_SYNC_START = 0, +- AW88399_ASYNC_START, +-}; +- +-struct aw88399 { +- struct aw_device *aw_pa; +- struct mutex lock; +- struct gpio_desc *reset_gpio; +- struct delayed_work start_work; +- struct regmap *regmap; +- struct aw_container *aw_cfg; +- +- unsigned int check_val; +- unsigned int crc_init_val; +- unsigned int vcalb_init_val; +- unsigned int dither_st; +-}; +- +-#endif ++#endif /* __AW88399_H__ */ +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -1849,16 +1849,26 @@ static int tasdev_load_blk(struct tasdevice_priv *tas_priv, + } + } + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the hardware time to stabilize before ++ * next block re-transmission attempt. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + + if (block->is_pchksum_present) { + ret = tasdev_block_chksum(tas_priv, block, chn); + if (ret == -EAGAIN) { +- if (block->nr_retry > 0) ++ if (block->nr_retry > 0) { ++ /* Give the bus time to recover after ++ * a checksum mismatch error. ++ */ ++ usleep_range(2000, 2500); + continue; ++ } + } else if (ret < 0) /*err in current device, skip it*/ + break; + } +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -1118,7 +1118,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-format", &asrc_fmt); +- priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ priv->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -1358,7 +1358,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- asrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ asrc->asrc_format = asrc_fmt; + if (ret) { + ret = of_property_read_u32(np, "fsl,asrc-width", &width); + if (ret) { +diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c +--- a/sound/soc/fsl/fsl_asrc_m2m.c ++++ b/sound/soc/fsl/fsl_asrc_m2m.c +@@ -367,13 +367,13 @@ static int fsl_asrc_m2m_comp_set_params(struct snd_compr_stream *stream, + if (ret) + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.format) & cap.fmt_in) +- pair->sample_format[IN] = (__force snd_pcm_format_t)params->codec.format; ++ if (pcm_format_to_bits(params->codec.format) & cap.fmt_in) ++ pair->sample_format[IN] = params->codec.format; + else + return -EINVAL; + +- if (pcm_format_to_bits((__force snd_pcm_format_t)params->codec.pcm_format) & cap.fmt_out) +- pair->sample_format[OUT] = (__force snd_pcm_format_t)params->codec.pcm_format; ++ if (pcm_format_to_bits(params->codec.pcm_format) & cap.fmt_out) ++ pair->sample_format[OUT] = params->codec.pcm_format; + else + return -EINVAL; + +@@ -600,7 +600,7 @@ static int fsl_asrc_m2m_fill_codec_caps(struct fsl_asrc *asrc, + cap.rate_in, + cap.rate_in_count * sizeof(__u32)); + codec->descriptor[j].num_sample_rates = cap.rate_in_count; +- codec->descriptor[j].formats = (__force __u32)k; ++ codec->descriptor[j].formats = k; + codec->descriptor[j].pcm_formats = cap.fmt_out; + codec->descriptor[j].src.out_sample_rate_min = cap.rate_out[0]; + codec->descriptor[j].src.out_sample_rate_max = +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -2227,7 +2227,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) + } + + ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt); +- easrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ easrc->asrc_format = asrc_fmt; + if (ret) { + dev_err(dev, "failed to asrc format\n"); + return ret; +diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c +--- a/sound/soc/fsl/fsl_qmc_audio.c ++++ b/sound/soc/fsl/fsl_qmc_audio.c +@@ -503,8 +503,8 @@ static int qmc_dai_constraints_interleaved(struct snd_pcm_substream *substream, + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_INTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +@@ -532,8 +532,8 @@ static int qmc_dai_constraints_noninterleaved(struct snd_pcm_substream *substrea + return ret; + } + +- access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | +- 1ULL << (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; ++ access = 1ULL << SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED | ++ 1ULL << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; + ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS, + access); + if (ret) { +diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c +--- a/sound/soc/fsl/imx-card.c ++++ b/sound/soc/fsl/imx-card.c +@@ -531,7 +531,7 @@ static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); +- snd_mask_set(mask, (__force unsigned int)data->asrc_format); ++ snd_mask_set(mask, data->asrc_format); + + return 0; + } +@@ -684,7 +684,7 @@ static int imx_card_parse_of(struct imx_card_data *data) + } + + ret = of_property_read_u32(args.np, "fsl,asrc-format", &asrc_fmt); +- data->asrc_format = (__force snd_pcm_format_t)asrc_fmt; ++ data->asrc_format = asrc_fmt; + if (ret) { + /* Fallback to old binding; translate to asrc_format */ + ret = of_property_read_u32(args.np, "fsl,asrc-width", &width); +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -144,7 +144,7 @@ static int avs_probe_compr_set_params(struct snd_compr_stream *cstream, + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; +- bps = snd_pcm_format_physical_width((__force snd_pcm_format_t)params->codec.format); ++ bps = snd_pcm_format_physical_width(params->codec.format); + if (bps < 0) + return bps; + format_val = snd_hdac_stream_format(params->codec.ch_out, bps, params->codec.sample_rate); +diff --git a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-da7219-max98357.c +@@ -172,7 +172,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +@@ -184,7 +184,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +--- a/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c ++++ b/sound/soc/mediatek/mt8183/mt8183-mt6358-ts3a227-max98357.c +@@ -99,7 +99,7 @@ static int mt8183_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S32_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +@@ -112,7 +112,7 @@ static int mt8183_rt1015_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + return 0; +diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366.c b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +--- a/sound/soc/mediatek/mt8186/mt8186-mt6366.c ++++ b/sound/soc/mediatek/mt8186/mt8186-mt6366.c +@@ -387,7 +387,7 @@ static int mt8186_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, fmt); + +diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +--- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c ++++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c +@@ -623,7 +623,7 @@ static int mt8188_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8189/mt8189-nau8825.c b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +--- a/sound/soc/mediatek/mt8189/mt8189-nau8825.c ++++ b/sound/soc/mediatek/mt8189/mt8189-nau8825.c +@@ -174,7 +174,7 @@ static int mt8189_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -382,7 +382,7 @@ static int mt8192_i2s_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8195/mt8195-mt6359.c b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +--- a/sound/soc/mediatek/mt8195/mt8195-mt6359.c ++++ b/sound/soc/mediatek/mt8195/mt8195-mt6359.c +@@ -387,7 +387,7 @@ static int mt8195_dptx_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +@@ -650,7 +650,7 @@ static int mt8195_etdm_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + { + /* fix BE i2s format to S24_LE, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -180,7 +180,7 @@ static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + + /* fix BE i2s format to 32bit, clean param mask first */ + snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), +- 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST); ++ 0, SNDRV_PCM_FORMAT_LAST); + + params_set_format(params, SNDRV_PCM_FORMAT_S32_LE); + return 0; +diff --git a/sound/soc/meson/meson-codec-glue.c b/sound/soc/meson/meson-codec-glue.c +--- a/sound/soc/meson/meson-codec-glue.c ++++ b/sound/soc/meson/meson-codec-glue.c +@@ -74,7 +74,7 @@ int meson_codec_glue_input_hw_params(struct snd_pcm_substream *substream, + data->params.rates = snd_pcm_rate_to_rate_bit(params_rate(params)); + data->params.rate_min = params_rate(params); + data->params.rate_max = params_rate(params); +- data->params.formats = 1ULL << (__force int) params_format(params); ++ data->params.formats = 1ULL << params_format(params); + data->params.channels_min = params_channels(params); + data->params.channels_max = params_channels(params); + data->params.sig_bits = dai->driver->playback.sig_bits; +diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c +--- a/sound/sparc/dbri.c ++++ b/sound/sparc/dbri.c +@@ -1840,6 +1840,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + int rval = D_INTR_GETRVAL(x); + #endif + ++#ifdef DBRI_DEBUG + if (channel == D_INTR_CMD) { + dprintk(D_CMD, "INTR: Command: %-5s Value:%d\n", + cmds[command], val); +@@ -1847,6 +1848,7 @@ static void dbri_process_one_interrupt(struct snd_dbri *dbri, int x) + dprintk(D_INT, "INTR: Chan:%d Code:%d Val:%#x\n", + channel, code, rval); + } ++#endif + + switch (code) { + case D_INTR_CMDI: +diff --git a/sound/usb/6fire/firmware.c b/sound/usb/6fire/firmware.c +--- a/sound/usb/6fire/firmware.c ++++ b/sound/usb/6fire/firmware.c +@@ -194,23 +194,20 @@ static int usb6fire_fw_ezusb_upload( + int ret; + u8 data; + struct usb_device *device = interface_to_usbdev(intf); +- const struct firmware *fw = NULL; +- struct ihex_record *rec = kmalloc_obj(struct ihex_record); ++ struct ihex_record *rec __free(kfree) = kmalloc_obj(struct ihex_record); + + if (!rec) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { +- kfree(rec); + dev_err(&intf->dev, + "error requesting ezusb firmware %s.\n", fwname); + return ret; + } + ret = usb6fire_fw_ihex_init(fw, rec); + if (ret < 0) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "error validating ezusb firmware %s.\n", fwname); + return ret; +@@ -219,8 +216,6 @@ static int usb6fire_fw_ezusb_upload( + data = 0x01; /* stop ezusb cpu */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, &data, 1); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: begin message.\n", + fwname); +@@ -231,8 +226,6 @@ static int usb6fire_fw_ezusb_upload( + ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address, + rec->data, rec->len); + if (ret) { +- kfree(rec); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload ezusb firmware %s: data urb.\n", + fwname); +@@ -240,8 +233,6 @@ static int usb6fire_fw_ezusb_upload( + } + } + +- release_firmware(fw); +- kfree(rec); + if (postdata) { /* write data after firmware has been uploaded */ + ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr, + postdata, postlen); +@@ -270,19 +261,18 @@ static int usb6fire_fw_fpga_upload( + int ret; + int i; + struct usb_device *device = interface_to_usbdev(intf); +- u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); ++ u8 *buffer __free(kfree) = kmalloc(FPGA_BUFSIZE, GFP_KERNEL); + const char *c; + const char *end; +- const struct firmware *fw; + + if (!buffer) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fwname, &device->dev); + if (ret < 0) { + dev_err(&intf->dev, "unable to get fpga firmware %s.\n", + fwname); +- kfree(buffer); + return -EIO; + } + +@@ -291,8 +281,6 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0); + if (ret) { +- kfree(buffer); +- release_firmware(fw); + dev_err(&intf->dev, + "unable to upload fpga firmware: begin urb.\n"); + return ret; +@@ -304,15 +292,11 @@ static int usb6fire_fw_fpga_upload( + + ret = usb6fire_fw_fpga_write(device, buffer, i); + if (ret < 0) { +- release_firmware(fw); +- kfree(buffer); + dev_err(&intf->dev, + "unable to upload fpga firmware: fw urb.\n"); + return ret; + } + } +- release_firmware(fw); +- kfree(buffer); + + ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0); + if (ret) { +diff --git a/sound/usb/card.c b/sound/usb/card.c +--- a/sound/usb/card.c ++++ b/sound/usb/card.c +@@ -715,7 +715,7 @@ static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) + + /* old style option found: the position-based integer value */ + if (quirk_flags[idx] && +- !kstrtou32(quirk_flags[idx], 0, &chip->quirk_flags)) { ++ !kstrtou64(quirk_flags[idx], 0, &chip->quirk_flags)) { + snd_usb_apply_flag_dbg("module param", chip, chip->quirk_flags); + return; + } +@@ -905,6 +905,40 @@ static int try_to_register_card(struct snd_usb_audio *chip, int ifnum) + return 0; + } + ++static void usb_audio_disconnect_components(struct snd_usb_audio *chip) ++{ ++ struct snd_usb_stream *as; ++ struct snd_usb_endpoint *ep; ++ struct usb_mixer_interface *mixer; ++ struct list_head *p; ++ ++ /* release the pcm resources */ ++ list_for_each_entry(as, &chip->pcm_list, list) { ++ snd_usb_stream_disconnect(as); ++ } ++ /* release the endpoint resources */ ++ list_for_each_entry(ep, &chip->ep_list, list) { ++ snd_usb_endpoint_release(ep); ++ } ++ /* release the midi resources */ ++ list_for_each(p, &chip->midi_list) { ++ snd_usbmidi_disconnect(p); ++ } ++ snd_usb_midi_v2_disconnect_all(chip); ++ /* ++ * Nice to check quirk && quirk->shares_media_device and ++ * then call the snd_media_device_delete(). Don't have ++ * access to the quirk here. snd_media_device_delete() ++ * accesses mixer_list ++ */ ++ snd_media_device_delete(chip); ++ ++ /* release mixer resources */ ++ list_for_each_entry(mixer, &chip->mixer_list, list) { ++ snd_usb_mixer_disconnect(mixer); ++ } ++} ++ + /* + * probe the active usb device + * +@@ -1077,8 +1111,10 @@ static int usb_audio_probe(struct usb_interface *intf, + * decrement before memory is possibly returned. + */ + atomic_dec(&chip->active); +- if (!chip->num_interfaces) ++ if (!chip->num_interfaces) { ++ usb_audio_disconnect_components(chip); + snd_card_free(chip->card); ++ } + } + return err; + } +@@ -1091,48 +1127,18 @@ static bool __usb_audio_disconnect(struct usb_interface *intf, + struct snd_usb_audio *chip, + struct snd_card *card) + { +- struct list_head *p; +- + guard(mutex)(®ister_mutex); + + if (platform_ops && platform_ops->disconnect_cb) + platform_ops->disconnect_cb(chip); + + if (atomic_inc_return(&chip->shutdown) == 1) { +- struct snd_usb_stream *as; +- struct snd_usb_endpoint *ep; +- struct usb_mixer_interface *mixer; +- + /* wait until all pending tasks done; + * they are protected by snd_usb_lock_shutdown() + */ + snd_refcount_sync(&chip->usage_count); + snd_card_disconnect(card); +- /* release the pcm resources */ +- list_for_each_entry(as, &chip->pcm_list, list) { +- snd_usb_stream_disconnect(as); +- } +- /* release the endpoint resources */ +- list_for_each_entry(ep, &chip->ep_list, list) { +- snd_usb_endpoint_release(ep); +- } +- /* release the midi resources */ +- list_for_each(p, &chip->midi_list) { +- snd_usbmidi_disconnect(p); +- } +- snd_usb_midi_v2_disconnect_all(chip); +- /* +- * Nice to check quirk && quirk->shares_media_device and +- * then call the snd_media_device_delete(). Don't have +- * access to the quirk here. snd_media_device_delete() +- * accesses mixer_list +- */ +- snd_media_device_delete(chip); +- +- /* release mixer resources */ +- list_for_each_entry(mixer, &chip->mixer_list, list) { +- snd_usb_mixer_disconnect(mixer); +- } ++ usb_audio_disconnect_components(chip); + } + + if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) +diff --git a/sound/usb/clock.c b/sound/usb/clock.c +--- a/sound/usb/clock.c ++++ b/sound/usb/clock.c +@@ -600,7 +600,7 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, + static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + const struct audioformat *fmt, int rate) + { +- int cur_rate, prev_rate; ++ int cur_rate, prev_rate = 0; + int clock; + + /* First, try to find a valid clock. This may trigger +@@ -625,9 +625,12 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, + return clock; + } + +- prev_rate = get_sample_rate_v2v3(chip, fmt->iface, fmt->altsetting, clock); +- if (prev_rate == rate) +- goto validation; ++ if (!(chip->quirk_flags & QUIRK_FLAG_ALWAYS_SET_RATE)) { ++ prev_rate = get_sample_rate_v2v3(chip, fmt->iface, ++ fmt->altsetting, clock); ++ if (prev_rate == rate) ++ goto validation; ++ } + + cur_rate = snd_usb_set_sample_rate_v2v3(chip, fmt, clock, rate); + if (cur_rate < 0) { +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -1237,7 +1237,10 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + /* try to use enough URBs to contain an entire ALSA buffer */ + max_urbs = min((unsigned) MAX_URBS, + MAX_QUEUE * packs_per_ms / urb_packs); +- ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ ep->nurbs = MAX_URBS; ++ else ++ ep->nurbs = min(max_urbs, urbs_per_period * ep->cur_buffer_periods); + } + + if (fmt->fmt_type == UAC_FORMAT_TYPE_II) +@@ -1262,6 +1265,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep) + goto out_of_memory; + u->urb->pipe = ep->pipe; + u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ if (chip->quirk_flags & QUIRK_FLAG_PLAYBACK_URB_FIXUP) ++ u->urb->transfer_flags |= URB_ISO_ASAP; + u->urb->interval = 1 << ep->datainterval; + u->urb->context = u; + u->urb->complete = snd_complete_urb; +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,6 +505,19 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + ++/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels ++ * and sits on the monitor mixer branch, but it is traced through to the ++ * Speaker output terminal and gets named "Speaker Playback Volume". Userspace ++ * then adopts it as the stream's hardware volume, and any setting below 0 dB ++ * attenuates some channels but not others (20 dB imbalance at 80%). Give it a ++ * non-standard name so that it is no longer taken for the stream's master ++ * volume, while remaining reachable for anyone who wants the monitor gain. ++ */ ++static const struct usbmix_name_map audient_id14_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -588,6 +601,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2573, 0x0008), + .map = maya44_map, + }, ++ { ++ /* Audient iD14 */ ++ .id = USB_ID(0x2708, 0x0008), ++ .map = audient_id14_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3920,6 +3920,7 @@ static int snd_rme_digiface_controls_create(struct usb_mixer_interface *mixer) + #define SND_DJM_450_IDX 0x5 + #define SND_DJM_A9_IDX 0x6 + #define SND_DJM_V10_IDX 0x7 ++#define SND_DJM_S11_IDX 0x8 + + #define SND_DJM_CTL(_name, suffix, _default_value, _windex) { \ + .name = _name, \ +@@ -4260,6 +4261,21 @@ static const struct snd_djm_ctl snd_djm_ctls_v10[] = { + // playback channels are fixed and controlled by hardware knobs on the mixer + }; + ++// DJM-S11 ++static const u16 snd_djm_opts_s11_cap1[] = { ++ 0x0100, 0x0103, 0x0106, 0x0107, 0x0108, 0x0109, 0x010d }; ++static const u16 snd_djm_opts_s11_cap2[] = { ++ 0x0200, 0x0203, 0x0206, 0x0207, 0x0208, 0x0209, 0x020d }; ++static const u16 snd_djm_opts_s11_cap3[] = { ++ 0x0307, 0x0308, 0x0309, 0x030a, 0x030d, 0x0311, 0x0312 }; ++ ++static const struct snd_djm_ctl snd_djm_ctls_s11[] = { ++ SND_DJM_CTL("Master Input Level Capture Switch", cap_level, 0, SND_DJM_WINDEX_CAPLVL), ++ SND_DJM_CTL("Input 1 Capture Switch", s11_cap1, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 2 Capture Switch", s11_cap2, 1, SND_DJM_WINDEX_CAP), ++ SND_DJM_CTL("Input 3 Capture Switch", s11_cap3, 3, SND_DJM_WINDEX_CAP) ++}; ++ + static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_250MK2_IDX] = SND_DJM_DEVICE(250mk2), + [SND_DJM_750_IDX] = SND_DJM_DEVICE(750), +@@ -4269,6 +4285,7 @@ static const struct snd_djm_device snd_djm_devices[] = { + [SND_DJM_450_IDX] = SND_DJM_DEVICE(450), + [SND_DJM_A9_IDX] = SND_DJM_DEVICE(a9), + [SND_DJM_V10_IDX] = SND_DJM_DEVICE(v10), ++ [SND_DJM_S11_IDX] = SND_DJM_DEVICE(s11), + }; + + static int snd_djm_controls_info(struct snd_kcontrol *kctl, +@@ -4573,6 +4590,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) + case USB_ID(0x2b73, 0x0034): /* Pioneer DJ DJM-V10 */ + err = snd_djm_controls_create(mixer, SND_DJM_V10_IDX); + break; ++ case USB_ID(0x2b73, 0x0037): /* Pioneer DJ DJM-S11 */ ++ err = snd_djm_controls_create(mixer, SND_DJM_S11_IDX); ++ break; + case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ + err = hp_dock_mixer_create(mixer); + break; +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -3320,6 +3320,73 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * Pioneer DJ / AlphaTheta DJM-S11 ++ */ ++ USB_DEVICE(0x2b73, 0x0037), ++ QUIRK_DRIVER_INFO { ++ QUIRK_DATA_COMPOSITE { ++ { ++ QUIRK_DATA_STANDARD_MIXER(0) ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(1) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 14, ++ .iface = 1, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x01, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ QUIRK_DATA_AUDIOFORMAT(2) { ++ .formats = SNDRV_PCM_FMTBIT_S24_3LE, ++ .channels = 10, ++ .iface = 2, ++ .altsetting = 1, ++ .altset_idx = 1, ++ .endpoint = 0x82, ++ .ep_attr = USB_ENDPOINT_XFER_ISOC | ++ USB_ENDPOINT_SYNC_ASYNC | ++ USB_ENDPOINT_USAGE_IMPLICIT_FB, ++ .rates = SNDRV_PCM_RATE_48000, ++ .rate_min = 48000, ++ .rate_max = 48000, ++ .nr_rates = 1, ++ .rate_table = (unsigned int[]) { 48000 }, ++ .clock = 1, ++ .fmt_type = UAC_FORMAT_TYPE_I ++ } ++ }, ++ { ++ /* Audio Control (unknown purpose) */ ++ .ifnum = 3, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ { ++ .ifnum = 4, ++ .type = QUIRK_MIDI_STANDARD_INTERFACE ++ }, ++ { ++ /* HID */ ++ .ifnum = 5, ++ .type = QUIRK_IGNORE_INTERFACE ++ }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* + * MacroSilicon MS2100/MS2106 based AV capture cards +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2145,7 +2145,7 @@ struct usb_string_match { + + struct usb_audio_quirk_flags_table { + u32 id; +- u32 flags; ++ u64 flags; + const struct usb_string_match *usb_string_match; + }; + +@@ -2314,7 +2314,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0661, 0x0883, /* iBasso DC04 Ultra */ + QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x0666, 0x0880, /* SPACETOUCH USB Audio */ +- QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), ++ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY | ++ QUIRK_FLAG_CTL_MSG_DELAY_5M), + DEVICE_FLG(0x06f8, 0xb000, /* Hercules DJ Console (Windows Edition) */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x06f8, 0xd002, /* Hercules DJ Console (Macintosh Edition) */ +@@ -2337,6 +2338,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x0951, 0x16ad, /* Kingston HyperX */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), ++ DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ ++ QUIRK_FLAG_ALWAYS_SET_RATE), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2432,6 +2435,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x1bcf, 0x2283, /* NexiGo N930AF FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), ++ DEVICE_FLG(0x1e0b, 0xd01e, /* Generic USB Audio Device */ ++ QUIRK_FLAG_PLAYBACK_URB_FIXUP), + DEVICE_FLG(0x1ff7, 0x0f81, /* SC13A Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2040, 0x7200, /* Hauppauge HVR-950Q */ +@@ -2528,6 +2533,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ + QUIRK_FLAG_ALIGN_TRANSFER), ++ DEVICE_FLG(0x84ef, 0x002a, /* Valeton GP-200 */ ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), + +@@ -2641,18 +2648,12 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), + QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), ++ QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL + }; + +-const char *snd_usb_quirk_flag_find_name(unsigned long index) +-{ +- if (index >= ARRAY_SIZE(snd_usb_audio_quirk_flag_names)) +- return NULL; +- +- return snd_usb_audio_quirk_flag_names[index]; +-} +- +-u32 snd_usb_quirk_flags_from_name(const char *name) ++static u64 snd_usb_quirk_flags_from_name(const char *name) + { + int i; + +@@ -2661,7 +2662,7 @@ u32 snd_usb_quirk_flags_from_name(const char *name) + + for (i = 0; snd_usb_audio_quirk_flag_names[i]; i++) { + if (strcasecmp(name, snd_usb_audio_quirk_flag_names[i]) == 0) +- return BIT_U32(i); ++ return BIT_U64(i); + } + + return 0; +@@ -2719,7 +2720,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + { + u16 chip_vid = USB_ID_VENDOR(chip->usb_id); + u16 chip_pid = USB_ID_PRODUCT(chip->usb_id); +- u32 mask_flags, unmask_flags, bit; ++ u64 mask_flags, unmask_flags, bit; + char *p, *field, *flag; + bool is_unmask; + u16 vid, pid; +@@ -2773,7 +2774,7 @@ void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + is_unmask = false; + } + +- if (!kstrtou32(flag, 16, &bit)) { ++ if (!kstrtou64(flag, 16, &bit)) { + if (is_unmask) + unmask_flags |= bit; + else +diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h +--- a/sound/usb/quirks.h ++++ b/sound/usb/quirks.h +@@ -56,7 +56,4 @@ void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip); + void snd_usb_init_quirk_flags_parse_string(struct snd_usb_audio *chip, + const char *str); + +-const char *snd_usb_quirk_flag_find_name(unsigned long flag); +-u32 snd_usb_quirk_flags_from_name(const char *name); +- + #endif /* __USBAUDIO_QUIRKS_H */ +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -44,7 +44,7 @@ struct snd_usb_audio { + atomic_t active; + atomic_t shutdown; + struct snd_refcount usage_count; +- unsigned int quirk_flags; ++ u64 quirk_flags; + unsigned int need_delayed_register:1; /* warn for delayed registration */ + int num_interfaces; + int last_iface; +@@ -254,6 +254,16 @@ extern bool snd_usb_skip_validation; + * check being non-fatal and only disabling GET_CUR instead of the whole mixer. + * The current volume will then be provided by the internal cache that stores + * the last set volume ++ * QUIRK_FLAG_PLAYBACK_URB_FIXUP ++ * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. ++ * This is needed for devices that exhibit boot-time audio stuttering due ++ * to insufficient buffer depth combined with xHCI scheduling variability. ++ * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling ++ * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. ++ * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * Issue SET_CUR for the sample rate even when the clock already reports the ++ * requested rate. A device advertising a single rate is otherwise never sent ++ * the request at all, and some require it before streaming will start. + */ + + enum { +@@ -288,10 +298,12 @@ enum { + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, + QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, ++ QUIRK_TYPE_ALWAYS_SET_RATE = 32, + /* Please also edit snd_usb_audio_quirk_flag_names */ + }; + +-#define QUIRK_FLAG(x) BIT_U32(QUIRK_TYPE_ ## x) ++#define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) + + #define QUIRK_FLAG_GET_SAMPLE_RATE QUIRK_FLAG(GET_SAMPLE_RATE) + #define QUIRK_FLAG_SHARE_MEDIA_DEVICE QUIRK_FLAG(SHARE_MEDIA_DEVICE) +@@ -324,5 +336,7 @@ enum { + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) + #define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) ++#define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + + #endif /* __USBAUDIO_H */ +diff --git a/sound/usb/usx2y/us144mkii_pcm.h b/sound/usb/usx2y/us144mkii_pcm.h +--- a/sound/usb/usx2y/us144mkii_pcm.h ++++ b/sound/usb/usx2y/us144mkii_pcm.h +@@ -7,7 +7,7 @@ + #include "us144mkii.h" + + /** +- * tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. ++ * var tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM. + * + * Defines the supported PCM formats, rates, channels, and buffer/period sizes + * for the TASCAM US-144MKII audio interface. +@@ -15,14 +15,14 @@ + extern const struct snd_pcm_hardware tascam_pcm_hw; + + /** +- * tascam_playback_ops - ALSA PCM operations for playback. ++ * var tascam_playback_ops - ALSA PCM operations for playback. + * + * This structure defines the callback functions for playback stream operations. + */ + extern const struct snd_pcm_ops tascam_playback_ops; + + /** +- * tascam_capture_ops - ALSA PCM operations for capture. ++ * var tascam_capture_ops - ALSA PCM operations for capture. + * + * This structure defines the callback functions for capture stream operations. + */ +diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c +--- a/sound/usb/usx2y/usbusx2yaudio.c ++++ b/sound/usb/usx2y/usbusx2yaudio.c +@@ -167,7 +167,6 @@ static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, i + if (!urb) + return -ENODEV; + urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks +- urb->hcpriv = NULL; + urb->dev = subs->usx2y->dev; /* we need to set this at each time */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { diff --git a/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch.sig new file mode 100644 index 0000000..3f24244 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0510-sound-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch b/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch new file mode 100644 index 0000000..75c49b4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch @@ -0,0 +1,37891 @@ +diff --git a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +--- a/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c ++++ b/drivers/firmware/cirrus/test/cs_dsp_mock_regmap.c +@@ -147,7 +147,7 @@ static const struct reg_default halo_register_defaults[] = { + { 0x2b805c0, 0 }, /* HALO_SCRATCH1 */ + { 0x2b805c8, 0 }, /* HALO_SCRATCH2 */ + { 0x2b805d0, 0 }, /* HALO_SCRATCH3 */ +- { 0x2b805c8, 0 }, /* HALO_SCRATCH4 */ ++ { 0x2b805d8, 0 }, /* HALO_SCRATCH4 */ + { 0x2bc1000, 0 }, /* HALO_CCM_CORE_CONTROL */ + { 0x2bc7000, 0 }, /* HALO_WDT_CONTROL */ + +diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c +--- a/drivers/soundwire/bus_type.c ++++ b/drivers/soundwire/bus_type.c +@@ -105,6 +105,9 @@ static int sdw_bus_probe(struct device *dev) + } + slave->index = ret; + ++ /* Create IRQ mapping now so the driver can get it in probe() */ ++ sdw_irq_create_mapping(slave); ++ + ret = drv->probe(slave, id); + if (ret) { + ida_free(&slave->bus->slave_ida, slave->index); +@@ -117,9 +120,6 @@ static int sdw_bus_probe(struct device *dev) + if (drv->ops && drv->ops->read_prop) + drv->ops->read_prop(slave); + +- if (slave->prop.use_domain_irq) +- sdw_irq_create_mapping(slave); +- + /* init the dynamic sysfs attributes we need */ + ret = sdw_slave_sysfs_dpn_init(slave); + if (ret < 0) +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -152,6 +152,10 @@ + #define LPI_MI2S_TX_4 146 + #define SENARY_MI2S_RX 147 + #define SENARY_MI2S_TX 148 ++#define LPI_MI2S_RX_5 149 ++#define LPI_MI2S_TX_5 150 ++#define LPI_MI2S_RX_6 151 ++#define LPI_MI2S_TX_6 152 + + #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 + #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 +@@ -233,11 +237,39 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + ++#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++ + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 + #define LPASS_HW_DCODEC_VOTE 103 +- +-#define Q6AFE_MAX_CLK_ID 104 ++#define LPASS_HW_LPR_VOTE 104 + + #define LPASS_CLK_ATTRIBUTE_INVALID 0x0 + #define LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +diff --git a/include/linux/platform_data/omap-twl4030.h b/include/linux/platform_data/omap-twl4030.h +deleted file mode 100644 +--- a/include/linux/platform_data/omap-twl4030.h ++++ /dev/null +@@ -1,42 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/** +- * omap-twl4030.h - ASoC machine driver for TI SoC based boards with twl4030 +- * codec, header. +- * +- * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com +- * All rights reserved. +- * +- * Author: Peter Ujfalusi +- */ +- +-#ifndef _OMAP_TWL4030_H_ +-#define _OMAP_TWL4030_H_ +- +-/* To select if only one channel is connected in a stereo port */ +-#define OMAP_TWL4030_LEFT (1 << 0) +-#define OMAP_TWL4030_RIGHT (1 << 1) +- +-struct omap_tw4030_pdata { +- const char *card_name; +- /* Voice port is connected to McBSP3 */ +- bool voice_connected; +- +- /* The driver will parse the connection flags if this flag is set */ +- bool custom_routing; +- /* Flags to indicate connected audio ports. */ +- u8 has_hs; +- u8 has_hf; +- u8 has_predriv; +- u8 has_carkit; +- bool has_ear; +- +- bool has_mainmic; +- bool has_submic; +- bool has_hsmic; +- bool has_carkitmic; +- bool has_digimic0; +- bool has_digimic1; +- u8 has_linein; +-}; +- +-#endif /* _OMAP_TWL4030_H_ */ +diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h +--- a/include/sound/cs35l56.h ++++ b/include/sound/cs35l56.h +@@ -417,7 +417,6 @@ void cs35l56_wait_control_port_ready(void); + void cs35l56_wait_min_reset_pulse(void); + void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_suspend_common(struct cs35l56_base *cs35l56_base); + int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_soundwire); +diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h +--- a/include/sound/dmaengine_pcm.h ++++ b/include/sound/dmaengine_pcm.h +@@ -175,12 +175,6 @@ int snd_dmaengine_pcm_prepare_slave_config(struct snd_pcm_substream *substream, + struct dmaengine_pcm { + struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1]; + const struct snd_dmaengine_pcm_config *config; +- struct snd_soc_component component; + unsigned int flags; + }; +- +-static inline struct dmaengine_pcm *soc_component_to_pcm(struct snd_soc_component *p) +-{ +- return container_of(p, struct dmaengine_pcm, component); +-} + #endif +diff --git a/include/sound/hda-mlink.h b/include/sound/hda-mlink.h +--- a/include/sound/hda-mlink.h ++++ b/include/sound/hda-mlink.h +@@ -9,6 +9,22 @@ + struct hdac_bus; + struct hdac_ext_link; + ++/** ++ * enum hda_bus_ml_link_type - mlink link type, used by SOF link DMA ++ * allocator constraints (see struct sof_intel_hda_dev). ++ * ++ * @HDA_BUS_ML_LINK_HDA: non-alt link, i.e. HDA codec or iDisp ++ * @HDA_BUS_ML_LINK_SDW: alt link, SoundWire ++ * @HDA_BUS_ML_LINK_UAOL: alt link, USB Audio Offload ++ * @HDA_BUS_ML_LINK_OTHER: alt link, SSP or DMIC ++ */ ++enum hda_bus_ml_link_type { ++ HDA_BUS_ML_LINK_HDA, ++ HDA_BUS_ML_LINK_SDW, ++ HDA_BUS_ML_LINK_UAOL, ++ HDA_BUS_ML_LINK_OTHER, ++}; ++ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK) + + int hda_bus_ml_init(struct hdac_bus *bus); +@@ -49,11 +65,12 @@ int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num); + int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + int channel_mask, int stream_id, int dir); + +-void hda_bus_ml_put_all(struct hdac_bus *bus); + void hda_bus_ml_reset_losidv(struct hdac_bus *bus); + int hda_bus_ml_resume(struct hdac_bus *bus); + int hda_bus_ml_suspend(struct hdac_bus *bus); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink); ++ + struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus); + struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus); +@@ -169,11 +186,13 @@ hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } + +-static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { } + static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { } + static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; } + static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; } + ++static inline enum hda_bus_ml_link_type ++hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) { return HDA_BUS_ML_LINK_HDA; } ++ + static inline struct hdac_ext_link * + hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; } + +diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h +--- a/include/sound/sdca_asoc.h ++++ b/include/sound/sdca_asoc.h +@@ -25,6 +25,8 @@ struct snd_soc_dai_driver; + struct snd_soc_dai_ops; + struct snd_soc_dapm_route; + struct snd_soc_dapm_widget; ++struct snd_soc_pcm_stream; ++struct sdca_entity; + + /* convenient macro to handle the mono volume in 7.8 fixed format representation */ + #define SDCA_SINGLE_Q78_TLV(xname, xreg, xmin, xmax, xstep, tlv_array) \ +@@ -82,6 +84,11 @@ int sdca_asoc_populate_component(struct device *dev, + struct snd_soc_dai_driver **dai_drv, int *num_dai_drv, + const struct snd_soc_dai_ops *ops); + ++int sdca_asoc_populate_rate_format(struct device *dev, ++ struct sdca_function_data *function, ++ struct sdca_entity *entity, ++ struct snd_soc_pcm_stream *stream); ++ + int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, + struct sdca_function_data *function, + struct snd_pcm_substream *substream, +@@ -100,9 +107,9 @@ int sdca_asoc_q78_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + int sdca_asoc_q78_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays); ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays); + #endif // __SDCA_ASOC_H__ +diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h +--- a/include/sound/sdca_function.h ++++ b/include/sound/sdca_function.h +@@ -298,7 +298,7 @@ enum sdca_xu_controls { + }; + + /** +- * enum sdca_set_index_range - Column definitions UMP SetIndex ++ * enum sdca_fdl_set_index_range - Column definitions UMP SetIndex + */ + enum sdca_fdl_set_index_range { + SDCA_FDL_SET_INDEX_SET_NUMBER = 0, +@@ -803,6 +803,8 @@ struct sdca_control_range { + * @mode: Access mode of the Control. + * @layers: Bitmask of access layers of the Control. + * @deferrable: Indicates if the access to the Control can be deferred. ++ * @is_volatile: Indicates the Control registers are forced to be treated ++ * as volatile. + * @has_default: Indicates the Control has a default value to be written. + * @has_reset: Indicates the Control has a defined reset value. + * @has_fixed: Indicates the Control only supports a single value. +@@ -1116,7 +1118,6 @@ struct sdca_entity_ge { + + /** + * struct sdca_entity_hide - information specific to HIDE Entities +- * @hid: HID device structure + * @num_hidtx_ids: number of HIDTx Report ID + * @num_hidrx_ids: number of HIDRx Report ID + * @hidtx_ids: HIDTx Report ID +@@ -1127,11 +1128,8 @@ struct sdca_entity_ge { + * within this Device + * @max_delay: the maximum time in microseconds allowed for the Device + * to change the ownership from Device to Host +- * @hid_report_desc: HID Report Descriptor for the HIDE Entity +- * @hid_desc: HID descriptor for the HIDE Entity + */ + struct sdca_entity_hide { +- struct hid_device *hid; + unsigned int *hidtx_ids; + unsigned int *hidrx_ids; + int num_hidtx_ids; +@@ -1139,12 +1137,10 @@ struct sdca_entity_hide { + unsigned int af_number_list[SDCA_MAX_FUNCTION_COUNT]; + unsigned int hide_reside_function_num; + unsigned int max_delay; +- unsigned char *hid_report_desc; +- struct hid_descriptor hid_desc; + }; + + /** +- * enum sdca_xu_reset_machanism - SDCA FDL Resets ++ * enum sdca_xu_reset_mechanism - SDCA FDL Resets + */ + enum sdca_xu_reset_mechanism { + SDCA_XU_RESET_FUNCTION = 0x0, +@@ -1401,6 +1397,16 @@ struct sdca_fdl_data { + int num_sets; + }; + ++/** ++ * struct sdca_function_hid - information about a function's HID descriptors ++ * @report_desc: HID Report Descriptor for the HID Function ++ * @desc: HID descriptor for the HID Function ++ */ ++struct sdca_function_hid { ++ unsigned char *report_desc; ++ struct hid_descriptor desc; ++}; ++ + /** + * struct sdca_function_data - top-level information for one SDCA function + * @desc: Pointer to short descriptor from initial parsing. +@@ -1415,6 +1421,7 @@ struct sdca_fdl_data { + * @reset_max_delay: Maximum Function reset delay in microseconds, before an + * error should be reported. + * @fdl_data: FDL data for this Function, if available. ++ * @hid: HID data for this Function, if available. + */ + struct sdca_function_data { + struct sdca_function_desc *desc; +@@ -1430,6 +1437,10 @@ struct sdca_function_data { + unsigned int reset_max_delay; + + struct sdca_fdl_data fdl_data; ++ ++ union { ++ struct sdca_function_hid hid; ++ }; + }; + + static inline u32 sdca_range(struct sdca_control_range *range, +@@ -1451,8 +1462,7 @@ static inline u32 sdca_range_search(struct sdca_control_range *range, + return 0; + } + +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function); ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function); + + const char *sdca_find_terminal_name(enum sdca_terminal_type type); + +@@ -1469,5 +1479,7 @@ struct sdca_control_range *sdca_selector_find_range(struct device *dev, + struct sdca_cluster *sdca_id_find_cluster(struct device *dev, + struct sdca_function_data *function, + const int id); ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, ++ const char *entity_label); + + #endif +diff --git a/include/sound/sdca_hid.h b/include/sound/sdca_hid.h +--- a/include/sound/sdca_hid.h ++++ b/include/sound/sdca_hid.h +@@ -16,18 +16,21 @@ struct sdca_interrupt; + + #if IS_ENABLED(CONFIG_SND_SOC_SDCA_HID) + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity); ++int sdca_add_hid_device(struct sdca_interrupt *interrupt); ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt); + int sdca_hid_process_report(struct sdca_interrupt *interrupt); + + #else + +-static inline int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++static inline int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { + return 0; + } + ++static inline void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++} ++ + static inline int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + return 0; +diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h +--- a/include/sound/simple_card_utils.h ++++ b/include/sound/simple_card_utils.h +@@ -181,7 +181,7 @@ void simple_util_canonicalize_platform(struct snd_soc_dai_link_component *platfo + void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + int is_single_links); + +-void simple_util_clean_reference(struct snd_soc_card *card); ++void simple_util_clean_reference(struct simple_util_priv *priv); + + void simple_util_parse_convert(struct device_node *np, char *prefix, + struct simple_util_data *data); +@@ -189,18 +189,37 @@ bool simple_util_is_convert_required(const struct simple_util_data *data); + + int simple_util_get_sample_fmt(struct simple_util_data *data); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix); +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix); ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property); ++static inline int simple_util_parse_routing(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_routing, ++ prefix, "routing"); ++} ++ ++static inline int simple_util_parse_widgets(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_audio_simple_widgets, ++ prefix, "widgets"); ++} ++ ++static inline int simple_util_parse_pin_switches(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_pin_switches, ++ prefix, "pin-switches"); ++} ++ ++static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char *prefix) ++{ ++ return simple_util_parse_property(priv, snd_soc_of_parse_aux_devs, ++ prefix, "aux-devs"); ++} + + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, char *pin); +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, +- char *prefix); ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); + int simple_util_init_priv(struct simple_util_priv *priv, + struct link_info *li); + void simple_util_remove(struct platform_device *pdev); +diff --git a/include/sound/soc-acpi.h b/include/sound/soc-acpi.h +--- a/include/sound/soc-acpi.h ++++ b/include/sound/soc-acpi.h +@@ -57,7 +57,7 @@ static inline struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg) + #endif + + /** +- * snd_soc_acpi_mach_params: interface for machine driver configuration ++ * struct snd_soc_acpi_mach_params - interface for machine driver configuration + * + * @acpi_ipc_irq_index: used for BYT-CR detection + * @platform: string used for HDAudio codec support +@@ -93,7 +93,7 @@ struct snd_soc_acpi_mach_params { + }; + + /** +- * snd_soc_acpi_endpoint - endpoint descriptor ++ * struct snd_soc_acpi_endpoint - endpoint descriptor + * @num: endpoint number (mandatory, unique per device) + * @aggregated: 0 (independent) or 1 (logically grouped) + * @group_position: zero-based order (only when @aggregated is 1) +@@ -107,7 +107,7 @@ struct snd_soc_acpi_endpoint { + }; + + /** +- * snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device ++ * struct snd_soc_acpi_adr_device - descriptor for _ADR-enumerated device + * @adr: 64 bit ACPI _ADR value + * @num_endpoints: number of endpoints for this device + * @endpoints: array of endpoints +@@ -121,7 +121,7 @@ struct snd_soc_acpi_adr_device { + }; + + /** +- * snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices ++ * struct snd_soc_acpi_link_adr - ACPI-based list of _ADR enumerated devices + * @mask: one bit set indicates the link this list applies to + * @num_adr: ARRAY_SIZE of devices + * @adr_d: array of devices +@@ -167,8 +167,8 @@ struct snd_soc_acpi_link_adr { + #define SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME BIT(4) + + /** +- * snd_soc_acpi_mach: ACPI-based machine descriptor. Most of the fields are +- * related to the hardware, except for the firmware and topology file names. ++ * struct snd_soc_acpi_mach - ACPI-based machine descriptor. Most of the fields ++ * are related to the hardware, except for the firmware and topology file names. + * A platform supported by legacy and Sound Open Firmware (SOF) would expose + * all firmware/topology related fields. + * +@@ -192,6 +192,7 @@ struct snd_soc_acpi_link_adr { + * the initial selection in the snd_soc_acpi_mach table. + * @pdata: intended for platform data or machine specific-ops. This structure + * is not constant since this field may be updated at run-time ++ * @mach_params: machine driver configuration + * @sof_tplg_filename: Sound Open Firmware topology file name, if enabled + * @tplg_quirk_mask: quirks to select different topology files dynamically + * @get_function_tplg_files: This is an optional callback, if specified then instead of +@@ -199,11 +200,11 @@ struct snd_soc_acpi_link_adr { + * files to be loaded. + * Return value: The number of the files or negative ERRNO. 0 means that the single topology + * file should be used, no function topology split can be used on the machine. +- * @card: the pointer of the card +- * @mach: the pointer of the machine driver +- * @prefix: the prefix of the topology file name. Typically, it is the path. +- * @tplg_files: the pointer of the array of the topology file names. +- * @best_effort: ignore non supported links and try to build the card in best effort ++ * card: the pointer of the card ++ * mach: the pointer of the machine driver ++ * prefix: the prefix of the topology file name. Typically, it is the path. ++ * tplg_files: the pointer of the array of the topology file names. ++ * best_effort: ignore non supported links and try to build the card in best effort + * with supported links + */ + /* Descriptor for SST ASoC machine driver */ +diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h +--- a/include/sound/soc-component.h ++++ b/include/sound/soc-component.h +@@ -253,6 +253,9 @@ struct snd_soc_component { + void *mark_pm; + + struct dentry *debugfs_root; ++ ++ /* Component private data */ ++ void *priv; + }; + + #define for_each_component_dais(component, dai)\ +@@ -283,6 +286,14 @@ static inline int snd_soc_component_cache_sync( + return regcache_sync(component->regmap); + } + ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); ++const char *snd_soc_component_name(struct snd_soc_component *component); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); ++void *snd_soc_component_to_priv(struct snd_soc_component *component); ++ + void snd_soc_component_set_aux(struct snd_soc_component *component, + struct snd_soc_aux_dev *aux); + int snd_soc_component_init(struct snd_soc_component *component); +diff --git a/include/sound/soc.h b/include/sound/soc.h +--- a/include/sound/soc.h ++++ b/include/sound/soc.h +@@ -447,15 +447,16 @@ static inline int snd_soc_resume(struct device *dev) + } + #endif + int snd_soc_poweroff(struct device *dev); +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev); +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai); +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, int num_dai); ++#define snd_soc_register_component(x, ...) _Generic((x), \ ++struct device * : snd_soc_register_component_d, \ ++struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) ++ + int devm_snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, int num_dai); +diff --git a/include/sound/sof/dai-amd.h b/include/sound/sof/dai-amd.h +--- a/include/sound/sof/dai-amd.h ++++ b/include/sound/sof/dai-amd.h +@@ -18,6 +18,7 @@ struct sof_ipc_dai_acp_params { + uint32_t fsync_rate; /* FSYNC frequency in Hz */ + uint32_t tdm_slots; + uint32_t tdm_mode; ++ uint32_t format; + } __packed; + + /* ACPDMIC Configuration Request - SOF_IPC_DAI_AMD_CONFIG */ +diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h +--- a/include/sound/sof/dai.h ++++ b/include/sound/sof/dai.h +@@ -90,6 +90,8 @@ enum sof_ipc_dai_type { + SOF_DAI_AMD_HS_VIRTUAL, /**< AMD ACP HS VIRTUAL */ + SOF_DAI_IMX_MICFIL, /** < i.MX MICFIL PDM */ + SOF_DAI_AMD_SDW, /**< AMD ACP SDW */ ++ SOF_DAI_INTEL_UAOL, /**< Intel UAOL */ ++ SOF_DAI_AMD_I2S, /**< AMD ACP I2S */ + }; + + /* general purpose DAI configuration */ +@@ -121,6 +123,7 @@ struct sof_ipc_dai_config { + struct sof_ipc_dai_mtk_afe_params afe; + struct sof_ipc_dai_micfil_params micfil; + struct sof_ipc_dai_acp_sdw_params acp_sdw; ++ struct sof_ipc_dai_acp_params acp_i2s; + }; + } __packed; + +diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h +--- a/include/sound/sof/header.h ++++ b/include/sound/sof/header.h +@@ -179,7 +179,7 @@ struct sof_ipc_compound_hdr { + uint32_t count; /**< count of 0 means end of compound sequence */ + } __packed; + +-/** ++/* + * OOPS header architecture specific data. + */ + struct sof_ipc_dsp_oops_arch_hdr { +@@ -187,7 +187,7 @@ struct sof_ipc_dsp_oops_arch_hdr { + uint32_t totalsize; /* Total size of oops message */ + } __packed; + +-/** ++/* + * OOPS header platform specific data. + */ + struct sof_ipc_dsp_oops_plat_hdr { +diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h +--- a/include/sound/sof/ipc4/header.h ++++ b/include/sound/sof/ipc4/header.h +@@ -187,6 +187,10 @@ enum sof_ipc4_pipeline_state { + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID_MASK GENMASK(23, 20) + #define SOF_IPC4_GLB_PIPE_EXT_CORE_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_CORE_ID_SHIFT) + ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT 29 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_MASK BIT(29) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_SHIFT) ++ + /* pipeline set state ipc msg */ + #define SOF_IPC4_GLB_PIPE_STATE_ID_SHIFT 16 + #define SOF_IPC4_GLB_PIPE_STATE_ID_MASK GENMASK(23, 16) +@@ -536,12 +540,63 @@ enum sof_ipc4_notification_type { + SOF_IPC4_NOTIFY_TYPE_LAST, + }; + ++enum sof_ipc4_resource_type { ++ SOF_IPC4_MODULE_INSTANCE, ++ SOF_IPC4_PIPELINE, ++ SOF_IPC4_GATEWAY, ++ SOF_IPC4_EDF_TASK, ++ SOF_IPC4_INVALID_RESOURCE_TYPE, ++}; ++ ++enum sof_ipc4_event_type { ++ /* Underrun detected by the Mixer */ ++ SOF_IPC4_MIXER_UNDERRUN_DETECTED = 1, ++ /* Error caught during data processing */ ++ SOF_IPC4_PROCESS_DATA_ERROR = 3, ++ /* Underrun detected by gateway. */ ++ SOF_IPC4_GATEWAY_UNDERRUN_DETECTED = 6, ++ /* Overrun detected by gateway */ ++ SOF_IPC4_GATEWAY_OVERRUN_DETECTED, ++}; ++ ++/** ++ * struct sof_ipc4_process_data_error_event_data - process data error event payload ++ * @error_code: Error code returned by data processing function ++ */ ++struct sof_ipc4_process_data_error_event_data { ++ uint32_t error_code; ++}; ++ ++/** ++ * struct sof_ipc4_mixer_underrun_event_data - mixer underrun event payload ++ * @eos_flag: Indicates EndOfStream ++ * @data_mixed: Data processed by module (in bytes) ++ * @expected_data_mixed: Expected data to be processed (in bytes) ++ */ ++struct sof_ipc4_mixer_underrun_event_data { ++ uint32_t eos_flag; ++ uint32_t data_mixed; ++ uint32_t expected_data_mixed; ++}; ++ ++/** ++ * union sof_ipc4_resource_event_data - resource event specific payload ++ * @dws: Raw event data payload as six dwords ++ * @process_data_error: SOF_IPC4_PROCESS_DATA_ERROR payload ++ * @mixer_underrun: SOF_IPC4_MIXER_UNDERRUN_DETECTED payload ++ */ ++union sof_ipc4_resource_event_data { ++ uint32_t dws[6]; ++ struct sof_ipc4_process_data_error_event_data process_data_error; ++ struct sof_ipc4_mixer_underrun_event_data mixer_underrun; ++}; ++ + struct sof_ipc4_notify_resource_data { + uint32_t resource_type; + uint32_t resource_id; + uint32_t event_type; + uint32_t reserved; +- uint32_t data[6]; ++ union sof_ipc4_resource_event_data data; + } __packed __aligned(4); + + #define SOF_IPC4_DEBUG_DESCRIPTOR_SIZE 12 /* 3 x u32 */ +@@ -654,11 +709,81 @@ enum sof_ipc4_mod_init_ext_obj_id { + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + }; + +-/* DP module memory configuration data object for ext_init object array */ ++/* DP module memory configuration data object for object array */ + struct sof_ipc4_mod_init_ext_dp_memory_data { ++ u32 domain_id; /* userspace domain ID */ ++ u32 stack_bytes; /* required stack size in bytes */ ++ u32 heap_bytes; /* required heap size in bytes */ ++} __packed __aligned(4); ++ ++/* ++ * This set of macros are very similar to the set above, but these are ++ * for building payload to SOF_IPC4_GLB_CREATE_PIPELINE message. ++ * ++ * Macros for creating struct sof_ipc4_glb_pipe_payload payload with ++ * its associated data. struct sof_ipc4_glb_pipe_payload should be the ++ * first piece of payload following SOF_IPC4_GLB_CREATE_PIPELINE msg, ++ * and its existence is indicated with SOF_IPC4_GLB_PIPE_PAYLOAD bit. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_payload.word0 ++ */ ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_MASK GENMASK(23, 0) ++#define SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT 24 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK BIT(24) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_SHIFT) ++ ++struct sof_ipc4_glb_pipe_payload { ++ u32 word0; ++ u32 rsvd1; ++ u32 rsvd2; ++} __packed __aligned(4); ++ ++/* ++ * SOF_IPC4_GLB_CREATE_PIPELINE payload may be followed by arbitrary ++ * number of object array objects. SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY-bit ++ * indicates that an array object follows struct ++ * sof_ipc4_glb_pipe_payload. ++ * ++ * The object header's SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST-bit in struct ++ * sof_ipc4_glb_pipe_ext_object indicates if the array is continued ++ * with another object. The header has also fields to identify the ++ * object, SOF_IPC4_GLB_PIPE_EXT_OBJ_ID, and to indicate the object's ++ * size in 32-bit words, SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS, not ++ * including the header itself. ++ * ++ * The macros below apply to sof_ipc4_glb_pipe_ext_object.header ++ */ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT 0 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK BIT(0) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT 1 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_MASK GENMASK(15, 1) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_ID_SHIFT) ++ ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT 16 ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_MASK GENMASK(31, 16) ++#define SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(x) ((x) << SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS_SHIFT) ++ ++struct sof_ipc4_glb_pipe_ext_object { ++ u32 header; ++ u32 data[]; ++} __packed __aligned(4); ++ ++enum sof_ipc4_glb_pipe_ext_obj_id { ++ SOF_IPC4_GLB_PIPE_DATA_ID_INVALID = 0, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++ SOF_IPC4_GLB_PIPE_DATA_ID_MAX = SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA, ++}; ++ ++/* Pipeline memory configuration data object for ext_init object array */ ++struct sof_ipc4_glb_pipe_ext_obj_memory_data { + u32 domain_id; /* userspace domain ID */ +- u32 stack_bytes; /* stack size in bytes, 0 means default size */ +- u32 heap_bytes; /* stack size in bytes, 0 means default size */ ++ u32 stack_bytes; /* stack size in bytes */ ++ u32 heap_bytes; /* heap size in bytes */ + } __packed __aligned(4); + + /** @}*/ +diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h +--- a/include/sound/tas2781-dsp.h ++++ b/include/sound/tas2781-dsp.h +@@ -201,6 +201,11 @@ struct tasdevice_rca { + int ncfgs; + struct tasdevice_config_info **cfg_info; + int profile_cfg_id; ++ /* ++ * Used among SmartAMP for PDM microphone recording or IV data ++ * capture. ++ */ ++ int capture_profile_id; + /* + * Since version 0x105, the keyword 'init' was introduced into the + * profile, which is used for chip initialization, particularly to +@@ -222,7 +227,7 @@ void tasdevice_calbin_remove(void *context); + int tasdevice_select_tuningprm_cfg(void *context, int prm, + int cfg_no, int rca_conf_no); + int tasdevice_prmg_load(void *context, int prm_no); +-void tasdevice_tuning_switch(void *context, int state); ++void tasdevice_tuning_switch(void *context, int state, bool is_cap); + int tas2781_load_calibration(void *context, char *file_name, + unsigned short i); + +diff --git a/include/sound/tlv320aic32x4.h b/include/sound/tlv320aic32x4.h +deleted file mode 100644 +--- a/include/sound/tlv320aic32x4.h ++++ /dev/null +@@ -1,43 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-only */ +-/* +- * tlv320aic32x4.h -- TLV320AIC32X4 Soc Audio driver platform data +- * +- * Copyright 2011 Vista Silicon S.L. +- * +- * Author: Javier Martin +- */ +- +-#ifndef _AIC32X4_PDATA_H +-#define _AIC32X4_PDATA_H +- +-#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 +-#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 +-#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 +-#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 +-#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 +- +-#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 +-#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 +- +-/* GPIO API */ +-#define AIC32X4_MFPX_DEFAULT_VALUE 0xff +- +-#define AIC32X4_MFP1_DIN_DISABLED 0 +-#define AIC32X4_MFP1_DIN_ENABLED 0x2 +-#define AIC32X4_MFP1_GPIO_IN 0x4 +- +-#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 +- +-#define AIC32X4_MFP_GPIO_ENABLED 0x4 +- +-#define AIC32X4_MFP5_GPIO_DISABLED 0x0 +-#define AIC32X4_MFP5_GPIO_INPUT 0x8 +-#define AIC32X4_MFP5_GPIO_OUTPUT 0xc +-#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 +-#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 +- +-struct aic32x4_setup_data { +- unsigned int gpio_func[5]; +-}; +-#endif +diff --git a/include/sound/wm8904.h b/include/sound/wm8904.h +--- a/include/sound/wm8904.h ++++ b/include/sound/wm8904.h +@@ -116,7 +116,7 @@ + #define WM8904_DRC_REGS 4 + #define WM8904_EQ_REGS 24 + +-/** ++/* + * DRC configurations are specified with a label and a set of register + * values to write (the enable bits will be ignored). At runtime an + * enumerated control will be presented for each DRC block allowing +@@ -131,7 +131,7 @@ struct wm8904_drc_cfg { + u16 regs[WM8904_DRC_REGS]; + }; + +-/** ++/* + * ReTune Mobile configurations are specified with a label, sample + * rate and set of values to write (the enable bits will be ignored). + * +diff --git a/include/uapi/sound/skl-tplg-interface.h b/include/uapi/sound/skl-tplg-interface.h +deleted file mode 100644 +--- a/include/uapi/sound/skl-tplg-interface.h ++++ /dev/null +@@ -1,168 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * skl-tplg-interface.h - Intel DSP FW private data interface +- * +- * Copyright (C) 2015 Intel Corp +- * Author: Jeeja KP +- * Nilofer, Samreen +- */ +- +-#ifndef __HDA_TPLG_INTERFACE_H__ +-#define __HDA_TPLG_INTERFACE_H__ +- +-#include +- +-/* +- * Default types range from 0~12. type can range from 0 to 0xff +- * SST types start at higher to avoid any overlapping in future +- */ +-#define SKL_CONTROL_TYPE_BYTE_TLV 0x100 +-#define SKL_CONTROL_TYPE_MIC_SELECT 0x102 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT 0x103 +-#define SKL_CONTROL_TYPE_MULTI_IO_SELECT_DMIC 0x104 +- +-#define HDA_SST_CFG_MAX 900 /* size of copier cfg*/ +-#define MAX_IN_QUEUE 8 +-#define MAX_OUT_QUEUE 8 +- +-#define SKL_UUID_STR_SZ 40 +-/* Event types goes here */ +-/* Reserve event type 0 for no event handlers */ +-enum skl_event_types { +- SKL_EVENT_NONE = 0, +- SKL_MIXER_EVENT, +- SKL_MUX_EVENT, +- SKL_VMIXER_EVENT, +- SKL_PGA_EVENT +-}; +- +-/** +- * enum skl_ch_cfg - channel configuration +- * +- * @SKL_CH_CFG_MONO: One channel only +- * @SKL_CH_CFG_STEREO: L & R +- * @SKL_CH_CFG_2_1: L, R & LFE +- * @SKL_CH_CFG_3_0: L, C & R +- * @SKL_CH_CFG_3_1: L, C, R & LFE +- * @SKL_CH_CFG_QUATRO: L, R, Ls & Rs +- * @SKL_CH_CFG_4_0: L, C, R & Cs +- * @SKL_CH_CFG_5_0: L, C, R, Ls & Rs +- * @SKL_CH_CFG_5_1: L, C, R, Ls, Rs & LFE +- * @SKL_CH_CFG_DUAL_MONO: One channel replicated in two +- * @SKL_CH_CFG_I2S_DUAL_STEREO_0: Stereo(L,R) in 4 slots, 1st stream:[ L, R, -, - ] +- * @SKL_CH_CFG_I2S_DUAL_STEREO_1: Stereo(L,R) in 4 slots, 2nd stream:[ -, -, L, R ] +- * @SKL_CH_CFG_INVALID: Invalid +- */ +-enum skl_ch_cfg { +- SKL_CH_CFG_MONO = 0, +- SKL_CH_CFG_STEREO = 1, +- SKL_CH_CFG_2_1 = 2, +- SKL_CH_CFG_3_0 = 3, +- SKL_CH_CFG_3_1 = 4, +- SKL_CH_CFG_QUATRO = 5, +- SKL_CH_CFG_4_0 = 6, +- SKL_CH_CFG_5_0 = 7, +- SKL_CH_CFG_5_1 = 8, +- SKL_CH_CFG_DUAL_MONO = 9, +- SKL_CH_CFG_I2S_DUAL_STEREO_0 = 10, +- SKL_CH_CFG_I2S_DUAL_STEREO_1 = 11, +- SKL_CH_CFG_7_1 = 12, +- SKL_CH_CFG_4_CHANNEL = SKL_CH_CFG_7_1, +- SKL_CH_CFG_INVALID +-}; +- +-enum skl_module_type { +- SKL_MODULE_TYPE_MIXER = 0, +- SKL_MODULE_TYPE_COPIER, +- SKL_MODULE_TYPE_UPDWMIX, +- SKL_MODULE_TYPE_SRCINT, +- SKL_MODULE_TYPE_ALGO, +- SKL_MODULE_TYPE_BASE_OUTFMT, +- SKL_MODULE_TYPE_KPB, +- SKL_MODULE_TYPE_MIC_SELECT, +-}; +- +-enum skl_core_affinity { +- SKL_AFFINITY_CORE_0 = 0, +- SKL_AFFINITY_CORE_1, +- SKL_AFFINITY_CORE_MAX +-}; +- +-enum skl_pipe_conn_type { +- SKL_PIPE_CONN_TYPE_NONE = 0, +- SKL_PIPE_CONN_TYPE_FE, +- SKL_PIPE_CONN_TYPE_BE +-}; +- +-enum skl_hw_conn_type { +- SKL_CONN_NONE = 0, +- SKL_CONN_SOURCE = 1, +- SKL_CONN_SINK = 2 +-}; +- +-enum skl_dev_type { +- SKL_DEVICE_BT = 0x0, +- SKL_DEVICE_DMIC = 0x1, +- SKL_DEVICE_I2S = 0x2, +- SKL_DEVICE_SLIMBUS = 0x3, +- SKL_DEVICE_HDALINK = 0x4, +- SKL_DEVICE_HDAHOST = 0x5, +- SKL_DEVICE_NONE +-}; +- +-/** +- * enum skl_interleaving - interleaving style +- * +- * @SKL_INTERLEAVING_PER_CHANNEL: [s1_ch1...s1_chN,...,sM_ch1...sM_chN] +- * @SKL_INTERLEAVING_PER_SAMPLE: [s1_ch1...sM_ch1,...,s1_chN...sM_chN] +- */ +-enum skl_interleaving { +- SKL_INTERLEAVING_PER_CHANNEL = 0, +- SKL_INTERLEAVING_PER_SAMPLE = 1, +-}; +- +-enum skl_sample_type { +- SKL_SAMPLE_TYPE_INT_MSB = 0, +- SKL_SAMPLE_TYPE_INT_LSB = 1, +- SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- SKL_SAMPLE_TYPE_FLOAT = 4 +-}; +- +-enum module_pin_type { +- /* All pins of the module takes same PCM inputs or outputs +- * e.g. mixout +- */ +- SKL_PIN_TYPE_HOMOGENEOUS, +- /* All pins of the module takes different PCM inputs or outputs +- * e.g mux +- */ +- SKL_PIN_TYPE_HETEROGENEOUS, +-}; +- +-enum skl_module_param_type { +- SKL_PARAM_DEFAULT = 0, +- SKL_PARAM_INIT, +- SKL_PARAM_SET, +- SKL_PARAM_BIND +-}; +- +-struct skl_dfw_algo_data { +- __u32 set_params:2; +- __u32 rsvd:30; +- __u32 param_id; +- __u32 max; +- char params[]; +-} __packed; +- +-enum skl_tkn_dir { +- SKL_DIR_IN, +- SKL_DIR_OUT +-}; +- +-enum skl_tuple_type { +- SKL_TYPE_TUPLE, +- SKL_TYPE_DATA +-}; +- +-#endif +diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h +--- a/include/uapi/sound/snd_ar_tokens.h ++++ b/include/uapi/sound/snd_ar_tokens.h +@@ -58,7 +58,7 @@ enum ar_event_types { + #define SND_SOC_AR_TPLG_FE_BE_GRAPH_CTL_MIX 256 + #define SND_SOC_AR_TPLG_VOL_CTL 257 + +-/** ++/* + * %AR_TKN_U32_SUB_GRAPH_INSTANCE_ID: Sub Graph Instance Id + * + * %AR_TKN_U32_SUB_GRAPH_PERF_MODE: Performance mode of subgraph +@@ -168,6 +168,60 @@ enum ar_event_types { + * LOG_WAIT = 0, + * LOG_IMMEDIATELY = 1 + * ++ * %AR_TKN_U16_MODULE_SYNC_SRC: Frame sync source ++ * AR_AUDIO_IF_SYNC_SRC_EXTERNAL = 0, ++ * AR_AUDIO_IF_SYNC_SRC_INTERNAL = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: Enable data-out tri-state control ++ * AR_AUDIO_IF_CTRL_DATA_OE_DISABLE = 0, ++ * AR_AUDIO_IF_CTRL_DATA_OE_ENABLE = 1 ++ * ++ * %AR_TKN_U32_MODULE_SLOT_MASK: Active TDM slot bitmask ++ * ++ * %AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: Number of slots per TDM frame ++ * ++ * %AR_TKN_U16_MODULE_SLOT_WIDTH: Slot width in bits (16 or 32) ++ * ++ * %AR_TKN_U16_MODULE_SYNC_MODE: Frame sync mode ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT = 0, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT = 1, ++ * AR_AUDIO_IF_FRAME_SYNC_MODE_LONG = 2 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: Invert frame sync pulse polarity ++ * AR_AUDIO_IF_SYNC_NORMAL = 0, ++ * AR_AUDIO_IF_SYNC_INVERTED = 1 ++ * ++ * %AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: Data delay relative to frame sync ++ * AR_AUDIO_IF_DATA_DELAY_NONE = 0, ++ * AR_AUDIO_IF_DATA_DELAY_1_CYCLE = 1, ++ * AR_AUDIO_IF_DATA_DELAY_2_CYCLE = 2 ++ * ++ * %AR_TKN_U16_MODULE_INTF_MODE: Audio IF interface mode ++ * AR_AUDIO_IF_INTF_MODE_TDM = 0, ++ * AR_AUDIO_IF_INTF_MODE_PCM = 1, ++ * AR_AUDIO_IF_INTF_MODE_I2S = 2 ++ * ++ * %AR_TKN_U16_MODULE_QAIF_TYPE: QAIF hardware port type index ++ * AR_AUDIO_IF_QAIF = 0, ++ * AR_AUDIO_IF_QAIF_VA = 1 ++ * ++ * %AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: Active lane bitmask for multi-lane ++ * ++ * %AR_TKN_U32_MODULE_FRAME_SYNC_RATE: Frame sync rate in Hz ++ * ++ * %AR_TKN_U16_MODULE_BIT_CLK_TYPE: Bit clock type ++ * AR_AUDIO_IF_BIT_CLK_INTERNAL = 0, ++ * AR_AUDIO_IF_BIT_CLK_EXTERNAL = 1, ++ * AR_AUDIO_IF_BIT_CLK_SKIP = 2 ++ * ++ * %AR_TKN_U8_MODULE_INV_INT_BIT_CLK: Invert internal bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * ++ * %AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: Invert external bit clock ++ * AR_AUDIO_IF_CLK_NORMAL = 0, ++ * AR_AUDIO_IF_CLK_INVERTED = 1 ++ * + * %AR_TKN_DAI_INDEX: dai index + * + */ +@@ -240,6 +294,45 @@ enum ar_event_types { + #define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 + #define AR_TKN_U32_MODULE_LOG_MODE 261 + ++#define AR_TKN_U16_MODULE_SYNC_SRC 262 ++#define AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE 263 ++#define AR_TKN_U32_MODULE_SLOT_MASK 264 ++#define AR_TKN_U16_MODULE_NSLOTS_PER_FRAME 265 ++#define AR_TKN_U16_MODULE_SLOT_WIDTH 266 ++#define AR_TKN_U16_MODULE_SYNC_MODE 267 ++#define AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE 268 ++#define AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY 269 ++#define AR_TKN_U16_MODULE_INTF_MODE 270 ++#define AR_TKN_U16_MODULE_QAIF_TYPE 271 ++#define AR_TKN_U32_MODULE_ACTIVE_LANE_MASK 272 ++#define AR_TKN_U32_MODULE_FRAME_SYNC_RATE 273 ++#define AR_TKN_U16_MODULE_BIT_CLK_TYPE 274 ++#define AR_TKN_U8_MODULE_INV_INT_BIT_CLK 275 ++#define AR_TKN_U8_MODULE_INV_EXT_BIT_CLK 276 ++ ++#define AR_AUDIO_IF_SYNC_SRC_EXTERNAL 0 ++#define AR_AUDIO_IF_SYNC_SRC_INTERNAL 1 ++#define AR_AUDIO_IF_CTRL_DATA_OE_DISABLE 0 ++#define AR_AUDIO_IF_CTRL_DATA_OE_ENABLE 1 ++#define AR_AUDIO_IF_INTF_MODE_TDM 0 ++#define AR_AUDIO_IF_INTF_MODE_PCM 1 ++#define AR_AUDIO_IF_INTF_MODE_I2S 2 ++#define AR_AUDIO_IF_QAIF 0 ++#define AR_AUDIO_IF_QAIF_VA 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT 0 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT 1 ++#define AR_AUDIO_IF_FRAME_SYNC_MODE_LONG 2 ++#define AR_AUDIO_IF_SYNC_NORMAL 0 ++#define AR_AUDIO_IF_SYNC_INVERTED 1 ++#define AR_AUDIO_IF_DATA_DELAY_NONE 0 ++#define AR_AUDIO_IF_DATA_DELAY_1_CYCLE 1 ++#define AR_AUDIO_IF_DATA_DELAY_2_CYCLE 2 ++#define AR_AUDIO_IF_BIT_CLK_INTERNAL 0 ++#define AR_AUDIO_IF_BIT_CLK_EXTERNAL 1 ++#define AR_AUDIO_IF_BIT_CLK_SKIP 2 ++#define AR_AUDIO_IF_CLK_NORMAL 0 ++#define AR_AUDIO_IF_CLK_INVERTED 1 ++ + #define SND_SOC_AR_TPLG_MODULE_CFG_TYPE 0x01001006 + struct audioreach_module_priv_data { + __le32 size; /* size in bytes of the array, including all elements */ +diff --git a/include/uapi/sound/snd_sst_tokens.h b/include/uapi/sound/snd_sst_tokens.h +deleted file mode 100644 +--- a/include/uapi/sound/snd_sst_tokens.h ++++ /dev/null +@@ -1,324 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +-/* +- * snd_sst_tokens.h - Intel SST tokens definition +- * +- * Copyright (C) 2016 Intel Corp +- * Author: Shreyas NC +- */ +-#ifndef __SND_SST_TOKENS_H__ +-#define __SND_SST_TOKENS_H__ +- +-/** +- * %SKL_TKN_UUID: Module UUID +- * +- * %SKL_TKN_U8_BLOCK_TYPE: Type of the private data block.Can be: +- * tuples, bytes, short and words +- * +- * %SKL_TKN_U8_IN_PIN_TYPE: Input pin type, +- * homogenous=0, heterogenous=1 +- * +- * %SKL_TKN_U8_OUT_PIN_TYPE: Output pin type, +- * homogenous=0, heterogenous=1 +- * %SKL_TKN_U8_DYN_IN_PIN: Configure Input pin dynamically +- * if true +- * +- * %SKL_TKN_U8_DYN_OUT_PIN: Configure Output pin dynamically +- * if true +- * +- * %SKL_TKN_U8_IN_QUEUE_COUNT: Store the number of Input pins +- * +- * %SKL_TKN_U8_OUT_QUEUE_COUNT: Store the number of Output pins +- * +- * %SKL_TKN_U8_TIME_SLOT: TDM slot number +- * +- * %SKL_TKN_U8_CORE_ID: Stores module affinity value.Can take +- * the values: +- * SKL_AFFINITY_CORE_0 = 0, +- * SKL_AFFINITY_CORE_1, +- * SKL_AFFINITY_CORE_MAX +- * +- * %SKL_TKN_U8_MOD_TYPE: Module type value. +- * +- * %SKL_TKN_U8_CONN_TYPE: Module connection type can be a FE, +- * BE or NONE as defined : +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA) +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA) +- * +- * %SKL_TKN_U8_DEV_TYPE: Type of device to which the module is +- * connected +- * Can take the values: +- * SKL_DEVICE_BT = 0x0, +- * SKL_DEVICE_DMIC = 0x1, +- * SKL_DEVICE_I2S = 0x2, +- * SKL_DEVICE_SLIMBUS = 0x3, +- * SKL_DEVICE_HDALINK = 0x4, +- * SKL_DEVICE_HDAHOST = 0x5, +- * SKL_DEVICE_NONE +- * +- * %SKL_TKN_U8_HW_CONN_TYPE: Connection type of the HW to which the +- * module is connected +- * SKL_CONN_NONE = 0, +- * SKL_CONN_SOURCE = 1, +- * SKL_CONN_SINK = 2 +- * +- * %SKL_TKN_U16_PIN_INST_ID: Stores the pin instance id +- * +- * %SKL_TKN_U16_MOD_INST_ID: Stores the mdule instance id +- * +- * %SKL_TKN_U32_MAX_MCPS: Module max mcps value +- * +- * %SKL_TKN_U32_MEM_PAGES: Module resource pages +- * +- * %SKL_TKN_U32_OBS: Stores Output Buffer size +- * +- * %SKL_TKN_U32_IBS: Stores input buffer size +- * +- * %SKL_TKN_U32_VBUS_ID: Module VBUS_ID. PDM=0, SSP0=0, +- * SSP1=1,SSP2=2, +- * SSP3=3, SSP4=4, +- * SSP5=5, SSP6=6,INVALID +- * +- * %SKL_TKN_U32_PARAMS_FIXUP: Module Params fixup mask +- * %SKL_TKN_U32_CONVERTER: Module params converter mask +- * %SKL_TKN_U32_PIPE_ID: Stores the pipe id +- * +- * %SKL_TKN_U32_PIPE_CONN_TYPE: Type of the token to which the pipe is +- * connected to. It can be +- * SKL_PIPE_CONN_TYPE_NONE = 0, +- * SKL_PIPE_CONN_TYPE_FE = 1 (HOST_DMA), +- * SKL_PIPE_CONN_TYPE_BE = 2 (LINK_DMA), +- * +- * %SKL_TKN_U32_PIPE_PRIORITY: Pipe priority value +- * %SKL_TKN_U32_PIPE_MEM_PGS: Pipe resource pages +- * +- * %SKL_TKN_U32_DIR_PIN_COUNT: Value for the direction to set input/output +- * formats and the pin count. +- * The first 4 bits have the direction +- * value and the next 4 have +- * the pin count value. +- * SKL_DIR_IN = 0, SKL_DIR_OUT = 1. +- * The input and output formats +- * share the same set of tokens +- * with the distinction between input +- * and output made by reading direction +- * token. +- * +- * %SKL_TKN_U32_FMT_CH: Supported channel count +- * +- * %SKL_TKN_U32_FMT_FREQ: Supported frequency/sample rate +- * +- * %SKL_TKN_U32_FMT_BIT_DEPTH: Supported container size +- * +- * %SKL_TKN_U32_FMT_SAMPLE_SIZE:Number of samples in the container +- * +- * %SKL_TKN_U32_FMT_CH_CONFIG: Supported channel configurations for the +- * input/output. +- * +- * %SKL_TKN_U32_FMT_INTERLEAVE: Interleaving style which can be per +- * channel or per sample. The values can be : +- * SKL_INTERLEAVING_PER_CHANNEL = 0, +- * SKL_INTERLEAVING_PER_SAMPLE = 1, +- * +- * %SKL_TKN_U32_FMT_SAMPLE_TYPE: +- * Specifies the sample type. Can take the +- * values: SKL_SAMPLE_TYPE_INT_MSB = 0, +- * SKL_SAMPLE_TYPE_INT_LSB = 1, +- * SKL_SAMPLE_TYPE_INT_SIGNED = 2, +- * SKL_SAMPLE_TYPE_INT_UNSIGNED = 3, +- * SKL_SAMPLE_TYPE_FLOAT = 4 +- * +- * %SKL_TKN_U32_CH_MAP: Channel map values +- * %SKL_TKN_U32_MOD_SET_PARAMS: It can take these values: +- * SKL_PARAM_DEFAULT, SKL_PARAM_INIT, +- * SKL_PARAM_SET, SKL_PARAM_BIND +- * +- * %SKL_TKN_U32_MOD_PARAM_ID: ID of the module params +- * +- * %SKL_TKN_U32_CAPS_SET_PARAMS: +- * Set params value +- * +- * %SKL_TKN_U32_CAPS_PARAMS_ID: Params ID +- * +- * %SKL_TKN_U32_CAPS_SIZE: Caps size +- * +- * %SKL_TKN_U32_PROC_DOMAIN: Specify processing domain +- * +- * %SKL_TKN_U32_LIB_COUNT: Specifies the number of libraries +- * +- * %SKL_TKN_STR_LIB_NAME: Specifies the library name +- * +- * %SKL_TKN_U32_PMODE: Specifies the power mode for pipe +- * +- * %SKL_TKL_U32_D0I3_CAPS: Specifies the D0i3 capability for module +- * +- * %SKL_TKN_U32_DMA_BUF_SIZE: DMA buffer size in millisec +- * +- * %SKL_TKN_U32_PIPE_DIR: Specifies pipe direction. Can be +- * playback/capture. +- * +- * %SKL_TKN_U32_NUM_CONFIGS: Number of pipe configs +- * +- * %SKL_TKN_U32_PATH_MEM_PGS: Size of memory (in pages) required for pipeline +- * and its data +- * +- * %SKL_TKN_U32_PIPE_CONFIG_ID: Config id for the modules in the pipe +- * and PCM params supported by that pipe +- * config. This is used as index to fill +- * up the pipe config and module config +- * structure. +- * +- * %SKL_TKN_U32_CFG_FREQ: +- * %SKL_TKN_U8_CFG_CHAN: +- * %SKL_TKN_U8_CFG_BPS: PCM params (freq, channels, bits per sample) +- * supported for each of the pipe configs. +- * +- * %SKL_TKN_CFG_MOD_RES_ID: Module's resource index for each of the +- * pipe config +- * +- * %SKL_TKN_CFG_MOD_FMT_ID: Module's interface index for each of the +- * pipe config +- * +- * %SKL_TKN_U8_NUM_MOD: Number of modules in the manifest +- * +- * %SKL_TKN_MM_U8_MOD_IDX: Current index of the module in the manifest +- * +- * %SKL_TKN_MM_U8_NUM_RES: Number of resources for the module +- * +- * %SKL_TKN_MM_U8_NUM_INTF: Number of interfaces for the module +- * +- * %SKL_TKN_MM_U32_RES_ID: Resource index for the resource info to +- * be filled into. +- * A module can support multiple resource +- * configuration and is represnted as a +- * resource table. This index is used to +- * fill information into appropriate index. +- * +- * %SKL_TKN_MM_U32_CPS: DSP cycles per second +- * +- * %SKL_TKN_MM_U32_DMA_SIZE: Allocated buffer size for gateway DMA +- * +- * %SKL_TKN_MM_U32_CPC: DSP cycles allocated per frame +- * +- * %SKL_TKN_MM_U32_RES_PIN_ID: Resource pin index in the module +- * +- * %SKL_TKN_MM_U32_INTF_PIN_ID: Interface index in the module +- * +- * %SKL_TKN_MM_U32_PIN_BUF: Buffer size of the module pin +- * +- * %SKL_TKN_MM_U32_FMT_ID: Format index for each of the interface/ +- * format information to be filled into. +- * +- * %SKL_TKN_MM_U32_NUM_IN_FMT: Number of input formats +- * %SKL_TKN_MM_U32_NUM_OUT_FMT: Number of output formats +- * +- * %SKL_TKN_U32_ASTATE_IDX: Table Index for the A-State entry to be filled +- * with kcps and clock source +- * +- * %SKL_TKN_U32_ASTATE_COUNT: Number of valid entries in A-State table +- * +- * %SKL_TKN_U32_ASTATE_KCPS: Specifies the core load threshold (in kilo +- * cycles per second) below which DSP is clocked +- * from source specified by clock source. +- * +- * %SKL_TKN_U32_ASTATE_CLK_SRC: Clock source for A-State entry +- * +- * %SKL_TKN_U32_FMT_CFG_IDX: Format config index +- * +- * module_id and loadable flags dont have tokens as these values will be +- * read from the DSP FW manifest +- * +- * Tokens defined can be used either in the manifest or widget private data. +- * +- * SKL_TKN_MM is used as a suffix for all tokens that represent +- * module data in the manifest. +- */ +-enum SKL_TKNS { +- SKL_TKN_UUID = 1, +- SKL_TKN_U8_NUM_BLOCKS, +- SKL_TKN_U8_BLOCK_TYPE, +- SKL_TKN_U8_IN_PIN_TYPE, +- SKL_TKN_U8_OUT_PIN_TYPE, +- SKL_TKN_U8_DYN_IN_PIN, +- SKL_TKN_U8_DYN_OUT_PIN, +- SKL_TKN_U8_IN_QUEUE_COUNT, +- SKL_TKN_U8_OUT_QUEUE_COUNT, +- SKL_TKN_U8_TIME_SLOT, +- SKL_TKN_U8_CORE_ID, +- SKL_TKN_U8_MOD_TYPE, +- SKL_TKN_U8_CONN_TYPE, +- SKL_TKN_U8_DEV_TYPE, +- SKL_TKN_U8_HW_CONN_TYPE, +- SKL_TKN_U16_MOD_INST_ID, +- SKL_TKN_U16_BLOCK_SIZE, +- SKL_TKN_U32_MAX_MCPS, +- SKL_TKN_U32_MEM_PAGES, +- SKL_TKN_U32_OBS, +- SKL_TKN_U32_IBS, +- SKL_TKN_U32_VBUS_ID, +- SKL_TKN_U32_PARAMS_FIXUP, +- SKL_TKN_U32_CONVERTER, +- SKL_TKN_U32_PIPE_ID, +- SKL_TKN_U32_PIPE_CONN_TYPE, +- SKL_TKN_U32_PIPE_PRIORITY, +- SKL_TKN_U32_PIPE_MEM_PGS, +- SKL_TKN_U32_DIR_PIN_COUNT, +- SKL_TKN_U32_FMT_CH, +- SKL_TKN_U32_FMT_FREQ, +- SKL_TKN_U32_FMT_BIT_DEPTH, +- SKL_TKN_U32_FMT_SAMPLE_SIZE, +- SKL_TKN_U32_FMT_CH_CONFIG, +- SKL_TKN_U32_FMT_INTERLEAVE, +- SKL_TKN_U32_FMT_SAMPLE_TYPE, +- SKL_TKN_U32_FMT_CH_MAP, +- SKL_TKN_U32_PIN_MOD_ID, +- SKL_TKN_U32_PIN_INST_ID, +- SKL_TKN_U32_MOD_SET_PARAMS, +- SKL_TKN_U32_MOD_PARAM_ID, +- SKL_TKN_U32_CAPS_SET_PARAMS, +- SKL_TKN_U32_CAPS_PARAMS_ID, +- SKL_TKN_U32_CAPS_SIZE, +- SKL_TKN_U32_PROC_DOMAIN, +- SKL_TKN_U32_LIB_COUNT, +- SKL_TKN_STR_LIB_NAME, +- SKL_TKN_U32_PMODE, +- SKL_TKL_U32_D0I3_CAPS, /* Typo added at v4.10 */ +- SKL_TKN_U32_D0I3_CAPS = SKL_TKL_U32_D0I3_CAPS, +- SKL_TKN_U32_DMA_BUF_SIZE, +- +- SKL_TKN_U32_PIPE_DIRECTION, +- SKL_TKN_U32_PIPE_CONFIG_ID, +- SKL_TKN_U32_NUM_CONFIGS, +- SKL_TKN_U32_PATH_MEM_PGS, +- +- SKL_TKN_U32_CFG_FREQ, +- SKL_TKN_U8_CFG_CHAN, +- SKL_TKN_U8_CFG_BPS, +- SKL_TKN_CFG_MOD_RES_ID, +- SKL_TKN_CFG_MOD_FMT_ID, +- SKL_TKN_U8_NUM_MOD, +- +- SKL_TKN_MM_U8_MOD_IDX, +- SKL_TKN_MM_U8_NUM_RES, +- SKL_TKN_MM_U8_NUM_INTF, +- SKL_TKN_MM_U32_RES_ID, +- SKL_TKN_MM_U32_CPS, +- SKL_TKN_MM_U32_DMA_SIZE, +- SKL_TKN_MM_U32_CPC, +- SKL_TKN_MM_U32_RES_PIN_ID, +- SKL_TKN_MM_U32_INTF_PIN_ID, +- SKL_TKN_MM_U32_PIN_BUF, +- SKL_TKN_MM_U32_FMT_ID, +- SKL_TKN_MM_U32_NUM_IN_FMT, +- SKL_TKN_MM_U32_NUM_OUT_FMT, +- +- SKL_TKN_U32_ASTATE_IDX, +- SKL_TKN_U32_ASTATE_COUNT, +- SKL_TKN_U32_ASTATE_KCPS, +- SKL_TKN_U32_ASTATE_CLK_SRC, +- +- SKL_TKN_U32_FMT_CFG_IDX = 96, +- SKL_TKN_MAX = SKL_TKN_U32_FMT_CFG_IDX, +-}; +- +-#endif +diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h +--- a/include/uapi/sound/sof/tokens.h ++++ b/include/uapi/sound/sof/tokens.h +@@ -111,8 +111,8 @@ + + #define SOF_TKN_COMP_SCHED_DOMAIN 418 + #define SOF_TKN_COMP_DOMAIN_ID 419 +-#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 420 +-#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 421 ++#define SOF_TKN_COMP_STACK_BYTES_REQUIREMENT 420 ++#define SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT 421 + + /* SSP */ + #define SOF_TKN_INTEL_SSP_CLKS_CONTROL 500 +@@ -223,6 +223,7 @@ + #define SOF_TKN_AMD_ACPI2S_RATE 1700 + #define SOF_TKN_AMD_ACPI2S_CH 1701 + #define SOF_TKN_AMD_ACPI2S_TDM_MODE 1702 ++#define SOF_TKN_AMD_ACPI2S_FORMAT 1703 + + /* MICFIL PDM */ + #define SOF_TKN_IMX_MICFIL_RATE 2000 +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +@@ -171,13 +171,13 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + case HDA_GEN_PCM_ACT_OPEN: + pm_runtime_get_sync(dev); + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + tas_hda->priv->playback_started = true; + } + break; + case HDA_GEN_PCM_ACT_CLOSE: + scoped_guard(mutex, &tas_hda->priv->codec_lock) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -720,7 +720,7 @@ static int tas2781_runtime_suspend(struct device *dev) + * Stop the playback if it's unused. + */ + if (tas_hda->priv->playback_started) { +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + tas_hda->priv->playback_started = false; + } + +@@ -750,7 +750,7 @@ static int tas2781_system_suspend(struct device *dev) + + /* Shutdown chip before system suspend */ + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 1); ++ tasdevice_tuning_switch(tas_hda->priv, 1, false); + + /* + * Reset GPIO may be shared, so cannot reset here. +@@ -783,7 +783,7 @@ static int tas2781_system_resume(struct device *dev) + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + if (tas_hda->priv->playback_started) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + + return 0; + } +diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +--- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c ++++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +@@ -399,11 +399,11 @@ static void tas2781_hda_playback_hook(struct device *dev, int action) + pm_runtime_get_sync(dev); + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_hda->priv, 0); ++ tasdevice_tuning_switch(tas_hda->priv, 0, false); + } else if (action == HDA_GEN_PCM_ACT_CLOSE) { + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + pm_runtime_put_autosuspend(dev); + } + } +@@ -847,7 +847,7 @@ static int tas2781_runtime_suspend(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + tas_priv->tasdevice[tas_priv->index].cur_book = -1; + tas_priv->tasdevice[tas_priv->index].cur_conf = -1; +@@ -864,7 +864,7 @@ static int tas2781_runtime_resume(struct device *dev) + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + + return 0; + } +@@ -882,7 +882,7 @@ static int tas2781_system_suspend(struct device *dev) + /* Shutdown chip before system suspend */ + if (tas_priv->fw_state == TASDEVICE_DSP_FW_ALL_OK + && tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 1); ++ tasdevice_tuning_switch(tas_priv, 1, false); + + return 0; + } +@@ -917,7 +917,7 @@ static int tas2781_system_resume(struct device *dev) + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + + if (tas_priv->playback_started) +- tasdevice_tuning_switch(tas_priv, 0); ++ tasdevice_tuning_switch(tas_priv, 0, false); + } + + return ret; +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -63,6 +63,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA1"), ++ }, ++ }, + {} + }; + +@@ -379,5 +386,17 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { + }; + EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); + ++struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { ++ { ++ .id = "AMDI1010", ++ .drv_name = "acp7x-dsp", ++ .pdata = &acp_quirk_data, ++ .fw_filename = "sof-acp7x.ri", ++ .sof_tplg_filename = "sof-acp7x.tplg", ++ }, ++ {}, ++}; ++EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); ++ + MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); + MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c +--- a/sound/soc/amd/acp-pcm-dma.c ++++ b/sound/soc/amd/acp-pcm-dma.c +@@ -691,12 +691,10 @@ static irqreturn_t dma_irq_handler(int irq, void *arg) + { + u16 dscr_idx; + u32 intr_flag, ext_intr_status; +- struct audio_drv_data *irq_data; ++ struct audio_drv_data *irq_data = arg; + void __iomem *acp_mmio; +- struct device *dev = arg; + bool valid_irq = false; + +- irq_data = dev_get_drvdata(dev); + acp_mmio = irq_data->acp_mmio; + + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT); +@@ -1291,10 +1289,10 @@ static int acp_audio_probe(struct platform_device *pdev) + + irq = platform_get_irq(pdev, 0); + if (irq < 0) +- return -ENODEV; ++ return irq; + + status = devm_request_irq(&pdev->dev, irq, dma_irq_handler, +- 0, "ACP_IRQ", &pdev->dev); ++ 0, "ACP_IRQ", audio_drv_data); + if (status) { + dev_err(&pdev->dev, "ACP IRQ request failed\n"); + return status; +diff --git a/sound/soc/amd/acp/acp-i2s.c b/sound/soc/amd/acp/acp-i2s.c +--- a/sound/soc/amd/acp/acp-i2s.c ++++ b/sound/soc/amd/acp/acp-i2s.c +@@ -686,6 +686,10 @@ static int acp_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_d + return 0; + } + ++static const u64 acp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .startup = acp_i2s_startup, + .hw_params = acp_i2s_hwparams, +@@ -693,6 +697,8 @@ const struct snd_soc_dai_ops asoc_acp_cpu_dai_ops = { + .trigger = acp_i2s_trigger, + .set_fmt = acp_i2s_set_fmt, + .set_tdm_slot = acp_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_NS_GPL(asoc_acp_cpu_dai_ops, "SND_SOC_ACP_COMMON"); + +diff --git a/sound/soc/amd/acp/acp-mach-common.c b/sound/soc/amd/acp/acp-mach-common.c +--- a/sound/soc/amd/acp/acp-mach-common.c ++++ b/sound/soc/amd/acp/acp-mach-common.c +@@ -938,15 +938,10 @@ static int acp_max98388_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai = + snd_soc_card_get_codec_dai(card, + MAX98388_CODEC_DAI); +- int ret; + +- ret = snd_soc_dai_set_fmt(codec_dai, +- SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | +- SND_SOC_DAIFMT_NB_NF); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_fmt(codec_dai, ++ SND_SOC_DAIFMT_CBC_CFC | SND_SOC_DAIFMT_I2S | ++ SND_SOC_DAIFMT_NB_NF); + } + + static const struct snd_soc_ops acp_max98388_ops = { +diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c +--- a/sound/soc/amd/acp/acp-pci.c ++++ b/sound/soc/amd/acp/acp-pci.c +@@ -118,17 +118,10 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + if (!chip) + return -ENOMEM; + +- if (pci_enable_device(pci)) ++ if (pcim_enable_device(pci)) + return dev_err_probe(&pci->dev, -ENODEV, + "pci_enable_device failed\n"); + +- ret = pci_request_regions(pci, "AMD ACP3x audio"); +- if (ret < 0) { +- dev_err(&pci->dev, "pci_request_regions failed\n"); +- ret = -ENOMEM; +- goto disable_pci; +- } +- + pci_set_master(pci); + + chip->acp_rev = pci->revision; +@@ -161,24 +154,21 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + break; + default: + dev_err(dev, "Unsupported device revision:0x%x\n", pci->revision); +- ret = -EINVAL; +- goto release_regions; ++ return -EINVAL; + } + chip->flag = flag; + + addr = pci_resource_start(pci, 0); +- chip->base = devm_ioremap(&pci->dev, addr, pci_resource_len(pci, 0)); +- if (!chip->base) { +- ret = -ENOMEM; +- goto release_regions; +- } ++ chip->base = pcim_iomap_region(pci, 0, "AMD ACP3x audio"); ++ if (IS_ERR(chip->base)) ++ return PTR_ERR(chip->base); + + chip->addr = addr; + + chip->acp_hw_ops_init(chip); + ret = acp_hw_init(chip); + if (ret) +- goto release_regions; ++ goto de_init; + + ret = devm_request_irq(dev, pci->irq, irq_handler, + IRQF_SHARED, "ACP_I2S_IRQ", chip); +@@ -214,10 +204,6 @@ static int acp_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id + + de_init: + acp_hw_deinit(chip); +-release_regions: +- pci_release_regions(pci); +-disable_pci: +- pci_disable_device(pci); + + return ret; + }; +diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +--- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c +@@ -531,15 +531,15 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(soc_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + soc_sdw_quirk, quirk_override); + soc_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = soc_sdw_quirk; +- dev_dbg(card->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); ++ dev_dbg(&pdev->dev, "legacy quirk 0x%lx\n", ctx->mc_quirk); + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ + for (i = 0; i < ctx->codec_info_list_count; i++) + codec_info_list[i].amp_num = 0; +@@ -556,12 +556,12 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:acp-dmic cfg-mics:%d", + card->components, + 1); +@@ -570,9 +570,9 @@ static int mc_probe(struct platform_device *pdev) + } + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -590,8 +590,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sdw", }, +- {} ++ { .name = "amd_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); + if (ret < 0) + return ret; + +@@ -399,12 +399,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -423,15 +423,15 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + /* Register the card */ +- ret = devm_snd_soc_register_card(card->dev, card); ++ ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -449,8 +449,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "amd_sof_sdw", }, +- {} ++ { .name = "amd_sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/amd/acp/acp-sof-mach.c b/sound/soc/amd/acp/acp-sof-mach.c +--- a/sound/soc/amd/acp/acp-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sof-mach.c +@@ -128,31 +128,31 @@ static int acp_sof_probe(struct platform_device *pdev) + static const struct platform_device_id board_ids[] = { + { + .name = "rt5682-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_rt1019_data, + }, + { + .name = "rt5682-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682_max_data, + }, + { + .name = "rt5682s-max", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_max_data, + }, + { + .name = "rt5682s-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_rt1019_data, + }, + { + .name = "nau8825-max", +- .driver_data = (kernel_ulong_t)&sof_nau8825_data ++ .driver_data = (kernel_ulong_t)&sof_nau8825_data, + }, + { + .name = "rt5682s-hs-rt1019", +- .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data ++ .driver_data = (kernel_ulong_t)&sof_rt5682s_hs_rt1019_data, + }, + { + .name = "nau8821-max", +- .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data ++ .driver_data = (kernel_ulong_t)&sof_nau8821_max98388_data, + }, + { } + }; +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -659,6 +659,56 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_l1u0_tas2783x2_l1u8b[] = { + {} + }; + ++static const struct snd_soc_acpi_adr_device rt712_vb_l1u0_adr[] = { ++ { ++ .adr = 0x000130025D071201ull, ++ /* ++ * On this platform speakers are provided by two TAS2783 amps, ++ * so the AIF2 amp path is left unused: jack + DMIC only. ++ */ ++ .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), ++ .endpoints = jack_dmic_endpoints, ++ .name_prefix = "rt712" ++ } ++}; ++ ++/* ++ * Unique ID 0xC drives the left speaker and 0x9 the right one. The order of the ++ * entries matters as much as the endpoints: asoc_sdw_parse_sdw_endpoints() ++ * appends them to the dailink in array order and sdw_compute_slave_ports() ++ * hands out payload block offsets in that same order, so the first entry is the ++ * one that receives channel 0. ++ */ ++static const struct snd_soc_acpi_adr_device tas2783x2_l0u9c_adr[] = { ++ { ++ .adr = 0x00003C0102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_l_endpoint, ++ .name_prefix = "tas2783-1", ++ }, ++ { ++ .adr = 0x0000390102000001ull, ++ .num_endpoints = 1, ++ .endpoints = &spk_r_endpoint, ++ .name_prefix = "tas2783-2", ++ }, ++}; ++ ++/* HP OmniBook X Flip 14-kc0xxx (board 8EA1) */ ++static const struct snd_soc_acpi_link_adr acp70_tas2783x2_l0u9c_rt712_vb_l1u0[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783x2_l0u9c_adr), ++ .adr_d = tas2783x2_l0u9c_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_l1u0_adr), ++ .adr_d = rt712_vb_l1u0_adr, ++ }, ++ {} ++}; ++ + static const struct snd_soc_acpi_endpoint rt721_endpoints[] = { + { /* Jack Playback/Capture Endpoint (AIF1) */ + .num = 0, +@@ -704,6 +754,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { + .links = acp70_tas2783_2, + .drv_name = "amd_sdw", + }, ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783x2_l0u9c_rt712_vb_l1u0, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_rt1320_l0_rt722_l1, +diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h +--- a/sound/soc/amd/mach-config.h ++++ b/sound/soc/amd/mach-config.h +@@ -28,6 +28,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; + extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; ++extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[]; + + struct config_entry { + u32 flags; +diff --git a/sound/soc/amd/raven/acp3x-i2s.c b/sound/soc/amd/raven/acp3x-i2s.c +--- a/sound/soc/amd/raven/acp3x-i2s.c ++++ b/sound/soc/amd/raven/acp3x-i2s.c +@@ -250,11 +250,17 @@ static int acp3x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp3x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp3x_i2s_dai_ops = { + .hw_params = acp3x_i2s_hwparams, + .trigger = acp3x_i2s_trigger, + .set_fmt = acp3x_i2s_set_fmt, + .set_tdm_slot = acp3x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp3x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp3x_dai_component = { +diff --git a/sound/soc/amd/vangogh/acp5x-i2s.c b/sound/soc/amd/vangogh/acp5x-i2s.c +--- a/sound/soc/amd/vangogh/acp5x-i2s.c ++++ b/sound/soc/amd/vangogh/acp5x-i2s.c +@@ -337,11 +337,17 @@ static int acp5x_i2s_trigger(struct snd_pcm_substream *substream, + return ret; + } + ++static const u64 acp5x_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = { + .hw_params = acp5x_i2s_hwparams, + .trigger = acp5x_i2s_trigger, + .set_fmt = acp5x_i2s_set_fmt, + .set_tdm_slot = acp5x_i2s_set_tdm_slot, ++ .auto_selectable_formats = &acp5x_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_component_driver acp5x_dai_component = { +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -795,12 +795,12 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + } + }, + { +- .driver_data = &acp6x_card, +- .matches = { +- DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), +- DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), +- } +- }, ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook_ASUSLaptop M6501RR_M6501RR"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c +--- a/sound/soc/apple/mca.c ++++ b/sound/soc/apple/mca.c +@@ -133,12 +133,17 @@ struct mca_cluster { + struct clk *clk_parent; + struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1]; + +- bool port_started[SNDRV_PCM_STREAM_LAST + 1]; +- int port_driver; /* The cluster driving this cluster's port */ ++ bool clk_provider; ++ ++ bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1]; ++ int port_clk_driver; /* The cluster driving this cluster's port */ + + bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1]; + struct device_link *pd_link; + ++ /* In case of clock consumer FE */ ++ int syncgen_in_use; ++ + unsigned int bclk_ratio; + + /* Masks etc. picked up via the set_tdm_slot method */ +@@ -157,7 +162,7 @@ struct mca_data { + struct reset_control *rstc; + struct device_link *pd_link; + +- /* Mutex for accessing port_driver of foreign clusters */ ++ /* Mutex for accessing port_clk_driver of foreign clusters */ + struct mutex port_mutex; + + int nclusters; +@@ -220,6 +225,12 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd, + FIELD_PREP(SERDES_CONF_SYNC_SEL, 0)); + mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL, + FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1)); ++ /* ++ * ADMAC gets started right after this. This delay seems ++ * to be needed for that to be reliable, e.g. ensure the ++ * clock is stable? ++ */ ++ udelay(100); + break; + default: + break; +@@ -256,11 +267,29 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd, + return 0; + } + ++static int mca_fe_get_portmask(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dpcm *dpcm; ++ int mask = 0; ++ ++ for_each_dpcm_be(fe, substream->stream, dpcm) { ++ int no = mca_dai_to_cluster(snd_soc_rtd_to_cpu(dpcm->be, 0))->no; ++ ++ mask |= 1 << no; ++ } ++ ++ return mask; ++} ++ + static int mca_fe_enable_clocks(struct mca_cluster *cl) + { + struct mca_data *mca = cl->host; + int ret; + ++ if (!cl->clk_provider) ++ return -EINVAL; ++ + ret = clk_prepare_enable(cl->clk_parent); + if (ret) { + dev_err(mca->dev, +@@ -311,7 +340,7 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + for (i = 0; i < mca->nclusters; i++) { + be_cl = &mca->clusters[i]; + +- if (be_cl->port_driver != cl->no) ++ if (be_cl->port_clk_driver != cl->no) + continue; + + for_each_pcm_streams(stream) { +@@ -323,36 +352,6 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl) + return false; + } + +-static int mca_be_prepare(struct snd_pcm_substream *substream, +- struct snd_soc_dai *dai) +-{ +- struct mca_cluster *cl = mca_dai_to_cluster(dai); +- struct mca_data *mca = cl->host; +- struct mca_cluster *fe_cl; +- int ret; +- +- if (cl->port_driver < 0) +- return -EINVAL; +- +- fe_cl = &mca->clusters[cl->port_driver]; +- +- /* +- * Typically the CODECs we are paired with will require clocks +- * to be present at time of unmute with the 'mute_stream' op +- * or at time of DAPM widget power-up. We need to enable clocks +- * here at the latest (frontend prepare would be too late). +- */ +- if (!mca_fe_clocks_in_use(fe_cl)) { +- ret = mca_fe_enable_clocks(fe_cl); +- if (ret < 0) +- return ret; +- } +- +- cl->clocks_in_use[substream->stream] = true; +- +- return 0; +-} +- + static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +@@ -360,7 +359,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + struct mca_data *mca = cl->host; + struct mca_cluster *fe_cl; + +- if (cl->port_driver < 0) ++ if (cl->port_clk_driver < 0) + return -EINVAL; + + /* +@@ -368,7 +367,7 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + * belong to the same PCM, accesses should have been + * synchronized at ASoC level. + */ +- fe_cl = &mca->clusters[cl->port_driver]; ++ fe_cl = &mca->clusters[cl->port_clk_driver]; + if (!mca_fe_clocks_in_use(fe_cl)) + return 0; /* Nothing to do */ + +@@ -380,6 +379,56 @@ static int mca_be_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int mca_fe_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ /* Turn on the cluster power domain if not already in use */ ++ if (!cl->syncgen_in_use) { ++ int port = ffs(mca_fe_get_portmask(substream)); ++ ++ cl->pd_link = device_link_add(mca->dev, cl->pd_dev, ++ DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | ++ DL_FLAG_RPM_ACTIVE); ++ if (!cl->pd_link) { ++ dev_err(mca->dev, ++ "cluster %d: unable to prop-up power domain\n", cl->no); ++ return -EINVAL; ++ } ++ ++ mca_modify(cl, REG_SYNCGEN_MCLK_SEL, SYNCGEN_MCLK_SEL, BIT(port)); ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, ++ SYNCGEN_STATUS_EN); ++ } ++ cl->syncgen_in_use |= 1 << substream->stream; ++ ++ return 0; ++} ++ ++static int mca_fe_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ ++ if (cl->clk_provider) ++ return 0; ++ ++ cl->syncgen_in_use &= ~(1 << substream->stream); ++ if (cl->syncgen_in_use) ++ return 0; ++ ++ mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0); ++ if (cl->pd_link) ++ device_link_del(cl->pd_link); ++ ++ return 0; ++} ++ + static unsigned int mca_crop_mask(unsigned int mask, int nchans) + { + while (hweight32(mask) > nchans) +@@ -390,7 +439,7 @@ static unsigned int mca_crop_mask(unsigned int mask, int nchans) + + static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + unsigned int mask, int slots, int nchans, +- int slot_width, bool is_tx, int port) ++ int slot_width, bool is_tx, int portmask) + { + __iomem void *serdes_base = cl->base + serdes_unit; + u32 serdes_conf, serdes_conf_mask; +@@ -449,7 +498,7 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit, + serdes_base + REG_RX_SERDES_SLOTMASK); + writel_relaxed(~((u32)mca_crop_mask(mask, nchans)), + serdes_base + REG_RX_SERDES_SLOTMASK + 0x4); +- writel_relaxed(1 << port, ++ writel_relaxed(portmask, + serdes_base + REG_RX_SERDES_PORT); + } + +@@ -505,9 +554,18 @@ static int mca_fe_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + u32 serdes_conf = 0; + u32 bitstart; + +- if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != +- SND_SOC_DAIFMT_BP_FP) ++ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { ++ case SND_SOC_DAIFMT_BP_FP: ++ cl->clk_provider = true; ++ break; ++ ++ case SND_SOC_DAIFMT_BC_FC: ++ cl->clk_provider = false; ++ break; ++ ++ default: + goto err; ++ } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: +@@ -564,24 +622,6 @@ static int mca_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) + return 0; + } + +-static int mca_fe_get_port(struct snd_pcm_substream *substream) +-{ +- struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *be; +- struct snd_soc_dpcm *dpcm; +- +- be = NULL; +- for_each_dpcm_be(fe, substream->stream, dpcm) { +- be = dpcm->be; +- break; +- } +- +- if (!be) +- return -EINVAL; +- +- return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no; +-} +- + static int mca_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +@@ -595,7 +635,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + unsigned long bclk_ratio; + unsigned int tdm_slots, tdm_slot_width, tdm_mask; + u32 regval, pad; +- int ret, port, nchans_ceiled; ++ int ret, portmask, nchans_ceiled; + + if (!cl->tdm_slot_width) { + /* +@@ -644,13 +684,13 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + tdm_mask = (1 << tdm_slots) - 1; + } + +- port = mca_fe_get_port(substream); +- if (port < 0) +- return port; ++ portmask = mca_fe_get_portmask(substream); ++ if (!portmask) ++ return -EINVAL; + + ret = mca_configure_serdes(cl, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF, + tdm_mask, tdm_slots, params_channels(params), +- tdm_slot_width, is_tx, port); ++ tdm_slot_width, is_tx, portmask); + if (ret) + return ret; + +@@ -699,6 +739,19 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 mca_fe_selectable_formats[] = ++{ ++ /* pattern 1 */ ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF, ++ ++ /* pattern 2 */ ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF ++}; ++ + static const struct snd_soc_dai_ops mca_fe_ops = { + .startup = mca_fe_startup, + .set_fmt = mca_fe_set_fmt, +@@ -706,66 +759,128 @@ static const struct snd_soc_dai_ops mca_fe_ops = { + .set_tdm_slot = mca_fe_set_tdm_slot, + .hw_params = mca_fe_hw_params, + .trigger = mca_fe_trigger, ++ .prepare = mca_fe_prepare, ++ .hw_free = mca_fe_hw_free, ++ .auto_selectable_formats = mca_fe_selectable_formats, ++ .num_auto_selectable_formats = ARRAY_SIZE(mca_fe_selectable_formats), + }; + +-static bool mca_be_started(struct mca_cluster *cl) ++/* ++ * Is there a FE attached which will be feeding this port's clocks? ++ */ ++static bool mca_be_clk_started(struct mca_cluster *cl) + { + int stream; + + for_each_pcm_streams(stream) +- if (cl->port_started[stream]) ++ if (cl->port_clk_started[stream]) + return true; + return false; + } + ++static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be, ++ int stream) ++{ ++ struct snd_soc_pcm_runtime *fe = NULL; ++ struct snd_soc_dpcm *dpcm; ++ ++ for_each_dpcm_fe(be, stream, dpcm) { ++ if (fe && dpcm->fe != fe) { ++ dev_err(be->dev, "many FE per one BE unsupported\n"); ++ return NULL; ++ } ++ ++ fe = dpcm->fe; ++ } ++ ++ return fe; ++} ++ ++static int mca_be_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); ++ struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_data *mca = cl->host; ++ struct mca_cluster *fe_cl, *fe_clk_cl; ++ int ret; ++ ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (cl->port_clk_driver < 0) ++ return 0; ++ ++ fe_clk_cl = &mca->clusters[cl->port_clk_driver]; ++ ++ /* ++ * Typically the CODECs we are paired with will require clocks ++ * to be present at time of unmute with the 'mute_stream' op ++ * or at time of DAPM widget power-up. We need to enable clocks ++ * here at the latest (frontend prepare would be too late). ++ */ ++ if (!mca_fe_clocks_in_use(fe_clk_cl)) { ++ ret = mca_fe_enable_clocks(fe_clk_cl); ++ if (ret < 0) ++ return ret; ++ } ++ ++ cl->clocks_in_use[substream->stream] = true; ++ ++ return 0; ++} ++ + static int mca_be_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); +- struct snd_soc_pcm_runtime *fe; ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); + struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; +- struct snd_soc_dpcm *dpcm; +- +- fe = NULL; +- +- for_each_dpcm_fe(be, substream->stream, dpcm) { +- if (fe && dpcm->fe != fe) { +- dev_err(mca->dev, "many FE per one BE unsupported\n"); +- return -EINVAL; +- } +- +- fe = dpcm->fe; +- } + + if (!fe) + return -EINVAL; +- + fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), ++ cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, ++ PORT_ENABLES_TX_DATA); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return 0; ++ ++ if (mca_be_clk_started(cl)) { + /* + * Port is already started in the other direction. + * Make sure there isn't a conflict with another cluster +- * driving the port. ++ * driving the port clocks. + */ +- if (cl->port_driver != fe_cl->no) ++ if (cl->port_clk_driver != fe_cl->no) + return -EINVAL; + +- cl->port_started[substream->stream] = true; ++ cl->port_clk_started[substream->stream] = true; + return 0; + } + +- writel_relaxed(PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA, +- cl->base + REG_PORT_ENABLES); + writel_relaxed(FIELD_PREP(PORT_CLOCK_SEL, fe_cl->no + 1), + cl->base + REG_PORT_CLOCK_SEL); ++ + writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no), + cl->base + REG_PORT_DATA_SEL); ++ ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, ++ PORT_ENABLES_CLOCKS); ++ + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = fe_cl->no; +- cl->port_started[substream->stream] = true; ++ cl->port_clk_driver = fe_cl->no; ++ cl->port_clk_started[substream->stream] = true; + + return 0; + } +@@ -773,20 +888,34 @@ static int mca_be_startup(struct snd_pcm_substream *substream, + static void mca_be_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream); + struct mca_cluster *cl = mca_dai_to_cluster(dai); ++ struct mca_cluster *fe_cl; + struct mca_data *mca = cl->host; + +- cl->port_started[substream->stream] = false; ++ if (!fe) ++ return; ++ fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0)); + +- if (!mca_be_started(cl)) { ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0); ++ writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ } ++ ++ if (!fe_cl->clk_provider) ++ return; ++ ++ cl->port_clk_started[substream->stream] = false; ++ if (!mca_be_clk_started(cl)) { + /* + * Were we the last direction to shutdown? +- * Turn off the lights. ++ * Turn off the lights (clocks). + */ +- writel_relaxed(0, cl->base + REG_PORT_ENABLES); +- writel_relaxed(0, cl->base + REG_PORT_DATA_SEL); ++ mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, 0); ++ writel_relaxed(0, cl->base + REG_PORT_CLOCK_SEL); + scoped_guard(mutex, &mca->port_mutex) +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + } + } + +@@ -1092,7 +1221,7 @@ static int apple_mca_probe(struct platform_device *pdev) + cl->host = mca; + cl->no = i; + cl->base = base + CLUSTER_STRIDE * i; +- cl->port_driver = -1; ++ cl->port_clk_driver = -1; + cl->clk_parent = of_clk_get(pdev->dev.of_node, i); + if (IS_ERR(cl->clk_parent)) { + dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n", +diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c +--- a/sound/soc/atmel/atmel-i2s.c ++++ b/sound/soc/atmel/atmel-i2s.c +@@ -540,12 +540,16 @@ static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 atmel_i2s_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S; ++ + static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .probe = atmel_i2s_dai_probe, + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, ++ .auto_selectable_formats = &atmel_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_i2s_dai = { +diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c +--- a/sound/soc/atmel/atmel_ssc_dai.c ++++ b/sound/soc/atmel/atmel_ssc_dai.c +@@ -825,6 +825,11 @@ static int atmel_ssc_resume(struct snd_soc_component *component) + #define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + ++static const u64 atmel_ssc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A; ++ + static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .startup = atmel_ssc_startup, + .shutdown = atmel_ssc_shutdown, +@@ -833,6 +838,8 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .hw_params = atmel_ssc_hw_params, + .set_fmt = atmel_ssc_set_dai_fmt, + .set_clkdiv = atmel_ssc_set_dai_clkdiv, ++ .auto_selectable_formats = &atmel_ssc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver atmel_ssc_dai = { +diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c +--- a/sound/soc/atmel/atmel_wm8904.c ++++ b/sound/soc/atmel/atmel_wm8904.c +@@ -82,32 +82,33 @@ static struct snd_soc_card atmel_asoc_wm8904_card = { + + static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int ret; + + if (!np) { +- dev_err(&pdev->dev, "only device tree supported\n"); ++ dev_err(dev, "only device tree supported\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse card name\n"); ++ dev_err(dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio routing\n"); ++ dev_err(dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { +- dev_err(&pdev->dev, "failed to get dai and pcm info\n"); ++ dev_err(dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } +@@ -117,7 +118,7 @@ static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { +- dev_err(&pdev->dev, "failed to get codec info\n"); ++ dev_err(dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } +diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c +--- a/sound/soc/atmel/mchp-i2s-mcc.c ++++ b/sound/soc/atmel/mchp-i2s-mcc.c +@@ -912,6 +912,13 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 mchp_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_GATED | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .probe = mchp_i2s_mcc_dai_probe, + .set_sysclk = mchp_i2s_mcc_set_sysclk, +@@ -922,6 +929,8 @@ static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, ++ .auto_selectable_formats = &mchp_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 +diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c +--- a/sound/soc/atmel/mchp-pdmc.c ++++ b/sound/soc/atmel/mchp-pdmc.c +@@ -741,6 +741,8 @@ static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd, + return ret; + } + ++static const u64 mchp_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_PDM; ++ + static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .probe = mchp_pdmc_dai_probe, + .set_fmt = mchp_pdmc_set_fmt, +@@ -748,6 +750,8 @@ static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = { + .hw_params = mchp_pdmc_hw_params, + .trigger = mchp_pdmc_trigger, + .pcm_new = &mchp_pdmc_pcm_new, ++ .auto_selectable_formats = &mchp_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver mchp_pdmc_dai = { +diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c +--- a/sound/soc/au1x/db1200.c ++++ b/sound/soc/au1x/db1200.c +@@ -42,7 +42,7 @@ static const struct platform_device_id db1200_pids[] = { + .name = "db1550-i2s", + .driver_data = 5, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, db1200_pids); + +diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c +--- a/sound/soc/au1x/i2sc.c ++++ b/sound/soc/au1x/i2sc.c +@@ -202,11 +202,22 @@ static int au1xi2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xi2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xi2s_dai_ops = { + .startup = au1xi2s_startup, + .trigger = au1xi2s_trigger, + .hw_params = au1xi2s_hw_params, + .set_fmt = au1xi2s_set_fmt, ++ .auto_selectable_formats = &au1xi2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver au1xi2s_dai_driver = { +diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c +--- a/sound/soc/au1x/psc-ac97.c ++++ b/sound/soc/au1x/psc-ac97.c +@@ -210,9 +210,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, + { + struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); + unsigned long r, ro, stat; +- int chans, t, stype = substream->stream; +- +- chans = params_channels(params); ++ int t, stype = substream->stream; + + r = ro = __raw_readl(AC97_CFG(pscdata)); + stat = __raw_readl(AC97_STAT(pscdata)); +diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c +--- a/sound/soc/au1x/psc-i2s.c ++++ b/sound/soc/au1x/psc-i2s.c +@@ -262,11 +262,22 @@ static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 au1xpsc_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { + .startup = au1xpsc_i2s_startup, + .trigger = au1xpsc_i2s_trigger, + .hw_params = au1xpsc_i2s_hw_params, + .set_fmt = au1xpsc_i2s_set_fmt, ++ .auto_selectable_formats = &au1xpsc_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = { +diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c +--- a/sound/soc/bcm/bcm2835-i2s.c ++++ b/sound/soc/bcm/bcm2835-i2s.c +@@ -748,6 +748,17 @@ static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 bcm2835_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .probe = bcm2835_i2s_dai_probe, + .startup = bcm2835_i2s_startup, +@@ -758,6 +769,8 @@ static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { + .set_fmt = bcm2835_i2s_set_dai_fmt, + .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio, + .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot, ++ .auto_selectable_formats = &bcm2835_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver bcm2835_i2s_dai = { +diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c +--- a/sound/soc/bcm/cygnus-ssp.c ++++ b/sound/soc/bcm/cygnus-ssp.c +@@ -1133,6 +1133,11 @@ static int cygnus_ssp_resume(struct snd_soc_component *component) + #define cygnus_ssp_resume NULL + #endif + ++static const u64 cygnus_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, +@@ -1141,6 +1146,8 @@ static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, ++ .auto_selectable_formats = &cygnus_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops cygnus_spdif_dai_ops = { +diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c +--- a/sound/soc/cirrus/ep93xx-i2s.c ++++ b/sound/soc/cirrus/ep93xx-i2s.c +@@ -401,6 +401,15 @@ static int ep93xx_i2s_resume(struct snd_soc_component *component) + #define ep93xx_i2s_resume NULL + #endif + ++static const u64 ep93xx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .probe = ep93xx_i2s_dai_probe, + .startup = ep93xx_i2s_startup, +@@ -408,6 +417,8 @@ static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, ++ .auto_selectable_formats = &ep93xx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -238,6 +238,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_RT715_SDCA_SDW + imply SND_SOC_RT721_SDCA_SDW + imply SND_SOC_RT722_SDCA_SDW ++ imply SND_SOC_RT766_SDCA_SDW + imply SND_SOC_RT1308_SDW + imply SND_SOC_RT1316_SDW + imply SND_SOC_RT1318 +@@ -367,6 +368,7 @@ config SND_SOC_ALL_CODECS + imply SND_SOC_WSA881X + imply SND_SOC_WSA883X + imply SND_SOC_WSA884X ++ imply SND_SOC_WSA885X + imply SND_SOC_ZL38060 + help + Normally ASoC codec drivers are only built if a machine driver which +@@ -615,8 +617,8 @@ config SND_SOC_AK4613 + depends on I2C + + config SND_SOC_AK4619 +- tristate "AKM AK4619 CODEC" +- depends on I2C ++ tristate "AKM AK4619 CODEC" ++ depends on I2C + + config SND_SOC_AK4642 + tristate "AKM AK4642 CODEC" +@@ -894,6 +896,7 @@ config SND_SOC_CS35L56_SDW + tristate "Cirrus Logic CS35L56 CODEC (SDW)" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE ++ select IRQ_DOMAIN + select SND_SOC_CS35L56 + select SND_SOC_CS35L56_SHARED + help +@@ -1206,16 +1209,16 @@ config SND_SOC_JZ4725B_CODEC + will be called snd-soc-jz4725b-codec. + + config SND_SOC_JZ4760_CODEC +- depends on MACH_INGENIC || COMPILE_TEST +- depends on OF +- select REGMAP +- tristate "Ingenic JZ4760 internal CODEC" +- help +- Enable support for the internal CODEC found in the JZ4760 SoC +- from Ingenic. ++ depends on MACH_INGENIC || COMPILE_TEST ++ depends on OF ++ select REGMAP ++ tristate "Ingenic JZ4760 internal CODEC" ++ help ++ Enable support for the internal CODEC found in the JZ4760 SoC ++ from Ingenic. + +- This driver can also be built as a module. If so, the module +- will be called snd-soc-jz4760-codec. ++ This driver can also be built as a module. If so, the module ++ will be called snd-soc-jz4760-codec. + + config SND_SOC_JZ4770_CODEC + depends on MACH_INGENIC || COMPILE_TEST +@@ -1893,7 +1896,7 @@ config SND_SOC_RT5670 + depends on I2C + + config SND_SOC_RT5677 +- tristate ++ tristate "Realtek RT5677 Codec" + depends on I2C + select REGMAP_I2C + select REGMAP_IRQ +@@ -1983,6 +1986,15 @@ config SND_SOC_RT715_SDCA_SDW + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + ++config SND_SOC_RT766_SDCA_SDW ++ tristate "Realtek RT766 SDCA Codec - SDW" ++ depends on SOUNDWIRE ++ depends on SND_SOC_SDCA ++ select SND_SOC_SDCA_HID ++ select SND_SOC_SDCA_IRQ ++ select REGMAP_SOUNDWIRE ++ select REGMAP_SOUNDWIRE_MBQ ++ + config SND_SOC_RT9120 + tristate "Richtek RT9120 Stereo Class-D Amplifier" + depends on I2C +@@ -2302,8 +2314,8 @@ config SND_SOC_TLV320ADC3XXX + depends on I2C + depends on GPIOLIB + help +- Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 +- ADCs. ++ Enable support for Texas Instruments TLV320ADC3001 and TLV320ADC3101 ++ ADCs. + + config SND_SOC_TLV320AIC23 + tristate +@@ -2776,6 +2788,16 @@ config SND_SOC_WSA884X + This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D + Smart Speaker Amplifier. + ++config SND_SOC_WSA885X ++ tristate "WSA885X Codec" ++ depends on I2C ++ select REGMAP_I2C ++ help ++ This enables support for Qualcomm WSA885X Stereo Smart Speaker ++ Amplifier. The codec driver programs the amplifier register ++ map and exposes the DAI and mixer controls used by Qualcomm ++ audio machine drivers. ++ + config SND_SOC_ZL38060 + tristate "Microsemi ZL38060 Connected Home Audio Processor" + depends on SPI_MASTER +@@ -2899,28 +2921,32 @@ config SND_SOC_TPA6130A2 + depends on I2C + + config SND_SOC_LPASS_MACRO_COMMON +- tristate ++ tristate + + config SND_SOC_LPASS_WSA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm WSA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_VA_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm VA Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_RX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm RX Macro in LPASS(Low Power Audio SubSystem)" + + config SND_SOC_LPASS_TX_MACRO + depends on COMMON_CLK ++ depends on PM_CLK + select REGMAP_MMIO + select SND_SOC_LPASS_MACRO_COMMON + tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile +--- a/sound/soc/codecs/Makefile ++++ b/sound/soc/codecs/Makefile +@@ -285,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o + snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o + snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o + snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o ++snd-soc-rt766-sdca-y := rt766-sdca.o rt766-sdca-sdw.o + snd-soc-rt9120-y := rt9120.o + snd-soc-rt9123-y := rt9123.o + snd-soc-rt9123p-y := rt9123p.o +@@ -427,6 +428,7 @@ snd-soc-wm-hubs-y := wm_hubs.o + snd-soc-wsa881x-y := wsa881x.o + snd-soc-wsa883x-y := wsa883x.o + snd-soc-wsa884x-y := wsa884x.o ++snd-soc-wsa885x-y := wsa885x.o + snd-soc-zl38060-y := zl38060.o + # Amp + snd-soc-max9877-y := max9877.o +@@ -726,6 +728,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o + obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o + obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o + obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o ++obj-$(CONFIG_SND_SOC_RT766_SDCA_SDW) += snd-soc-rt766-sdca.o + obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o + obj-$(CONFIG_SND_SOC_RT9123) += snd-soc-rt9123.o + obj-$(CONFIG_SND_SOC_RT9123P) += snd-soc-rt9123p.o +@@ -878,6 +881,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o + obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o + obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o + obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o ++obj-$(CONFIG_SND_SOC_WSA885X) += snd-soc-wsa885x.o + obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o + + # Amp +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,14 +14,17 @@ + * for ST-Ericsson. + */ + ++#include + #include + #include + #include + #include + #include + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -51,9 +54,32 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + ++/* Nr of FIR/IIR-coeff banks in ANC-block */ ++#define AB8500_NR_OF_ANC_COEFF_BANKS 2 ++ ++/* Minimum duration to keep ANC IIR Init bit high or ++low before proceeding with the configuration sequence */ ++#define AB8500_ANC_SM_DELAY 2000 ++ ++/* Sidetone states */ ++static const char * const enum_sid_state[] = { ++ "Unconfigured", ++ "Apply FIR", ++ "FIR is configured", ++}; ++enum sid_state { ++ SID_UNCONFIGURED = 0, ++ SID_APPLY_FIR = 1, ++ SID_FIR_CONFIGURED = 2, ++}; ++ + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; ++ struct mutex ctrl_lock; ++ ++ /* Sidetone */ ++ enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -616,6 +642,9 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + ++ SND_SOC_DAPM_INPUT("ANC Configure Input"), ++ SND_SOC_DAPM_OUTPUT("ANC Configure Output"), ++ + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -658,6 +687,10 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + ++ /* ANC Configure */ ++ {"ANC Configure Input", NULL, "Main Supply"}, ++ {"ANC Configure Output", NULL, "ANC Configure Input"}, ++ + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -939,6 +972,75 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + ++/* ++ * Control-events ++ */ ++ ++static int sid_status_control_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ ++ guard(mutex)(&drvdata->ctrl_lock); ++ ucontrol->value.enumerated.item[0] = drvdata->sid_status; ++ ++ return 0; ++} ++ ++/* Write sidetone FIR-coefficients configuration sequence */ ++static int sid_status_control_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); ++ unsigned int param, sidconf; ++ int status = 1; ++ ++ dev_dbg(component->dev, "%s: Enter\n", __func__); ++ ++ if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { ++ dev_err(component->dev, ++ "%s: ERROR: This control supports '%s' only!\n", ++ __func__, enum_sid_state[SID_APPLY_FIR]); ++ return -EIO; ++ } ++ ++ guard(mutex)(&drvdata->ctrl_lock); ++ ++ sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); ++ if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { ++ if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { ++ dev_err(component->dev, "%s: Sidetone busy while off!\n", ++ __func__); ++ status = -EPERM; ++ } else { ++ status = -EBUSY; ++ } ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ return status; ++ } ++ ++ snd_soc_component_write(component, AB8500_SIDFIRADR, 0); ++ ++ for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); ++ snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); ++ } ++ ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), ++ BIT(AB8500_SIDFIRADR_FIRSIDSET)); ++ snd_soc_component_update_bits(component, AB8500_SIDFIRADR, ++ BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); ++ ++ drvdata->sid_status = SID_FIR_CONFIGURED; ++ ++ dev_dbg(component->dev, "%s: Exit\n", __func__); ++ ++ return status; ++} ++ + /* + * Controls - Non-DAPM ASoC + */ +@@ -1222,6 +1324,9 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + ++/* Sidetone */ ++static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); ++ + /* ANC */ + + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1512,6 +1617,8 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ ++ SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, ++ sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2041,8 +2148,10 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + + static int ab8500_codec_probe(struct snd_soc_component *component) + { ++ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; ++ struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2075,6 +2184,10 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + ++ snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); ++ ++ mutex_init(&drvdata->ctrl_lock); ++ + return status; + } + +@@ -2103,6 +2216,7 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; ++ drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c +--- a/sound/soc/codecs/ad1836.c ++++ b/sound/soc/codecs/ad1836.c +@@ -193,9 +193,15 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 ad1836_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, ++ .auto_selectable_formats = &ad1836_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define AD183X_DAI(_name, num_dacs, num_adcs) \ +diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c +--- a/sound/soc/codecs/ad193x.c ++++ b/sound/soc/codecs/ad193x.c +@@ -394,6 +394,14 @@ static int ad193x_startup(struct snd_pcm_substream *substream, + &constr); + } + ++static const u64 ad193x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + static const struct snd_soc_dai_ops ad193x_dai_ops = { + .startup = ad193x_startup, + .hw_params = ad193x_hw_params, +@@ -401,6 +409,8 @@ static const struct snd_soc_dai_ops ad193x_dai_ops = { + .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, + .set_fmt = ad193x_set_dai_fmt, ++ .auto_selectable_formats = &ad193x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/adau7118-hw.c b/sound/soc/codecs/adau7118-hw.c +--- a/sound/soc/codecs/adau7118-hw.c ++++ b/sound/soc/codecs/adau7118-hw.c +@@ -22,7 +22,7 @@ static const struct of_device_id adau7118_of_match[] = { + MODULE_DEVICE_TABLE(of, adau7118_of_match); + + static const struct platform_device_id adau7118_id[] = { +- { .name = "adau7118" }, ++ { .name = "adau7118" }, + { } + }; + MODULE_DEVICE_TABLE(platform, adau7118_id); +diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c +--- a/sound/soc/codecs/adav80x.c ++++ b/sound/soc/codecs/adav80x.c +@@ -743,11 +743,19 @@ static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + adav80x->rate = 0; + } + ++static const u64 adav80x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, ++ .auto_selectable_formats = &adav80x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ +diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c +--- a/sound/soc/codecs/ak4613.c ++++ b/sound/soc/codecs/ak4613.c +@@ -95,6 +95,7 @@ + * see + * AK4613_ENABLE_TDM_TEST + */ ++#include + #include + #include + #include +@@ -384,7 +385,7 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + priv->cnt--; + if (priv->cnt < 0) { + dev_err(dev, "unexpected counter error\n"); +@@ -392,7 +393,6 @@ static void ak4613_dai_shutdown(struct snd_pcm_substream *substream, + } + if (!priv->cnt) + priv->ctrl1 = 0; +- mutex_unlock(&priv->lock); + } + + static void ak4613_hw_constraints(struct ak4613_priv *priv, +@@ -507,10 +507,9 @@ static int ak4613_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct ak4613_priv *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->lock); ++ guard(mutex)(&priv->lock); + ak4613_hw_constraints(priv, substream); + priv->cnt++; +- mutex_unlock(&priv->lock); + + return 0; + } +@@ -599,45 +598,47 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + */ + ret = -EINVAL; + +- mutex_lock(&priv->lock); +- if (priv->cnt > 1) { +- /* +- * If it was already working, use current priv->ctrl1 +- */ +- ret = 0; +- } else { +- /* +- * It is not yet working, +- */ +- unsigned int channel = params_channels(params); +- u8 tdm; ++ scoped_guard(mutex, &priv->lock) { ++ if (priv->cnt > 1) { ++ /* ++ * If it was already working, use current priv->ctrl1 ++ */ ++ ret = 0; ++ } else { ++ /* ++ * It is not yet working, ++ */ ++ unsigned int channel = params_channels(params); ++ u8 tdm; + +- /* STEREO or TDM */ +- if (channel == 2) +- tdm = AK4613_CONFIG_MODE_STEREO; +- else +- tdm = AK4613_CONFIG_GET(priv, MODE); ++ /* STEREO or TDM */ ++ if (channel == 2) ++ tdm = AK4613_CONFIG_MODE_STEREO; ++ else ++ tdm = AK4613_CONFIG_GET(priv, MODE); + +- for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { +- const struct ak4613_interface *iface = ak4613_iface + i; ++ for (i = ARRAY_SIZE(ak4613_iface) - 1; i >= 0; i--) { ++ const struct ak4613_interface *iface = ak4613_iface + i; + +- if ((iface->fmt == fmt) && (iface->width == width)) { +- /* +- * Ctrl1 +- * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | +- * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| +- * < tdm > < iface->dif > +- */ +- priv->ctrl1 = (tdm << 6) | (iface->dif << 3); +- ret = 0; +- break; ++ if (iface->fmt == fmt && iface->width == width) { ++ /* ++ * Ctrl1 ++ * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | ++ * |TDM1|TDM0|DIF2|DIF1|DIF0|ATS1|ATS0|SMUTE| ++ * < tdm > < iface->dif > ++ */ ++ priv->ctrl1 = (tdm << 6) | (iface->dif << 3); ++ ret = 0; ++ break; ++ } + } + } + } +- mutex_unlock(&priv->lock); + +- if (ret < 0) +- goto hw_params_end; ++ if (ret < 0) { ++ dev_warn(dev, "unsupported data width/format combination\n"); ++ return ret; ++ } + + snd_soc_component_update_bits(component, CTRL1, FMT_MASK, priv->ctrl1); + snd_soc_component_update_bits(component, CTRL2, DFS_MASK, ctrl2); +@@ -645,10 +646,6 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, + snd_soc_component_update_bits(component, ICTRL, ICTRL_MASK, priv->ic); + snd_soc_component_update_bits(component, OCTRL, OCTRL_MASK, priv->oc); + +-hw_params_end: +- if (ret < 0) +- dev_warn(dev, "unsupported data width/format combination\n"); +- + return ret; + } + +diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c +--- a/sound/soc/codecs/arizona-jack.c ++++ b/sound/soc/codecs/arizona-jack.c +@@ -5,6 +5,7 @@ + * Copyright (C) 2012-2014 Wolfson Microelectronics plc + */ + ++#include + #include + #include + #include +@@ -707,15 +708,13 @@ static void arizona_micd_timeout_work(struct work_struct *work) + struct arizona_priv, + micd_timeout_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + dev_dbg(info->arizona->dev, "MICD timed out, reporting HP\n"); + + info->detecting = false; + + arizona_identify_headphone(info); +- +- mutex_unlock(&info->lock); + } + + static int arizona_micd_adc_read(struct arizona_priv *info) +@@ -921,12 +920,11 @@ static void arizona_micd_detect(struct work_struct *work) + + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + /* If the cable was removed while measuring ignore the result */ + if (!(info->jack->status & SND_JACK_MECHANICAL)) { + dev_dbg(arizona->dev, "Ignoring MICDET for removed cable\n"); +- mutex_unlock(&info->lock); + return; + } + +@@ -936,7 +934,6 @@ static void arizona_micd_detect(struct work_struct *work) + arizona_button_reading(info); + + pm_runtime_mark_last_busy(arizona->dev); +- mutex_unlock(&info->lock); + } + + static irqreturn_t arizona_micdet(int irq, void *data) +@@ -948,10 +945,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) + cancel_delayed_work_sync(&info->micd_detect_work); + cancel_delayed_work_sync(&info->micd_timeout_work); + +- mutex_lock(&info->lock); +- if (!info->detecting) +- debounce = 0; +- mutex_unlock(&info->lock); ++ scoped_guard(mutex, &info->lock) { ++ if (!info->detecting) ++ debounce = 0; ++ } + + if (debounce) + queue_delayed_work(system_power_efficient_wq, +@@ -969,9 +966,8 @@ static void arizona_hpdet_work(struct work_struct *work) + struct arizona_priv, + hpdet_work.work); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + arizona_start_hpdet_acc_id(info); +- mutex_unlock(&info->lock); + } + + static int arizona_hpdet_wait(struct arizona_priv *info) +@@ -1018,9 +1014,9 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work); + +- pm_runtime_get_sync(arizona->dev); ++ guard(pm_runtime_active_auto)(arizona->dev); + +- mutex_lock(&info->lock); ++ guard(mutex)(&info->lock); + + if (info->micd_clamp) { + mask = ARIZONA_MICD_CLAMP_STS; +@@ -1036,8 +1032,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); + if (ret) { + dev_err(arizona->dev, "Failed to read jackdet status: %d\n", ret); +- mutex_unlock(&info->lock); +- pm_runtime_put_autosuspend(arizona->dev); + return IRQ_NONE; + } + +@@ -1056,62 +1050,61 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + &info->micd_timeout_work, + msecs_to_jiffies(micd_timeout)); + } ++ } else { ++ info->last_jackdet = val; + +- goto out; +- } +- info->last_jackdet = val; ++ if (info->last_jackdet == present) { ++ dev_dbg(arizona->dev, "Detected jack\n"); ++ snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); + +- if (info->last_jackdet == present) { +- dev_dbg(arizona->dev, "Detected jack\n"); +- snd_soc_jack_report(info->jack, SND_JACK_MECHANICAL, SND_JACK_MECHANICAL); ++ info->detecting = true; ++ info->mic = false; ++ info->jack_flips = 0; + +- info->detecting = true; +- info->mic = false; +- info->jack_flips = 0; ++ if (!arizona->pdata.hpdet_acc_id) { ++ arizona_start_mic(info); ++ } else { ++ queue_delayed_work(system_power_efficient_wq, ++ &info->hpdet_work, ++ msecs_to_jiffies(HPDET_DEBOUNCE)); ++ } + +- if (!arizona->pdata.hpdet_acc_id) { +- arizona_start_mic(info); ++ if (info->micd_clamp || !arizona->pdata.jd_invert) ++ regmap_update_bits(arizona->regmap, ++ ARIZONA_JACK_DETECT_DEBOUNCE, ++ ARIZONA_MICD_CLAMP_DB | ++ ARIZONA_JD1_DB, 0); + } else { +- queue_delayed_work(system_power_efficient_wq, +- &info->hpdet_work, +- msecs_to_jiffies(HPDET_DEBOUNCE)); +- } ++ dev_dbg(arizona->dev, "Detected jack removal\n"); ++ ++ arizona_stop_mic(info); ++ ++ info->num_hpdet_res = 0; ++ for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) ++ info->hpdet_res[i] = 0; ++ info->mic = false; ++ info->hpdet_done = false; ++ info->hpdet_retried = false; ++ ++ snd_soc_jack_report(info->jack, 0, ++ ARIZONA_JACK_MASK | info->micd_button_mask); ++ ++ /* ++ * If the jack was removed during a headphone detection we ++ * need to wait for the headphone detection to finish, as ++ * it can not be aborted. We don't want to be able to start ++ * a new headphone detection from a fresh insert until this ++ * one is finished. ++ */ ++ arizona_hpdet_wait(info); + +- if (info->micd_clamp || !arizona->pdata.jd_invert) + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | +- ARIZONA_JD1_DB, 0); +- } else { +- dev_dbg(arizona->dev, "Detected jack removal\n"); +- +- arizona_stop_mic(info); +- +- info->num_hpdet_res = 0; +- for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) +- info->hpdet_res[i] = 0; +- info->mic = false; +- info->hpdet_done = false; +- info->hpdet_retried = false; +- +- snd_soc_jack_report(info->jack, 0, ARIZONA_JACK_MASK | info->micd_button_mask); +- +- /* +- * If the jack was removed during a headphone detection we +- * need to wait for the headphone detection to finish, as +- * it can not be aborted. We don't want to be able to start +- * a new headphone detection from a fresh insert until this +- * one is finished. +- */ +- arizona_hpdet_wait(info); +- +- regmap_update_bits(arizona->regmap, +- ARIZONA_JACK_DETECT_DEBOUNCE, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, +- ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, ++ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); ++ } + } + +-out: + /* Clear trig_sts to make sure DCVDD is not forced up */ + regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, + ARIZONA_MICD_CLAMP_FALL_TRIG_STS | +@@ -1119,10 +1112,6 @@ static irqreturn_t arizona_jackdet(int irq, void *data) + ARIZONA_JD1_FALL_TRIG_STS | + ARIZONA_JD1_RISE_TRIG_STS); + +- mutex_unlock(&info->lock); +- +- pm_runtime_put_autosuspend(arizona->dev); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c +--- a/sound/soc/codecs/arizona.c ++++ b/sound/soc/codecs/arizona.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -1158,17 +1159,16 @@ int arizona_dvfs_up(struct snd_soc_component *component, unsigned int flags) + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + if (!priv->dvfs_cached && !priv->dvfs_reqs) { + ret = arizona_dvfs_enable(component); + if (ret) +- goto err; ++ return ret; + } + + priv->dvfs_reqs |= flags; +-err: +- mutex_unlock(&priv->dvfs_lock); ++ + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_up); +@@ -1179,7 +1179,7 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + unsigned int old_reqs; + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + old_reqs = priv->dvfs_reqs; + priv->dvfs_reqs &= ~flags; +@@ -1187,7 +1187,6 @@ int arizona_dvfs_down(struct snd_soc_component *component, unsigned int flags) + if (!priv->dvfs_cached && old_reqs && !priv->dvfs_reqs) + ret = arizona_dvfs_disable(component); + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_down); +@@ -1199,7 +1198,7 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + struct arizona_priv *priv = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&priv->dvfs_lock); ++ guard(mutex)(&priv->dvfs_lock); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +@@ -1222,7 +1221,6 @@ int arizona_dvfs_sysclk_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&priv->dvfs_lock); + return ret; + } + EXPORT_SYMBOL_GPL(arizona_dvfs_sysclk_ev); +@@ -1657,13 +1655,11 @@ static void arizona_wm5102_set_dac_comp(struct snd_soc_component *component, + { 0x80, 0x0 }, + }; + +- mutex_lock(&arizona->dac_comp_lock); +- +- dac_comp[1].def = arizona->dac_comp_coeff; +- if (rate >= 176400) +- dac_comp[2].def = arizona->dac_comp_enabled; +- +- mutex_unlock(&arizona->dac_comp_lock); ++ scoped_guard(mutex, &arizona->dac_comp_lock) { ++ dac_comp[1].def = arizona->dac_comp_coeff; ++ if (rate >= 176400) ++ dac_comp[2].def = arizona->dac_comp_enabled; ++ } + + regmap_multi_reg_write(arizona->regmap, + dac_comp, +diff --git a/sound/soc/codecs/aw87390.c b/sound/soc/codecs/aw87390.c +--- a/sound/soc/codecs/aw87390.c ++++ b/sound/soc/codecs/aw87390.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -225,11 +226,10 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + struct aw87390 *aw87390 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + ret = aw87390_dev_set_profile_index(aw87390->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change\n"); +- mutex_unlock(&aw87390->lock); + return 0; + } + +@@ -238,8 +238,6 @@ static int aw87390_profile_set(struct snd_kcontrol *kcontrol, + aw87390_power_on(aw87390->aw_pa); + } + +- mutex_unlock(&aw87390->lock); +- + return 1; + } + +@@ -250,7 +248,7 @@ static const struct snd_kcontrol_new aw87390_controls[] = { + + static int aw87390_request_firmware_file(struct aw87390 *aw87390) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw87390->aw_pa->fw_status = AW87390_DEV_FW_FAILED; +@@ -265,14 +263,11 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + + aw87390->aw_cfg = devm_kzalloc(aw87390->aw_pa->dev, + struct_size(aw87390->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw87390->aw_cfg) { +- release_firmware(cont); ++ if (!aw87390->aw_cfg) + return -ENOMEM; +- } + + aw87390->aw_cfg->len = cont->size; + memcpy(aw87390->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) { +@@ -280,14 +275,12 @@ static int aw87390_request_firmware_file(struct aw87390 *aw87390) + return ret; + } + +- mutex_lock(&aw87390->lock); ++ guard(mutex)(&aw87390->lock); + + ret = aw88395_dev_cfg_load(aw87390->aw_pa, aw87390->aw_cfg); + if (ret) + dev_err(aw87390->aw_pa->dev, "aw_dev acf parse failed\n"); + +- mutex_unlock(&aw87390->lock); +- + return ret; + } + +diff --git a/sound/soc/codecs/aw88081.c b/sound/soc/codecs/aw88081.c +--- a/sound/soc/codecs/aw88081.c ++++ b/sound/soc/codecs/aw88081.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -763,9 +764,8 @@ static void aw88081_startup_work(struct work_struct *work) + struct aw88081 *aw88081 = + container_of(work, struct aw88081, start_work.work); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + aw88081_start_pa(aw88081); +- mutex_unlock(&aw88081->lock); + } + + static void aw88081_start(struct aw88081 *aw88081, bool sync_start) +@@ -942,11 +942,10 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + ret = aw88081_dev_set_profile_index(aw88081->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88081->lock); + return 0; + } + +@@ -955,8 +954,6 @@ static int aw88081_profile_set(struct snd_kcontrol *kcontrol, + aw88081_start(aw88081, AW88081_SYNC_START); + } + +- mutex_unlock(&aw88081->lock); +- + return 1; + } + +@@ -1136,7 +1133,7 @@ static int aw88081_dev_init(struct aw88081 *aw88081, struct aw_container *aw_cfg + + static int aw88081_request_firmware_file(struct aw88081 *aw88081) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -1150,26 +1147,21 @@ static int aw88081_request_firmware_file(struct aw88081 *aw88081) + AW88081_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88081->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); + + aw88081->aw_cfg = aw_cfg; + +- release_firmware(cont); +- + ret = aw88395_dev_load_acf_check(aw88081->aw_pa, aw88081->aw_cfg); + if (ret) + return ret; + +- mutex_lock(&aw88081->lock); +- ret = aw88081_dev_init(aw88081, aw88081->aw_cfg); +- mutex_unlock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + +- return ret; ++ return aw88081_dev_init(aw88081, aw88081->aw_cfg); + } + + static int aw88081_playback_event(struct snd_soc_dapm_widget *w, +@@ -1178,7 +1170,7 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88081 *aw88081 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88081->lock); ++ guard(mutex)(&aw88081->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88081_start(aw88081, AW88081_ASYNC_START); +@@ -1189,7 +1181,6 @@ static int aw88081_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88081->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88166.c b/sound/soc/codecs/aw88166.c +--- a/sound/soc/codecs/aw88166.c ++++ b/sound/soc/codecs/aw88166.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -1173,9 +1174,8 @@ static void aw88166_startup_work(struct work_struct *work) + struct aw88166 *aw88166 = + container_of(work, struct aw88166, start_work.work); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + aw88166_start_pa(aw88166); +- mutex_unlock(&aw88166->lock); + } + + static void aw88166_start(struct aw88166 *aw88166, bool sync_start) +@@ -1413,11 +1413,10 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + ret = aw88166_dev_set_profile_index(aw88166->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88166->lock); + return 0; + } + +@@ -1426,8 +1425,6 @@ static int aw88166_profile_set(struct snd_kcontrol *kcontrol, + aw88166_start(aw88166, AW88166_SYNC_START); + } + +- mutex_unlock(&aw88166->lock); +- + return 1; + } + +@@ -1573,7 +1570,7 @@ static int aw88166_dev_init(struct aw88166 *aw88166, struct aw_container *aw_cfg + + static int aw88166_request_firmware_file(struct aw88166 *aw88166) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + const char *fw_name; + int ret; + +@@ -1593,13 +1590,11 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + + aw88166->aw_cfg = devm_kzalloc(aw88166->aw_pa->dev, + struct_size(aw88166->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88166->aw_cfg) { +- release_firmware(cont); ++ if (!aw88166->aw_cfg) + return -ENOMEM; +- } ++ + aw88166->aw_cfg->len = (int)cont->size; + memcpy(aw88166->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88166->aw_pa, aw88166->aw_cfg); + if (ret) { +@@ -1607,12 +1602,12 @@ static int aw88166_request_firmware_file(struct aw88166 *aw88166) + return ret; + } + +- mutex_lock(&aw88166->lock); +- /* aw device init */ +- ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); +- if (ret) +- dev_err(aw88166->aw_pa->dev, "dev init failed\n"); +- mutex_unlock(&aw88166->lock); ++ scoped_guard(mutex, &aw88166->lock) { ++ /* aw device init */ ++ ret = aw88166_dev_init(aw88166, aw88166->aw_cfg); ++ if (ret) ++ dev_err(aw88166->aw_pa->dev, "dev init failed\n"); ++ } + + return ret; + } +@@ -1639,7 +1634,7 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88166 *aw88166 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88166->lock); ++ guard(mutex)(&aw88166->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88166_start(aw88166, AW88166_ASYNC_START); +@@ -1650,7 +1645,6 @@ static int aw88166_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88166->lock); + + return 0; + } +diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c +--- a/sound/soc/codecs/aw88261.c ++++ b/sound/soc/codecs/aw88261.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -960,11 +961,10 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + ret = aw88261_dev_set_profile_index(aw88261->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88261->lock); + return 0; + } + +@@ -973,8 +973,6 @@ static int aw88261_profile_set(struct snd_kcontrol *kcontrol, + aw88261_start(aw88261); + } + +- mutex_unlock(&aw88261->lock); +- + return 1; + } + +@@ -1038,7 +1036,7 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88261->lock); ++ guard(mutex)(&aw88261->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88261_start(aw88261); +@@ -1049,7 +1047,6 @@ static int aw88261_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88261->lock); + + return 0; + } +@@ -1152,7 +1149,7 @@ static int aw88261_dev_init(struct aw88261 *aw88261, struct aw_container *aw_cfg + + static int aw88261_request_firmware_file(struct aw88261 *aw88261) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + const char *fw_name; + int ret; +@@ -1172,13 +1169,11 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + fw_name, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88261->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88261->aw_cfg = aw_cfg; + +@@ -1188,12 +1183,12 @@ static int aw88261_request_firmware_file(struct aw88261 *aw88261) + return ret; + } + +- mutex_lock(&aw88261->lock); +- /* aw device init */ +- ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); +- if (ret) +- dev_err(aw88261->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88261->lock); ++ scoped_guard(mutex, &aw88261->lock) { ++ /* aw device init */ ++ ret = aw88261_dev_init(aw88261, aw88261->aw_cfg); ++ if (ret) ++ dev_err(aw88261->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395.c b/sound/soc/codecs/aw88395/aw88395.c +--- a/sound/soc/codecs/aw88395/aw88395.c ++++ b/sound/soc/codecs/aw88395/aw88395.c +@@ -8,6 +8,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -51,9 +52,8 @@ static void aw88395_startup_work(struct work_struct *work) + struct aw88395 *aw88395 = + container_of(work, struct aw88395, start_work.work); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + aw88395_start_pa(aw88395); +- mutex_unlock(&aw88395->lock); + } + + static void aw88395_start(struct aw88395 *aw88395, bool sync_start) +@@ -224,11 +224,10 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + int ret; + + /* pa stop or stopping just set profile */ +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + ret = aw88395_dev_set_profile_index(aw88395->aw_pa, ucontrol->value.integer.value[0]); + if (ret < 0) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88395->lock); + return 0; + } + +@@ -237,8 +236,6 @@ static int aw88395_profile_set(struct snd_kcontrol *kcontrol, + aw88395_start(aw88395, AW88395_SYNC_START); + } + +- mutex_unlock(&aw88395->lock); +- + return 1; + } + +@@ -366,7 +363,7 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88395 *aw88395 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88395->lock); ++ guard(mutex)(&aw88395->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88395_start(aw88395, AW88395_ASYNC_START); +@@ -377,7 +374,6 @@ static int aw88395_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88395->lock); + + return 0; + } +@@ -461,7 +457,7 @@ static void aw88395_hw_reset(struct aw88395 *aw88395) + + static int aw88395_request_firmware_file(struct aw88395 *aw88395) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + struct aw_container *aw_cfg; + int ret; + +@@ -477,13 +473,11 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + AW88395_ACF_FILE, cont ? cont->size : 0); + + aw_cfg = devm_kzalloc(aw88395->aw_pa->dev, struct_size(aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw_cfg) { +- release_firmware(cont); ++ if (!aw_cfg) + return -ENOMEM; +- } ++ + aw_cfg->len = (int)cont->size; + memcpy(aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + aw88395->aw_cfg = aw_cfg; + +@@ -495,12 +489,12 @@ static int aw88395_request_firmware_file(struct aw88395 *aw88395) + + dev_dbg(aw88395->aw_pa->dev, "%s : bin load success\n", __func__); + +- mutex_lock(&aw88395->lock); +- /* aw device init */ +- ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); +- if (ret < 0) +- dev_err(aw88395->aw_pa->dev, "dev init failed"); +- mutex_unlock(&aw88395->lock); ++ scoped_guard(mutex, &aw88395->lock) { ++ /* aw device init */ ++ ret = aw88395_dev_init(aw88395->aw_pa, aw88395->aw_cfg); ++ if (ret < 0) ++ dev_err(aw88395->aw_pa->dev, "dev init failed"); ++ } + + return ret; + } +diff --git a/sound/soc/codecs/aw88395/aw88395_device.c b/sound/soc/codecs/aw88395/aw88395_device.c +--- a/sound/soc/codecs/aw88395/aw88395_device.c ++++ b/sound/soc/codecs/aw88395/aw88395_device.c +@@ -8,6 +8,7 @@ + // Author: Ben Yi + // + ++#include + #include + #include + #include +@@ -70,7 +71,7 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_write_16bit(aw_dev, dsp_addr, dsp_data); +@@ -93,7 +94,6 @@ int aw_dev_dsp_write(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -156,7 +156,7 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + u32 reg_value; + int ret; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + switch (data_type) { + case AW_DSP_16_DATA: + ret = aw_dev_dsp_read_16bit(aw_dev, dsp_addr, dsp_data); +@@ -179,7 +179,6 @@ int aw_dev_dsp_read(struct aw_device *aw_dev, + /* clear dsp chip select state*/ + if (regmap_read(aw_dev->regmap, AW88395_ID_REG, ®_value)) + dev_err(aw_dev->dev, "%s fail to clear chip state. Err=%d\n", __func__, ret); +- mutex_unlock(&aw_dev->dsp_lock); + + return ret; + } +@@ -1110,42 +1109,36 @@ static int aw_dev_dsp_update_container(struct aw_device *aw_dev, + #ifdef AW88395_DSP_I2C_WRITES + u32 tmp_len; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + + for (i = 0; i < len; i += AW88395_MAX_RAM_WRITE_BYTE_SIZE) { + tmp_len = min(len - i, AW88395_MAX_RAM_WRITE_BYTE_SIZE); + ret = regmap_raw_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + &data[i], tmp_len); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #else + __be16 reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* i2c write */ + ret = regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, base); + if (ret) +- goto error_operation; ++ return ret; + for (i = 0; i < len; i += 2) { + reg_val = cpu_to_be16p((u16 *)(data + i)); + ret = regmap_write(aw_dev->regmap, AW88395_DSPMDAT_REG, + (u16)reg_val); + if (ret) +- goto error_operation; ++ return ret; + } +- mutex_unlock(&aw_dev->dsp_lock); + #endif + + return 0; +- +-error_operation: +- mutex_unlock(&aw_dev->dsp_lock); +- return ret; + } + + static int aw_dev_dsp_update_fw(struct aw_device *aw_dev, +@@ -1231,14 +1224,14 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + { + unsigned int reg_val; + +- mutex_lock(&aw_dev->dsp_lock); ++ guard(mutex)(&aw_dev->dsp_lock); + /* check the odd bits of reg 0x40 */ + regmap_write(aw_dev->regmap, AW88395_DSPMADD_REG, AW88395_DSP_ODD_NUM_BIT_TEST); + regmap_read(aw_dev->regmap, AW88395_DSPMADD_REG, ®_val); + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 odd bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check the even bits of reg 0x40 */ +@@ -1247,7 +1240,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check reg 0x40 even bit failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_fw_base_addr */ +@@ -1256,7 +1249,7 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_EVEN_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp fw addr failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_EVEN_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } + + /* check dsp_cfg_base_addr */ +@@ -1265,15 +1258,10 @@ static int aw_dev_check_sram(struct aw_device *aw_dev) + if (reg_val != AW88395_DSP_ODD_NUM_BIT_TEST) { + dev_err(aw_dev->dev, "check dsp cfg failed, read[0x%x] != write[0x%x]", + reg_val, AW88395_DSP_ODD_NUM_BIT_TEST); +- goto error; ++ return -EPERM; + } +- mutex_unlock(&aw_dev->dsp_lock); + + return 0; +- +-error: +- mutex_unlock(&aw_dev->dsp_lock); +- return -EPERM; + } + + int aw88395_dev_fw_update(struct aw_device *aw_dev, bool up_dsp_fw_en, bool force_up_en) +diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c +--- a/sound/soc/codecs/aw88399-lib.c ++++ b/sound/soc/codecs/aw88399-lib.c +@@ -1282,7 +1282,7 @@ static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg + + int aw88399_request_firmware_file(struct aw88399 *aw88399) + { +- const struct firmware *cont = NULL; ++ const struct firmware *cont __free(firmware) = NULL; + int ret; + + aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; +@@ -1298,13 +1298,11 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399) + + aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, + struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); +- if (!aw88399->aw_cfg) { +- release_firmware(cont); ++ if (!aw88399->aw_cfg) + return -ENOMEM; +- } ++ + aw88399->aw_cfg->len = (int)cont->size; + memcpy(aw88399->aw_cfg->data, cont->data, cont->size); +- release_firmware(cont); + + ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); + if (ret) { +diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c +--- a/sound/soc/codecs/aw88399.c ++++ b/sound/soc/codecs/aw88399.c +@@ -7,6 +7,7 @@ + // Author: Weidong Wang + // + ++#include + #include + #include + #include +@@ -497,11 +498,10 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(codec); + int ret; + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + ret = aw88399_dev_set_profile_index(aw88399->aw_pa, ucontrol->value.integer.value[0]); + if (ret) { + dev_dbg(codec->dev, "profile index does not change"); +- mutex_unlock(&aw88399->lock); + return 0; + } + +@@ -510,8 +510,6 @@ static int aw88399_profile_set(struct snd_kcontrol *kcontrol, + aw88399_start(aw88399, AW88399_SYNC_START); + } + +- mutex_unlock(&aw88399->lock); +- + return 1; + } + +@@ -690,7 +688,7 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct aw88399 *aw88399 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&aw88399->lock); ++ guard(mutex)(&aw88399->lock); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + aw88399_start(aw88399, AW88399_ASYNC_START); +@@ -701,7 +699,6 @@ static int aw88399_playback_event(struct snd_soc_dapm_widget *w, + default: + break; + } +- mutex_unlock(&aw88399->lock); + + return 0; + } +diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c +--- a/sound/soc/codecs/bt-sco.c ++++ b/sound/soc/codecs/bt-sco.c +@@ -85,13 +85,9 @@ static int bt_sco_probe(struct platform_device *pdev) + } + + static const struct platform_device_id bt_sco_driver_ids[] = { +- { +- .name = "dfbmcs320", +- }, +- { +- .name = "bt-sco", +- }, +- {}, ++ { .name = "dfbmcs320" }, ++ { .name = "bt-sco" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); + +diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c +--- a/sound/soc/codecs/cpcap.c ++++ b/sound/soc/codecs/cpcap.c +@@ -26,14 +26,6 @@ + /* Register 9 - CPCAP_REG_INTS2 --- Interrupt Sense 2 */ + #define CPCAP_BIT_PTT_S 11 /* Push To Talk */ + +-/* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */ +-#define CPCAP_BIT_AUDIO_LOW_PWR 6 +-#define CPCAP_BIT_AUD_LOWPWR_SPEED 5 +-#define CPCAP_BIT_VAUDIOPRISTBY 4 +-#define CPCAP_BIT_VAUDIO_MODE1 2 +-#define CPCAP_BIT_VAUDIO_MODE0 1 +-#define CPCAP_BIT_V_AUDIO_EN 0 +- + /* Register 513 CPCAP_REG_CC --- CODEC */ + #define CPCAP_BIT_CDC_CLK2 15 + #define CPCAP_BIT_CDC_CLK1 14 +@@ -239,7 +231,6 @@ struct cpcap_reg_info { + }; + + static const struct cpcap_reg_info cpcap_default_regs[] = { +- { CPCAP_REG_VAUDIOC, 0x003F, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, +@@ -1265,6 +1256,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = dai->component; + struct device *dev = component->dev; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1298,7 +1290,7 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + return err; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, rtd->dai_link->dai_fmt); + } + + static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, +@@ -1391,121 +1383,8 @@ static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + return 0; + } + +- +-/* +- * Configure codec for voice call if requested. +- * +- * We can configure most with snd_soc_dai_set_sysclk(), snd_soc_dai_set_fmt() +- * and snd_soc_dai_set_tdm_slot(). This function configures the rest of the +- * cpcap related hardware as CPU is not involved in the voice call. +- */ +-static int cpcap_voice_call(struct cpcap_audio *cpcap, struct snd_soc_dai *dai, +- bool voice_call) +-{ +- int mask, err; +- +- /* Modem to codec VAUDIO_MODE1 */ +- mask = BIT(CPCAP_BIT_VAUDIO_MODE1); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Clear MIC1_MUX for call */ +- mask = BIT(CPCAP_BIT_MIC1_MUX); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? 0 : mask); +- if (err) +- return err; +- +- /* Set MIC2_MUX for call */ +- mask = BIT(CPCAP_BIT_MB_ON1L) | BIT(CPCAP_BIT_MB_ON1R) | +- BIT(CPCAP_BIT_MIC2_MUX) | BIT(CPCAP_BIT_MIC2_PGA_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable LDSP for call */ +- mask = BIT(CPCAP_BIT_A2_LDSP_L_EN) | BIT(CPCAP_BIT_A2_LDSP_R_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable CPCAP_BIT_PGA_CDC_EN for call */ +- mask = BIT(CPCAP_BIT_PGA_CDC_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Unmute voice for call */ +- if (dai) { +- err = snd_soc_dai_digital_mute(dai, !voice_call, +- SNDRV_PCM_STREAM_PLAYBACK); +- if (err) +- return err; +- } +- +- /* Set modem to codec mic CDC and HPF for call */ +- mask = BIT(CPCAP_BIT_MIC2_CDC_EN) | BIT(CPCAP_BIT_CDC_EN_RX) | +- BIT(CPCAP_BIT_AUDOHPF_1) | BIT(CPCAP_BIT_AUDOHPF_0) | +- BIT(CPCAP_BIT_AUDIHPF_1) | BIT(CPCAP_BIT_AUDIHPF_0); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, +- mask, voice_call ? mask : 0); +- if (err) +- return err; +- +- /* Enable modem to codec CDC for call*/ +- mask = BIT(CPCAP_BIT_CDC_CLK_EN); +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- mask, voice_call ? mask : 0); +- +- return err; +-} +- +-static int cpcap_voice_set_tdm_slot(struct snd_soc_dai *dai, +- unsigned int tx_mask, unsigned int rx_mask, +- int slots, int slot_width) +-{ +- struct snd_soc_component *component = dai->component; +- struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +- int err, ts_mask, mask; +- bool voice_call; +- +- /* +- * Primitive test for voice call, probably needs more checks +- * later on for 16-bit calls detected, Bluetooth headset etc. +- */ +- if (tx_mask == 0 && rx_mask == 1 && slot_width == 8) +- voice_call = true; +- else +- voice_call = false; +- +- ts_mask = 0x7 << CPCAP_BIT_MIC2_TIMESLOT0; +- ts_mask |= 0x7 << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- mask = (tx_mask & 0x7) << CPCAP_BIT_MIC2_TIMESLOT0; +- mask |= (rx_mask & 0x7) << CPCAP_BIT_MIC1_RX_TIMESLOT0; +- +- err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, +- ts_mask, mask); +- if (err) +- return err; +- +- err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, slot_width * 1000); +- if (err) +- return err; +- +- err = cpcap_voice_call(cpcap, dai, voice_call); +- if (err) +- return err; +- +- return 0; +-} +- +-static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int cpcap_voice_set_mute(struct snd_soc_dai *dai, ++ int mute, int direction) + { + struct snd_soc_component *component = dai->component; + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); +@@ -1526,7 +1405,6 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, +- .set_tdm_slot = cpcap_voice_set_tdm_slot, + .mute_stream = cpcap_voice_set_mute, + .no_capture_mute = 1, + }; +diff --git a/sound/soc/codecs/cros_ec_codec.c b/sound/soc/codecs/cros_ec_codec.c +--- a/sound/soc/codecs/cros_ec_codec.c ++++ b/sound/soc/codecs/cros_ec_codec.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -319,10 +320,18 @@ static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + (uint8_t *)&p, sizeof(p), NULL, 0); + } + ++static const u64 i2s_rx_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops i2s_rx_dai_ops = { + .hw_params = i2s_rx_hw_params, + .set_fmt = i2s_rx_set_fmt, + .set_bclk_ratio = i2s_rx_set_bclk_ratio, ++ .auto_selectable_formats = &i2s_rx_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static int i2s_rx_event(struct snd_soc_dapm_widget *w, +@@ -608,10 +617,10 @@ static void wov_copy_work(struct work_struct *w) + container_of(w, struct cros_ec_codec_priv, wov_copy_work.work); + int ret; + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + if (!priv->wov_substream) { + dev_warn(priv->dev, "no pcm substream\n"); +- goto leave; ++ return; + } + + if (ec_codec_capable(priv, EC_CODEC_CAP_WOV_AUDIO_SHM)) +@@ -624,8 +633,6 @@ static void wov_copy_work(struct work_struct *w) + msecs_to_jiffies(10)); + else if (ret) + dev_err(priv->dev, "failed to read audio data\n"); +-leave: +- mutex_unlock(&priv->wov_dma_lock); + } + + static int wov_enable_get(struct snd_kcontrol *kcontrol, +@@ -895,12 +902,11 @@ static int wov_pcm_hw_params(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); ++ guard(mutex)(&priv->wov_dma_lock); + priv->wov_substream = substream; + priv->wov_rp = priv->wov_wp = 0; + priv->wov_dma_offset = 0; + priv->wov_burst_read = true; +- mutex_unlock(&priv->wov_dma_lock); + + return 0; + } +@@ -911,10 +917,10 @@ static int wov_pcm_hw_free(struct snd_soc_component *component, + struct cros_ec_codec_priv *priv = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->wov_dma_lock); +- wov_queue_dequeue(priv, wov_queue_size(priv)); +- priv->wov_substream = NULL; +- mutex_unlock(&priv->wov_dma_lock); ++ scoped_guard(mutex, &priv->wov_dma_lock) { ++ wov_queue_dequeue(priv, wov_queue_size(priv)); ++ priv->wov_substream = NULL; ++ } + + cancel_delayed_work_sync(&priv->wov_copy_work); + +diff --git a/sound/soc/codecs/cs-amp-lib.c b/sound/soc/codecs/cs-amp-lib.c +--- a/sound/soc/codecs/cs-amp-lib.c ++++ b/sound/soc/codecs/cs-amp-lib.c +@@ -7,6 +7,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -83,10 +84,12 @@ static int cs_amp_write_cal_coeff(struct cs_dsp *dsp, + KUNIT_STATIC_STUB_REDIRECT(cs_amp_write_cal_coeff, dsp, controls, ctl_name, val); + + if (IS_REACHABLE(CONFIG_FW_CS_DSP)) { +- mutex_lock(&dsp->pwr_lock); +- cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, controls->mem_region, controls->alg_id); +- ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); +- mutex_unlock(&dsp->pwr_lock); ++ scoped_guard(mutex, &dsp->pwr_lock) { ++ cs_ctl = cs_dsp_get_ctl(dsp, ctl_name, ++ controls->mem_region, ++ controls->alg_id); ++ ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, &beval, sizeof(beval)); ++ } + + if (ret < 0) { + dev_err(dsp->dev, "Failed to write to '%s': %d\n", ctl_name, ret); +diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c +--- a/sound/soc/codecs/cs35l32.c ++++ b/sound/soc/codecs/cs35l32.c +@@ -538,7 +538,15 @@ static int cs35l32_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs35l32->reset_gpio, 1); + + regcache_cache_only(cs35l32->regmap, false); +- regcache_sync(cs35l32->regmap); ++ ret = regcache_sync(cs35l32->regmap); ++ if (ret) { ++ regcache_cache_only(cs35l32->regmap, true); ++ regcache_mark_dirty(cs35l32->regmap); ++ gpiod_set_value_cansleep(cs35l32->reset_gpio, 0); ++ regulator_bulk_disable(ARRAY_SIZE(cs35l32->supplies), ++ cs35l32->supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -231,7 +231,7 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + * a soft reset. + */ + if (cs35l56->base.init_done) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + out: + pm_runtime_put_autosuspend(cs35l56->base.dev); +@@ -240,47 +240,17 @@ static void cs35l56_sdw_init(struct sdw_slave *peripheral) + static int cs35l56_sdw_interrupt(struct sdw_slave *peripheral, + struct sdw_slave_intr_status *status) + { +- struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +- +- /* SoundWire core holds our pm_runtime when calling this function. */ +- +- dev_dbg(cs35l56->base.dev, "int control_port=%#x\n", status->control_port); +- +- if ((status->control_port & SDW_SCP_INT1_IMPL_DEF) == 0) +- return 0; +- + /* +- * Prevent bus manager suspending and possibly issuing a +- * bus-reset before the queued work has run. ++ * The IRQ itself was handled through the regmap_irq handler, this is ++ * just clearing up the additional Cirrus SoundWire registers that are ++ * not covered by the SoundWire framework or the IRQ handler itself. + */ +- pm_runtime_get_noresume(cs35l56->base.dev); +- +- /* +- * Mask and clear until it has been handled. +- * None of the interrupts are time-critical so use the +- * power-efficient queue. +- */ +- cs35l56_mask_soundwire_interrupts(peripheral); +- queue_work(system_power_efficient_wq, &cs35l56->sdw_irq_work); ++ sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + + return 0; + } + +-static void cs35l56_sdw_irq_work(struct work_struct *work) +-{ +- struct cs35l56_private *cs35l56 = container_of(work, +- struct cs35l56_private, +- sdw_irq_work); +- +- cs35l56_irq(-1, &cs35l56->base); +- +- /* unmask interrupts */ +- if (!cs35l56->sdw_irq_no_unmask) +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); +- +- pm_runtime_put_autosuspend(cs35l56->base.dev); +-} +- + static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); +@@ -302,6 +272,7 @@ static int cs35l56_sdw_read_prop(struct sdw_slave *peripheral) + prop->source_ports = BIT(CS35L56_SDW1_CAPTURE_PORT); + prop->sink_ports = BIT(CS35L56_SDW1_PLAYBACK_PORT); + prop->paging_support = true; ++ prop->use_domain_irq = true; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | SDW_SCP_INT1_IMPL_DEF; + +@@ -406,7 +377,7 @@ static int __maybe_unused cs35l56_sdw_runtime_resume(struct device *dev) + if (ret) + return ret; + +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + + return 0; + } +@@ -418,21 +389,12 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + if (!cs35l56->base.init_done) + return 0; + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ /* runtime_resume unmasks the interrupt */ ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } + +-static int __maybe_unused cs35l56_sdw_system_resume(struct device *dev) +-{ +- struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); +- +- cs35l56->sdw_irq_no_unmask = false; +- /* runtime_resume re-enables the interrupt */ +- +- return cs35l56_system_resume(dev); +-} +- + static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_device_id *id) + { + struct device *dev = &peripheral->dev; +@@ -447,7 +409,6 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + cs35l56->base.dev = dev; + cs35l56->sdw_peripheral = peripheral; + cs35l56->sdw_link_num = peripheral->bus->link_id; +- INIT_WORK(&cs35l56->sdw_irq_work, cs35l56_sdw_irq_work); + + dev_set_drvdata(dev, cs35l56); + +@@ -457,6 +418,7 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + regmap_config = &cs35l56_regmap_sdw; + break; + case 0x3563: ++ case 0x3562: + regmap_config = &cs35l63_regmap_sdw; + break; + default: +@@ -483,21 +445,21 @@ static int cs35l56_sdw_probe(struct sdw_slave *peripheral, const struct sdw_devi + /* Start in cache-only until device is enumerated */ + regcache_cache_only(cs35l56->base.regmap, true); + +- return cs35l56_common_probe(cs35l56, -EINVAL); ++ return cs35l56_common_probe(cs35l56, peripheral->irq); + } + + static void cs35l56_sdw_remove(struct sdw_slave *peripheral) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(&peripheral->dev); + +- cs35l56_disable_sdw_interrupts(cs35l56); ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + cs35l56_remove(cs35l56); + } + + static const struct dev_pm_ops cs35l56_sdw_pm = { + SET_RUNTIME_PM_OPS(cs35l56_sdw_runtime_suspend, cs35l56_sdw_runtime_resume, NULL) +- SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_sdw_system_resume) ++ SYSTEM_SLEEP_PM_OPS(cs35l56_sdw_system_suspend, cs35l56_system_resume) + LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_system_suspend_late, cs35l56_system_resume_early) + /* NOIRQ stage not needed, SoundWire doesn't use a hard IRQ */ + }; +@@ -505,6 +467,7 @@ static const struct dev_pm_ops cs35l56_sdw_pm = { + static const struct sdw_device_id cs35l56_sdw_id[] = { + SDW_SLAVE_ENTRY(0x01FA, 0x3556, 0x3556), + SDW_SLAVE_ENTRY(0x01FA, 0x3557, 0x3557), ++ SDW_SLAVE_ENTRY(0x01FA, 0x3562, 0x3562), + SDW_SLAVE_ENTRY(0x01FA, 0x3563, 0x3563), + {}, + }; +diff --git a/sound/soc/codecs/cs35l56-shared.c b/sound/soc/codecs/cs35l56-shared.c +--- a/sound/soc/codecs/cs35l56-shared.c ++++ b/sound/soc/codecs/cs35l56-shared.c +@@ -96,6 +96,7 @@ int cs35l56_set_patch(struct cs35l56_base *cs35l56_base) + ARRAY_SIZE(cs35l56_patch_fw)); + break; + case 0x63: ++ case 0x62: + ret = regmap_register_patch(cs35l56_base->regmap, cs35l63_patch_fw, + ARRAY_SIZE(cs35l63_patch_fw)); + break; +@@ -389,6 +390,7 @@ static void cs35l56_set_fw_reg_table(struct cs35l56_base *cs35l56_base) + } + break; + case 0x63: ++ case 0x62: + cs35l56_base->fw_reg = &cs35l63_fw_reg; + break; + } +@@ -595,6 +597,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + break; + case 0x63: ++ case 0x62: + regmap_multi_reg_write_bypassed(cs35l56_base->regmap, + cs35l63_system_reset_seq, + ARRAY_SIZE(cs35l63_system_reset_seq)); +@@ -613,26 +616,7 @@ void cs35l56_system_reset(struct cs35l56_base *cs35l56_base, bool is_soundwire) + } + EXPORT_SYMBOL_NS_GPL(cs35l56_system_reset, "SND_SOC_CS35L56_SHARED"); + +-int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) +-{ +- int ret; +- +- if (irq < 1) +- return 0; +- +- ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, +- IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, +- "cs35l56", cs35l56_base); +- if (!ret) +- cs35l56_base->irq = irq; +- else +- dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); +- +- return ret; +-} +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); +- +-irqreturn_t cs35l56_irq(int irq, void *data) ++static irqreturn_t cs35l56_irq(int irq, void *data) + { + struct cs35l56_base *cs35l56_base = data; + unsigned int status1 = 0, status8 = 0, status20 = 0; +@@ -640,23 +624,22 @@ irqreturn_t cs35l56_irq(int irq, void *data) + unsigned int val; + int rv; + +- irqreturn_t ret = IRQ_NONE; +- + if (!cs35l56_base->init_done) + return IRQ_NONE; + +- mutex_lock(&cs35l56_base->irq_lock); ++ guard(mutex)(&cs35l56_base->irq_lock); + +- rv = pm_runtime_resume_and_get(cs35l56_base->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED(cs35l56_base->dev, pm); ++ rv = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (rv < 0) { + dev_err(cs35l56_base->dev, "irq: failed to get pm_runtime: %d\n", rv); +- goto err_unlock; ++ return IRQ_NONE; + } + + regmap_read(cs35l56_base->regmap, CS35L56_IRQ1_STATUS, &val); + if ((val & CS35L56_IRQ1_STS_MASK) == 0) { + dev_dbg(cs35l56_base->dev, "Spurious IRQ: no pending interrupt\n"); +- goto err; ++ return IRQ_NONE; + } + + /* Ack interrupts */ +@@ -680,7 +663,7 @@ irqreturn_t cs35l56_irq(int irq, void *data) + + /* Check to see if unmasked bits are active */ + if (!status1 && !status8 && !status20) +- goto err; ++ return IRQ_NONE; + + if (status1 & CS35L56_AMP_SHORT_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Amp short error\n"); +@@ -688,16 +671,27 @@ irqreturn_t cs35l56_irq(int irq, void *data) + if (status8 & CS35L56_TEMP_ERR_EINT1_MASK) + dev_crit(cs35l56_base->dev, "Overtemp error\n"); + +- ret = IRQ_HANDLED; ++ return IRQ_HANDLED; ++} + +-err: +- pm_runtime_put(cs35l56_base->dev); +-err_unlock: +- mutex_unlock(&cs35l56_base->irq_lock); ++int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq) ++{ ++ int ret; ++ ++ if (irq < 1) ++ return 0; ++ ++ ret = devm_request_threaded_irq(cs35l56_base->dev, irq, NULL, cs35l56_irq, ++ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_LOW, ++ "cs35l56", cs35l56_base); ++ if (!ret) ++ cs35l56_base->irq = irq; ++ else ++ dev_err(cs35l56_base->dev, "Failed to get IRQ: %d\n", ret); + + return ret; + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_irq, "SND_SOC_CS35L56_SHARED"); ++EXPORT_SYMBOL_NS_GPL(cs35l56_irq_request, "SND_SOC_CS35L56_SHARED"); + + int cs35l56_is_fw_reload_needed(struct cs35l56_base *cs35l56_base) + { +@@ -816,14 +810,10 @@ int cs35l56_runtime_resume_common(struct cs35l56_base *cs35l56_base, bool is_sou + if (!cs35l56_base->init_done) + return 0; + +- if (!cs35l56_base->can_hibernate) +- goto out_sync; +- +- /* Must be done before releasing cache-only */ +- if (!is_soundwire) ++ /* Hibernate wake must be done before releasing cache-only */ ++ if (cs35l56_base->can_hibernate && !is_soundwire) + cs35l56_issue_wake_event(cs35l56_base); + +-out_sync: + ret = cs35l56_wait_for_firmware_boot(cs35l56_base); + if (ret) { + dev_err(cs35l56_base->dev, "Hibernate wake failed: %d\n", ret); +@@ -1470,6 +1460,7 @@ int cs35l56_hw_init(struct cs35l56_base *cs35l56_base) + cs35l56_base->calibration_controls = &cs35l56_calibration_controls; + break; + case 0x35A630: ++ case 0x35A620: + cs35l56_base->calibration_controls = &cs35l63_calibration_controls; + devid = devid >> 4; + break; +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -18,11 +18,9 @@ + #include + #include + #include +-#include + #include + #include + #include +-#include + #include + #include + #include +@@ -39,55 +37,49 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + +-/* +- * snd_soc_register_component() can call component_probe() on all instances +- * in a card, so deferred registration must be protected across all instances. +- */ +-static DEFINE_MUTEX(cs35l56_component_register_lock); +-static bool cs35l56_shutting_down; +- +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* ++ * Mask unconditionally. ++ * + * The read of GEN_INT_STAT_1 is required as per the SoundWire spec + * for interrupt status bits to clear. + * GEN_INT_MASK_1 masks the _inputs_ to GEN_INT_STAT1. + */ +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); +- sdw_read_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1); +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, 0); ++ sdw_read_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1); ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_STAT_1, 0xFF); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_mask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral) ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { +- sdw_write_no_pm(peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); ++ if (!cs35l56->base.irq) ++ return; ++ ++ sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, ++ CS35L56_SDW_INT_MASK_CODEC_IRQ); + } + EXPORT_SYMBOL_NS_GPL(cs35l56_unmask_soundwire_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56) + { + if (!cs35l56->sdw_peripheral) + return; + +- cs35l56->sdw_irq_no_unmask = true; +- flush_work(&cs35l56->sdw_irq_work); +- +- /* Mask interrupts and flush in case sdw_irq_work was queued again */ +- cs35l56_mask_soundwire_interrupts(cs35l56->sdw_peripheral); +- flush_work(&cs35l56->sdw_irq_work); ++ cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->base.irq) ++ disable_irq(cs35l56->base.irq); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_disable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) ++static void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56) + { +- if (!cs35l56->sdw_peripheral) ++ if (!cs35l56->sdw_peripheral || !cs35l56->base.irq) + return; + +- cs35l56->sdw_irq_no_unmask = false; +- cs35l56_unmask_soundwire_interrupts(cs35l56->sdw_peripheral); ++ enable_irq(cs35l56->base.irq); ++ cs35l56_unmask_soundwire_interrupts(cs35l56); + } +-EXPORT_SYMBOL_NS_GPL(cs35l56_enable_sdw_interrupts, "SND_SOC_CS35L56_CORE"); + + static int cs35l56_dsp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); +@@ -837,11 +829,7 @@ static void cs35l56_patch(struct cs35l56_private *cs35l56, bool firmware_missing + { + int ret; + +- /* +- * Disable SoundWire interrupts to prevent race with IRQ work. +- * Setting sdw_irq_no_unmask prevents the handler re-enabling +- * the SoundWire interrupt. +- */ ++ /* Disable SoundWire interrupts to prevent race with IRQ handler thread */ + cs35l56_disable_sdw_interrupts(cs35l56); + + ret = cs35l56_firmware_shutdown(&cs35l56->base); +@@ -1405,6 +1393,7 @@ static int _cs35l56_component_probe(struct snd_soc_component *component) + ARRAY_SIZE(cs35l56_controls)); + break; + case 0x63: ++ case 0x62: + ret = snd_soc_add_component_controls(component, cs35l63_controls, + ARRAY_SIZE(cs35l63_controls)); + break; +@@ -1968,11 +1957,6 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); + int ret; + +- guard(mutex)(&cs35l56_component_register_lock); +- +- if (cs35l56_shutting_down) +- return; +- + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2233,37 +2217,6 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif + +-static int cs35l56_reboot_notify(struct notifier_block *nb, +- unsigned long action, void *data) +-{ +- guard(mutex)(&cs35l56_component_register_lock); +- cs35l56_shutting_down = true; +- +- return NOTIFY_DONE; +-} +- +-static struct notifier_block cs35l56_reboot_notifier = { +- .notifier_call = cs35l56_reboot_notify, +-}; +- +-static int __init cs35l56_modinit(void) +-{ +- /* +- * Use reboot notifier to prevent race between shutdown and +- * snd_soc_register_component(). Driver shutdown() callback would +- * run too late, after device_shutdown() is already walking the +- * device list that component registration can modify. +- */ +- return register_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_init(cs35l56_modinit); +- +-static void __exit cs35l56_modexit(void) +-{ +- unregister_reboot_notifier(&cs35l56_reboot_notifier); +-} +-module_exit(cs35l56_modexit); +- + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -32,7 +32,6 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; +- struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -40,11 +39,8 @@ struct cs35l56_private { + struct sdw_slave *sdw_peripheral; + struct regmap *sdw_bus_regmap; + const char *fallback_fw_suffix; +- struct work_struct sdw_irq_work; +- bool sdw_irq_no_unmask; + bool soft_resetting; + bool sdw_attached; +- bool component_registered; + struct completion init_completion; + + int speaker_id; +@@ -67,10 +63,8 @@ static inline struct cs35l56_private *cs35l56_private_from_base(struct cs35l56_b + + extern const struct dev_pm_ops cs35l56_pm_ops_i2c_spi; + +-void cs35l56_mask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_unmask_soundwire_interrupts(struct sdw_slave *peripheral); +-void cs35l56_disable_sdw_interrupts(struct cs35l56_private *cs35l56); +-void cs35l56_enable_sdw_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56); ++void cs35l56_unmask_soundwire_interrupts(struct cs35l56_private *cs35l56); + + int cs35l56_system_suspend(struct device *dev); + int cs35l56_system_suspend_late(struct device *dev); +@@ -78,7 +72,6 @@ int cs35l56_system_suspend_no_irq(struct device *dev); + int cs35l56_system_resume_no_irq(struct device *dev); + int cs35l56_system_resume_early(struct device *dev); + int cs35l56_system_resume(struct device *dev); +-irqreturn_t cs35l56_irq(int irq, void *data); + int cs35l56_irq_request(struct cs35l56_base *cs35l56_base, int irq); + int cs35l56_common_probe(struct cs35l56_private *cs35l56, int irq); + int cs35l56_init(struct cs35l56_private *cs35l56); +diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c +--- a/sound/soc/codecs/cs40l50-codec.c ++++ b/sound/soc/codecs/cs40l50-codec.c +@@ -288,8 +288,8 @@ static int cs40l50_codec_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id cs40l50_id[] = { +- { "cs40l50-codec", }, +- {} ++ { .name = "cs40l50-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs40l50_id); + +diff --git a/sound/soc/codecs/cs42l42-sdw.c b/sound/soc/codecs/cs42l42-sdw.c +--- a/sound/soc/codecs/cs42l42-sdw.c ++++ b/sound/soc/codecs/cs42l42-sdw.c +@@ -481,10 +481,20 @@ static int cs42l42_sdw_runtime_resume(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + + /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ ret = regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ if (ret) ++ goto err_sync; ++ ++ ret = regcache_sync(cs42l42->regmap); ++ if (ret) ++ goto err_sync; + + return 0; ++ ++err_sync: ++ regcache_cache_only(cs42l42->regmap, true); ++ regcache_mark_dirty(cs42l42->regmap); ++ return ret; + } + + static int cs42l42_sdw_resume(struct device *dev) +diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c +--- a/sound/soc/codecs/cs42l42.c ++++ b/sound/soc/codecs/cs42l42.c +@@ -9,6 +9,7 @@ + * Author: Michael White + */ + ++#include + #include + #include + #include +@@ -565,7 +566,7 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l42->irq_lock); ++ guard(mutex)(&cs42l42->irq_lock); + cs42l42->jack = jk; + + if (jk) { +@@ -581,7 +582,6 @@ static int cs42l42_set_jack(struct snd_soc_component *component, struct snd_soc_ + break; + } + } +- mutex_unlock(&cs42l42->irq_lock); + + return 0; + } +@@ -1668,13 +1668,10 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + unsigned int current_button_status; + unsigned int i; + +- pm_runtime_get_sync(cs42l42->dev); +- mutex_lock(&cs42l42->irq_lock); +- if (cs42l42->suspended || !cs42l42->init_done) { +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); ++ guard(pm_runtime_active_auto)(cs42l42->dev); ++ guard(mutex)(&cs42l42->irq_lock); ++ if (cs42l42->suspended || !cs42l42->init_done) + return IRQ_NONE; +- } + + /* Read sticky registers to clear interurpt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { +@@ -1774,9 +1771,6 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l42->irq_lock); +- pm_runtime_put_autosuspend(cs42l42->dev); +- + return IRQ_HANDLED; + } + EXPORT_SYMBOL_NS_GPL(cs42l42_irq_thread, "SND_SOC_CS42L42_CORE"); +@@ -2163,23 +2157,23 @@ int cs42l42_suspend(struct device *dev) + * future interrupts. This ensures a safe disable if the interrupt + * is shared. + */ +- mutex_lock(&cs42l42->irq_lock); +- cs42l42->suspended = true; ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ cs42l42->suspended = true; + +- /* Save register values that will be overwritten by shutdown sequence */ +- for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { +- regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); +- save_regs[i] = (u8)reg; ++ /* Save register values that will be overwritten by shutdown sequence */ ++ for (i = 0; i < ARRAY_SIZE(cs42l42_shutdown_seq); ++i) { ++ regmap_read(cs42l42->regmap, cs42l42_shutdown_seq[i].reg, ®); ++ save_regs[i] = (u8)reg; ++ } ++ ++ /* Shutdown codec */ ++ regmap_multi_reg_write(cs42l42->regmap, ++ cs42l42_shutdown_seq, ++ ARRAY_SIZE(cs42l42_shutdown_seq)); ++ ++ /* All interrupt sources are now disabled */ + } + +- /* Shutdown codec */ +- regmap_multi_reg_write(cs42l42->regmap, +- cs42l42_shutdown_seq, +- ARRAY_SIZE(cs42l42_shutdown_seq)); +- +- /* All interrupt sources are now disabled */ +- mutex_unlock(&cs42l42->irq_lock); +- + /* Wait for power-down complete */ + msleep(CS42L42_PDN_DONE_TIME_MS); + ret = regmap_read_poll_timeout(cs42l42->regmap, +@@ -2250,13 +2244,13 @@ void cs42l42_resume_restore(struct device *dev) + regcache_cache_only(cs42l42->regmap, false); + regcache_mark_dirty(cs42l42->regmap); + +- mutex_lock(&cs42l42->irq_lock); +- /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ +- regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); +- regcache_sync(cs42l42->regmap); ++ scoped_guard(mutex, &cs42l42->irq_lock) { ++ /* Sync LATCH_TO_VP first so the VP domain registers sync correctly */ ++ regcache_sync_region(cs42l42->regmap, CS42L42_MIC_DET_CTL1, CS42L42_MIC_DET_CTL1); ++ regcache_sync(cs42l42->regmap); + +- cs42l42->suspended = false; +- mutex_unlock(&cs42l42->irq_lock); ++ cs42l42->suspended = false; ++ } + + dev_dbg(dev, "System resumed\n"); + } +diff --git a/sound/soc/codecs/cs42l43-jack.c b/sound/soc/codecs/cs42l43-jack.c +--- a/sound/soc/codecs/cs42l43-jack.c ++++ b/sound/soc/codecs/cs42l43-jack.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + + #include ++#include + #include + #include + #include +@@ -67,6 +68,21 @@ static int cs42l43_find_index(struct cs42l43_codec *priv, const char * const pro + return -EINVAL; + } + ++static void cs42l43_apply_accdet_config(struct cs42l43_codec *priv, ++ unsigned int autocontrol, ++ unsigned int pdncntl) ++{ ++ struct cs42l43 *cs42l43 = priv->core; ++ ++ regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, ++ CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | ++ CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); ++ regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, ++ CS42L43_RING_SENSE_EN_MASK, pdncntl); ++ ++ dev_dbg(priv->dev, "Successfully configured accessory detect\n"); ++} ++ + int cs42l43_set_jack(struct snd_soc_component *component, + struct snd_soc_jack *jack, void *d) + { +@@ -80,31 +96,34 @@ int cs42l43_set_jack(struct snd_soc_component *component, + + dev_dbg(priv->dev, "Configure accessory detect\n"); + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for jack config: %d\n", ret); + return ret; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + priv->jack_hp = jack; + +- if (!jack) +- goto done; ++ if (!jack) { ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); ++ return 0; ++ } + + ret = device_property_count_u32(cs42l43->dev, "cirrus,buttons-ohms"); + if (ret != -EINVAL) { + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,buttons-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + + if (ret > CS42L43_N_BUTTONS) { + ret = -EINVAL; + dev_err(priv->dev, "Property cirrus,buttons-ohms too many entries\n"); +- goto error; ++ return ret; + } + + ret = device_property_read_u32_array(cs42l43->dev, "cirrus,buttons-ohms", +@@ -112,7 +131,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + if (ret < 0) { + dev_err(priv->dev, "Property cirrus,button-ohms malformed: %d\n", + ret); +- goto error; ++ return ret; + } + } else { + priv->buttons[0] = 70; +@@ -124,7 +143,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + ret = cs42l43_find_index(priv, "cirrus,detect-us", 50000, &priv->detect_us, + cs42l43_accdet_us, ARRAY_SIZE(cs42l43_accdet_us)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_AUTO_HSDET_TIME_SHIFT; + +@@ -134,7 +153,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_ramp_ms, cs42l43_accdet_ramp_ms, + ARRAY_SIZE(cs42l43_accdet_ramp_ms)); + if (ret < 0) +- goto error; ++ return ret; + + hs2 |= ret << CS42L43_HSBIAS_RAMP_SHIFT; + +@@ -142,7 +161,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->bias_sense_ua, cs42l43_accdet_bias_sense, + ARRAY_SIZE(cs42l43_accdet_bias_sense)); + if (ret < 0) +- goto error; ++ return ret; + + if (priv->bias_sense_ua) + autocontrol |= ret << CS42L43_HSBIAS_SENSE_TRIP_SHIFT; +@@ -154,7 +173,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_debounce_ms); + if (ret < 0 && ret != -EINVAL) { + dev_err(priv->dev, "Property cirrus,tip-debounce-ms malformed: %d\n", ret); +- goto error; ++ return ret; + } + + /* This tip sense invert is set normally, as TIPSENSE_INV already inverted */ +@@ -170,7 +189,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_fall_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_FALLING_DB_TIME_SHIFT; + +@@ -178,7 +197,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + &priv->tip_rise_db_ms, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + tip_deb |= ret << CS42L43_TIPSENSE_RISING_DB_TIME_SHIFT; + +@@ -199,7 +218,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_FALLING_DB_TIME_SHIFT; + +@@ -207,7 +226,7 @@ int cs42l43_set_jack(struct snd_soc_component *component, + NULL, cs42l43_accdet_db_ms, + ARRAY_SIZE(cs42l43_accdet_db_ms)); + if (ret < 0) +- goto error; ++ return ret; + + ring_deb |= ret << CS42L43_RINGSENSE_RISING_DB_TIME_SHIFT; + pdncntl |= CS42L43_RING_SENSE_EN_MASK; +@@ -228,23 +247,9 @@ int cs42l43_set_jack(struct snd_soc_component *component, + CS42L43_HSBIAS_RAMP_MASK | CS42L43_HSDET_MODE_MASK | + CS42L43_AUTO_HSDET_TIME_MASK, hs2); + +-done: +- ret = 0; ++ cs42l43_apply_accdet_config(priv, autocontrol, pdncntl); + +- regmap_update_bits(cs42l43->regmap, CS42L43_HS_BIAS_SENSE_AND_CLAMP_AUTOCONTROL, +- CS42L43_JACKDET_MODE_MASK | CS42L43_S0_AUTO_ADCMUTE_DISABLE_MASK | +- CS42L43_HSBIAS_SENSE_TRIP_MASK, autocontrol); +- regmap_update_bits(cs42l43->regmap, CS42L43_PDNCNTL, +- CS42L43_RING_SENSE_EN_MASK, pdncntl); +- +- dev_dbg(priv->dev, "Successfully configured accessory detect\n"); +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return ret; ++ return 0; + } + + static void cs42l43_start_hs_bias(struct cs42l43_codec *priv, bool type_detect) +@@ -370,22 +375,22 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + { + struct cs42l43_codec *priv = data; + struct cs42l43 *cs42l43 = priv->core; +- irqreturn_t iret = IRQ_NONE; + unsigned int buttons = 0; + unsigned int val = 0; + int i, ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button press: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button press IRQ\n"); +- goto error; ++ return IRQ_NONE; + } + + // Wait for 2 full cycles of comb filter to ensure good reading +@@ -396,12 +401,12 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + /* Bail if jack removed, the button is irrelevant and likely invalid */ + if (!cs42l43_jack_present(priv)) { + dev_dbg(priv->dev, "Button ignored due to removal\n"); +- goto error; ++ return IRQ_NONE; + } + + if (val & CS42L43_HSBIAS_CLAMP_STS_MASK) { + dev_dbg(priv->dev, "Button ignored due to bias sense\n"); +- goto error; ++ return IRQ_NONE; + } + + val = (val & CS42L43_HSDET_DC_STS_MASK) >> CS42L43_HSDET_DC_STS_SHIFT; +@@ -424,45 +429,32 @@ irqreturn_t cs42l43_button_press(int irq, void *data) + + snd_soc_jack_report(priv->jack_hp, buttons, CS42L43_JACK_BUTTONS); + +- iret = IRQ_HANDLED; +- +-error: +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + irqreturn_t cs42l43_button_release(int irq, void *data) + { + struct cs42l43_codec *priv = data; +- irqreturn_t iret = IRQ_NONE; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for button release: %d\n", ret); +- return iret; ++ return IRQ_NONE; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (priv->button_detect_running) { +- dev_dbg(priv->dev, "Button release IRQ\n"); +- +- snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); +- +- iret = IRQ_HANDLED; +- } else { ++ if (!priv->button_detect_running) { + dev_dbg(priv->dev, "Spurious button release IRQ\n"); ++ return IRQ_NONE; + } + +- mutex_unlock(&priv->jack_lock); ++ dev_dbg(priv->dev, "Button release IRQ\n"); ++ snd_soc_jack_report(priv->jack_hp, 0, CS42L43_JACK_BUTTONS); + +- pm_runtime_put_autosuspend(priv->dev); +- +- return iret; ++ return IRQ_HANDLED; + } + + void cs42l43_bias_sense_timeout(struct work_struct *work) +@@ -472,13 +464,14 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- ret = pm_runtime_resume_and_get(priv->dev); ++ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(priv->dev, pm); ++ ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) { + dev_err(priv->dev, "Failed to resume for bias sense: %d\n", ret); + return; + } + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + + if (cs42l43_jack_present(priv) && priv->button_detect_running) { + dev_dbg(priv->dev, "Bias sense timeout out, restore bias\n"); +@@ -491,10 +484,6 @@ void cs42l43_bias_sense_timeout(struct work_struct *work) + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK, + CS42L43_AUTO_HSBIAS_CLAMP_EN_MASK); + } +- +- mutex_unlock(&priv->jack_lock); +- +- pm_runtime_put_autosuspend(priv->dev); + } + + static const struct reg_sequence cs42l43_3pole_patch[] = { +@@ -896,9 +885,8 @@ int cs42l43_jack_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + ucontrol->value.integer.value[0] = priv->jack_override; +- mutex_unlock(&priv->jack_lock); + + return 0; + } +@@ -914,17 +902,13 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + if (override >= e->items) + return -EINVAL; + +- mutex_lock(&priv->jack_lock); ++ guard(mutex)(&priv->jack_lock); + +- if (!cs42l43_jack_present(priv)) { +- mutex_unlock(&priv->jack_lock); ++ if (!cs42l43_jack_present(priv)) + return -EBUSY; +- } + +- if (override == priv->jack_override) { +- mutex_unlock(&priv->jack_lock); ++ if (override == priv->jack_override) + return 0; +- } + + priv->jack_override = override; + +@@ -984,7 +968,5 @@ int cs42l43_jack_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *u + cs42l43_jack_override_modes[override].report); + } + +- mutex_unlock(&priv->jack_lock); +- + return 1; + } +diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c +--- a/sound/soc/codecs/cs42l43.c ++++ b/sound/soc/codecs/cs42l43.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1207,14 +1208,12 @@ static void cs42l43_spk_vu_sync(struct cs42l43_codec *priv) + { + struct cs42l43 *cs42l43 = priv->core; + +- mutex_lock(&priv->spk_vu_lock); ++ guard(mutex)(&priv->spk_vu_lock); + + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, CS42L43_AMP1_2_VU_MASK); + regmap_update_bits(cs42l43->regmap, CS42L43_INTP_VOLUME_CTRL1, + CS42L43_AMP1_2_VU_MASK, 0); +- +- mutex_unlock(&priv->spk_vu_lock); + } + + static int cs42l43_shutter_get(struct cs42l43_codec *priv, unsigned int shift) +@@ -1601,7 +1600,7 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + struct cs42l43 *cs42l43 = priv->core; + int ret; + +- mutex_lock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1626,8 +1625,6 @@ static int cs42l43_pll_ev(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&cs42l43->pll_lock); +- + return ret; + } + +@@ -2565,13 +2562,10 @@ static int cs42l43_set_sysclk(struct snd_soc_component *component, int clk_id, + { + struct cs42l43_codec *priv = snd_soc_component_get_drvdata(component); + struct cs42l43 *cs42l43 = priv->core; +- int ret; + +- mutex_lock(&cs42l43->pll_lock); +- ret = cs42l43_set_pll(priv, src, freq); +- mutex_unlock(&cs42l43->pll_lock); ++ guard(mutex)(&cs42l43->pll_lock); + +- return ret; ++ return cs42l43_set_pll(priv, src, freq); + } + + static int cs42l43_component_probe(struct snd_soc_component *component) +@@ -2941,8 +2935,8 @@ static const struct dev_pm_ops cs42l43_codec_pm_ops = { + }; + + static const struct platform_device_id cs42l43_codec_id_table[] = { +- { "cs42l43-codec", }, +- {} ++ { .name = "cs42l43-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, cs42l43_codec_id_table); + +diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c +--- a/sound/soc/codecs/cs42l84.c ++++ b/sound/soc/codecs/cs42l84.c +@@ -10,6 +10,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -281,10 +282,9 @@ static int cs42l84_set_jack(struct snd_soc_component *component, struct snd_soc_ + struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component); + + /* Prevent race with interrupt handler */ +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + cs42l84->jack = jk; + snd_soc_jack_report(jk, cs42l84->hs_type, SND_JACK_HEADSET); +- mutex_unlock(&cs42l84->irq_lock); + + return 0; + } +@@ -831,7 +831,7 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + u8 current_ring_state; + int i; + +- mutex_lock(&cs42l84->irq_lock); ++ guard(mutex)(&cs42l84->irq_lock); + /* Read sticky registers to clear interrupt */ + for (i = 0; i < ARRAY_SIZE(stickies); i++) { + regmap_read(cs42l84->regmap, irq_params_table[i].status_addr, +@@ -902,8 +902,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + break; + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +@@ -919,8 +917,6 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data) + } + } + +- mutex_unlock(&cs42l84->irq_lock); +- + return IRQ_HANDLED; + } + +diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c +--- a/sound/soc/codecs/cs43130.c ++++ b/sound/soc/codecs/cs43130.c +@@ -6,6 +6,7 @@ + * + * Authors: Li Xu + */ ++#include + #include + #include + #include +@@ -818,26 +819,26 @@ static int cs43130_dsd_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (params_rate(params)) { + case 176400: + dsd_speed = 0; +@@ -881,26 +882,26 @@ static int cs43130_hw_params(struct snd_pcm_substream *substream, + unsigned int required_clk; + u8 dsd_speed; + +- mutex_lock(&cs43130->clk_mutex); +- if (!cs43130->clk_req) { +- /* no DAI is currently using clk */ +- if (!(CS43130_MCLK_22M % params_rate(params))) +- required_clk = CS43130_MCLK_22M; +- else +- required_clk = CS43130_MCLK_24M; ++ scoped_guard(mutex, &cs43130->clk_mutex) { ++ if (!cs43130->clk_req) { ++ /* no DAI is currently using clk */ ++ if (!(CS43130_MCLK_22M % params_rate(params))) ++ required_clk = CS43130_MCLK_22M; ++ else ++ required_clk = CS43130_MCLK_24M; + +- cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); +- if (cs43130->pll_bypass) +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); +- else +- cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ cs43130_set_pll(component, 0, 0, cs43130->mclk, required_clk); ++ if (cs43130->pll_bypass) ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT); ++ else ++ cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL); ++ } ++ ++ cs43130->clk_req++; ++ if (cs43130->clk_req == 2) ++ cs43130_pcm_dsd_mix(true, cs43130->regmap); + } + +- cs43130->clk_req++; +- if (cs43130->clk_req == 2) +- cs43130_pcm_dsd_mix(true, cs43130->regmap); +- mutex_unlock(&cs43130->clk_mutex); +- + switch (dai->id) { + case CS43130_ASP_DOP_DAI: + case CS43130_XSP_DOP_DAI: +@@ -988,14 +989,13 @@ static int cs43130_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct cs43130_private *cs43130 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&cs43130->clk_mutex); ++ guard(mutex)(&cs43130->clk_mutex); + cs43130->clk_req--; + if (!cs43130->clk_req) { + /* no DAI is currently using clk */ + cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO); + cs43130_pcm_dsd_mix(false, cs43130->regmap); + } +- mutex_unlock(&cs43130->clk_mutex); + + return 0; + } +diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c +--- a/sound/soc/codecs/cs4349.c ++++ b/sound/soc/codecs/cs4349.c +@@ -340,7 +340,13 @@ static int cs4349_runtime_resume(struct device *dev) + gpiod_set_value_cansleep(cs4349->reset_gpio, 1); + + regcache_cache_only(cs4349->regmap, false); +- regcache_sync(cs4349->regmap); ++ ret = regcache_sync(cs4349->regmap); ++ if (ret) { ++ regcache_cache_only(cs4349->regmap, true); ++ regcache_mark_dirty(cs4349->regmap); ++ gpiod_set_value_cansleep(cs4349->reset_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/cs47l15.c b/sound/soc/codecs/cs47l15.c +--- a/sound/soc/codecs/cs47l15.c ++++ b/sound/soc/codecs/cs47l15.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1285,9 +1286,8 @@ static int cs47l15_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1317,9 +1317,8 @@ static void cs47l15_component_remove(struct snd_soc_component *component) + struct cs47l15 *cs47l15 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l15->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l15->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs47l35.c b/sound/soc/codecs/cs47l35.c +--- a/sound/soc/codecs/cs47l35.c ++++ b/sound/soc/codecs/cs47l35.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1566,9 +1567,8 @@ static int cs47l35_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1600,9 +1600,8 @@ static void cs47l35_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l35->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L35_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l35->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l85.c b/sound/soc/codecs/cs47l85.c +--- a/sound/soc/codecs/cs47l85.c ++++ b/sound/soc/codecs/cs47l85.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2504,9 +2505,8 @@ static int cs47l85_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2537,9 +2537,8 @@ static void cs47l85_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l85->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L85_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l85->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l90.c b/sound/soc/codecs/cs47l90.c +--- a/sound/soc/codecs/cs47l90.c ++++ b/sound/soc/codecs/cs47l90.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -2423,9 +2424,8 @@ static int cs47l90_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -2456,9 +2456,8 @@ static void cs47l90_component_remove(struct snd_soc_component *component) + struct madera *madera = cs47l90->core.madera; + int i; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + for (i = 0; i < CS47L90_NUM_ADSP; i++) + wm_adsp2_component_remove(&cs47l90->core.adsp[i], component); +diff --git a/sound/soc/codecs/cs47l92.c b/sound/soc/codecs/cs47l92.c +--- a/sound/soc/codecs/cs47l92.c ++++ b/sound/soc/codecs/cs47l92.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -1892,9 +1893,8 @@ static int cs47l92_component_probe(struct snd_soc_component *component) + + snd_soc_component_init_regmap(component, madera->regmap); + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = snd_soc_component_to_dapm(component); +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = snd_soc_component_to_dapm(component); + + ret = madera_init_inputs(component); + if (ret) +@@ -1922,9 +1922,8 @@ static void cs47l92_component_remove(struct snd_soc_component *component) + struct cs47l92 *cs47l92 = snd_soc_component_get_drvdata(component); + struct madera *madera = cs47l92->core.madera; + +- mutex_lock(&madera->dapm_ptr_lock); +- madera->dapm = NULL; +- mutex_unlock(&madera->dapm_ptr_lock); ++ scoped_guard(mutex, &madera->dapm_ptr_lock) ++ madera->dapm = NULL; + + wm_adsp2_component_remove(&cs47l92->core.adsp[0], component); + } +diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c +--- a/sound/soc/codecs/cs48l32.c ++++ b/sound/soc/codecs/cs48l32.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,13 @@ static int cs48l32_rate_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v + int ret; + + /* Prevent any mixer mux changes while we do this */ +- mutex_lock(&cs48l32_codec->rate_lock); ++ guard(mutex)(&cs48l32_codec->rate_lock); + + /* The write must be guarded by a number of SYSCLK cycles */ + cs48l32_spin_sysclk(cs48l32_codec); + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + cs48l32_spin_sysclk(cs48l32_codec); + +- mutex_unlock(&cs48l32_codec->rate_lock); +- + return ret; + } + +@@ -2242,7 +2241,6 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + struct cs48l32_dai_priv *dai_priv = &cs48l32_codec->dai[dai->id - 1]; + unsigned int base = dai->driver->base; + unsigned int current_asp_rate, target_asp_rate; +- bool change_rate_domain = false; + int ret; + + if (clk_id == dai_priv->clk) +@@ -2284,19 +2282,15 @@ static int cs48l32_dai_set_sysclk(struct snd_soc_dai *dai, + + if ((current_asp_rate & CS48L32_ASP_RATE_MASK) != + (target_asp_rate & CS48L32_ASP_RATE_MASK)) { +- change_rate_domain = true; +- +- mutex_lock(&cs48l32_codec->rate_lock); + /* Guard the rate change with SYSCLK cycles */ +- cs48l32_spin_sysclk(cs48l32_codec); +- } +- +- snd_soc_component_update_bits(component, base + CS48L32_ASP_CONTROL1, +- CS48L32_ASP_RATE_MASK, target_asp_rate); +- +- if (change_rate_domain) { +- cs48l32_spin_sysclk(cs48l32_codec); +- mutex_unlock(&cs48l32_codec->rate_lock); ++ scoped_guard(mutex, &cs48l32_codec->rate_lock) { ++ cs48l32_spin_sysclk(cs48l32_codec); ++ snd_soc_component_update_bits(component, ++ base + CS48L32_ASP_CONTROL1, ++ CS48L32_ASP_RATE_MASK, ++ target_asp_rate); ++ cs48l32_spin_sysclk(cs48l32_codec); ++ } + } + } + +diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c +--- a/sound/soc/codecs/cx2072x.c ++++ b/sound/soc/codecs/cx2072x.c +@@ -9,6 +9,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -1408,7 +1409,7 @@ static int cx2072x_jack_status_check(void *data) + unsigned int type = 0; + int state = 0; + +- mutex_lock(&cx2072x->lock); ++ guard(mutex)(&cx2072x->lock); + + regmap_read(cx2072x->regmap, CX2072X_PORTA_PIN_SENSE, &jack); + jack = jack >> 24; +@@ -1434,8 +1435,6 @@ static int cx2072x_jack_status_check(void *data) + /* clear interrupt */ + regmap_write(cx2072x->regmap, CX2072X_UM_INTERRUPT_CRTL_E, 0x12 << 24); + +- mutex_unlock(&cx2072x->lock); +- + dev_dbg(codec->dev, "CX2072X_HSDETECT type=0x%X,Jack state = %x\n", + type, state); + return state; +diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c +--- a/sound/soc/codecs/da7213.c ++++ b/sound/soc/codecs/da7213.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -216,13 +217,10 @@ static int da7213_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -230,13 +228,10 @@ static int da7213_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -244,13 +239,10 @@ static int da7213_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -258,13 +250,10 @@ static int da7213_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -465,9 +454,8 @@ static int da7213_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7213->ctrl_lock); +- ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7213->ctrl_lock); ++ scoped_guard(mutex, &da7213->ctrl_lock) ++ ret = regmap_raw_read(da7213->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -499,12 +487,11 @@ static int da7213_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7213->ctrl_lock); ++ guard(mutex)(&da7213->ctrl_lock); + ret = regmap_raw_read(da7213->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7213->regmap, reg, + &val_new, sizeof(val_new)); +- mutex_unlock(&da7213->ctrl_lock); + + if (ret < 0) + return ret; +diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c +--- a/sound/soc/codecs/da7219.c ++++ b/sound/soc/codecs/da7219.c +@@ -8,6 +8,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -256,13 +257,10 @@ static int da7219_volsw_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_volsw(kcontrol, ucontrol); + } + + static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, +@@ -270,13 +268,10 @@ static int da7219_volsw_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_volsw(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_volsw(kcontrol, ucontrol); + } + + static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, +@@ -284,13 +279,10 @@ static int da7219_enum_locked_get(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_get_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_get_enum_double(kcontrol, ucontrol); + } + + static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, +@@ -298,13 +290,10 @@ static int da7219_enum_locked_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = snd_soc_put_enum_double(kcontrol, ucontrol); +- mutex_unlock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + +- return ret; ++ return snd_soc_put_enum_double(kcontrol, ucontrol); + } + + /* ALC */ +@@ -422,9 +411,8 @@ static int da7219_tonegen_freq_get(struct snd_kcontrol *kcontrol, + __le16 val; + int ret; + +- mutex_lock(&da7219->ctrl_lock); +- ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); +- mutex_unlock(&da7219->ctrl_lock); ++ scoped_guard(mutex, &da7219->ctrl_lock) ++ ret = regmap_raw_read(da7219->regmap, reg, &val, sizeof(val)); + + if (ret) + return ret; +@@ -456,12 +444,11 @@ static int da7219_tonegen_freq_put(struct snd_kcontrol *kcontrol, + */ + val_new = cpu_to_le16(ucontrol->value.integer.value[0]); + +- mutex_lock(&da7219->ctrl_lock); ++ guard(mutex)(&da7219->ctrl_lock); + ret = regmap_raw_read(da7219->regmap, reg, &val_old, sizeof(val_old)); + if (ret == 0 && (val_old != val_new)) + ret = regmap_raw_write(da7219->regmap, reg, +- &val_new, sizeof(val_new)); +- mutex_unlock(&da7219->ctrl_lock); ++ &val_new, sizeof(val_new)); + + if (ret < 0) + return ret; +@@ -1167,15 +1154,12 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- if ((da7219->clk_src == clk_id) && (da7219->mclk_rate == freq)) { +- mutex_unlock(&da7219->pll_lock); ++ if (da7219->clk_src == clk_id && da7219->mclk_rate == freq) + return 0; +- } + +- if ((freq < 2000000) || (freq > 54000000)) { +- mutex_unlock(&da7219->pll_lock); ++ if (freq < 2000000 || freq > 54000000) { + dev_err(codec_dai->dev, "Unsupported MCLK value %d\n", + freq); + return -EINVAL; +@@ -1193,7 +1177,6 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + break; + default: + dev_err(codec_dai->dev, "Unknown clock source %d\n", clk_id); +- mutex_unlock(&da7219->pll_lock); + return -EINVAL; + } + +@@ -1203,17 +1186,13 @@ static int da7219_set_dai_sysclk(struct snd_soc_dai *codec_dai, + freq = clk_round_rate(da7219->mclk, freq); + ret = clk_set_rate(da7219->mclk, freq); + if (ret) { +- dev_err(codec_dai->dev, "Failed to set clock rate %d\n", +- freq); +- mutex_unlock(&da7219->pll_lock); ++ dev_err(codec_dai->dev, "Failed to set clock rate %d\n", freq); + return ret; + } + } + + da7219->mclk_rate = freq; + +- mutex_unlock(&da7219->pll_lock); +- + return 0; + } + +@@ -1296,13 +1275,10 @@ static int da7219_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + { + struct snd_soc_component *component = codec_dai->component; + struct da7219_priv *da7219 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&da7219->pll_lock); +- ret = da7219_set_pll(component, source, fout); +- mutex_unlock(&da7219->pll_lock); ++ guard(mutex)(&da7219->pll_lock); + +- return ret; ++ return da7219_set_pll(component, source, fout); + } + + static int da7219_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c +--- a/sound/soc/codecs/es8316.c ++++ b/sound/soc/codecs/es8316.c +@@ -9,11 +9,13 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -30,6 +32,13 @@ static const unsigned int supported_mclk_lrck_ratios[] = { + 256, 384, 400, 500, 512, 768, 1024 + }; + ++static const char * const es8316_supply_names[] = { ++ "avdd", ++ "cpvdd", ++ "dvdd", ++ "pvdd", ++}; ++ + struct es8316_priv { + struct mutex lock; + struct clk *mclk; +@@ -620,15 +629,15 @@ static irqreturn_t es8316_irq(int irq, void *data) + struct snd_soc_component *comp = es8316->component; + unsigned int flags; + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + regmap_read(es8316->regmap, ES8316_GPIO_FLAG, &flags); + if (flags == 0x00) +- goto out; /* Powered-down / reset */ ++ return IRQ_HANDLED; /* Powered-down / reset */ + + /* Catch spurious IRQ before set_jack is called */ + if (!es8316->jack) +- goto out; ++ return IRQ_HANDLED; + + if (es8316->jd_inverted) + flags ^= ES8316_GPIO_FLAG_HP_NOT_INSERTED; +@@ -681,8 +690,6 @@ static irqreturn_t es8316_irq(int irq, void *data) + } + } + +-out: +- mutex_unlock(&es8316->lock); + return IRQ_HANDLED; + } + +@@ -699,18 +706,16 @@ static void es8316_enable_jack_detect(struct snd_soc_component *component, + es8316->jd_inverted = device_property_read_bool(component->dev, + "everest,jack-detect-inverted"); + +- mutex_lock(&es8316->lock); ++ scoped_guard(mutex, &es8316->lock) { ++ es8316->jack = jack; + +- es8316->jack = jack; ++ if (es8316->jack->status & SND_JACK_MICROPHONE) ++ es8316_enable_micbias_for_mic_gnd_short_detect(component); + +- if (es8316->jack->status & SND_JACK_MICROPHONE) +- es8316_enable_micbias_for_mic_gnd_short_detect(component); +- +- snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, +- ES8316_GPIO_ENABLE_INTERRUPT, +- ES8316_GPIO_ENABLE_INTERRUPT); +- +- mutex_unlock(&es8316->lock); ++ snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, ++ ES8316_GPIO_ENABLE_INTERRUPT, ++ ES8316_GPIO_ENABLE_INTERRUPT); ++ } + + /* Enable irq and sync initial jack state */ + enable_irq(es8316->irq); +@@ -726,7 +731,7 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + + disable_irq(es8316->irq); + +- mutex_lock(&es8316->lock); ++ guard(mutex)(&es8316->lock); + + snd_soc_component_update_bits(component, ES8316_GPIO_DEBOUNCE, + ES8316_GPIO_ENABLE_INTERRUPT, 0); +@@ -737,8 +742,6 @@ static void es8316_disable_jack_detect(struct snd_soc_component *component) + } + + es8316->jack = NULL; +- +- mutex_unlock(&es8316->lock); + } + + static int es8316_set_jack(struct snd_soc_component *component, +@@ -871,6 +874,11 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) + + i2c_set_clientdata(i2c_client, es8316); + ++ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), ++ es8316_supply_names); ++ if (ret) ++ return dev_err_probe(dev, ret, "unable to enable supplies\n"); ++ + es8316->regmap = devm_regmap_init_i2c(i2c_client, &es8316_regmap); + if (IS_ERR(es8316->regmap)) + return PTR_ERR(es8316->regmap); +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -6,6 +6,7 @@ + // Authors: David Yang + // + ++#include + #include + #include + #include +@@ -790,7 +791,7 @@ static void es8326_jack_button_handler(struct work_struct *work) + if (!(es8326->jack->status & SND_JACK_HEADSET)) /* Jack unplugged */ + return; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + switch (iface) { + case 0x93: +@@ -845,7 +846,6 @@ static void es8326_jack_button_handler(struct work_struct *work) + } + es8326_disable_micbias(es8326->component); + } +- mutex_unlock(&es8326->lock); + } + + static void es8326_jack_detect_handler(struct work_struct *work) +@@ -855,7 +855,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + struct snd_soc_component *comp = es8326->component; + unsigned int iface; + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + iface = snd_soc_component_read(comp, ES8326_HPDET_STA); + dev_dbg(comp->dev, "gpio flag %#04x", iface); + +@@ -873,7 +873,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + regmap_update_bits(es8326->regmap, ES8326_HPDET_TYPE, + ES8326_HP_DET_JACK_POL, (es8326->jd_inverted ? + ~es8326->jack_pol : es8326->jack_pol)); +- goto exit; ++ return; + } + + if ((iface & ES8326_HPINSERT_FLAG) == 0) { +@@ -930,7 +930,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + queue_delayed_work(system_dfl_wq, &es8326->jack_detect_work, + msecs_to_jiffies(400)); + es8326->hp = 1; +- goto exit; ++ return; + } + if (es8326->jack->status & SND_JACK_HEADSET) { + /* detect button */ +@@ -939,7 +939,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) + (ES8326_INT_SRC_PIN9 | ES8326_INT_SRC_BUTTON)); + es8326_enable_micbias(es8326->component); + queue_delayed_work(system_dfl_wq, &es8326->button_press_work, 10); +- goto exit; ++ return; + } + if ((iface & ES8326_HPBUTTON_FLAG) == 0x01) { + dev_dbg(comp->dev, "Headphone detected\n"); +@@ -958,8 +958,6 @@ static void es8326_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + } +-exit: +- mutex_unlock(&es8326->lock); + } + + static irqreturn_t es8326_irq(int irq, void *dev_id) +@@ -1200,13 +1198,12 @@ static void es8326_enable_jack_detect(struct snd_soc_component *component, + { + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&es8326->lock); +- if (es8326->jd_inverted) +- snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, +- ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); +- es8326->jack = jack; +- +- mutex_unlock(&es8326->lock); ++ scoped_guard(mutex, &es8326->lock) { ++ if (es8326->jd_inverted) ++ snd_soc_component_update_bits(component, ES8326_HPDET_TYPE, ++ ES8326_HP_DET_JACK_POL, ~es8326->jack_pol); ++ es8326->jack = jack; ++ } + es8326_irq(es8326->irq, es8326); + } + +@@ -1219,13 +1216,12 @@ static void es8326_disable_jack_detect(struct snd_soc_component *component) + return; /* Already disabled (or never enabled) */ + cancel_delayed_work_sync(&es8326->jack_detect_work); + +- mutex_lock(&es8326->lock); ++ guard(mutex)(&es8326->lock); + if (es8326->jack->status & SND_JACK_MICROPHONE) { + es8326_disable_micbias(component); + snd_soc_jack_report(es8326->jack, 0, SND_JACK_HEADSET); + } + es8326->jack = NULL; +- mutex_unlock(&es8326->lock); + } + + static int es8326_set_jack(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c +--- a/sound/soc/codecs/es8328.c ++++ b/sound/soc/codecs/es8328.c +@@ -405,6 +405,11 @@ static const struct snd_soc_dapm_route es8328_dapm_routes[] = { + + { "Mic Bias", NULL, "Mic Bias Gen" }, + ++ { "LINPUT1", NULL, "Mic Bias" }, ++ { "RINPUT1", NULL, "Mic Bias" }, ++ { "LINPUT2", NULL, "Mic Bias" }, ++ { "RINPUT2", NULL, "Mic Bias" }, ++ + { "Left Mixer", NULL, "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -36,6 +36,10 @@ struct es8389_private { + unsigned int sysclk; + int mastermode; + ++ u8 hpfl; ++ u8 hpfr; ++ u32 hpf_freq; ++ u32 capture_rate; + u8 mclk_src; + u8 vddd; + int version; +@@ -50,10 +54,29 @@ static const char * const es8389_core_supplies[] = { + static bool es8389_volatile_register(struct device *dev, + unsigned int reg) + { +- if ((reg <= 0xff)) +- return true; +- else ++ switch (reg) { ++ case ES8389_ADCL_VOL: ++ case ES8389_ADCR_VOL: ++ case ES8389_MIC1_GAIN: ++ case ES8389_MIC2_GAIN: ++ case ES8389_DACL_VOL: ++ case ES8389_DACR_VOL: ++ case ES8389_ALC_ON: ++ case ES8389_ALC_CTL: ++ case ES8389_ALC_TARGET: ++ case ES8389_ALC_GAIN: ++ case ES8389_ADC_MUTE: ++ case ES8389_OSR_VOL: ++ case ES8389_DAC_INV: ++ case ES8389_MIX_VOL: ++ case ES8389_DAC_MIX: ++ case ES8389_ADC_RESET: ++ case ES8389_ADC_MODE: ++ case ES8389_DMIC_EN: + return false; ++ default: ++ return true; ++ } + } + + static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -9550, 50, 0); +@@ -63,6 +86,92 @@ static const DECLARE_TLV_DB_SCALE(mix_vol_tlv, -9500, 100, 0); + static const DECLARE_TLV_DB_SCALE(alc_target_tlv, -3200, 200, 0); + static const DECLARE_TLV_DB_SCALE(alc_max_level, -3200, 200, 0); + ++static const u32 hpf_table[10][10] = { ++ {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, ++ {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, ++ {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, ++ {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, ++ {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, ++ {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, ++ {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, ++ {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, ++ {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, ++ {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} ++}; ++ ++static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2, u32 *out) ++{ ++ int best_row = -1, best_col = -1; ++ u32 min_diff = U32_MAX; ++ u32 f, diff; ++ int i, j; ++ ++ if (target_hz > 1020) ++ return false; ++ ++ for (i = 0; i < 10; i++) { ++ for (j = i; j < 10; j++) { ++ f = hpf_table[i][j]; ++ ++ diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); ++ if (diff < min_diff) { ++ min_diff = diff; ++ best_row = i; ++ best_col = j; ++ *out = f; ++ } ++ } ++ } ++ ++ *hpf1 = best_col + ES8389_HPF_OFFSET; ++ *hpf2 = best_row + ES8389_HPF_OFFSET; ++ ++ return true; ++} ++ ++static int es8389_hpf_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = es8389->hpf_freq; ++ return 0; ++} ++ ++static int es8389_hpf_set(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ u32 freq; ++ bool hpf; ++ ++ if (es8389->hpf_freq == ucontrol->value.integer.value[0]) ++ return 0; ++ ++ if (es8389->capture_rate) { ++ freq = (ucontrol->value.integer.value[0] * 48000) / es8389->capture_rate; ++ ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) ++ return -EBUSY; ++ ++ if (es8389->hpf_freq != ucontrol->value.integer.value[0]) ++ dev_dbg(component->dev, "At the %u Hz sampling rate, %ld Hz could not be obtained." ++ "the frequency has been set to the closest value, %u Hz\n", ++ es8389->capture_rate, ucontrol->value.integer.value[0], es8389->hpf_freq); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); ++ } else { ++ es8389->hpf_freq = ucontrol->value.integer.value[0]; ++ dev_dbg(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); ++ } ++ ++ return 1; ++} ++ + static int es8389_dmic_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -143,6 +252,16 @@ static const struct soc_enum alc_ramprate = + static const struct soc_enum alc_winsize = + SOC_ENUM_SINGLE(ES8389_ALC_CTL, 0, 16, winsize); + ++static const char *const es8389_adcl_mux_txt[] = { ++ "Normal", ++ "ADC2 channel to ADC1 channel", ++}; ++ ++static const char *const es8389_adcr_mux_txt[] = { ++ "Normal", ++ "ADC1 channel to ADC2 channel", ++}; ++ + static const char *const es8389_outl_mux_txt[] = { + "Normal", + "DAC2 channel to DAC1 channel", +@@ -170,6 +289,20 @@ static const unsigned int es8389_pga_values[] = { + 1, 5, 6 + }; + ++static const struct soc_enum es8389_adcl_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 5, ++ ARRAY_SIZE(es8389_adcl_mux_txt), es8389_adcl_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcl_mux_controls = ++ SOC_DAPM_ENUM("INPUTL MUX", es8389_adcl_mux_enum); ++ ++static const struct soc_enum es8389_adcr_mux_enum = ++ SOC_ENUM_SINGLE(ES8389_ADC_MODE, 4, ++ ARRAY_SIZE(es8389_adcr_mux_txt), es8389_adcr_mux_txt); ++ ++static const struct snd_kcontrol_new es8389_adcr_mux_controls = ++ SOC_DAPM_ENUM("INPUTR MUX", es8389_adcr_mux_enum); ++ + static const struct soc_enum es8389_outl_mux_enum = + SOC_ENUM_SINGLE(ES8389_DAC_MIX, 5, + ARRAY_SIZE(es8389_outl_mux_txt), es8389_outl_mux_txt); +@@ -240,6 +373,8 @@ static const struct snd_kcontrol_new es8389_snd_controls[] = { + SOC_DOUBLE("ADC OSR Volume ON Switch", ES8389_ADC_MUTE, 6, 7, 1, 0), + SOC_SINGLE_TLV("ADC OSR Volume", ES8389_OSR_VOL, 0, 0xFF, 0, adc_vol_tlv), + SOC_DOUBLE("ADC OUTPUT Invert Switch", ES8389_ADC_HPF2, 5, 6, 1, 0), ++ SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, ++ es8389_hpf_get, es8389_hpf_set), + + SOC_SINGLE_TLV("DACL Playback Volume", ES8389_DACL_VOL, 0, 0xFF, 0, dac_vol_tlv), + SOC_SINGLE_TLV("DACR Playback Volume", ES8389_DACR_VOL, 0, 0xFF, 0, dac_vol_tlv), +@@ -298,6 +433,8 @@ static const struct snd_soc_dapm_widget es8389_dapm_widgets[] = { + &es8389_adc_mixer_controls[0], + ARRAY_SIZE(es8389_adc_mixer_controls)), + SND_SOC_DAPM_MUX("ADC MUX", SND_SOC_NOPM, 0, 0, &es8389_dmic_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_adcl_mux_controls), ++ SND_SOC_DAPM_MUX("INPUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_adcr_mux_controls), + + SND_SOC_DAPM_MUX("OUTL MUX", SND_SOC_NOPM, 0, 0, &es8389_outl_mux_controls), + SND_SOC_DAPM_MUX("OUTR MUX", SND_SOC_NOPM, 0, 0, &es8389_outr_mux_controls), +@@ -311,10 +448,15 @@ static const struct snd_soc_dapm_route es8389_dapm_routes[] = { + {"ADCL", NULL, "PGAL"}, + {"ADCR", NULL, "PGAR"}, + ++ {"INPUTL MUX", "Normal", "ADCL"}, ++ {"INPUTL MUX", "ADC2 channel to ADC1 channel", "ADCR"}, ++ {"INPUTR MUX", "Normal", "ADCR"}, ++ {"INPUTR MUX", "ADC1 channel to ADC2 channel", "ADCL"}, ++ + {"ADC Mixer", "DACL ADCL Mixer", "DACL"}, + {"ADC Mixer", "DACR ADCR Mixer", "DACR"}, +- {"ADC Mixer", NULL, "ADCL"}, +- {"ADC Mixer", NULL, "ADCR"}, ++ {"ADC Mixer", NULL, "INPUTL MUX"}, ++ {"ADC Mixer", NULL, "INPUTR MUX"}, + + {"ADC MUX", "AMIC", "ADC Mixer"}, + {"ADC MUX", "DMIC", "DMIC"}, +@@ -415,52 +557,54 @@ static const struct _coeff_div coeff_div[] = { + {36, 576000, 16000, 0x00, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {48, 768000, 16000, 0x02, 0x57, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {50, 800000, 16000, 0x00, 0x7E, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {64, 1024000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {72, 1152000, 16000, 0x00, 0x45, 0x24, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {96, 1536000, 16000, 0x02, 0x55, 0x84, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {128, 2048000, 16000, 0x00, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {144, 2304000, 16000, 0x00, 0x51, 0x00, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x23, 0x8F, 0xBF, 0xC0, 0x1F, 0x8F, 0x01, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {150, 2400000, 16000, 0x02, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {192, 3072000, 16000, 0x02, 0x65, 0x25, 0xE0, 0x00, 0xE1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {256, 4096000, 16000, 0x00, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {300, 4800000, 16000, 0x02, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {384, 6144000, 16000, 0x02, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {512, 8192000, 16000, 0x01, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {750, 12000000, 16000, 0x0E, 0x7E, 0x01, 0xC9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {768, 12288000, 16000, 0x02, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1024, 16384000, 16000, 0x03, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1152, 18432000, 16000, 0x08, 0x51, 0x04, 0xD0, 0x01, 0xC1, 0x90, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1200, 19200000, 16000, 0x0B, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1500, 24000000, 16000, 0x0E, 0x26, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, +- {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xD1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, ++ {1536, 24576000, 16000, 0x05, 0x41, 0x04, 0xC0, 0x01, 0xC1, 0x90, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0xFF, 0x7F, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {1625, 26000000, 16000, 0x40, 0x6E, 0x05, 0xC8, 0x01, 0xC2, 0x90, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x12, 0x31, 0x0E, 2, 2}, + {800, 19200000, 24000, 0x07, 0x66, 0x01, 0xD9, 0x00, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0xC7, 0x95, 0x00, 0x12, 0x00, 0x1A, 0x49, 0x14, 2, 2}, + {375, 12000000, 32000, 0x0E, 0x2E, 0x05, 0xC8, 0x00, 0xC2, 0x80, 0x40, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 0}, +- {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, +- {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {600, 19200000, 32000, 0x05, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x23, 0x61, 0x1B, 2, 2}, ++ {32, 1411200, 44100, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 2822400, 44100, 0x00, 0x51, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 5644800, 44100, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 11289600, 44100, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 22579200, 44100, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {32, 1536000, 48000, 0x00, 0x45, 0xA4, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {48, 2304000, 48000, 0x02, 0x55, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {50, 2400000, 48000, 0x00, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {64, 3072000, 48000, 0x00, 0x51, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {100, 4800000, 48000, 0x00, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {125, 6000000, 48000, 0x04, 0x6E, 0x05, 0xC8, 0x10, 0xC2, 0x80, 0x00, 0x01, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {128, 6144000, 48000, 0x00, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {200, 9600000, 48000, 0x01, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x00, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {250, 12000000, 48000, 0x04, 0x76, 0x01, 0xC8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xD2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, +- {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xD1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {256, 12288000, 48000, 0x01, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {384, 18432000, 48000, 0x02, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {400, 19200000, 48000, 0x03, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0x40, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {500, 24000000, 48000, 0x04, 0x46, 0x01, 0xD8, 0x10, 0xC2, 0x80, 0xC0, 0x00, 0x18, 0x95, 0xD0, 0xC0, 0x63, 0x95, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, ++ {512, 24576000, 48000, 0x03, 0x41, 0x04, 0xD0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {800, 38400000, 48000, 0x18, 0x45, 0x04, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x1F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x00, 0x12, 0x00, 0x35, 0x91, 0x28, 2, 2}, + {128, 11289600, 88200, 0x00, 0x50, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0x40, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x32, 0x89, 0x25, 2, 2}, +- {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xD1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, ++ {64, 6144000, 96000, 0x00, 0x41, 0x00, 0xD0, 0x10, 0xC1, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {96, 9216000, 96000, 0x02, 0x43, 0x00, 0xC0, 0x10, 0xC0, 0x80, 0x00, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {256, 24576000, 96000, 0x00, 0x40, 0x00, 0xC0, 0x10, 0xC1, 0x80, 0xC0, 0x00, 0x9F, 0x7F, 0xBF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x35, 0x91, 0x28, 2, 2}, + {128, 24576000, 192000, 0x00, 0x50, 0x00, 0xC0, 0x18, 0xC1, 0x81, 0xC0, 0x00, 0x8F, 0x7F, 0xBF, 0xC0, 0x3F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 2, 2}, ++ {64, 12288000, 192000, 0x00, 0x41, 0x00, 0xC0, 0x18, 0xC1, 0x80, 0x00, 0x00, 0x8F, 0x7F, 0xEF, 0xC0, 0x7F, 0x7F, 0x80, 0x12, 0xC0, 0x3F, 0xF9, 0x3F, 1, 0}, + }; + + static inline int get_coeff(u8 vddd, u8 dmic, int mclk, int rate) +@@ -564,6 +708,8 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + int coeff, ret; + u8 dmic_enable, state = 0; + unsigned int regv; ++ u32 freq; ++ bool hpf; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: +@@ -590,7 +736,7 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + + if (es8389->mclk_src == ES8389_SCLK_PIN) { + regmap_update_bits(es8389->regmap, ES8389_MASTER_CLK, +- ES8389_MCLK_SOURCE, es8389->mclk_src); ++ ES8389_MCLK_MASK, ES8389_MCLK_FROM_SCLK); + es8389->sysclk = params_channels(params) * params_width(params) * params_rate(params); + } + +@@ -641,6 +787,28 @@ static int es8389_pcm_hw_params(struct snd_pcm_substream *substream, + dev_warn(component->dev, "Clock coefficients do not match"); + } + ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ++ es8389->capture_rate = params_rate(params); ++ freq = (es8389->hpf_freq * 48000) / params_rate(params); ++ hpf = find_best_hpf_freq(freq, &es8389->hpfl, &es8389->hpfr, &es8389->hpf_freq); ++ if (!hpf) { ++ dev_err(component->dev, "The HPF frequency is invalid\n"); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ++ es8389->capture_rate = 0; ++ + return 0; + } + +@@ -721,8 +889,8 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8389->regmap, ES8389_DAC_FORMAT_MUTE, + 0x03, 0x00); + } else { +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x0a); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x0a); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, es8389->hpfl); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, es8389->hpfr); + regmap_update_bits(es8389->regmap, ES8389_ADC_FORMAT_MUTE, + 0x03, 0x00); + } +@@ -738,6 +906,7 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) + + static const struct snd_soc_dai_ops es8389_ops = { + .hw_params = es8389_pcm_hw_params, ++ .hw_free = es8389_pcm_hw_free, + .set_fmt = es8389_set_dai_fmt, + .set_sysclk = es8389_set_dai_sysclk, + .set_tdm_slot = es8389_set_tdm_slot, +@@ -771,7 +940,7 @@ static void es8389_init(struct snd_soc_component *component) + + regmap_read(es8389->regmap, ES8389_MAX_REGISTER, ®); + es8389->version = reg; +- regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x00); ++ regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x56); + regmap_write(es8389->regmap, ES8389_RESET, 0x7E); + regmap_write(es8389->regmap, ES8389_ISO_CTL, 0x38); + regmap_write(es8389->regmap, ES8389_ADC_HPF1, 0x64); +@@ -823,7 +992,7 @@ static void es8389_init(struct snd_soc_component *component) + regmap_write(es8389->regmap, ES8389_SCLK_DIV, 0x04); + regmap_write(es8389->regmap, ES8389_LRCK_DIV1, 0x01); + regmap_write(es8389->regmap, ES8389_LRCK_DIV2, 0x00); +- regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x00); ++ regmap_write(es8389->regmap, ES8389_OSC_CLK, 0x10); + regmap_write(es8389->regmap, ES8389_ADC_OSR, 0x1F); + regmap_write(es8389->regmap, ES8389_ADC_DSP, 0x7F); + regmap_write(es8389->regmap, ES8389_ADC_MUTE, 0xC0); +@@ -861,13 +1030,13 @@ static int es8389_resume(struct snd_soc_component *component) + regcache_cache_only(es8389->regmap, false); + regcache_cache_bypass(es8389->regmap, true); + regmap_read(es8389->regmap, ES8389_RESET, ®v); +- regcache_cache_bypass(es8389->regmap, false); + + if (regv == 0xff) + es8389_init(component); + else + es8389_set_bias_level(component, SND_SOC_BIAS_ON); + ++ regcache_cache_bypass(es8389->regmap, false); + regcache_sync(es8389->regmap); + + return 0; +@@ -913,6 +1082,7 @@ static int es8389_probe(struct snd_soc_component *component) + return ret; + } + ++ es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); + es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); + +diff --git a/sound/soc/codecs/es8389.h b/sound/soc/codecs/es8389.h +--- a/sound/soc/codecs/es8389.h ++++ b/sound/soc/codecs/es8389.h +@@ -106,6 +106,9 @@ + #define ES8389_MIC_SEL_MASK (7 << 4) + #define ES8389_MIC_DEFAULT (1 << 4) + ++#define ES8389_HPF_DEFAULT 16 ++#define ES8389_HPF_OFFSET 4 ++ + #define ES8389_MASTER_MODE_EN (1 << 0) + + #define ES8389_TDM_OFF (0 << 0) +@@ -116,9 +119,11 @@ + #define ES8389_TDM_SLOT (0x70 << 0) + #define ES8389_TDM_SHIFT 4 + +-#define ES8389_MCLK_SOURCE (1 << 6) +-#define ES8389_MCLK_PIN (1 << 6) +-#define ES8389_SCLK_PIN (0 << 6) ++#define ES8389_MCLK_MASK (3 << 6) ++#define ES8389_MCLK_FROM_SCLK (1 << 6) ++#define ES8389_MCLK_SOURCE ES8389_MCLK_PIN ++#define ES8389_MCLK_PIN 0 ++#define ES8389_SCLK_PIN 1 + + /* ES8389_FMT */ + #define ES8389_S24_LE (0 << 5) +diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c +--- a/sound/soc/codecs/es9356.c ++++ b/sound/soc/codecs/es9356.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -19,8 +20,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + #include +@@ -500,16 +499,19 @@ static int es9356_power_state(struct snd_soc_dai *dai, unsigned char ps, unsigne + } + + /* power state changes are not independent across functions */ +- mutex_lock(&es9356->pde_lock); +- ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps0:ps3); +- if (ret) { +- regmap_write(es9356->regmap, +- SDW_SDCA_CTL(func, pde_entity, ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ps?ps3:ps0); +- es9356_pde_transition_delay(es9356, func, pde_entity, ps?ps3:ps0); +- } else +- dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ps?ps0:ps3); +- +- mutex_unlock(&es9356->pde_lock); ++ scoped_guard(mutex, &es9356->pde_lock) { ++ ret = es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps0 : ps3); ++ if (ret) { ++ regmap_write(es9356->regmap, ++ SDW_SDCA_CTL(func, pde_entity, ++ ES9356_SDCA_CTL_REQ_POWER_STATE, 0), ++ ps ? ps3 : ps0); ++ es9356_pde_transition_delay(es9356, func, pde_entity, ps ? ps3 : ps0); ++ } else { ++ dev_dbg(component->dev, "%s PDE is already %d\n", __func__, ++ ps ? ps0 : ps3); ++ } ++ } + + if (rate) + regmap_write(es9356->regmap, +@@ -671,7 +673,7 @@ static int es9356_sdca_mbq_size(struct device *dev, unsigned int reg) + } + } + +-static struct regmap_sdw_mbq_cfg es9356_mbq_config = { ++static const struct regmap_sdw_mbq_cfg es9356_mbq_config = { + .mbq_size = es9356_sdca_mbq_size, + }; + +@@ -1091,9 +1093,8 @@ static int es9356_sdca_dev_system_suspend(struct device *dev) + { + struct es9356_sdw_priv *es9356 = dev_get_drvdata(dev); + +- mutex_lock(&es9356->disable_irq_lock); +- es9356->disable_irq = true; +- mutex_unlock(&es9356->disable_irq_lock); ++ scoped_guard(mutex, &es9356->disable_irq_lock) ++ es9356->disable_irq = true; + + return es9356_sdca_dev_suspend(dev); + } +diff --git a/sound/soc/codecs/fs-amp-lib.c b/sound/soc/codecs/fs-amp-lib.c +--- a/sound/soc/codecs/fs-amp-lib.c ++++ b/sound/soc/codecs/fs-amp-lib.c +@@ -221,7 +221,7 @@ static void fs_print_firmware_info(struct fs_amp_lib *amp_lib) + + int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + { +- const struct firmware *cont; ++ const struct firmware *cont __free(firmware) = NULL; + struct fs_fwm_header *hdr; + int ret; + +@@ -237,7 +237,6 @@ int fs_amp_load_firmware(struct fs_amp_lib *amp_lib, const char *name) + dev_info(amp_lib->dev, "Loading %s - size: %zu\n", name, cont->size); + + hdr = devm_kmemdup(amp_lib->dev, cont->data, cont->size, GFP_KERNEL); +- release_firmware(cont); + if (!hdr) + return -ENOMEM; + +diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c +--- a/sound/soc/codecs/fs210x.c ++++ b/sound/soc/codecs/fs210x.c +@@ -4,6 +4,7 @@ + // + // Copyright (C) 2016-2025 Shanghai FourSemi Semiconductor Co.,Ltd. + ++#include + #include + #include + #include +@@ -771,9 +772,8 @@ static int fs210x_dai_hw_params(struct snd_pcm_substream *substream, + if (fs210x->devid == FS2105S_DEVICE_ID && fs210x->srate == 16000) + return -EOPNOTSUPP; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_set_hw_params(fs210x); +- mutex_unlock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) ++ ret = fs210x_set_hw_params(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to set hw params: %d\n", ret); + +@@ -790,15 +790,11 @@ static int fs210x_dai_mute(struct snd_soc_dai *dai, int mute, int stream) + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + if (mute) { + cancel_delayed_work_sync(&fs210x->fault_check_work); + cancel_delayed_work_sync(&fs210x->start_work); +@@ -817,15 +813,11 @@ static int fs210x_dai_trigger(struct snd_pcm_substream *substream, + + fs210x = snd_soc_component_get_drvdata(dai->component); + +- mutex_lock(&fs210x->lock); +- +- if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return 0; ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || fs210x->is_playing) ++ return 0; + } + +- mutex_unlock(&fs210x->lock); +- + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: +@@ -853,13 +845,11 @@ static void fs210x_start_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, start_work.work); + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + ret = fs210x_dev_play(fs210x); + if (ret) + dev_err(fs210x->dev, "Failed to start playing: %d\n", ret); +- +- mutex_unlock(&fs210x->lock); + } + + static void fs210x_fault_check_work(struct work_struct *work) +@@ -870,15 +860,13 @@ static void fs210x_fault_check_work(struct work_struct *work) + + fs210x = container_of(work, struct fs210x_priv, fault_check_work.work); + +- mutex_lock(&fs210x->lock); ++ scoped_guard(mutex, &fs210x->lock) { ++ if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) ++ return; + +- if (!fs210x->is_inited || fs210x->is_suspended || !fs210x->is_playing) { +- mutex_unlock(&fs210x->lock); +- return; ++ ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); + } + +- ret = fs210x_reg_read(fs210x, FS210X_05H_ANASTAT, &status); +- mutex_unlock(&fs210x->lock); + if (ret) + return; + +@@ -991,7 +979,7 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + if (fs210x->scene_id < 1) + return -EINVAL; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + /* + * FS210x has scene(s) as below: + * init scene: id = 0 +@@ -1000,7 +988,6 @@ static int fs210x_effect_scene_get(struct snd_kcontrol *kcontrol, + */ + index = fs210x->scene_id - 1; + ucontrol->value.integer.value[0] = index; +- mutex_unlock(&fs210x->lock); + + return 0; + } +@@ -1019,7 +1006,7 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + /* + * FS210x has scene(s) as below: +@@ -1029,17 +1016,14 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + */ + scene_id = ucontrol->value.integer.value[0] + 1; + scene_count = fs210x->amp_lib.scene_count - 1; /* Skip init scene */ +- if (scene_id < 1 || scene_id > scene_count) { +- mutex_unlock(&fs210x->lock); ++ if (scene_id < 1 || scene_id > scene_count) + return -ERANGE; +- } + + if (scene_id != fs210x->scene_id) + is_changed = true; + + if (fs210x->is_suspended) { + fs210x->scene_id = scene_id; +- mutex_unlock(&fs210x->lock); + return is_changed; + } + +@@ -1047,8 +1031,6 @@ static int fs210x_effect_scene_put(struct snd_kcontrol *kcontrol, + if (ret) + dev_err(fs210x->dev, "Failed to set scene: %d\n", ret); + +- mutex_unlock(&fs210x->lock); +- + if (!ret && is_changed) + return 1; + +@@ -1062,12 +1044,10 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + struct fs210x_priv *fs210x = snd_soc_component_get_drvdata(cmpnt); + int ret = 0; + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- if (fs210x->is_suspended) { +- mutex_unlock(&fs210x->lock); ++ if (fs210x->is_suspended) + return 0; +- } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -1088,8 +1068,6 @@ static int fs210x_playback_event(struct snd_soc_dapm_widget *w, + break; + } + +- mutex_unlock(&fs210x->lock); +- + return ret; + } + +@@ -1220,11 +1198,9 @@ static int fs210x_probe(struct snd_soc_component *cmpnt) + if (ret) + return ret; + +- mutex_lock(&fs210x->lock); +- ret = fs210x_init_chip(fs210x); +- mutex_unlock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + +- return ret; ++ return fs210x_init_chip(fs210x); + } + + static void fs210x_remove(struct snd_soc_component *cmpnt) +@@ -1251,15 +1227,15 @@ static int fs210x_suspend(struct snd_soc_component *cmpnt) + + regcache_cache_only(fs210x->regmap, true); + +- mutex_lock(&fs210x->lock); +- fs210x->cur_scene = NULL; +- fs210x->is_inited = false; +- fs210x->is_playing = false; +- fs210x->is_suspended = true; ++ scoped_guard(mutex, &fs210x->lock) { ++ fs210x->cur_scene = NULL; ++ fs210x->is_inited = false; ++ fs210x->is_playing = false; ++ fs210x->is_suspended = true; + +- gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ +- fsleep(30000); /* >= 30ms */ +- mutex_unlock(&fs210x->lock); ++ gpiod_set_value_cansleep(fs210x->gpio_sdz, 1); /* Active */ ++ fsleep(30000); /* >= 30ms */ ++ } + + cancel_delayed_work_sync(&fs210x->start_work); + cancel_delayed_work_sync(&fs210x->fault_check_work); +@@ -1288,14 +1264,11 @@ static int fs210x_resume(struct snd_soc_component *cmpnt) + return ret; + } + +- mutex_lock(&fs210x->lock); ++ guard(mutex)(&fs210x->lock); + + fs210x->is_suspended = false; +- ret = fs210x_init_chip(fs210x); + +- mutex_unlock(&fs210x->lock); +- +- return ret; ++ return fs210x_init_chip(fs210x); + } + #else + #define fs210x_suspend NULL +diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c +--- a/sound/soc/codecs/hdac_hda.c ++++ b/sound/soc/codecs/hdac_hda.c +@@ -437,7 +437,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + + #ifdef CONFIG_SND_HDA_PATCH_LOADER + if (loadable_patch[hda_pvt->dev_index] && *loadable_patch[hda_pvt->dev_index]) { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + + dev_info(&hdev->dev, "Applying patch firmware '%s'\n", + loadable_patch[hda_pvt->dev_index]); +@@ -451,7 +451,6 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) + dev_err(&hdev->dev, "%s: failed to load hda patch %d\n", __func__, ret); + goto error_no_pm; + } +- release_firmware(fw); + } + } + #endif +diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c +--- a/sound/soc/codecs/hdac_hdmi.c ++++ b/sound/soc/codecs/hdac_hdmi.c +@@ -10,6 +10,7 @@ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + ++#include + #include + #include + #include +@@ -537,10 +538,11 @@ static struct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt( + continue; + + list_for_each_entry(port, &pcm->port_list, head) { +- mutex_lock(&pcm->lock); +- ret = hdac_hdmi_query_port_connlist(hdev, +- port->pin, port); +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ ret = hdac_hdmi_query_port_connlist(hdev, ++ port->pin, ++ port); ++ } + if (ret < 0) + continue; + +@@ -640,11 +642,11 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream, + pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt); + + if (pcm) { +- mutex_lock(&pcm->lock); +- pcm->chmap_set = false; +- memset(pcm->chmap, 0, sizeof(pcm->chmap)); +- pcm->channels = 0; +- mutex_unlock(&pcm->lock); ++ scoped_guard(mutex, &pcm->lock) { ++ pcm->chmap_set = false; ++ memset(pcm->chmap, 0, sizeof(pcm->chmap)); ++ pcm->channels = 0; ++ } + } + + if (dai_map->port) +@@ -922,7 +924,7 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + if (port == NULL) + return -EINVAL; + +- mutex_lock(&hdmi->pin_mutex); ++ guard(mutex)(&hdmi->pin_mutex); + list_for_each_entry(pcm, &hdmi->pcm_list, head) { + if (list_empty(&pcm->port_list)) + continue; +@@ -945,12 +947,10 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol, + list_add_tail(&port->head, &pcm->port_list); + if (port->eld.monitor_present && port->eld.eld_valid) { + hdac_hdmi_jack_report_sync(pcm, port, true); +- mutex_unlock(&hdmi->pin_mutex); + return ret; + } + } + } +- mutex_unlock(&hdmi->pin_mutex); + + return ret; + } +@@ -1274,67 +1274,65 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, + * In case of non MST pin, get_eld info API expectes port + * to be -1. + */ +- mutex_lock(&hdmi->pin_mutex); +- port->eld.monitor_present = false; ++ scoped_guard(mutex, &hdmi->pin_mutex) { ++ port->eld.monitor_present = false; + +- if (pin->mst_capable) +- port_id = port->id; ++ if (pin->mst_capable) ++ port_id = port->id; + +- size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, +- &port->eld.monitor_present, +- port->eld.eld_buffer, +- ELD_MAX_SIZE); ++ size = snd_hdac_acomp_get_eld(hdev, pin->nid, port_id, ++ &port->eld.monitor_present, ++ port->eld.eld_buffer, ++ ELD_MAX_SIZE); + +- if (size > 0) { +- size = min(size, ELD_MAX_SIZE); +- if (hdac_hdmi_parse_eld(hdev, port) < 0) +- size = -EINVAL; +- } +- +- eld_valid = port->eld.eld_valid; +- +- if (size > 0) { +- port->eld.eld_valid = true; +- port->eld.eld_size = size; +- } else { +- port->eld.eld_valid = false; +- port->eld.eld_size = 0; +- } +- +- eld_changed = (eld_valid != port->eld.eld_valid); +- +- pcm = hdac_hdmi_get_pcm(hdev, port); +- +- if (!port->eld.monitor_present || !port->eld.eld_valid) { +- +- dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", +- __func__, pin->nid, port->id); +- +- /* +- * PCMs are not registered during device probe, so don't +- * report jack here. It will be done in usermode mux +- * control select. +- */ +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, false); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ size = min(size, ELD_MAX_SIZE); ++ if (hdac_hdmi_parse_eld(hdev, port) < 0) ++ size = -EINVAL; + } + +- mutex_unlock(&hdmi->pin_mutex); +- return; +- } ++ eld_valid = port->eld.eld_valid; + +- if (port->eld.monitor_present && port->eld.eld_valid) { +- if (pcm) { +- hdac_hdmi_jack_report(pcm, port, true); +- schedule_work(&port->dapm_work); ++ if (size > 0) { ++ port->eld.eld_valid = true; ++ port->eld.eld_size = size; ++ } else { ++ port->eld.eld_valid = false; ++ port->eld.eld_size = 0; + } + +- print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, +- port->eld.eld_buffer, port->eld.eld_size, false); ++ eld_changed = (eld_valid != port->eld.eld_valid); + ++ pcm = hdac_hdmi_get_pcm(hdev, port); ++ ++ if (!port->eld.monitor_present || !port->eld.eld_valid) { ++ ++ dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n", ++ __func__, pin->nid, port->id); ++ ++ /* ++ * PCMs are not registered during device probe, so don't ++ * report jack here. It will be done in usermode mux ++ * control select. ++ */ ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, false); ++ schedule_work(&port->dapm_work); ++ } ++ ++ return; ++ } ++ ++ if (port->eld.monitor_present && port->eld.eld_valid) { ++ if (pcm) { ++ hdac_hdmi_jack_report(pcm, port, true); ++ schedule_work(&port->dapm_work); ++ } ++ ++ print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1, ++ port->eld.eld_buffer, port->eld.eld_size, false); ++ } + } +- mutex_unlock(&hdmi->pin_mutex); + + if (eld_changed && pcm) + snd_ctl_notify(hdmi->card, +@@ -1795,13 +1793,12 @@ static void hdac_hdmi_set_chmap(struct hdac_device *hdev, int pcm_idx, + if (list_empty(&pcm->port_list)) + return; + +- mutex_lock(&pcm->lock); ++ guard(mutex)(&pcm->lock); + pcm->chmap_set = true; + memcpy(pcm->chmap, chmap, ARRAY_SIZE(pcm->chmap)); + list_for_each_entry(port, &pcm->port_list, head) + if (prepared) + hdac_hdmi_setup_audio_infoframe(hdev, pcm, port); +- mutex_unlock(&pcm->lock); + } + + static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdev, int pcm_idx) +@@ -1866,8 +1863,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + snd_hdac_ext_bus_link_get(hdev->bus, hlink); + + hdmi_priv = devm_kzalloc(&hdev->dev, sizeof(*hdmi_priv), GFP_KERNEL); +- if (hdmi_priv == NULL) ++ if (hdmi_priv == NULL) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENOMEM; ++ } + + snd_hdac_register_chmap_ops(hdev, &hdmi_priv->chmap); + hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap; +@@ -1876,8 +1875,10 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc; + hdmi_priv->hdev = hdev; + +- if (!hdac_id) ++ if (!hdac_id) { ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); + return -ENODEV; ++ } + + if (hdac_id->driver_data) + hdmi_priv->drv_data = +@@ -1902,6 +1903,8 @@ static int hdac_hdmi_dev_probe(struct hdac_device *hdev) + if (ret < 0) { + dev_err(&hdev->dev, + "Failed in parse and map nid with err: %d\n", ret); ++ snd_hdac_ext_bus_link_put(hdev->bus, hlink); ++ snd_hdac_display_power(hdev->bus, hdev->addr, false); + return ret; + } + snd_hdac_refresh_widgets(hdev); +diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c +--- a/sound/soc/codecs/hdmi-codec.c ++++ b/sound/soc/codecs/hdmi-codec.c +@@ -4,6 +4,7 @@ + * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ + * Author: Jyri Sarha + */ ++#include + #include + #include + #include +@@ -456,31 +457,30 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + if (!((has_playback && tx) || (has_capture && !tx))) + return 0; + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + if (hcp->busy) { + dev_err(dai->dev, "Only one simultaneous stream supported!\n"); +- mutex_unlock(&hcp->lock); + return -EINVAL; + } + + if (hcp->hcd.ops->audio_startup) { + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); + if (ret) +- goto err; ++ return ret; + } + + if (tx && hcp->hcd.ops->get_eld) { + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); + if (ret) +- goto err; ++ return ret; + + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + + ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); + if (ret) +- goto err; ++ return ret; + + /* Select chmap supported */ + hdmi_codec_eld_chmap(hcp); +@@ -488,8 +488,6 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, + + hcp->busy = true; + +-err: +- mutex_unlock(&hcp->lock); + return ret; + } + +@@ -507,9 +505,8 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); + +- mutex_lock(&hcp->lock); ++ guard(mutex)(&hcp->lock); + hcp->busy = false; +- mutex_unlock(&hcp->lock); + } + + static int hdmi_codec_fill_codec_params(struct snd_soc_dai *dai, +diff --git a/sound/soc/codecs/idt821034.c b/sound/soc/codecs/idt821034.c +--- a/sound/soc/codecs/idt821034.c ++++ b/sound/soc/codecs/idt821034.c +@@ -7,6 +7,7 @@ + // Author: Herve Codina + + #include ++#include + #include + #include + #include +@@ -413,12 +414,12 @@ static int idt821034_kctrl_gain_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); +- if (IDT821034_ID_IS_OUT(mc->reg)) +- val = idt821034->amps.ch[ch].amp_out.gain; +- else +- val = idt821034->amps.ch[ch].amp_in.gain; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ if (IDT821034_ID_IS_OUT(mc->reg)) ++ val = idt821034->amps.ch[ch].amp_out.gain; ++ else ++ val = idt821034->amps.ch[ch].amp_in.gain; ++ } + + ucontrol->value.integer.value[0] = val & mask; + if (invert) +@@ -456,7 +457,7 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(mc->reg); + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(mc->reg)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -466,22 +467,18 @@ static int idt821034_kctrl_gain_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->gain == val) { +- ret = 0; +- goto end; +- } ++ if (amp->gain == val) ++ return 0; + + if (!amp->is_muted) { + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, val); + if (ret) +- goto end; ++ return ret; + } + + amp->gain = val; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, +@@ -495,11 +492,11 @@ static int idt821034_kctrl_mute_get(struct snd_kcontrol *kcontrol, + + ch = IDT821034_ID_GET_CHAN(id); + +- mutex_lock(&idt821034->mutex); +- is_muted = IDT821034_ID_IS_OUT(id) ? +- idt821034->amps.ch[ch].amp_out.is_muted : +- idt821034->amps.ch[ch].amp_in.is_muted; +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) { ++ is_muted = IDT821034_ID_IS_OUT(id) ? ++ idt821034->amps.ch[ch].amp_out.is_muted : ++ idt821034->amps.ch[ch].amp_in.is_muted; ++ } + + ucontrol->value.integer.value[0] = !is_muted; + +@@ -521,7 +518,7 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + ch = IDT821034_ID_GET_CHAN(id); + is_mute = !ucontrol->value.integer.value[0]; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + if (IDT821034_ID_IS_OUT(id)) { + amp = &idt821034->amps.ch[ch].amp_out; +@@ -531,21 +528,17 @@ static int idt821034_kctrl_mute_put(struct snd_kcontrol *kcontrol, + gain_type = IDT821034_GAIN_TX; + } + +- if (amp->is_muted == is_mute) { +- ret = 0; +- goto end; +- } ++ if (amp->is_muted == is_mute) ++ return 0; + + ret = idt821034_set_gain_channel(idt821034, ch, gain_type, + is_mute ? 0 : amp->gain); + if (ret) +- goto end; ++ return ret; + + amp->is_muted = is_mute; +- ret = 1; /* The value changed */ +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 1; + } + + static const DECLARE_TLV_DB_LINEAR(idt821034_gain_in, -300, 1300); +@@ -623,24 +616,20 @@ static int idt821034_power_event(struct snd_soc_dapm_widget *w, + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(component); + unsigned int id = w->shift; + u8 power, mask; +- int ret; + u8 ch; + + ch = IDT821034_ID_GET_CHAN(id); + mask = IDT821034_ID_IS_OUT(id) ? IDT821034_CONF_PWRUP_RX : IDT821034_CONF_PWRUP_TX; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + power = idt821034_get_channel_power(idt821034, ch); + if (SND_SOC_DAPM_EVENT_ON(event)) + power |= mask; + else + power &= ~mask; +- ret = idt821034_set_channel_power(idt821034, ch, power); + +- mutex_unlock(&idt821034->mutex); +- +- return ret; ++ return idt821034_set_channel_power(idt821034, ch, power); + } + + static const struct snd_soc_dapm_widget idt821034_dapm_widgets[] = { +@@ -717,9 +706,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_RX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_RX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set tx tdm slot failed (%d)\n", + ch, ret); +@@ -742,9 +731,9 @@ static int idt821034_dai_set_tdm_slot(struct snd_soc_dai *dai, + ch = 0; + while (mask && ch < IDT821034_NB_CHANNEL) { + if (mask & 0x1) { +- mutex_lock(&idt821034->mutex); +- ret = idt821034_set_channel_ts(idt821034, ch, IDT821034_CH_TX, slot); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_set_channel_ts(idt821034, ch, ++ IDT821034_CH_TX, slot); + if (ret) { + dev_err(dai->dev, "ch%u set rx tdm slot failed (%d)\n", + ch, ret); +@@ -769,9 +758,8 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -785,13 +773,10 @@ static int idt821034_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + default: + dev_err(dai->dev, "Unsupported DAI format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, +@@ -800,9 +785,8 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + { + struct idt821034 *idt821034 = snd_soc_component_get_drvdata(dai->component); + u8 conf; +- int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + conf = idt821034_get_codec_conf(idt821034); + +@@ -816,13 +800,10 @@ static int idt821034_dai_hw_params(struct snd_pcm_substream *substream, + default: + dev_err(dai->dev, "Unsupported PCM format 0x%x\n", + params_format(params)); +- ret = -EINVAL; +- goto end; ++ return -EINVAL; + } +- ret = idt821034_set_codec_conf(idt821034, conf); +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return idt821034_set_codec_conf(idt821034, conf); + } + + static const unsigned int idt821034_sample_bits[] = {8}; +@@ -897,11 +878,11 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + ret = idt821034_set_codec_conf(idt821034, 0); + if (ret) +- goto end; ++ return ret; + + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + idt821034->amps.ch[i].amp_out.gain = IDT821034_GAIN_OUT_INIT_RAW; +@@ -909,24 +890,21 @@ static int idt821034_reset_audio(struct idt821034 *idt821034) + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_RX, + idt821034->amps.ch[i].amp_out.gain); + if (ret) +- goto end; ++ return ret; + + idt821034->amps.ch[i].amp_in.gain = IDT821034_GAIN_IN_INIT_RAW; + idt821034->amps.ch[i].amp_in.is_muted = false; + ret = idt821034_set_gain_channel(idt821034, i, IDT821034_GAIN_TX, + idt821034->amps.ch[i].amp_in.gain); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_set_channel_power(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + } + +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ return 0; + } + + static int idt821034_component_probe(struct snd_soc_component *component) +@@ -965,7 +943,7 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_raw = idt821034_get_written_slic_raw(idt821034, ch); + if (val) +@@ -974,8 +952,6 @@ static int idt821034_chip_gpio_set(struct gpio_chip *c, unsigned int offset, + slic_raw &= ~mask; + ret = idt821034_write_slic_raw(idt821034, ch, slic_raw); + +- mutex_unlock(&idt821034->mutex); +- + if (ret) + dev_err(&idt821034->spi->dev, "set gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -991,9 +967,8 @@ static int idt821034_chip_gpio_get(struct gpio_chip *c, unsigned int offset) + u8 slic_raw; + int ret; + +- mutex_lock(&idt821034->mutex); +- ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); +- mutex_unlock(&idt821034->mutex); ++ scoped_guard(mutex, &idt821034->mutex) ++ ret = idt821034_read_slic_raw(idt821034, ch, &slic_raw); + if (ret) { + dev_err(&idt821034->spi->dev, "get gpio %d (%u, 0x%x) failed (%d)\n", + offset, ch, mask, ret); +@@ -1015,9 +990,8 @@ static int idt821034_chip_get_direction(struct gpio_chip *c, unsigned int offset + struct idt821034 *idt821034 = gpiochip_get_data(c); + u8 slic_dir; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + slic_dir = idt821034_get_slic_conf(idt821034, ch); +- mutex_unlock(&idt821034->mutex); + + return slic_dir & mask ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; + } +@@ -1034,7 +1008,7 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + if (mask & ~(IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN)) + return -EPERM; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) | mask; + +@@ -1044,7 +1018,6 @@ static int idt821034_chip_direction_input(struct gpio_chip *c, unsigned int offs + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1060,7 +1033,7 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + if (ret) + return ret; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + slic_conf = idt821034_get_slic_conf(idt821034, ch) & ~mask; + +@@ -1070,7 +1043,6 @@ static int idt821034_chip_direction_output(struct gpio_chip *c, unsigned int off + offset, ch, mask, ret); + } + +- mutex_unlock(&idt821034->mutex); + return ret; + } + +@@ -1079,24 +1051,22 @@ static int idt821034_reset_gpio(struct idt821034 *idt821034) + int ret; + u8 i; + +- mutex_lock(&idt821034->mutex); ++ guard(mutex)(&idt821034->mutex); + + /* IO0 and IO1 as input for all channels and output IO set to 0 */ + for (i = 0; i < IDT821034_NB_CHANNEL; i++) { + ret = idt821034_set_slic_conf(idt821034, i, + IDT821034_SLIC_IO1_IN | IDT821034_SLIC_IO0_IN); + if (ret) +- goto end; ++ return ret; + + ret = idt821034_write_slic_raw(idt821034, i, 0); + if (ret) +- goto end; ++ return ret; + + } +- ret = 0; +-end: +- mutex_unlock(&idt821034->mutex); +- return ret; ++ ++ return 0; + } + + static int idt821034_gpio_init(struct idt821034 *idt821034) +diff --git a/sound/soc/codecs/lochnagar-sc.c b/sound/soc/codecs/lochnagar-sc.c +--- a/sound/soc/codecs/lochnagar-sc.c ++++ b/sound/soc/codecs/lochnagar-sc.c +@@ -137,15 +137,23 @@ static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBP_CFP); + } + ++static const u64 lochnagar_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops lochnagar_sc_line_ops = { + .startup = lochnagar_sc_line_startup, + .shutdown = lochnagar_sc_line_shutdown, + .set_fmt = lochnagar_sc_set_line_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = { + .startup = lochnagar_sc_startup, + .set_fmt = lochnagar_sc_set_usb_fmt, ++ .auto_selectable_formats = &lochnagar_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver lochnagar_sc_dai[] = { +diff --git a/sound/soc/codecs/lpass-macro-common.c b/sound/soc/codecs/lpass-macro-common.c +--- a/sound/soc/codecs/lpass-macro-common.c ++++ b/sound/soc/codecs/lpass-macro-common.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2022, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -71,21 +72,16 @@ EXPORT_SYMBOL_GPL(lpass_macro_pds_exit); + + void lpass_macro_set_codec_version(enum lpass_codec_version version) + { +- mutex_lock(&lpass_codec_mutex); ++ guard(mutex)(&lpass_codec_mutex); + lpass_codec_version = version; +- mutex_unlock(&lpass_codec_mutex); + } + EXPORT_SYMBOL_GPL(lpass_macro_set_codec_version); + + enum lpass_codec_version lpass_macro_get_codec_version(void) + { +- enum lpass_codec_version ver; ++ guard(mutex)(&lpass_codec_mutex); + +- mutex_lock(&lpass_codec_mutex); +- ver = lpass_codec_version; +- mutex_unlock(&lpass_codec_mutex); +- +- return ver; ++ return lpass_codec_version; + } + EXPORT_SYMBOL_GPL(lpass_macro_get_codec_version); + +diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c +--- a/sound/soc/codecs/lpass-rx-macro.c ++++ b/sound/soc/codecs/lpass-rx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2030,9 +2031,10 @@ static struct snd_soc_dai_driver rx_macro_dai[] = { + }, + }; + +-static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) ++static int rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + { + struct regmap *regmap = rx->regmap; ++ int ret; + + if (mclk_enable) { + if (rx->rx_mclk_users == 0) { +@@ -2047,14 +2049,16 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_FS_MCLK_CNT_EN_MASK, + CDC_RX_FS_MCLK_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + rx->rx_mclk_users++; + } else { + if (rx->rx_mclk_users <= 0) { + dev_err(rx->dev, "%s: clock already disabled\n", __func__); + rx->rx_mclk_users = 0; +- return; ++ return 0; + } + rx->rx_mclk_users--; + if (rx->rx_mclk_users == 0) { +@@ -2068,6 +2072,8 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) + CDC_RX_CLK_MCLK2_EN_MASK, 0x0); + } + } ++ ++ return 0; + } + + static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, +@@ -2079,11 +2085,9 @@ static int rx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- rx_macro_mclk_enable(rx, true); +- break; ++ return rx_macro_mclk_enable(rx, true); + case SND_SOC_DAPM_POST_PMD: +- rx_macro_mclk_enable(rx, false); +- break; ++ return rx_macro_mclk_enable(rx, false); + default: + dev_err(component->dev, "%s: invalid DAPM event %d\n", __func__, event); + ret = -EINVAL; +@@ -3671,14 +3675,16 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct rx_macro *rx = to_rx_macro(hw); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_runtime_resume_and_get(rx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = rx_macro_mclk_enable(rx, true); + if (ret) { +- dev_err(rx->dev, "unable to prepare mclk\n"); ++ clk_disable_unprepare(rx->mclk); + return ret; + } + +- rx_macro_mclk_enable(rx, true); +- + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, + CDC_RX_SWR_CLK_EN_MASK, 1); + +@@ -3693,7 +3699,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_RX_SWR_CLK_EN_MASK, 0); + + rx_macro_mclk_enable(rx, false); +- clk_disable_unprepare(rx->mclk); ++ pm_runtime_put_autosuspend(rx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -3864,28 +3870,31 @@ static int rx_macro_probe(struct platform_device *pdev) + rx->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(rx->mclk, MCLK_FREQ); +- clk_set_rate(rx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(rx->macro); ++ ret = clk_set_rate(rx->mclk, MCLK_FREQ); + if (ret) + return ret; + +- ret = clk_prepare_enable(rx->dcodec); ++ ret = clk_set_rate(rx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(rx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(rx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ return ret; + + /* reset swr block */ + regmap_update_bits(rx->regmap, CDC_RX_CLK_RST_CTRL_SWR_CONTROL, +@@ -3902,46 +3911,25 @@ static int rx_macro_probe(struct platform_device *pdev) + rx_macro_dai, + ARRAY_SIZE(rx_macro_dai)); + if (ret) +- goto err_clkout; +- +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = rx_macro_register_mclk_output(rx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(rx->fsgen); +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +-err_mclk: +- clk_disable_unprepare(rx->dcodec); +-err_dcodec: +- clk_disable_unprepare(rx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + + return ret; + } + +-static void rx_macro_remove(struct platform_device *pdev) +-{ +- struct rx_macro *rx = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(rx->mclk); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->macro); +- clk_disable_unprepare(rx->dcodec); +-} +- + static const struct of_device_id rx_macro_dt_match[] = { + { + .compatible = "qcom,sc7280-lpass-rx-macro", +@@ -3969,13 +3957,17 @@ MODULE_DEVICE_TABLE(of, rx_macro_dt_match); + static int rx_macro_runtime_suspend(struct device *dev) + { + struct rx_macro *rx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rx->regmap, true); +- regcache_mark_dirty(rx->regmap); + +- clk_disable_unprepare(rx->fsgen); +- clk_disable_unprepare(rx->npl); +- clk_disable_unprepare(rx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(rx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(rx->regmap); + + return 0; + } +@@ -3985,33 +3977,23 @@ static int rx_macro_runtime_resume(struct device *dev) + struct rx_macro *rx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(rx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); + return ret; + } + +- ret = clk_prepare_enable(rx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(rx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } + regcache_cache_only(rx->regmap, false); +- regcache_sync(rx->regmap); ++ ret = regcache_sync(rx->regmap); ++ if (ret) { ++ regcache_cache_only(rx->regmap, true); ++ regcache_mark_dirty(rx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(rx->npl); +-err_npl: +- clk_disable_unprepare(rx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops rx_macro_pm_ops = { +@@ -4026,7 +4008,6 @@ static struct platform_driver rx_macro_driver = { + .pm = pm_ptr(&rx_macro_pm_ops), + }, + .probe = rx_macro_probe, +- .remove = rx_macro_remove, + }; + + module_platform_driver(rx_macro_driver); +diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c +--- a/sound/soc/codecs/lpass-tx-macro.c ++++ b/sound/soc/codecs/lpass-tx-macro.c +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -614,6 +615,7 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + bool mclk_enable) + { + struct regmap *regmap = tx->regmap; ++ int ret; + + if (mclk_enable) { + if (tx->tx_mclk_users == 0) { +@@ -626,7 +628,9 @@ static int tx_macro_mclk_enable(struct tx_macro *tx, + CDC_TX_FS_CNT_EN_MASK, + CDC_TX_FS_CNT_ENABLE); + regcache_mark_dirty(regmap); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) ++ return ret; + } + tx->tx_mclk_users++; + } else { +@@ -739,11 +743,9 @@ static int tx_macro_mclk_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +- tx_macro_mclk_enable(tx, true); +- break; ++ return tx_macro_mclk_enable(tx, true); + case SND_SOC_DAPM_POST_PMD: +- tx_macro_mclk_enable(tx, false); +- break; ++ return tx_macro_mclk_enable(tx, false); + default: + break; + } +@@ -2149,17 +2151,20 @@ static int swclk_gate_enable(struct clk_hw *hw) + struct regmap *regmap = tx->regmap; + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_runtime_resume_and_get(tx->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = tx_macro_mclk_enable(tx, true); + if (ret) { +- dev_err(tx->dev, "failed to enable mclk\n"); ++ pm_runtime_put_autosuspend(tx->dev); + return ret; + } + +- tx_macro_mclk_enable(tx, true); +- + regmap_update_bits(regmap, CDC_TX_CLK_RST_CTRL_SWR_CONTROL, + CDC_TX_SWR_CLK_EN_MASK, + CDC_TX_SWR_CLK_ENABLE); ++ + return 0; + } + +@@ -2172,7 +2177,7 @@ static void swclk_gate_disable(struct clk_hw *hw) + CDC_TX_SWR_CLK_EN_MASK, 0x0); + + tx_macro_mclk_enable(tx, false); +- clk_disable_unprepare(tx->mclk); ++ pm_runtime_put_autosuspend(tx->dev); + } + + static int swclk_gate_is_enabled(struct clk_hw *hw) +@@ -2315,28 +2320,31 @@ static int tx_macro_probe(struct platform_device *pdev) + tx->active_decimator[TX_MACRO_AIF3_CAP] = -1; + + /* set MCLK and NPL rates */ +- clk_set_rate(tx->mclk, MCLK_FREQ); +- clk_set_rate(tx->npl, MCLK_FREQ); +- +- ret = clk_prepare_enable(tx->macro); ++ ret = clk_set_rate(tx->mclk, MCLK_FREQ); + if (ret) + goto err; + +- ret = clk_prepare_enable(tx->dcodec); ++ ret = clk_set_rate(tx->npl, MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ goto err; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- ret = clk_prepare_enable(tx->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(tx->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ goto err; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ goto err; + + + /* reset soundwire block */ +@@ -2356,30 +2364,21 @@ static int tx_macro_probe(struct platform_device *pdev) + tx_macro_dai, + ARRAY_SIZE(tx_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = tx_macro_register_mclk_output(tx); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; + +-err_clkout: +- clk_disable_unprepare(tx->fsgen); +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +-err_mclk: +- clk_disable_unprepare(tx->dcodec); +-err_dcodec: +- clk_disable_unprepare(tx->macro); ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + err: + lpass_macro_pds_exit(tx->pds); + +@@ -2390,25 +2389,23 @@ static void tx_macro_remove(struct platform_device *pdev) + { + struct tx_macro *tx = dev_get_drvdata(&pdev->dev); + +- clk_disable_unprepare(tx->macro); +- clk_disable_unprepare(tx->dcodec); +- clk_disable_unprepare(tx->mclk); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->fsgen); +- + lpass_macro_pds_exit(tx->pds); + } + + static int tx_macro_runtime_suspend(struct device *dev) + { + struct tx_macro *tx = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(tx->regmap, true); +- regcache_mark_dirty(tx->regmap); + +- clk_disable_unprepare(tx->fsgen); +- clk_disable_unprepare(tx->npl); +- clk_disable_unprepare(tx->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(tx->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(tx->regmap); + + return 0; + } +@@ -2418,34 +2415,23 @@ static int tx_macro_runtime_resume(struct device *dev) + struct tx_macro *tx = dev_get_drvdata(dev); + int ret; + +- ret = clk_prepare_enable(tx->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); + return ret; + } + +- ret = clk_prepare_enable(tx->npl); +- if (ret) { +- dev_err(dev, "unable to prepare npl\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(tx->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- + regcache_cache_only(tx->regmap, false); +- regcache_sync(tx->regmap); ++ ret = regcache_sync(tx->regmap); ++ if (ret) { ++ regcache_cache_only(tx->regmap, true); ++ regcache_mark_dirty(tx->regmap); ++ pm_clk_suspend(dev); ++ return ret; ++ } + + return 0; +-err_fsgen: +- clk_disable_unprepare(tx->npl); +-err_npl: +- clk_disable_unprepare(tx->mclk); +- +- return ret; + } + + static const struct dev_pm_ops tx_macro_pm_ops = { +diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c +--- a/sound/soc/codecs/lpass-va-macro.c ++++ b/sound/soc/codecs/lpass-va-macro.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1351,34 +1352,43 @@ static int fsgen_gate_enable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; +- int ret; ++ int ret, rpm_ret; + +- if (va->has_swr_master) { +- ret = clk_prepare_enable(va->mclk); +- if (ret) +- return ret; +- } ++ ret = pm_runtime_resume_and_get(va->dev); ++ if (ret < 0) ++ return ret; + + ret = va_macro_mclk_enable(va, true); ++ if (ret) { ++ rpm_ret = pm_runtime_put_autosuspend(va->dev); ++ if (rpm_ret < 0) ++ dev_warn(va->dev, ++ "runtime PM put failed in fsgen enable unwind: %d\n", ++ rpm_ret); ++ return ret; ++ } + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, CDC_VA_SWR_CLK_ENABLE); + +- return ret; ++ return 0; + } + + static void fsgen_gate_disable(struct clk_hw *hw) + { + struct va_macro *va = to_va_macro(hw); + struct regmap *regmap = va->regmap; ++ int ret; + + if (va->has_swr_master) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_SWR_CONTROL, + CDC_VA_SWR_CLK_EN_MASK, 0x0); + + va_macro_mclk_enable(va, false); +- if (va->has_swr_master) +- clk_disable_unprepare(va->mclk); ++ ++ ret = pm_runtime_put_autosuspend(va->dev); ++ if (ret < 0) ++ dev_warn(va->dev, "runtime PM put failed in fsgen disable: %d\n", ret); + } + + static int fsgen_gate_is_enabled(struct clk_hw *hw) +@@ -1539,6 +1549,7 @@ static int va_macro_probe(struct platform_device *pdev) + void __iomem *base; + u32 sample_rate = 0; + int ret; ++ int rpm_ret; + + va = devm_kzalloc(dev, sizeof(*va), GFP_KERNEL); + if (!va) +@@ -1594,7 +1605,9 @@ static int va_macro_probe(struct platform_device *pdev) + va->has_npl_clk = data->has_npl_clk; + + /* mclk rate */ +- clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + + if (va->has_npl_clk) { + va->npl = devm_clk_get(dev, "npl"); +@@ -1603,25 +1616,29 @@ static int va_macro_probe(struct platform_device *pdev) + goto err; + } + +- clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ ret = clk_set_rate(va->npl, 2 * VA_MACRO_MCLK_FREQ); ++ if (ret) ++ goto err; + } + +- ret = clk_prepare_enable(va->macro); ++ ret = devm_pm_clk_create(dev); + if (ret) + goto err; + +- ret = clk_prepare_enable(va->dcodec); +- if (ret) +- goto err_dcodec; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ goto err; + +- ret = clk_prepare_enable(va->mclk); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_mclk; ++ goto err; + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) +- goto err_npl; ++ rpm_ret = pm_runtime_resume_and_get(dev); ++ if (rpm_ret < 0) { ++ ret = rpm_ret; ++ goto err; + } + + /** +@@ -1634,7 +1651,7 @@ static int va_macro_probe(struct platform_device *pdev) + /* read version from register */ + ret = va_macro_set_lpass_codec_version(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + } + + if (va->has_swr_master) { +@@ -1664,35 +1681,28 @@ static int va_macro_probe(struct platform_device *pdev) + va_macro_dais, + ARRAY_SIZE(va_macro_dais)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = va_macro_register_fsgen_output(va); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; + + va->fsgen = devm_clk_hw_get_clk(dev, &va->hw, "fsgen"); + if (IS_ERR(va->fsgen)) { + ret = PTR_ERR(va->fsgen); +- goto err_clkout; ++ goto err_rpm_put; + } + ++ rpm_ret = pm_runtime_put_autosuspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", rpm_ret); ++ + return 0; + +-err_clkout: +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +-err_npl: +- clk_disable_unprepare(va->mclk); +-err_mclk: +- clk_disable_unprepare(va->dcodec); +-err_dcodec: +- clk_disable_unprepare(va->macro); ++err_rpm_put: ++ rpm_ret = pm_runtime_put_sync_suspend(dev); ++ if (rpm_ret < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind: %d\n", rpm_ret); + err: + lpass_macro_pds_exit(va->pds); + +@@ -1703,53 +1713,52 @@ static void va_macro_remove(struct platform_device *pdev) + { + struct va_macro *va = dev_get_drvdata(&pdev->dev); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- clk_disable_unprepare(va->dcodec); +- clk_disable_unprepare(va->macro); +- + lpass_macro_pds_exit(va->pds); + } + + static int va_macro_runtime_suspend(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(va->regmap, true); ++ ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(va->regmap, false); ++ return ret; ++ } ++ + regcache_mark_dirty(va->regmap); + +- if (va->has_npl_clk) +- clk_disable_unprepare(va->npl); +- +- clk_disable_unprepare(va->mclk); +- + return 0; + } + + static int va_macro_runtime_resume(struct device *dev) + { + struct va_macro *va = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(va->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(va->dev, "unable to prepare mclk\n"); ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); + return ret; + } + +- if (va->has_npl_clk) { +- ret = clk_prepare_enable(va->npl); +- if (ret) { +- clk_disable_unprepare(va->mclk); +- dev_err(va->dev, "unable to prepare npl\n"); +- return ret; +- } +- } +- + regcache_cache_only(va->regmap, false); +- regcache_sync(va->regmap); ++ ++ ret = regcache_sync(va->regmap); ++ if (ret) { ++ regcache_cache_only(va->regmap, true); ++ regcache_mark_dirty(va->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(va->dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2529,15 +2530,13 @@ static const struct snd_soc_dapm_route wsa_audio_map[] = { + static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + { + struct regmap *regmap = wsa->regmap; ++ int ret; + + if (enable) { +- int ret; +- +- ret = clk_prepare_enable(wsa->mclk); +- if (ret) { +- dev_err(wsa->dev, "failed to enable mclk\n"); ++ ret = pm_runtime_resume_and_get(wsa->dev); ++ if (ret < 0) + return ret; +- } ++ + wsa_macro_mclk_enable(wsa, true); + + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2548,7 +2547,10 @@ static int wsa_swrm_clock(struct wsa_macro *wsa, bool enable) + regmap_update_bits(regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, + CDC_WSA_SWR_CLK_EN_MASK, 0); + wsa_macro_mclk_enable(wsa, false); +- clk_disable_unprepare(wsa->mclk); ++ ++ ret = pm_runtime_put_autosuspend(wsa->dev); ++ if (ret < 0) ++ dev_warn(wsa->dev, "runtime PM put failed: %d\n", ret); + } + + return 0; +@@ -2656,7 +2658,7 @@ static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) + init.num_parents = 1; + wsa->hw.init = &init; + hw = &wsa->hw; +- ret = clk_hw_register(wsa->dev, hw); ++ ret = devm_clk_hw_register(wsa->dev, hw); + if (ret) + return ret; + +@@ -2771,28 +2773,31 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa->dev = dev; + + /* set MCLK and NPL rates */ +- clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); +- clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); +- +- ret = clk_prepare_enable(wsa->macro); ++ ret = clk_set_rate(wsa->mclk, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err; ++ return ret; + +- ret = clk_prepare_enable(wsa->dcodec); ++ ret = clk_set_rate(wsa->npl, WSA_MACRO_MCLK_FREQ); + if (ret) +- goto err_dcodec; ++ return ret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = devm_pm_clk_create(dev); + if (ret) +- goto err_mclk; ++ return ret; + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) +- goto err_npl; ++ ret = of_pm_clk_add_clks(dev); ++ if (ret < 0) ++ return ret; + +- ret = clk_prepare_enable(wsa->fsgen); ++ pm_runtime_set_autosuspend_delay(dev, 100); ++ pm_runtime_use_autosuspend(dev); ++ ret = devm_pm_runtime_enable(dev); + if (ret) +- goto err_fsgen; ++ return ret; ++ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret < 0) ++ return ret; + + /* reset swr ip */ + regmap_update_bits(wsa->regmap, CDC_WSA_CLK_RST_CTRL_SWR_CONTROL, +@@ -2809,56 +2814,37 @@ static int wsa_macro_probe(struct platform_device *pdev) + wsa_macro_dai, + ARRAY_SIZE(wsa_macro_dai)); + if (ret) +- goto err_clkout; +- +- pm_runtime_set_autosuspend_delay(dev, 3000); +- pm_runtime_use_autosuspend(dev); +- pm_runtime_mark_last_busy(dev); +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); ++ goto err_rpm_put; + + ret = wsa_macro_register_mclk_output(wsa); + if (ret) +- goto err_clkout; ++ goto err_rpm_put; ++ ++ ret = pm_runtime_put_autosuspend(dev); ++ if (ret < 0) ++ dev_warn(dev, "runtime PM put failed after probe: %d\n", ret); + + return 0; +- +-err_clkout: +- clk_disable_unprepare(wsa->fsgen); +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +-err_mclk: +- clk_disable_unprepare(wsa->dcodec); +-err_dcodec: +- clk_disable_unprepare(wsa->macro); +-err: ++err_rpm_put: ++ if (pm_runtime_put_sync_suspend(dev) < 0) ++ dev_warn(dev, "runtime PM sync suspend failed in probe unwind\n"); + return ret; +- +-} +- +-static void wsa_macro_remove(struct platform_device *pdev) +-{ +- struct wsa_macro *wsa = dev_get_drvdata(&pdev->dev); +- +- clk_disable_unprepare(wsa->macro); +- clk_disable_unprepare(wsa->dcodec); +- clk_disable_unprepare(wsa->mclk); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->fsgen); + } + + static int wsa_macro_runtime_suspend(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(wsa->regmap, true); +- regcache_mark_dirty(wsa->regmap); + +- clk_disable_unprepare(wsa->fsgen); +- clk_disable_unprepare(wsa->npl); +- clk_disable_unprepare(wsa->mclk); ++ ret = pm_clk_suspend(dev); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, false); ++ return ret; ++ } ++ ++ regcache_mark_dirty(wsa->regmap); + + return 0; + } +@@ -2866,36 +2852,29 @@ static int wsa_macro_runtime_suspend(struct device *dev) + static int wsa_macro_runtime_resume(struct device *dev) + { + struct wsa_macro *wsa = dev_get_drvdata(dev); +- int ret; ++ int ret, sret; + +- ret = clk_prepare_enable(wsa->mclk); ++ ret = pm_clk_resume(dev); + if (ret) { +- dev_err(dev, "unable to prepare mclk\n"); ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ return ret; ++ } ++ regcache_cache_only(wsa->regmap, false); ++ ++ ret = regcache_sync(wsa->regmap); ++ if (ret) { ++ regcache_cache_only(wsa->regmap, true); ++ regcache_mark_dirty(wsa->regmap); ++ sret = pm_clk_suspend(dev); ++ if (sret) ++ dev_err(dev, ++ "failed to suspend clocks after regcache sync failure: %d\n", ++ sret); + return ret; + } + +- ret = clk_prepare_enable(wsa->npl); +- if (ret) { +- dev_err(dev, "unable to prepare mclkx2\n"); +- goto err_npl; +- } +- +- ret = clk_prepare_enable(wsa->fsgen); +- if (ret) { +- dev_err(dev, "unable to prepare fsgen\n"); +- goto err_fsgen; +- } +- +- regcache_cache_only(wsa->regmap, false); +- regcache_sync(wsa->regmap); +- + return 0; +-err_fsgen: +- clk_disable_unprepare(wsa->npl); +-err_npl: +- clk_disable_unprepare(wsa->mclk); +- +- return ret; + } + + static const struct dev_pm_ops wsa_macro_pm_ops = { +@@ -2929,7 +2908,6 @@ static struct platform_driver wsa_macro_driver = { + .pm = pm_ptr(&wsa_macro_pm_ops), + }, + .probe = wsa_macro_probe, +- .remove = wsa_macro_remove, + }; + + module_platform_driver(wsa_macro_driver); +diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c +--- a/sound/soc/codecs/madera.c ++++ b/sound/soc/codecs/madera.c +@@ -6,6 +6,7 @@ + // Cirrus Logic International Semiconductor Ltd. + // + ++#include + #include + #include + #include +@@ -513,7 +514,7 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + * We can't rely on the DAPM mutex for locking because we need a lock + * that can safely be called in hw_params + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: +@@ -532,8 +533,6 @@ int madera_domain_clk_ev(struct snd_soc_dapm_widget *w, + + madera_debug_dump_domain_groups(priv); + +- mutex_unlock(&priv->rate_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(madera_domain_clk_ev); +@@ -875,9 +874,8 @@ static int madera_adsp_rate_get(struct snd_kcontrol *kcontrol, + const int adsp_num = e->shift_l; + int item; + +- mutex_lock(&priv->rate_lock); +- cached_rate = priv->adsp_rate_cache[adsp_num]; +- mutex_unlock(&priv->rate_lock); ++ scoped_guard(mutex, &priv->rate_lock) ++ cached_rate = priv->adsp_rate_cache[adsp_num]; + + item = snd_soc_enum_val_to_item(e, cached_rate); + ucontrol->value.enumerated.item[0] = item; +@@ -893,7 +891,6 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + const int adsp_num = e->shift_l; + const unsigned int item = ucontrol->value.enumerated.item[0]; +- int ret = 0; + + if (item >= e->items) + return -EINVAL; +@@ -903,22 +900,20 @@ static int madera_adsp_rate_put(struct snd_kcontrol *kcontrol, + * maintain consistent behaviour that rate domains cannot be changed + * while in use since this is a hardware requirement + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, priv->adsp[adsp_num].cs_dsp.base)) { + dev_warn(priv->madera->dev, + "Cannot change '%s' while in use by active audio paths\n", + kcontrol->id.name); +- ret = -EBUSY; ++ return -EBUSY; + } else if (priv->adsp_rate_cache[adsp_num] != e->values[item]) { + /* Volatile register so defer until the codec is powered up */ + priv->adsp_rate_cache[adsp_num] = e->values[item]; +- ret = 1; ++ return 1; + } + +- mutex_unlock(&priv->rate_lock); +- +- return ret; ++ return 0; + } + + static const struct soc_enum madera_adsp_rate_enum[] = { +@@ -1061,15 +1056,13 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + * Prevent the domain powering up while we're checking whether it's + * safe to change rate domain + */ +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + val = snd_soc_component_read(component, e->reg); + val >>= e->shift_l; + val &= e->mask; +- if (snd_soc_enum_item_to_val(e, item) == val) { +- ret = 0; +- goto out; +- } ++ if (snd_soc_enum_item_to_val(e, item) == val) ++ return 0; + + if (!madera_can_change_grp_rate(priv, e->reg)) { + dev_warn(priv->madera->dev, +@@ -1082,8 +1075,6 @@ int madera_rate_put(struct snd_kcontrol *kcontrol, + ret = snd_soc_put_enum_double(kcontrol, ucontrol); + madera_spin_sysclk(priv); + } +-out: +- mutex_unlock(&priv->rate_lock); + + return ret; + } +@@ -3041,12 +3032,11 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + if ((cur & MADERA_AIF1_RATE_MASK) == (tar & MADERA_AIF1_RATE_MASK)) + return 0; + +- mutex_lock(&priv->rate_lock); ++ guard(mutex)(&priv->rate_lock); + + if (!madera_can_change_grp_rate(priv, base + MADERA_AIF_RATE_CTRL)) { + madera_aif_warn(dai, "Cannot change rate while active\n"); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + + /* Guard the rate change with SYSCLK cycles */ +@@ -3055,9 +3045,6 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, + MADERA_AIF1_RATE_MASK, tar); + madera_spin_sysclk(priv); + +-out: +- mutex_unlock(&priv->rate_lock); +- + return ret; + } + +@@ -3345,6 +3332,16 @@ static int madera_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + return 0; + } + ++static const u64 madera_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_NB_IF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_NF | ++ SND_SOC_POSSIBLE_DAIFMT_IB_IF; ++ + const struct snd_soc_dai_ops madera_dai_ops = { + .startup = &madera_startup, + .set_fmt = &madera_set_fmt, +@@ -3352,6 +3349,8 @@ const struct snd_soc_dai_ops madera_dai_ops = { + .hw_params = &madera_hw_params, + .set_sysclk = &madera_dai_set_sysclk, + .set_tristate = &madera_set_tristate, ++ .auto_selectable_formats = &madera_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + EXPORT_SYMBOL_GPL(madera_dai_ops); + +diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c +--- a/sound/soc/codecs/max98090.c ++++ b/sound/soc/codecs/max98090.c +@@ -2341,6 +2341,7 @@ static irqreturn_t max98090_interrupt(int irq, void *data) + * + * @component: MAX98090 component + * @jack: jack to report detection events on ++ * @data: can be used if codec driver need extra data for configuring jack + * + * Enable microphone detection via IRQ on the MAX98090. If GPIOs are + * being used to bring out signals to the processor then only platform +@@ -2646,12 +2647,18 @@ static void max98090_i2c_remove(struct i2c_client *client) + static int max98090_runtime_resume(struct device *dev) + { + struct max98090_priv *max98090 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98090->regmap, false); + + max98090_reset(max98090); + +- regcache_sync(max98090->regmap); ++ ret = regcache_sync(max98090->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98090->regmap, true); ++ regcache_mark_dirty(max98090->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c +--- a/sound/soc/codecs/max98095.c ++++ b/sound/soc/codecs/max98095.c +@@ -5,6 +5,7 @@ + * Copyright 2011 Maxim Integrated Products + */ + ++#include + #include + #include + #include +@@ -1532,15 +1533,17 @@ static int max98095_put_eq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_eq_band(component, channel, 0, coef_set->band1); +- m98095_eq_band(component, channel, 1, coef_set->band2); +- m98095_eq_band(component, channel, 2, coef_set->band3); +- m98095_eq_band(component, channel, 3, coef_set->band4); +- m98095_eq_band(component, channel, 4, coef_set->band5); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_eq_band(component, channel, 0, coef_set->band1); ++ m98095_eq_band(component, channel, 1, coef_set->band2); ++ m98095_eq_band(component, channel, 2, coef_set->band3); ++ m98095_eq_band(component, channel, 3, coef_set->band4); ++ m98095_eq_band(component, channel, 4, coef_set->band5); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +@@ -1683,12 +1686,14 @@ static int max98095_put_bq_enum(struct snd_kcontrol *kcontrol, + regsave = snd_soc_component_read(component, M98095_088_CFG_LEVEL); + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, 0); + +- mutex_lock(&max98095->lock); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, M98095_SEG); +- m98095_biquad_band(component, channel, 0, coef_set->band1); +- m98095_biquad_band(component, channel, 1, coef_set->band2); +- snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, M98095_SEG, 0); +- mutex_unlock(&max98095->lock); ++ scoped_guard(mutex, &max98095->lock) { ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, M98095_SEG); ++ m98095_biquad_band(component, channel, 0, coef_set->band1); ++ m98095_biquad_band(component, channel, 1, coef_set->band2); ++ snd_soc_component_update_bits(component, M98095_00F_HOST_CFG, ++ M98095_SEG, 0); ++ } + + /* Restore the original on/off state */ + snd_soc_component_update_bits(component, M98095_088_CFG_LEVEL, regmask, regsave); +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -100,7 +100,12 @@ static int max98363_resume(struct device *dev) + return ret; + + regcache_cache_only(max98363->regmap, false); +- regcache_sync(max98363->regmap); ++ ret = regcache_sync(max98363->regmap); ++ if (ret) { ++ regcache_cache_only(max98363->regmap, true); ++ regcache_mark_dirty(max98363->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c +--- a/sound/soc/codecs/max98373-i2c.c ++++ b/sound/soc/codecs/max98373-i2c.c +@@ -488,10 +488,17 @@ static int max98373_suspend(struct device *dev) + static int max98373_resume(struct device *dev) + { + struct max98373_priv *max98373 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98373->regmap, false); + max98373_reset(max98373, dev); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/max98373-sdw.c b/sound/soc/codecs/max98373-sdw.c +--- a/sound/soc/codecs/max98373-sdw.c ++++ b/sound/soc/codecs/max98373-sdw.c +@@ -277,7 +277,12 @@ static int max98373_resume(struct device *dev) + } + + regcache_cache_only(max98373->regmap, false); +- regcache_sync(max98373->regmap); ++ ret = regcache_sync(max98373->regmap); ++ if (ret) { ++ regcache_cache_only(max98373->regmap, true); ++ regcache_mark_dirty(max98373->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c +--- a/sound/soc/codecs/max98388.c ++++ b/sound/soc/codecs/max98388.c +@@ -863,10 +863,16 @@ static int max98388_suspend(struct device *dev) + static int max98388_resume(struct device *dev) + { + struct max98388_priv *max98388 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(max98388->regmap, false); + max98388_reset(max98388, dev); +- regcache_sync(max98388->regmap); ++ ret = regcache_sync(max98388->regmap); ++ if (ret) { ++ regcache_cache_only(max98388->regmap, true); ++ regcache_mark_dirty(max98388->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c +--- a/sound/soc/codecs/max98390.c ++++ b/sound/soc/codecs/max98390.c +@@ -788,7 +788,6 @@ static int max98390_dsm_init(struct snd_soc_component *component) + const char *vendor, *product; + struct max98390_priv *max98390 = + snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + char *dsm_param; + + vendor = dmi_get_system_info(DMI_SYS_VENDOR); +@@ -805,6 +804,8 @@ static int max98390_dsm_init(struct snd_soc_component *component) + snprintf(filename, sizeof(filename), "%s", + max98390->dsm_param_name); + } ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, component->dev); + if (ret) { + ret = request_firmware(&fw, "dsm_param.bin", component->dev); +@@ -812,7 +813,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + ret = request_firmware(&fw, "dsmparam.bin", + component->dev); + if (ret) +- goto err; ++ return ret; + } + } + +@@ -822,8 +823,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + if (fw->size < MAX98390_DSM_PARAM_MIN_SIZE) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + dsm_param = (char *)fw->data; + param_start_addr = (dsm_param[0] & 0xff) | (dsm_param[1] & 0xff) << 8; +@@ -833,8 +833,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + fw->size < param_size + MAX98390_DSM_PAYLOAD_OFFSET) { + dev_err(component->dev, + "param fw is invalid.\n"); +- ret = -EINVAL; +- goto err_alloc; ++ return -EINVAL; + } + regmap_write(max98390->regmap, MAX98390_R203A_AMP_EN, 0x80); + dsm_param += MAX98390_DSM_PAYLOAD_OFFSET; +@@ -842,10 +841,7 @@ static int max98390_dsm_init(struct snd_soc_component *component) + dsm_param, param_size); + regmap_write(max98390->regmap, MAX98390_R23E1_DSP_GLOBAL_EN, 0x01); + +-err_alloc: +- release_firmware(fw); +-err: +- return ret; ++ return 0; + } + + static void max98390_init_regs(struct snd_soc_component *component) +@@ -952,11 +948,17 @@ static int max98390_suspend(struct device *dev) + static int max98390_resume(struct device *dev) + { + struct max98390_priv *max98390 = dev_get_drvdata(dev); ++ int ret; + + dev_dbg(dev, "%s:Enter\n", __func__); + + regcache_cache_only(max98390->regmap, false); +- regcache_sync(max98390->regmap); ++ ret = regcache_sync(max98390->regmap); ++ if (ret) { ++ regcache_cache_only(max98390->regmap, true); ++ regcache_mark_dirty(max98390->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/max98396.c b/sound/soc/codecs/max98396.c +--- a/sound/soc/codecs/max98396.c ++++ b/sound/soc/codecs/max98396.c +@@ -1600,19 +1600,37 @@ static int max98396_resume(struct device *dev) + if (max98396->pvdd) { + ret = regulator_enable(max98396->pvdd); + if (ret < 0) +- return ret; ++ goto err_core_supplies; + } + + if (max98396->vbat) { + ret = regulator_enable(max98396->vbat); + if (ret < 0) +- return ret; ++ goto err_pvdd; + } + + regcache_cache_only(max98396->regmap, false); + max98396_reset(max98396, dev); +- regcache_sync(max98396->regmap); ++ ret = regcache_sync(max98396->regmap); ++ if (ret < 0) { ++ regcache_cache_only(max98396->regmap, true); ++ regcache_mark_dirty(max98396->regmap); ++ goto err_vbat; ++ } ++ + return 0; ++ ++err_vbat: ++ if (max98396->vbat) ++ regulator_disable(max98396->vbat); ++err_pvdd: ++ if (max98396->pvdd) ++ regulator_disable(max98396->pvdd); ++err_core_supplies: ++ regulator_bulk_disable(MAX98396_NUM_CORE_SUPPLIES, ++ max98396->core_supplies); ++ ++ return ret; + } + + static const struct dev_pm_ops max98396_pm = { +diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c +--- a/sound/soc/codecs/max98927.c ++++ b/sound/soc/codecs/max98927.c +@@ -742,11 +742,17 @@ static int max98927_suspend(struct device *dev) + static int max98927_resume(struct device *dev) + { + struct max98927_priv *max98927 = dev_get_drvdata(dev); ++ int ret; + + regmap_write(max98927->regmap, MAX98927_R0100_SOFT_RESET, + MAX98927_SOFT_RESET); + regcache_cache_only(max98927->regmap, false); +- regcache_sync(max98927->regmap); ++ ret = regcache_sync(max98927->regmap); ++ if (ret) { ++ regcache_cache_only(max98927->regmap, true); ++ regcache_mark_dirty(max98927->regmap); ++ return ret; ++ } + return 0; + } + +diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c +--- a/sound/soc/codecs/msm8916-wcd-analog.c ++++ b/sound/soc/codecs/msm8916-wcd-analog.c +@@ -217,9 +217,11 @@ + #define CDC_A_RX_HPH_BIAS_LDO_OCP (0xf195) + #define CDC_A_RX_HPH_BIAS_CNP (0xf196) + #define CDC_A_RX_HPH_CNP_EN (0xf197) ++#define CDC_A_RX_HPH_L_TEST (0xf19A) + #define CDC_A_RX_HPH_L_PA_DAC_CTL (0xf19B) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_MASK BIT(1) + #define RX_HPA_L_PA_DAC_CTL_DATA_RESET_RESET BIT(1) ++#define CDC_A_RX_HPH_R_TEST (0xf19C) + #define CDC_A_RX_HPH_R_PA_DAC_CTL (0xf19D) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET BIT(1) + #define RX_HPH_R_PA_DAC_CTL_DATA_RESET_MASK BIT(1) +@@ -232,6 +234,8 @@ + #define RX_EAR_CTL_PA_SEL_MASK BIT(7) + #define RX_EAR_CTL_PA_SEL BIT(7) + ++#define CDC_A_RX_EAR_STATUS (0xf1A1) ++ + #define CDC_A_SPKR_DAC_CTL (0xf1B0) + #define SPKR_DAC_CTL_DAC_RESET_MASK BIT(4) + #define SPKR_DAC_CTL_DAC_RESET_NORMAL 0 +@@ -250,6 +254,7 @@ + SPKR_DRV_CAL_EN | SPKR_DRV_SETTLE_EN | \ + SPKR_DRV_FW_EN | SPKR_DRV_BOOST_SET | \ + SPKR_DRV_CMFB_SET | SPKR_DRV_GAIN_SET) ++#define CDC_A_SPKR_ANA_BIAS_SET (0xf1B3) + #define CDC_A_SPKR_OCP_CTL (0xf1B4) + #define CDC_A_SPKR_PWRSTG_CTL (0xf1B5) + #define SPKR_PWRSTG_CTL_DAC_EN_MASK BIT(0) +@@ -264,6 +269,7 @@ + + #define CDC_A_SPKR_DRV_DBG (0xf1B7) + #define CDC_A_CURRENT_LIMIT (0xf1C0) ++#define CDC_A_BYPASS_MODE (0xf1C2) + #define CDC_A_BOOST_EN_CTL (0xf1C3) + #define CDC_A_SLOPE_COMP_IP_ZERO (0xf1C4) + #define CDC_A_SEC_ACCESS (0xf1D0) +@@ -286,6 +292,11 @@ static const char * const supply_names[] = { + + #define MBHC_MAX_BUTTONS (5) + ++struct wcd_reg_seq { ++ const struct reg_default *seq; ++ int seq_size; ++}; ++ + struct pm8916_wcd_analog_priv { + u16 pmic_rev; + u16 codec_version; +@@ -696,6 +707,59 @@ static int pm8916_wcd_analog_enable_ear_pa(struct snd_soc_dapm_widget *w, + return 0; + } + ++static int pm8916_wcd_analog_enable_hphl_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ /* wait 20 ms after the digital codec has powered down */ ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_L_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ ++static int pm8916_wcd_analog_enable_hphr_pa(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, ++ int event) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ ++ /* This quirk is not required for revisions prior to CAJON_2_0 */ ++ if (priv->codec_version < 4) ++ return 0; ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ usleep_range(7000, 7100); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x04); ++ break; ++ case SND_SOC_DAPM_POST_PMD: ++ msleep(20); ++ snd_soc_component_update_bits(component, CDC_A_RX_HPH_R_TEST, ++ 0x04, 0x00); ++ break; ++ } ++ return 0; ++} ++ + static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_RX_COM_OCP_CTL, 0xD1}, + {CDC_A_RX_COM_OCP_COUNT, 0xFF}, +@@ -715,9 +779,69 @@ static const struct reg_default wcd_reg_defaults_2_0[] = { + {CDC_A_MASTER_BIAS_CTL, 0x30}, + }; + ++static const struct wcd_reg_seq pm8916_data = { ++ .seq = wcd_reg_defaults_2_0, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_2_0), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8950[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0x82}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8950_data = { ++ .seq = wcd_reg_defaults_pm8950, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8950), ++}; ++ ++static const struct reg_default wcd_reg_defaults_pm8953[] = { ++ {CDC_A_RX_COM_OCP_CTL, 0xd1}, ++ {CDC_A_RX_COM_OCP_COUNT, 0xff}, ++ {CDC_D_SEC_ACCESS, 0xa5}, ++ {CDC_D_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_TX_1_2_OPAMP_BIAS, 0x4c}, ++ {CDC_A_NCP_FBCTRL, 0xa8}, ++ {CDC_A_NCP_VCTRL, 0xa4}, ++ {CDC_A_SPKR_DRV_CTL, 0x69}, ++ {CDC_A_SPKR_DRV_DBG, 0x01}, ++ {CDC_A_SEC_ACCESS, 0xa5}, ++ {CDC_A_PERPH_RESET_CTL3, 0x0f}, ++ {CDC_A_CURRENT_LIMIT, 0xa2}, ++ {CDC_A_BYPASS_MODE, 0x18}, ++ {CDC_A_SPKR_ANA_BIAS_SET, 0x41}, ++ {CDC_A_SPKR_DAC_CTL, 0x03}, ++ {CDC_A_SPKR_OCP_CTL, 0xe1}, ++ {CDC_A_RX_HPH_BIAS_PA, 0xfa}, ++ {CDC_A_RX_EAR_STATUS, 0x10}, ++ {CDC_A_MASTER_BIAS_CTL, 0x30}, ++ {CDC_A_MICB_1_INT_RBIAS, 0x00}, ++}; ++ ++static const struct wcd_reg_seq pm8953_data = { ++ .seq = wcd_reg_defaults_pm8953, ++ .seq_size = ARRAY_SIZE(wcd_reg_defaults_pm8953), ++}; ++ + static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + { + struct pm8916_wcd_analog_priv *priv = dev_get_drvdata(component->dev); ++ const struct wcd_reg_seq *wcd_reg_init_data; + int err, reg; + + err = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies); +@@ -738,9 +862,11 @@ static int pm8916_wcd_analog_probe(struct snd_soc_component *component) + snd_soc_component_write(component, CDC_D_PERPH_RESET_CTL4, 0x01); + snd_soc_component_write(component, CDC_A_PERPH_RESET_CTL4, 0x01); + +- for (reg = 0; reg < ARRAY_SIZE(wcd_reg_defaults_2_0); reg++) +- snd_soc_component_write(component, wcd_reg_defaults_2_0[reg].reg, +- wcd_reg_defaults_2_0[reg].def); ++ wcd_reg_init_data = of_device_get_match_data(component->dev); ++ ++ for (reg = 0; reg < wcd_reg_init_data->seq_size; reg++) ++ snd_soc_component_write(component, wcd_reg_init_data->seq[reg].reg, ++ wcd_reg_init_data->seq[reg].def); + + priv->component = component; + +@@ -883,11 +1009,15 @@ static const struct snd_soc_dapm_widget pm8916_wcd_analog_dapm_widgets[] = { + SND_SOC_DAPM_MUX("EAR_S", SND_SOC_NOPM, 0, 0, &ear_mux), + SND_SOC_DAPM_SUPPLY("EAR CP", CDC_A_NCP_EN, 4, 0, NULL, 0), + +- SND_SOC_DAPM_PGA("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHL PA", CDC_A_RX_HPH_CNP_EN, 5, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphl_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHL", SND_SOC_NOPM, 0, 0, &hphl_mux), + SND_SOC_DAPM_MIXER("HPHL DAC", CDC_A_RX_HPH_L_PA_DAC_CTL, 3, 0, NULL, + 0), +- SND_SOC_DAPM_PGA("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0), ++ SND_SOC_DAPM_PGA_E("HPHR PA", CDC_A_RX_HPH_CNP_EN, 4, 0, NULL, 0, ++ pm8916_wcd_analog_enable_hphr_pa, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HPHR", SND_SOC_NOPM, 0, 0, &hphr_mux), + SND_SOC_DAPM_MIXER("HPHR DAC", CDC_A_RX_HPH_R_PA_DAC_CTL, 3, 0, NULL, + 0), +@@ -1259,7 +1389,9 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev) + } + + static const struct of_device_id pm8916_wcd_analog_spmi_match_table[] = { +- { .compatible = "qcom,pm8916-wcd-analog-codec", }, ++ { .compatible = "qcom,pm8916-wcd-analog-codec", .data = &pm8916_data }, ++ { .compatible = "qcom,pm8950-wcd-analog-codec", .data = &pm8950_data }, ++ { .compatible = "qcom,pm8953-wcd-analog-codec", .data = &pm8953_data }, + { } + }; + +diff --git a/sound/soc/codecs/mt6357.c b/sound/soc/codecs/mt6357.c +--- a/sound/soc/codecs/mt6357.c ++++ b/sound/soc/codecs/mt6357.c +@@ -1834,8 +1834,8 @@ static int mt6357_platform_driver_probe(struct platform_device *pdev) + } + + static const struct platform_device_id mt6357_platform_ids[] = { +- {"mt6357-sound", 0}, +- { /* sentinel */ }, ++ { .name = "mt6357-sound" }, ++ { /* sentinel */ } + }; + MODULE_DEVICE_TABLE(platform, mt6357_platform_ids); + +diff --git a/sound/soc/codecs/mt6359-accdet.c b/sound/soc/codecs/mt6359-accdet.c +--- a/sound/soc/codecs/mt6359-accdet.c ++++ b/sound/soc/codecs/mt6359-accdet.c +@@ -6,6 +6,7 @@ + // Author: Argus Lin + // + ++#include + #include + #include + #include +@@ -398,14 +399,13 @@ static void mt6359_accdet_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, accdet_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + priv->pre_accdet_status = priv->accdet_status; + check_jack_btn_type(priv); + + if (priv->jack_plugged && + priv->pre_accdet_status != priv->accdet_status) + mt6359_accdet_jack_report(priv); +- mutex_unlock(&priv->res_lock); + } + + static void mt6359_accdet_jd_work(struct work_struct *work) +@@ -416,7 +416,7 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + struct mt6359_accdet *priv = + container_of(work, struct mt6359_accdet, jd_work); + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + if (priv->jd_sts == M_PLUG_IN) { + priv->jack_plugged = true; + +@@ -450,7 +450,6 @@ static void mt6359_accdet_jd_work(struct work_struct *work) + + if (priv->caps & ACCDET_PMIC_EINT_IRQ) + recover_eint_setting(priv); +- mutex_unlock(&priv->res_lock); + } + + static irqreturn_t mt6359_accdet_irq(int irq, void *data) +@@ -459,7 +458,7 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + unsigned int irq_val = 0, val = 0, value = 0; + int ret; + +- mutex_lock(&priv->res_lock); ++ guard(mutex)(&priv->res_lock); + regmap_read(priv->regmap, ACCDET_IRQ_ADDR, &irq_val); + + if (irq_val & ACCDET_IRQ_MASK_SFT) { +@@ -474,7 +473,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + 1000); + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -498,7 +496,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -521,7 +518,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + if (ret) { + dev_err(priv->dev, "%s(), ret %d\n", __func__, + ret); +- mutex_unlock(&priv->res_lock); + return IRQ_NONE; + } + regmap_update_bits(priv->regmap, ACCDET_IRQ_ADDR, +@@ -540,7 +536,6 @@ static irqreturn_t mt6359_accdet_irq(int irq, void *data) + + queue_work(priv->jd_workqueue, &priv->jd_work); + } +- mutex_unlock(&priv->res_lock); + + return IRQ_HANDLED; + } +diff --git a/sound/soc/codecs/ntpfw.c b/sound/soc/codecs/ntpfw.c +--- a/sound/soc/codecs/ntpfw.c ++++ b/sound/soc/codecs/ntpfw.c +@@ -89,7 +89,7 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + { + struct device *dev = &i2c->dev; + const struct ntpfw_chunk *chunk; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + const u8 *data; + size_t leftover; + int ret; +@@ -101,10 +101,8 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + return ret; + } + +- if (!ntpfw_verify(dev, fw->data, fw->size, magic)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify(dev, fw->data, fw->size, magic)) ++ return -EINVAL; + + data = fw->data + sizeof(struct ntpfw_header); + leftover = fw->size - sizeof(struct ntpfw_header); +@@ -112,23 +110,18 @@ int ntpfw_load(struct i2c_client *i2c, const char *name, u32 magic) + while (leftover) { + chunk = (struct ntpfw_chunk *)data; + +- if (!ntpfw_verify_chunk(dev, chunk, leftover)) { +- ret = -EINVAL; +- goto done; +- } ++ if (!ntpfw_verify_chunk(dev, chunk, leftover)) ++ return -EINVAL; + + ret = ntpfw_send_chunk(i2c, chunk); + if (ret) +- goto done; ++ return ret; + + data += be16_to_cpu(chunk->length) + sizeof(*chunk); + leftover -= be16_to_cpu(chunk->length) + sizeof(*chunk); + } + +-done: +- release_firmware(fw); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(ntpfw_load); + +diff --git a/sound/soc/codecs/ntpfw.h b/sound/soc/codecs/ntpfw.h +--- a/sound/soc/codecs/ntpfw.h ++++ b/sound/soc/codecs/ntpfw.h +@@ -1,5 +1,5 @@ + /* SPDX-License-Identifier: GPL-2.0-only */ +-/** ++/* + * ntpfw.h - Firmware helper functions for Neofidelity codecs + * + * Copyright (c) 2024, SaluteDevices. All Rights Reserved. +@@ -13,10 +13,11 @@ + /** + * ntpfw_load - load firmware to amplifier over i2c interface. + * +- * @i2c Pointer to amplifier's I2C client. +- * @name Firmware file name. +- * @magic Magic number to validate firmware. +- * @return 0 or error code upon error. ++ * @i2c: Pointer to amplifier's I2C client. ++ * @name: Firmware file name. ++ * @magic: Magic number to validate firmware. ++ * ++ * Returns: 0 or error code upon error. + */ + int ntpfw_load(struct i2c_client *i2c, const char *name, const u32 magic); + +diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c +--- a/sound/soc/codecs/pcm1681.c ++++ b/sound/soc/codecs/pcm1681.c +@@ -199,10 +199,17 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, + return pcm1681_set_deemph(component); + } + ++static const u64 pcm1681_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1681_dai_ops = { + .set_fmt = pcm1681_set_dai_fmt, + .hw_params = pcm1681_hw_params, + .mute_stream = pcm1681_mute, ++ .auto_selectable_formats = &pcm1681_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm1754.c b/sound/soc/codecs/pcm1754.c +--- a/sound/soc/codecs/pcm1754.c ++++ b/sound/soc/codecs/pcm1754.c +@@ -78,10 +78,16 @@ static int pcm1754_mute_stream(struct snd_soc_dai *dai, int mute, int stream) + return 0; + } + ++static const u64 pcm1754_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm1754_dai_ops = { + .set_fmt = pcm1754_set_dai_fmt, + .hw_params = pcm1754_hw_params, + .mute_stream = pcm1754_mute_stream, ++ .auto_selectable_formats = &pcm1754_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static const struct snd_soc_dai_driver pcm1754_dai = { +diff --git a/sound/soc/codecs/pcm1789.c b/sound/soc/codecs/pcm1789.c +--- a/sound/soc/codecs/pcm1789.c ++++ b/sound/soc/codecs/pcm1789.c +@@ -164,11 +164,18 @@ static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd, + return ret; + } + ++static const u64 pcm1789_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops pcm1789_dai_ops = { + .set_fmt = pcm1789_set_dai_fmt, + .hw_params = pcm1789_hw_params, + .mute_stream = pcm1789_mute, + .trigger = pcm1789_trigger, ++ .auto_selectable_formats = &pcm1789_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c +--- a/sound/soc/codecs/pcm179x.c ++++ b/sound/soc/codecs/pcm179x.c +@@ -142,10 +142,16 @@ static int pcm179x_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm179x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; ++ + static const struct snd_soc_dai_ops pcm179x_dai_ops = { + .set_fmt = pcm179x_set_dai_fmt, + .hw_params = pcm179x_hw_params, + .mute_stream = pcm179x_mute, ++ .auto_selectable_formats = &pcm179x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm186x.c b/sound/soc/codecs/pcm186x.c +--- a/sound/soc/codecs/pcm186x.c ++++ b/sound/soc/codecs/pcm186x.c +@@ -473,11 +473,20 @@ static int pcm186x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + return 0; + } + ++static const u64 pcm186x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm186x_dai_ops = { + .set_sysclk = pcm186x_set_dai_sysclk, + .set_tdm_slot = pcm186x_set_tdm_slot, + .set_fmt = pcm186x_set_fmt, + .hw_params = pcm186x_hw_params, ++ .auto_selectable_formats = &pcm186x_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver pcm1863_dai = { +diff --git a/sound/soc/codecs/pcm3060.c b/sound/soc/codecs/pcm3060.c +--- a/sound/soc/codecs/pcm3060.c ++++ b/sound/soc/codecs/pcm3060.c +@@ -164,10 +164,18 @@ static int pcm3060_hw_params(struct snd_pcm_substream *substream, + return 0; + } + ++static const u64 pcm3060_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_NB_NF; ++ + static const struct snd_soc_dai_ops pcm3060_dai_ops = { + .set_sysclk = pcm3060_set_sysclk, + .set_fmt = pcm3060_set_fmt, + .hw_params = pcm3060_hw_params, ++ .auto_selectable_formats = &pcm3060_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + #define PCM3060_DAI_RATES_ADC (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ +diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c +--- a/sound/soc/codecs/pcm512x.c ++++ b/sound/soc/codecs/pcm512x.c +@@ -6,7 +6,7 @@ + * Copyright 2014 Linaro Ltd + */ + +- ++#include + #include + #include + #include +@@ -399,10 +399,9 @@ static int pcm512x_digital_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + ucontrol->value.integer.value[0] = !(pcm512x->mute & 0x4); + ucontrol->value.integer.value[1] = !(pcm512x->mute & 0x2); +- mutex_unlock(&pcm512x->mutex); + + return 0; + } +@@ -414,7 +413,7 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component); + int ret, changed = 0; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if ((pcm512x->mute & 0x4) == (ucontrol->value.integer.value[0] << 2)) { + pcm512x->mute ^= 0x4; +@@ -430,13 +429,10 @@ static int pcm512x_digital_playback_switch_put(struct snd_kcontrol *kcontrol, + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- mutex_unlock(&pcm512x->mutex); + return ret; + } + } + +- mutex_unlock(&pcm512x->mutex); +- + return changed; + } + +@@ -1465,7 +1461,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + int ret; + unsigned int mute_det; + +- mutex_lock(&pcm512x->mutex); ++ guard(mutex)(&pcm512x->mutex); + + if (mute) { + pcm512x->mute |= 0x1; +@@ -1475,7 +1471,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to set digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1488,7 +1484,7 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + if (ret != 0) { + dev_err(component->dev, + "Failed to update digital mute: %d\n", ret); +- goto unlock; ++ return ret; + } + + regmap_read_poll_timeout(pcm512x->regmap, +@@ -1499,18 +1495,24 @@ static int pcm512x_mute(struct snd_soc_dai *dai, int mute, int direction) + 200, 10000); + } + +-unlock: +- mutex_unlock(&pcm512x->mutex); +- + return ret; + } + ++static const u64 pcm512x_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops pcm512x_dai_ops = { + .startup = pcm512x_dai_startup, + .hw_params = pcm512x_hw_params, + .set_fmt = pcm512x_set_fmt, + .mute_stream = pcm512x_mute, + .set_bclk_ratio = pcm512x_set_bclk_ratio, ++ .auto_selectable_formats = &pcm512x_selectable_formats, ++ .num_auto_selectable_formats = 1, + .no_capture_mute = 1, + }; + +diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c +--- a/sound/soc/codecs/pcm6240.c ++++ b/sound/soc/codecs/pcm6240.c +@@ -12,6 +12,7 @@ + // Author: Shenghao Ding + // + ++#include + #include + #include + #include +@@ -605,7 +606,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + unsigned int reg = mc->reg; + unsigned int val; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + if (pcm_dev->chip_id == PCM1690) { + ret = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL, +@@ -613,18 +614,18 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read mode err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + val &= PCM1690_REG_MODE_CTRL_DAMS_MSK; + /* Set to wide-range mode, before using vol ctrl. */ + if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { + ucontrol->value.integer.value[0] = -25500; +- goto out; ++ return ret; + } + /* Set to fine mode, before using fine vol ctrl. */ + if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { + ucontrol->value.integer.value[0] = -12750; +- goto out; ++ return ret; + } + } + +@@ -632,15 +633,14 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, + if (ret) { + dev_err(pcm_dev->dev, "%s: read err=%d\n", + __func__, ret); +- goto out; ++ return ret; + } + + val = (val >> shift) & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; + ucontrol->value.integer.value[0] = val; +-out: +- mutex_unlock(&pcm_dev->codec_lock); ++ + return ret; + } + +@@ -678,7 +678,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + unsigned int val, val_mask; + unsigned int reg = mc->reg; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + val = ucontrol->value.integer.value[0] & mask; + val = (val > max) ? max : val; + val = mc->invert ? max - val : val; +@@ -702,7 +702,7 @@ static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol, + __func__, rc); + else + rc = 1; +- mutex_unlock(&pcm_dev->codec_lock); ++ + return rc; + } + +@@ -1577,10 +1577,10 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + { + struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp); + struct i2c_adapter *adap = pcm_dev->client->adapter; +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret, i, j; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + + pcm_dev->component = comp; + +@@ -1588,7 +1588,7 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + for (j = 0; j < 2; j++) { + ret = pcmdev_gain_ctrl_add(pcm_dev, i, j); + if (ret < 0) +- goto out; ++ return ret; + } + } + +@@ -1621,21 +1621,17 @@ static int pcmdevice_comp_probe(struct snd_soc_component *comp) + if (ret) { + dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } + + ret = pcmdev_regbin_ready(fw_entry, pcm_dev); + if (ret) { + dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__, + pcm_dev->bin_name, ret); +- goto out; ++ return ret; + } +- ret = pcmdev_profile_ctrl_add(pcm_dev); +-out: +- release_firmware(fw_entry); + +- mutex_unlock(&pcm_dev->codec_lock); +- return ret; ++ return pcmdev_profile_ctrl_add(pcm_dev); + } + + +@@ -1645,9 +1641,8 @@ static void pcmdevice_comp_remove(struct snd_soc_component *codec) + + if (!pcm_dev) + return; +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_config_info_remove(pcm_dev); +- mutex_unlock(&pcm_dev->codec_lock); + } + + static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = { +@@ -1890,9 +1885,9 @@ static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream) + else + block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP; + +- mutex_lock(&pcm_dev->codec_lock); ++ guard(mutex)(&pcm_dev->codec_lock); + pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type); +- mutex_unlock(&pcm_dev->codec_lock); ++ + return 0; + } + +diff --git a/sound/soc/codecs/peb2466.c b/sound/soc/codecs/peb2466.c +--- a/sound/soc/codecs/peb2466.c ++++ b/sound/soc/codecs/peb2466.c +@@ -6,6 +6,7 @@ + // + // Author: Herve Codina + ++#include + #include + #include + #include +@@ -1537,17 +1538,14 @@ static int peb2466_fw_parse(struct snd_soc_component *component, + + static int peb2466_load_coeffs(struct snd_soc_component *component, const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, component->dev); + if (ret) + return ret; + +- ret = peb2466_fw_parse(component, fw->data, fw->size); +- release_firmware(fw); +- +- return ret; ++ return peb2466_fw_parse(component, fw->data, fw->size); + } + + static int peb2466_component_probe(struct snd_soc_component *component) +@@ -1704,13 +1702,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + * So, a specific cache value is used. + */ + +- mutex_lock(&peb2466->gpio.lock); ++ guard(mutex)(&peb2466->gpio.lock); + + cache = peb2466_chip_gpio_get_cache(peb2466, xr_reg); +- if (!cache) { +- ret = -EINVAL; +- goto end; +- } ++ if (!cache) ++ return -EINVAL; + + tmp = *cache; + tmp &= ~mask; +@@ -1718,14 +1714,11 @@ static int peb2466_chip_gpio_update_bits(struct peb2466 *peb2466, unsigned int x + + ret = regmap_write(peb2466->regmap, xr_reg, tmp); + if (ret) +- goto end; ++ return ret; + + *cache = tmp; +- ret = 0; + +-end: +- mutex_unlock(&peb2466->gpio.lock); +- return ret; ++ return 0; + } + + static int peb2466_chip_gpio_set(struct gpio_chip *c, unsigned int offset, +diff --git a/sound/soc/codecs/pm4125-sdw.c b/sound/soc/codecs/pm4125-sdw.c +--- a/sound/soc/codecs/pm4125-sdw.c ++++ b/sound/soc/codecs/pm4125-sdw.c +@@ -464,10 +464,16 @@ static int __maybe_unused pm4125_sdw_runtime_suspend(struct device *dev) + static int __maybe_unused pm4125_sdw_runtime_resume(struct device *dev) + { + struct pm4125_sdw_priv *priv = dev_get_drvdata(dev); ++ int ret; + + if (priv->regmap) { + regcache_cache_only(priv->regmap, false); +- regcache_sync(priv->regmap); ++ ret = regcache_sync(priv->regmap); ++ if (ret) { ++ regcache_cache_only(priv->regmap, true); ++ regcache_mark_dirty(priv->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/pm4125.c b/sound/soc/codecs/pm4125.c +--- a/sound/soc/codecs/pm4125.c ++++ b/sound/soc/codecs/pm4125.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1295,7 +1296,7 @@ static const struct irq_domain_ops pm4125_domain_ops = { + + static int pm4125_irq_init(struct pm4125_priv *pm4125, struct device *dev) + { +- pm4125->virq = irq_domain_add_linear(NULL, 1, &pm4125_domain_ops, NULL); ++ pm4125->virq = irq_domain_create_linear(NULL, 1, &pm4125_domain_ops, NULL); + if (!(pm4125->virq)) { + dev_err(dev, "%s: Failed to add IRQ domain\n", __func__); + return -EINVAL; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -784,7 +784,12 @@ static int rt1017_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1017->regmap, false); +- regcache_sync(rt1017->regmap); ++ ret = regcache_sync(rt1017->regmap); ++ if (ret) { ++ regcache_cache_only(rt1017->regmap, true); ++ regcache_mark_dirty(rt1017->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c +--- a/sound/soc/codecs/rt1316-sdw.c ++++ b/sound/soc/codecs/rt1316-sdw.c +@@ -756,7 +756,12 @@ static int rt1316_dev_resume(struct device *dev) + } + + regcache_cache_only(rt1316->regmap, false); +- regcache_sync(rt1316->regmap); ++ ret = regcache_sync(rt1316->regmap); ++ if (ret) { ++ regcache_cache_only(rt1316->regmap, true); ++ regcache_mark_dirty(rt1316->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -830,7 +830,12 @@ static int rt1318_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1318->regmap, false); +- regcache_sync(rt1318->regmap); ++ ret = regcache_sync(rt1318->regmap); ++ if (ret) { ++ regcache_cache_only(rt1318->regmap, true); ++ regcache_mark_dirty(rt1318->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -410,6 +410,402 @@ static const struct reg_sequence rt1321_blind_write[] = { + { 0x41001988, 0x03 }, + }; + ++static const struct reg_sequence rt1321_va1_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x0000d73c, 0xb7 }, ++ { 0x0000d73d, 0xd7 }, ++ { 0x0000d73e, 0x00 }, ++ { 0x0000d73f, 0x10 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2df80, 0x00 }, ++ { 0x3fc2df81, 0x00 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x0000d541, 0x40 }, ++ { 0x0000d486, 0x43 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x20009012, 0x00 }, ++ { 0x0000dd08, 0x17 }, ++ { 0x0000dd09, 0x0e }, ++ { 0x0000dd0a, 0x17 }, ++ { 0x0000dd0b, 0x0e }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000db00, 0x00 }, ++ { 0x0000db01, 0x00 }, ++ { 0x0000db02, 0x73 }, ++ { 0x0000db03, 0x00 }, ++ { 0x0000db04, 0x00 }, ++ { 0x0000db05, 0x00 }, ++ { 0x0000db06, 0x00 }, ++ { 0x0000db07, 0x00 }, ++ { 0x0000db08, 0x7f }, ++ { 0x0000db09, 0x00 }, ++ { 0x0000db1a, 0x00 }, ++ { 0x0000db1b, 0x00 }, ++ { 0x0000db19, 0x00 }, ++}; ++ ++static const struct reg_sequence rt1321_va2_blind_write[] = { ++ { 0x0000c003, 0xf0 }, ++ { 0x0000c01b, 0xfc }, ++ { 0x0000c5c3, 0xf2 }, ++ { 0x0000c5c2, 0x00 }, ++ { 0x0000c5c1, 0x10 }, ++ { 0x0000c5c0, 0x04 }, ++ { 0x0000c5c7, 0x03 }, ++ { 0x0000c5c6, 0x10 }, ++ { 0x0000c526, 0x47 }, ++ { 0x0000c5c4, 0x12 }, ++ { 0x0000c5c5, 0x60 }, ++ { 0x0000c520, 0x10 }, ++ { 0x0000c521, 0x32 }, ++ { 0x0000c5c7, 0x00 }, ++ { 0x0000c5c8, 0x03 }, ++ { 0x0000c5d3, 0x08 }, ++ { 0x0000c5d2, 0x0a }, ++ { 0x0000c5d1, 0x49 }, ++ { 0x0000c5d0, 0x0f }, ++ { 0x0000c580, 0x10 }, ++ { 0x0000c581, 0x32 }, ++ { 0x0000c582, 0x01 }, ++ { 0x0000c682, 0x60 }, ++ { 0x0000c019, 0x10 }, ++ { 0x0000c5f0, 0x01 }, ++ { 0x0000c5f7, 0x22 }, ++ { 0x0000c5f6, 0x22 }, ++ { 0x0000c057, 0x51 }, ++ { 0x0000c054, 0x55 }, ++ { 0x0000c053, 0x55 }, ++ { 0x0000c052, 0x55 }, ++ { 0x0000c051, 0x01 }, ++ { 0x0000c050, 0x15 }, ++ { 0x0000c060, 0x99 }, ++ { 0x0000c030, 0x55 }, ++ { 0x0000c061, 0x55 }, ++ { 0x0000c063, 0x55 }, ++ { 0x0000c065, 0xa5 }, ++ { 0x0000c06b, 0x0a }, ++ { 0x0000ca05, 0xd6 }, ++ { 0x0000ca06, 0x11 }, ++ { 0x0000ca07, 0x1e }, ++ { 0x0000ca25, 0xd6 }, ++ { 0x0000ca26, 0x11 }, ++ { 0x0000ca27, 0x1e }, ++ { 0x0000cd00, 0x05 }, ++ { 0x0000cd81, 0x49 }, ++ { 0x0000cd82, 0x49 }, ++ { 0x0000c604, 0x40 }, ++ { 0x0000c609, 0x40 }, ++ { 0x0000c046, 0xf7 }, ++ { 0x0000c045, 0xff }, ++ { 0x0000c044, 0xff }, ++ { 0x0000c043, 0xff }, ++ { 0x0000c042, 0xff }, ++ { 0x0000c041, 0xff }, ++ { 0x0000c040, 0xff }, ++ { 0x0000c049, 0xff }, ++ { 0x0000c028, 0x3f }, ++ { 0x0000c020, 0x3f }, ++ { 0x0000c032, 0x13 }, ++ { 0x0000c033, 0x01 }, ++ { 0x0000cc10, 0x01 }, ++ { 0x0000dc20, 0x03 }, ++ { 0x0000de03, 0x05 }, ++ { 0x0000dc00, 0x00 }, ++ { 0x0000c700, 0xf0 }, ++ { 0x0000c701, 0x13 }, ++ { 0x0000c900, 0xc3 }, ++ { 0x0000c570, 0x08 }, ++ { 0x0000c086, 0x02 }, ++ { 0x0000c085, 0x7f }, ++ { 0x0000c084, 0x00 }, ++ { 0x0000c081, 0xff }, ++ { 0x0000f084, 0x0f }, ++ { 0x0000f083, 0xff }, ++ { 0x0000f082, 0xff }, ++ { 0x0000f081, 0xff }, ++ { 0x0000f080, 0xff }, ++ { 0x20003003, 0x3f }, ++ { 0x20005818, 0x81 }, ++ { 0x20009018, 0x81 }, ++ { 0x2000301c, 0x81 }, ++ { 0x0000c003, 0xc0 }, ++ { 0x0000c047, 0x80 }, ++ { 0x0000d541, 0x80 }, ++ { 0x0000d487, 0x0b }, ++ { 0x0000d487, 0x3b }, ++ { 0x0000d486, 0xc3 }, ++ { 0x0000d470, 0x89 }, ++ { 0x0000d471, 0x3a }, ++ { 0x0000d472, 0x3d }, ++ { 0x0000d474, 0x11 }, ++ { 0x0000d475, 0x32 }, ++ { 0x0000d476, 0x64 }, ++ { 0x0000d477, 0x10 }, ++ { 0x0000d478, 0xff }, ++ { 0x0000d479, 0x20 }, ++ { 0x0000d47a, 0x10 }, ++ { 0x10008000, 0x67 }, ++ { 0x10008001, 0x80 }, ++ { 0x10008002, 0x00 }, ++ { 0x10008003, 0x00 }, ++ { 0x1000cd56, 0x00 }, ++ { 0x0000d486, 0x43 }, ++ { 0x3fc2dfc3, 0x00 }, ++ { 0x3fc2dfc2, 0x00 }, ++ { 0x3fc2dfc1, 0x00 }, ++ { 0x3fc2dfc0, 0x03 }, ++ { 0x3fc2dfc7, 0x00 }, ++ { 0x3fc2dfc6, 0x00 }, ++ { 0x3fc2dfc5, 0x00 }, ++ { 0x3fc2dfc4, 0x01 }, ++ { 0x3fc2dfa3, 0x00 }, ++ { 0x3fc2dfa2, 0x00 }, ++ { 0x3fc2dfa1, 0x00 }, ++ { 0x3fc2dfa0, 0x00 }, ++ { 0x3fc2df80, 0x10 }, ++ { 0x3fc2df81, 0x20 }, ++ { 0x3fc2df82, 0x00 }, ++ { 0x3fc2df83, 0x00 }, ++ { 0x3fc2df84, 0x50 }, ++ { 0x3fc2df85, 0x19 }, ++ { 0x3fc2df86, 0x00 }, ++ { 0x3fc2df87, 0x00 }, ++ { 0x3fc2df88, 0x52 }, ++ { 0x3fc2df89, 0x23 }, ++ { 0x3fc2df8a, 0x00 }, ++ { 0x3fc2df8b, 0x00 }, ++ { 0x3fc2df8c, 0xe0 }, ++ { 0x3fc2df8d, 0x2e }, ++ { 0x3fc2df8e, 0x00 }, ++ { 0x3fc2df8f, 0x00 }, ++ { 0x3fc2df90, 0xe0 }, ++ { 0x3fc2df91, 0x2e }, ++ { 0x3fc2df92, 0x00 }, ++ { 0x3fc2df93, 0x00 }, ++ { 0x3fc2df94, 0x01 }, ++ { 0x3fc2df95, 0x08 }, ++ { 0x3fc2df96, 0x00 }, ++ { 0x3fc2df97, 0x00 }, ++ { 0x3fc2df40, 0x80 }, ++ { 0x3fc2df41, 0xbb }, ++ { 0x3fc2df42, 0x00 }, ++ { 0x3fc2df43, 0x00 }, ++ { 0x3fc2df44, 0xc0 }, ++ { 0x3fc2df45, 0x99 }, ++ { 0x3fc2df46, 0x01 }, ++ { 0x3fc2df47, 0x00 }, ++ { 0x3fc2df48, 0x00 }, ++ { 0x3fc2df49, 0x00 }, ++ { 0x3fc2df4a, 0x00 }, ++ { 0x3fc2df4b, 0x00 }, ++ { 0x3fc2df4c, 0x00 }, ++ { 0x3fc2df4d, 0x00 }, ++ { 0x3fc2df4e, 0x00 }, ++ { 0x3fc2df4f, 0x00 }, ++ { 0x3fc2df50, 0x01 }, ++ { 0x3fc2df51, 0x00 }, ++ { 0x3fc2df52, 0x00 }, ++ { 0x3fc2df53, 0x00 }, ++ { 0x3fc2df54, 0x01 }, ++ { 0x3fc2df55, 0x00 }, ++ { 0x3fc2df56, 0x00 }, ++ { 0x3fc2df57, 0x00 }, ++ { 0x3fc2df58, 0x00 }, ++ { 0x3fc2df59, 0x00 }, ++ { 0x3fc2df5a, 0x00 }, ++ { 0x3fc2df5b, 0x00 }, ++ { 0x3fc2df5c, 0x01 }, ++ { 0x3fc2df5d, 0x00 }, ++ { 0x3fc2df5e, 0x00 }, ++ { 0x3fc2df5f, 0x00 }, ++ { 0x3fc2df60, 0x00 }, ++ { 0x3fc2df61, 0x00 }, ++ { 0x3fc2df62, 0x00 }, ++ { 0x3fc2df63, 0x00 }, ++ { 0x3fc2df64, 0x00 }, ++ { 0x3fc2df65, 0x00 }, ++ { 0x3fc2df66, 0x00 }, ++ { 0x3fc2df67, 0x10 }, ++ { 0x3fc2df68, 0x01 }, ++ { 0x3fc2df69, 0x00 }, ++ { 0x3fc2df6a, 0x00 }, ++ { 0x3fc2df6b, 0x00 }, ++ { 0x3fc2df6c, 0x01 }, ++ { 0x3fc2df6d, 0x00 }, ++ { 0x3fc2df6e, 0x00 }, ++ { 0x3fc2df6f, 0x00 }, ++ { 0x3fc2df70, 0x04 }, ++ { 0x3fc2df71, 0x00 }, ++ { 0x3fc2df72, 0x00 }, ++ { 0x3fc2df73, 0x00 }, ++ { 0x3fc2df74, 0x01 }, ++ { 0x3fc2df75, 0x00 }, ++ { 0x3fc2df76, 0x00 }, ++ { 0x3fc2df77, 0x00 }, ++ { 0x1000db00, 0x04 }, ++ { 0x1000db01, 0x00 }, ++ { 0x1000db02, 0x10 }, ++ { 0x1000db03, 0x00 }, ++ { 0x1000db04, 0x00 }, ++ { 0x1000db05, 0x45 }, ++ { 0x1000db06, 0x0d }, ++ { 0x1000db07, 0x01 }, ++ { 0x1000db08, 0x00 }, ++ { 0x1000db09, 0x00 }, ++ { 0x1000db0a, 0xbf }, ++ { 0x1000db0b, 0x0b }, ++ { 0x1000db0c, 0x11 }, ++ { 0x1000db0d, 0x00 }, ++ { 0x1000db0e, 0x00 }, ++ { 0x1000db0f, 0x00 }, ++ { 0x1000db10, 0x2c }, ++ { 0x1000db11, 0xfa }, ++ { 0x1000db12, 0x00 }, ++ { 0x1000db13, 0x00 }, ++ { 0x1000db14, 0x09 }, ++ { 0x0000d540, 0x21 }, ++ { 0x0000d714, 0x17 }, ++ { 0x0000dd0b, 0x0d }, ++ { 0x0000dd0a, 0xff }, ++ { 0x0000dd09, 0x0d }, ++ { 0x0000dd08, 0xff }, ++ { 0x0000c5fb, 0x12 }, ++ { 0x0000c570, 0x08 }, ++}; ++ + static const struct reg_default rt1320_reg_defaults[] = { + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_FU113, RT1320_SDCA_CTL_FU_MUTE, CH_01), 0x01 }, +@@ -440,6 +836,7 @@ static const struct reg_default rt1320_mbq_defaults[] = { + static bool rt1320_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000 ... 0xc086: + case 0xc400 ... 0xc409: + case 0xc480 ... 0xc48f: +@@ -497,10 +894,15 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x20003000 ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x20005800 ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x20009000 ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc000c0 ... 0x3fc2dfc8: + case 0x3fe00000 ... 0x3fe36fff: + /* 0x40801508/0x40801809/0x4080180a/0x40801909/0x4080190a */ +@@ -545,6 +947,7 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) + static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0xc000: + case 0xc003: + case 0xc081: +@@ -596,15 +999,20 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) + case 0x1000c000 ... 0x1000dfff: + case 0x1000f008: + case 0x1000f021: +- case 0x2000300f: ++ case 0x2000300e ... 0x2000300f: + case 0x2000301c: +- case 0x2000900f: ++ case 0x20003040 ... 0x20003059: ++ case 0x2000580e ... 0x2000580f: ++ case 0x20005818: ++ case 0x20005840 ... 0x20005859: ++ case 0x2000900e ... 0x2000900f: + case 0x20009018: ++ case 0x20009040 ... 0x20009059: + case 0x3fc2ab80 ... 0x3fc2ac4c: + case 0x3fc2b780: + case 0x3fc2bf80 ... 0x3fc2bf83: + case 0x3fc2bfc0 ... 0x3fc2bfc8: +- case 0x3fc2d300 ... 0x3fc2d354: ++ case 0x3fc2d300 ... 0x3fc2d3cc: + case 0x3fc2dfc0 ... 0x3fc2dfc8: + case 0x3fe2e000 ... 0x3fe2e003: + case SDW_SDCA_CTL(FUNC_NUM_MIC, RT1320_SDCA_ENT_PDE11, RT1320_SDCA_CTL_ACTUAL_POWER_STATE, 0): +@@ -892,6 +1300,42 @@ static int rt1320_check_fw_ready(struct rt1320_sdw_priv *rt1320) + return 0; + } + ++static int rt1320_dspfw_status(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ unsigned int fw_status_addr, fw_ready; ++ unsigned int dspfw_run; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ fw_status_addr = RT1320_DSPFW_STATUS_ADDR; ++ break; ++ case RT1321_DEV_ID: ++ fw_status_addr = RT1321_DSPFW_STATUS_ADDR; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); ++ fw_ready &= 0x1; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_read(rt1320->regmap, 0xf01e, &dspfw_run); ++ dspfw_run &= 0x1; ++ fw_ready = (!dspfw_run && fw_ready); ++ } ++ ++ if (fw_ready) { ++ dev_dbg(dev, "%s, DSP FW was already\n", __func__); ++ return 1; ++ } ++ ++ dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); ++ return 0; ++} ++ + static int rt1320_check_power_state_ready(struct rt1320_sdw_priv *rt1320, enum rt1320_power_state ps) + { + struct device *dev = &rt1320->sdw_slave->dev; +@@ -982,6 +1426,9 @@ static int rt1320_fw_param_protocol(struct rt1320_sdw_priv *rt1320, enum rt1320_ + if (!tempbuf) + return -ENOMEM; + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) ++ paramid += 0x0bff0000; ++ + paramhr.moudleid = 1; + paramhr.commandtype = cmdid; + /* 8 is "sizeof(paramid) + sizeof(paramlength)" */ +@@ -1120,45 +1567,73 @@ static void rt1320_calc_r0(struct rt1320_sdw_priv *rt1320) + l_calir0, l_calir0_lo, r_calir0, r_calir0_lo); + } + ++static int rt1320_pilot_tone_output(struct rt1320_sdw_priv *rt1320) ++{ ++ struct device *dev = &rt1320->sdw_slave->dev; ++ int l_targetpostgain, r_targetpostgain; ++ unsigned long long factor = (1 << 12); ++ int l_pilotgain[9], r_pilotgain[9]; ++ const int postgain_step = 234; ++ int targetGain; ++ ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ targetGain = -320000; ++ break; ++ case RT1321_DEV_ID: ++ targetGain = -420000; ++ if (rt1320->version_id == RT1321_VA0) ++ targetGain = -320000; ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; ++ } ++ ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 70, &l_pilotgain[0], sizeof(l_pilotgain)); ++ rt1320_fw_param_protocol(rt1320, RT1320_GET_PARAM, 71, &r_pilotgain[0], sizeof(r_pilotgain)); ++ dev_dbg(dev, "%s, LR pilotgain %d, %d\n", __func__, l_pilotgain[2], r_pilotgain[2]); ++ ++ /* calculate pilot tone gain */ ++ l_pilotgain[2] = (l_pilotgain[2] * 10000) / factor; ++ r_pilotgain[2] = (r_pilotgain[2] * 10000) / factor; ++ ++ /* calculate post gain to meet target gain */ ++ l_targetpostgain = abs(targetGain - l_pilotgain[2]) / postgain_step; ++ r_targetpostgain = abs(targetGain - r_pilotgain[2]) / postgain_step; ++ l_targetpostgain = 0xfff - l_targetpostgain; ++ r_targetpostgain = 0xfff - r_targetpostgain; ++ dev_dbg(dev, "%s, LR targetpostgain=0x%x, 0x%x\n", __func__, l_targetpostgain, r_targetpostgain); ++ ++ regmap_write(rt1320->regmap, 0xdd0b, (l_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd0a, l_targetpostgain & 0xff); ++ regmap_write(rt1320->regmap, 0xdd09, (r_targetpostgain & 0xf00) >> 8); ++ regmap_write(rt1320->regmap, 0xdd08, r_targetpostgain & 0xff); ++ ++ return 0; ++} ++ + static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = &rt1320->sdw_slave->dev; + struct rt1320_datafixpoint audfixpoint[2]; + unsigned int reg_c5fb, reg_c570, reg_cd00; +- unsigned int vol_reg[4], fw_ready; ++ unsigned int vol_reg[4]; + unsigned long long l_meanr0, r_meanr0; +- unsigned int fw_status_addr; + int l_re[5], r_re[5]; + int ret, tmp; + unsigned long long factor = (1 << 27); + unsigned short l_advancegain, r_advancegain; + unsigned int delay_s = 7; /* delay seconds for the calibration */ ++ int dspfw_status; + + if (!rt1320->component) + return; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- +- /* set volume 0dB */ + regmap_read(rt1320->regmap, 0xdd0b, &vol_reg[3]); + regmap_read(rt1320->regmap, 0xdd0a, &vol_reg[2]); + regmap_read(rt1320->regmap, 0xdd09, &vol_reg[1]); + regmap_read(rt1320->regmap, 0xdd08, &vol_reg[0]); +- regmap_write(rt1320->regmap, 0xdd0b, 0x0f); +- regmap_write(rt1320->regmap, 0xdd0a, 0xff); +- regmap_write(rt1320->regmap, 0xdd09, 0x0f); +- regmap_write(rt1320->regmap, 0xdd08, 0xff); +- + regmap_read(rt1320->regmap, 0xc5fb, ®_c5fb); + regmap_read(rt1320->regmap, 0xc570, ®_c570); + regmap_read(rt1320->regmap, 0xcd00, ®_cd00); +@@ -1171,9 +1646,8 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready. Please load DSP FW first\n", __func__); + goto _finish_; + } +@@ -1184,8 +1658,19 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + goto _finish_; + } + +- if (rt1320->dev_id == RT1320_DEV_ID) +- regmap_write(rt1320->regmap, 0xc5fb, 0x00); ++ /* fine tune pilot tone output */ ++ ret = rt1320_pilot_tone_output(rt1320); ++ if (ret < 0) { ++ dev_dbg(dev, "%s, Failed to tune pilot tone output\n", __func__); ++ goto _finish_; ++ } ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x12); ++ } ++ ++ regmap_write(rt1320->regmap, 0xc5fb, 0x00); + regmap_write(rt1320->regmap, 0xc570, 0x0b); + regmap_write(rt1320->regmap, 0xcd00, 0xc5); + +@@ -1246,6 +1731,11 @@ static void rt1320_calibrate(struct rt1320_sdw_priv *rt1320) + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x03); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x03); + ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ } ++ + /* advance gain will be set when R0 load, not here */ + regmap_write(rt1320->regmap, 0xdd0b, vol_reg[3]); + regmap_write(rt1320->regmap, 0xdd0a, vol_reg[2]); +@@ -1296,7 +1786,7 @@ static int rt1320_r0_cali_put(struct snd_kcontrol *kcontrol, + static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + { + struct sdw_slave *slave = rt1320->sdw_slave; +- const struct firmware *patch; ++ const struct firmware *patch __free(firmware) = NULL; + const char *filename; + unsigned int addr, val, min_addr, max_addr; + const unsigned char *ptr; +@@ -1312,6 +1802,9 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + max_addr = 0x10007fff; + break; + case RT1321_DEV_ID: ++ if (rt1320->version_id == RT1321_VA2) ++ return; ++ + filename = RT1321_VA_MCU_PATCH; + min_addr = 0x10008000; + max_addr = 0x10008fff; +@@ -1347,17 +1840,15 @@ static void rt1320_load_mcu_patch(struct rt1320_sdw_priv *rt1320) + + if (addr > max_addr || addr < min_addr) { + dev_err(&slave->dev, "%s: the address 0x%x is wrong", __func__, addr); +- goto _exit_; ++ return; + } + if (val > 0xff) { + dev_err(&slave->dev, "%s: the value 0x%x is wrong", __func__, val); +- goto _exit_; ++ return; + } + regmap_write(rt1320->regmap, addr, val); + } + } +-_exit_: +- release_firmware(patch); + } + } + +@@ -1382,30 +1873,17 @@ static void rt1320_vab_preset(struct rt1320_sdw_priv *rt1320) + static void rt1320_t0_load(struct rt1320_sdw_priv *rt1320, unsigned int l_t0, unsigned int r_t0) + { + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int factor = (1 << 22), fw_ready; ++ unsigned int factor = (1 << 22); + int l_t0_data[38], r_t0_data[38]; +- unsigned int fw_status_addr; +- +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } ++ int dspfw_status; + + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_warn(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _exit_; + } +@@ -1444,7 +1922,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + struct device *dev = &rt1320->sdw_slave->dev; + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; +- const struct firmware *rae_fw = NULL; ++ const struct firmware *rae_fw __free(firmware) = NULL; + unsigned int fw_offset; + unsigned char *fw_data; + unsigned char *param_data; +@@ -1483,22 +1961,46 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + request_firmware(&rae_fw, rae_filename, dev); + if (rae_fw) { + +- /* RAE CRC clear */ +- regmap_write(rt1320->regmap, 0xe80b, 0x0f); +- +- /* RAE stop & CRC disable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); +- +- while (--retry) { +- regmap_read(rt1320->regmap, 0xe83f, &value); +- if (value & 0x40) +- break; +- usleep_range(1000, 1100); +- } +- if (!retry && !(value & 0x40)) { +- dev_err(dev, "%s: RAE is not ready to load\n", __func__); +- release_firmware(rae_fw); +- return -ETIMEDOUT; ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0xe80b, 0x0f); ++ /* RAE stop & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0xbc, 0x00); ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0xe83f, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC clear */ ++ regmap_write(rt1320->regmap, 0x2000300e, 0xc0); ++ regmap_write(rt1320->regmap, 0x2000300f, 0x0f); ++ /* RAE stop & Phase sync & CRC disable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0xfe, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x00); ++ /* check whether write state is ready */ ++ while (--retry) { ++ regmap_read(rt1320->regmap, 0x20003043, &value); ++ if (value & 0x40) ++ break; ++ usleep_range(1000, 1100); ++ } ++ if (!retry && !(value & 0x40)) { ++ dev_err(dev, "%s: RAE is not ready to load\n", __func__); ++ return -ETIMEDOUT; ++ } ++ break; ++ default: ++ dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); ++ return -EINVAL; + } + + dev_dbg(dev, "%s, rae_fw size=0x%zx\n", __func__, rae_fw->size); +@@ -1551,7 +2053,6 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(rae_fw); + + } else { + dev_err(dev, "%s: Failed to load %s firmware\n", __func__, rae_filename); +@@ -1559,18 +2060,34 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) + goto _exit_; + } + +- /* RAE CRC enable */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); +- +- /* RAE update */ +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); +- regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); +- +- /* RAE run */ +- regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); +- +- regmap_read(rt1320->regmap, 0xe80b, &value); +- dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ switch (rt1320->dev_id) { ++ case RT1320_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x0c, 0x0c); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0xe80b, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0xe803, 0x80, 0x80); ++ regmap_read(rt1320->regmap, 0xe80b, &value); ++ dev_dbg(dev, "%s: CAE run => 0xe80b reg = 0x%x\n", __func__, value); ++ break; ++ case RT1321_DEV_ID: ++ /* RAE CRC enable */ ++ regmap_update_bits(rt1320->regmap, 0x20003003, 0x30, 0x30); ++ /* RAE update */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20009018, 0x80, 0x80); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x00); ++ regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); ++ /* RAE run */ ++ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); ++ /* Phase sync eanble */ ++ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); ++ break; ++ } + + rt1320->rae_update_done = true; + +@@ -1598,32 +2115,21 @@ struct rt1320_dspfwheader { + + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt1320->component); + struct device *dev = &rt1320->sdw_slave->dev; +- unsigned int val, i, fw_offset, fw_ready; +- unsigned int fw_status_addr; ++ unsigned int val, i, fw_offset; + struct rt1320_dspfwheader *fwheader; + struct rt1320_imageinfo *ptr_img; + struct sdw_bpt_section sec[10]; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + unsigned char *fw_data; + bool dev_fw_match = false; + static const char hdr_sig[] = "AFX"; + unsigned int hdr_size = 0; + const char *dmi_vendor, *dmi_product, *dmi_sku; + int len_vendor, len_product, len_sku; ++ unsigned char boot_mode = 0; /* 0: from RAM; 1: from ROM */ ++ unsigned char has_0x3fc00000 = 0; + char filename[512]; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return; +- } +- + dmi_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + dmi_product = dmi_get_system_info(DMI_PRODUCT_NAME); + dmi_sku = dmi_get_system_info(DMI_PRODUCT_SKU); +@@ -1653,17 +2159,12 @@ struct rt1320_dspfwheader { + RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (fw_ready) { ++ if (rt1320_dspfw_status(rt1320)) { + dev_dbg(dev, "%s, DSP FW was already\n", __func__); + rt1320->fw_load_done = true; + goto _exit_; + } + +- /* change to IRAM */ +- regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); +- + request_firmware(&fw, filename, dev); + if (fw) { + fwheader = (struct rt1320_dspfwheader *)fw->data; +@@ -1672,7 +2173,6 @@ struct rt1320_dspfwheader { + + if (fwheader->sync != 0x0a1c5679) { + dev_err(dev, "%s: FW sync error\n", __func__); +- release_firmware(fw); + goto _exit_; + } + +@@ -1709,9 +2209,11 @@ struct rt1320_dspfwheader { + dev_fw_match = true; + break; + case RT1321_DEV_ID: +- if (ptr_img->addr == 0x3fc00000) +- if (fw_data[9] == '1') ++ if (ptr_img->addr == 0x3fc00000) { ++ if (fw_data[7] == '1') + dev_fw_match = true; ++ has_0x3fc00000 = 1; ++ } + break; + default: + dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +@@ -1721,6 +2223,22 @@ struct rt1320_dspfwheader { + fw_offset += ptr_img->size; + } + ++ if (rt1320->dev_id == RT1321_DEV_ID && rt1320->version_id == RT1321_VA2) { ++ /* ++ * For 1321 VA2, if the FW doesn't include the section for address 0x3fc00000, ++ * it means the FW will boot from ROM and force dev_fw_match to true to download FW by BRA. ++ */ ++ if (!has_0x3fc00000) { ++ boot_mode = 1; ++ dev_fw_match = true; ++ } ++ dev_dbg(dev, "%s: Boot from %s for VA2\n", __func__, (boot_mode ? "ROM" : "RAM")); ++ } ++ ++ /* change to IRAM */ ++ if (!boot_mode) ++ regmap_update_bits(rt1320->regmap, 0xf01e, 0x80, 0x00); ++ + if (dev_fw_match) { + dev_dbg(dev, "%s, starting BRA downloading FW..\n", __func__); + rt1320->bra_msg.dev_num = rt1320->sdw_slave->dev_num; +@@ -1732,7 +2250,6 @@ struct rt1320_dspfwheader { + } + + regcache_cache_bypass(rt1320->regmap, false); +- release_firmware(fw); + + if (!dev_fw_match) { + dev_err(dev, "%s: FW file doesn't match to device\n", __func__); +@@ -1743,24 +2260,39 @@ struct rt1320_dspfwheader { + goto _exit_; + } + +- /* run RAM code */ +- regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); +- val |= 0x8; +- regmap_write(rt1320->regmap, 0x3fc2bfc0, val); +- +- /* clear frame counter */ + switch (rt1320->dev_id) { + case RT1320_DEV_ID: ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2bfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2bfc0, val); ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2bfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2bfc8, 0x00); + break; + case RT1321_DEV_ID: ++ if (!boot_mode) { ++ /* run RAM code */ ++ regmap_read(rt1320->regmap, 0x3fc2dfc0, &val); ++ val |= 0x8; ++ regmap_write(rt1320->regmap, 0x3fc2dfc0, val); ++ } ++ /* clear frame counter */ + regmap_write(rt1320->regmap, 0x3fc2dfcb, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfca, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc9, 0x00); + regmap_write(rt1320->regmap, 0x3fc2dfc8, 0x00); ++ /* enable handshake */ ++ regmap_write(rt1320->regmap, 0x3fc2dfc4, 0x00); ++ regmap_write(rt1320->regmap, 0xd470, 0xad); ++ /* minimum phase settings */ ++ regmap_write(rt1320->regmap, 0xc5c4, 0x10); ++ regmap_write(rt1320->regmap, 0x20003003, 0x31); ++ regmap_update_bits(rt1320->regmap, 0x20003002, 0x40, 0x00); ++ regmap_write(rt1320->regmap, 0xc5b3, 0x01); ++ regmap_write(rt1320->regmap, 0xc052, 0x11); + break; + } + +@@ -1843,14 +2375,35 @@ static void rt1320_vc_preset(struct rt1320_sdw_priv *rt1320) + + static void rt1321_preset(struct rt1320_sdw_priv *rt1320) + { ++ const struct reg_sequence *blindwrite; + unsigned int i, reg, val, delay; ++ unsigned int array_size; + +- for (i = 0; i < ARRAY_SIZE(rt1321_blind_write); i++) { +- reg = rt1321_blind_write[i].reg; +- val = rt1321_blind_write[i].def; +- delay = rt1321_blind_write[i].delay_us; ++ switch (rt1320->version_id) { ++ case RT1321_VA0: ++ blindwrite = rt1321_blind_write; ++ array_size = ARRAY_SIZE(rt1321_blind_write); ++ break; ++ case RT1321_VA1: ++ blindwrite = rt1321_va1_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va1_blind_write); ++ break; ++ case RT1321_VA2: ++ blindwrite = rt1321_va2_blind_write; ++ array_size = ARRAY_SIZE(rt1321_va2_blind_write); ++ break; ++ default: ++ dev_err(&rt1320->sdw_slave->dev, "%s: Unknown version ID %d\n", ++ __func__, rt1320->version_id); ++ return; ++ } + +- if (reg == 0x3fc2dfc3) ++ for (i = 0; i < array_size; i++) { ++ reg = blindwrite[i].reg; ++ val = blindwrite[i].def; ++ delay = blindwrite[i].delay_us; ++ ++ if (reg == 0x1000cd56) + rt1320_load_mcu_patch(rt1320); + + regmap_write(rt1320->regmap, reg, val); +@@ -1892,6 +2445,24 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) + regmap_read(rt1320->regmap, RT1320_DEV_ID_0, &val); + regmap_read(rt1320->regmap, RT1320_DEV_ID_1, &tmp); + rt1320->dev_id = (val << 8) | tmp; ++ ++ /* This is a workaround that reads the value twice to obtain the correct result. */ ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_0, &val); ++ rt1320_pr_read(rt1320, RT1320_HV_DEV_ID_1, &tmp); ++ val = (val << 8) | tmp; ++ ++ if (rt1320->dev_id == RT1321_DEV_ID) { ++ if (rt1320->version_id == 0x01) ++ rt1320->version_id = RT1321_VA2; ++ else if (val == RT1321_DEV_HV_VA0_ID) ++ rt1320->version_id = RT1321_VA0; ++ else if (val == RT1321_DEV_HV_VA1_ID) ++ rt1320->version_id = RT1321_VA1; ++ else ++ dev_err(dev, "%s: Unknown version ID 0x%x for RT1321\n", __func__, rt1320->version_id); ++ } + } + + regmap_read(rt1320->regmap, +@@ -2326,25 +2897,12 @@ static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); + static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + { + struct device *dev = regmap_get_device(rt1320->regmap); +- unsigned int fw_status_addr; +- unsigned int fw_ready; ++ int dspfw_status; + int ret = 0; + + if (!rt1320->r0_l_reg || !rt1320->r0_r_reg) + return -EINVAL; + +- switch (rt1320->dev_id) { +- case RT1320_DEV_ID: +- fw_status_addr = RT1320_DSPFW_STATUS_ADDR; +- break; +- case RT1321_DEV_ID: +- fw_status_addr = RT1321_DSPFW_STATUS_ADDR; +- break; +- default: +- dev_err(dev, "%s: Unknown device ID %d\n", __func__, rt1320->dev_id); +- return -EINVAL; +- } +- + regmap_write(rt1320->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, RT1320_SDCA_CTL_REQ_POWER_STATE, 0), 0x00); + ret = rt1320_pde_transition_delay(rt1320, FUNC_NUM_AMP, RT1320_SDCA_ENT_PDE23, 0x00); +@@ -2353,9 +2911,8 @@ static int rt1320_r0_load(struct rt1320_sdw_priv *rt1320) + goto _timeout_; + } + +- regmap_read(rt1320->regmap, fw_status_addr, &fw_ready); +- fw_ready &= 0x1; +- if (!fw_ready) { ++ dspfw_status = rt1320_dspfw_status(rt1320); ++ if (dspfw_status <= 0) { + dev_dbg(dev, "%s, DSP FW is NOT ready\n", __func__); + goto _timeout_; + } +@@ -2458,7 +3015,7 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!rt1320->hw_init) + return 0; + +- ret = pm_runtime_resume(component->dev); ++ ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + +@@ -2469,6 +3026,8 @@ static int rt1320_dspfw_load_put(struct snd_kcontrol *kcontrol, + if (!ucontrol->value.integer.value[0]) + rt1320->fw_load_done = false; + ++ pm_runtime_mark_last_busy(component->dev); ++ pm_runtime_put_autosuspend(component->dev); + return 0; + } + +@@ -2819,6 +3378,37 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static int rt1320_bus_config(struct sdw_slave *slave, ++ struct sdw_bus_params *params) ++{ ++ struct rt1320_sdw_priv *rt1320 = dev_get_drvdata(&slave->dev); ++ unsigned int clk_base; ++ ++ regmap_read(rt1320->regmap, 0x004d, &clk_base); ++ dev_dbg(&rt1320->sdw_slave->dev, "%s clk_base=%x", __func__, clk_base); ++ ++ switch (clk_base) { ++ case RT1320_CLK_FREQ_19_2_MHZ: ++ case RT1320_CLK_FREQ_24MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x04); ++ regmap_write(rt1320->regmap, 0xc601, 0x83); ++ regmap_write(rt1320->regmap, 0xc602, 0x1f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ case RT1320_CLK_FREQ_24_576MHZ: ++ case RT1320_CLK_FREQ_22_5792MHZ: ++ regmap_write(rt1320->regmap, 0xc600, 0x07); ++ regmap_write(rt1320->regmap, 0xc601, 0x80); ++ regmap_write(rt1320->regmap, 0xc602, 0x0f); ++ regmap_write(rt1320->regmap, 0xc603, 0x40); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + /* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now +@@ -2826,12 +3416,13 @@ static int rt1320_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + static const struct sdw_slave_ops rt1320_slave_ops = { + .read_prop = rt1320_read_prop, + .update_status = rt1320_update_status, ++ .bus_config = rt1320_bus_config, + }; + + static int rt1320_sdw_component_probe(struct snd_soc_component *component) + { +- int ret; + struct rt1320_sdw_priv *rt1320 = snd_soc_component_get_drvdata(component); ++ int ret; + + rt1320->component = component; + +@@ -3062,10 +3653,23 @@ static int rt1320_dev_resume(struct device *dev) + return ret; + + regcache_cache_only(rt1320->regmap, false); +- regcache_sync(rt1320->regmap); ++ ret = regcache_sync(rt1320->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt1320->mbq_regmap, false); +- regcache_sync(rt1320->mbq_regmap); ++ ret = regcache_sync(rt1320->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt1320->regmap, true); ++ regcache_cache_only(rt1320->mbq_regmap, true); ++ regcache_mark_dirty(rt1320->regmap); ++ regcache_mark_dirty(rt1320->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt1320_pm = { +diff --git a/sound/soc/codecs/rt1320-sdw.h b/sound/soc/codecs/rt1320-sdw.h +--- a/sound/soc/codecs/rt1320-sdw.h ++++ b/sound/soc/codecs/rt1320-sdw.h +@@ -17,12 +17,17 @@ + + #define RT1320_DEV_ID 0x6981 + #define RT1321_DEV_ID 0x7045 ++#define RT1321_DEV_HV_VA0_ID 0x6997 ++#define RT1321_DEV_HV_VA1_ID 0x7071 + + /* imp-defined registers */ + #define RT1320_DEV_VERSION_ID_1 0xc404 + #define RT1320_DEV_ID_1 0xc405 + #define RT1320_DEV_ID_0 0xc406 + ++#define RT1320_HV_DEV_ID_0 0xf622 ++#define RT1320_HV_DEV_ID_1 0xf623 ++ + #define RT1320_POWER_STATE 0xc560 + + #define RT1321_PATCH_MAIN_VER 0x1000cffe +@@ -94,6 +99,12 @@ enum rt1320_version_id { + RT1320_VC, + }; + ++enum rt1321_version_id { ++ RT1321_VA0, ++ RT1321_VA1, ++ RT1321_VA2, ++}; ++ + #define RT1320_VER_B_ID 0x07392238 + #define RT1320_VAB_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vab.bin" + #define RT1320_VC_MCU_PATCH "realtek/rt1320/rt1320-patch-code-vc.bin" +@@ -121,6 +132,7 @@ struct rt1320_datafixpoint { + int invrs; + }; + ++/* FW parameter id 1300 */ + typedef struct FwPara_HwSwGain { + unsigned int SwAdvGain; + unsigned int SwBasGain; +@@ -163,6 +175,13 @@ enum rt1320_rw_type { + RT1320_PARAM_READ = 3, + }; + ++enum { ++ RT1320_CLK_FREQ_19_2_MHZ = 1, ++ RT1320_CLK_FREQ_24MHZ = 2, ++ RT1320_CLK_FREQ_24_576MHZ = 3, ++ RT1320_CLK_FREQ_22_5792MHZ = 4, ++}; ++ + struct rt1320_sdw_priv { + struct snd_soc_component *component; + struct regmap *regmap; +diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c +--- a/sound/soc/codecs/rt5514-spi.c ++++ b/sound/soc/codecs/rt5514-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -79,17 +80,17 @@ static void rt5514_spi_copy_work(struct work_struct *work) + unsigned int cur_wp, remain_data; + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + if (!period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + + if (rt5514_dsp->buf_size % period_bytes) +@@ -111,7 +112,7 @@ static void rt5514_spi_copy_work(struct work_struct *work) + + if (remain_data < period_bytes) { + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- goto done; ++ return; + } + } + +@@ -146,9 +147,6 @@ static void rt5514_spi_copy_work(struct work_struct *work) + snd_pcm_period_elapsed(rt5514_dsp->substream); + + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +- +-done: +- mutex_unlock(&rt5514_dsp->dma_lock); + } + + static void rt5514_schedule_copy(struct rt5514_dsp *rt5514_dsp) +@@ -216,7 +214,7 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + snd_soc_component_get_drvdata(component); + u8 buf[8]; + +- mutex_lock(&rt5514_dsp->dma_lock); ++ guard(mutex)(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = substream; + rt5514_dsp->dma_offset = 0; + +@@ -225,8 +223,6 @@ static int rt5514_spi_hw_params(struct snd_soc_component *component, + if (buf[0] & RT5514_IRQ_STATUS_BIT) + rt5514_schedule_copy(rt5514_dsp); + +- mutex_unlock(&rt5514_dsp->dma_lock); +- + return 0; + } + +@@ -236,9 +232,8 @@ static int rt5514_spi_hw_free(struct snd_soc_component *component, + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5514_dsp->dma_lock); +- rt5514_dsp->substream = NULL; +- mutex_unlock(&rt5514_dsp->dma_lock); ++ scoped_guard(mutex, &rt5514_dsp->dma_lock) ++ rt5514_dsp->substream = NULL; + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + +diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c +--- a/sound/soc/codecs/rt5514.c ++++ b/sound/soc/codecs/rt5514.c +@@ -1073,12 +1073,18 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { +- rt5514->dsp_enabled = 0; +- regmap_multi_reg_write(rt5514->i2c_regmap, +- rt5514_i2c_patch, +- ARRAY_SIZE(rt5514_i2c_patch)); ++ ret = regmap_multi_reg_write(rt5514->i2c_regmap, ++ rt5514_i2c_patch, ++ ARRAY_SIZE(rt5514_i2c_patch)); ++ if (ret) ++ return ret; ++ + regcache_mark_dirty(rt5514->regmap); +- regcache_sync(rt5514->regmap); ++ ret = regcache_sync(rt5514->regmap); ++ if (ret) ++ return ret; ++ ++ rt5514->dsp_enabled = 0; + } + } + break; +diff --git a/sound/soc/codecs/rt5575-spi.c b/sound/soc/codecs/rt5575-spi.c +--- a/sound/soc/codecs/rt5575-spi.c ++++ b/sound/soc/codecs/rt5575-spi.c +@@ -93,7 +93,6 @@ static void rt5575_spi_burst_write(struct spi_device *spi, u32 addr, const u8 *t + int rt5575_spi_fw_load(struct spi_device *spi) + { + struct device *dev = &spi->dev; +- const struct firmware *firmware; + int i, ret; + static const char * const fw_path[] = { + "realtek/rt5575/rt5575_fw1.bin", +@@ -104,6 +103,7 @@ int rt5575_spi_fw_load(struct spi_device *spi) + static const u32 fw_addr[] = { 0x5f400000, 0x5f600000, 0x5f7fe000, 0x5f7ff000 }; + + for (i = 0; i < ARRAY_SIZE(fw_addr); i++) { ++ const struct firmware *firmware __free(firmware) = NULL; + ret = request_firmware(&firmware, fw_path[i], dev); + if (ret) { + dev_err(dev, "Request firmware failure: %d\n", ret); +@@ -111,7 +111,6 @@ int rt5575_spi_fw_load(struct spi_device *spi) + } + + rt5575_spi_burst_write(spi, fw_addr[i], firmware->data, firmware->size); +- release_firmware(firmware); + } + + return 0; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -3323,92 +3324,89 @@ static void rt5645_jack_detect_work(struct work_struct *work) + if (!rt5645->component) + return; + +- mutex_lock(&rt5645->jd_mutex); +- +- switch (rt5645->pdata.jd_mode) { +- case 0: /* Not using rt5645 JD */ +- if (rt5645->gpiod_hp_det) { +- gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); +- if (rt5645->pdata.inv_hp_pol) +- gpio_state ^= 1; +- dev_dbg(rt5645->component->dev, "gpio_state = %d\n", +- gpio_state); +- report = rt5645_jack_detect(rt5645->component, gpio_state); +- } +- snd_soc_jack_report(rt5645->hp_jack, +- report, SND_JACK_HEADPHONE); +- snd_soc_jack_report(rt5645->mic_jack, +- report, SND_JACK_MICROPHONE); +- mutex_unlock(&rt5645->jd_mutex); +- return; +- case 4: +- val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; +- break; +- default: /* read rt5645 jd1_1 status */ +- val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; +- break; +- +- } +- +- if (!val && (rt5645->jack_type == 0)) { /* jack in */ +- report = rt5645_jack_detect(rt5645->component, 1); +- } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { +- /* for push button and jack out */ +- btn_type = 0; +- if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { +- /* button pressed */ +- report = SND_JACK_HEADSET; +- btn_type = rt5645_button_detect(rt5645->component); +- /* rt5650 can report three kinds of button behavior, +- one click, double click and hold. However, +- currently we will report button pressed/released +- event. So all the three button behaviors are +- treated as button pressed. */ +- switch (btn_type) { +- case 0x8000: +- case 0x4000: +- case 0x2000: +- report |= SND_JACK_BTN_0; +- break; +- case 0x1000: +- case 0x0800: +- case 0x0400: +- report |= SND_JACK_BTN_1; +- break; +- case 0x0200: +- case 0x0100: +- case 0x0080: +- report |= SND_JACK_BTN_2; +- break; +- case 0x0040: +- case 0x0020: +- case 0x0010: +- report |= SND_JACK_BTN_3; +- break; +- case 0x0000: /* unpressed */ +- break; +- default: +- dev_err(rt5645->component->dev, +- "Unexpected button code 0x%04x\n", +- btn_type); +- break; ++ scoped_guard(mutex, &rt5645->jd_mutex) { ++ switch (rt5645->pdata.jd_mode) { ++ case 0: /* Not using rt5645 JD */ ++ if (rt5645->gpiod_hp_det) { ++ gpio_state = gpiod_get_value(rt5645->gpiod_hp_det); ++ if (rt5645->pdata.inv_hp_pol) ++ gpio_state ^= 1; ++ dev_dbg(rt5645->component->dev, "gpio_state = %d\n", ++ gpio_state); ++ report = rt5645_jack_detect(rt5645->component, gpio_state); + } ++ snd_soc_jack_report(rt5645->hp_jack, ++ report, SND_JACK_HEADPHONE); ++ snd_soc_jack_report(rt5645->mic_jack, ++ report, SND_JACK_MICROPHONE); ++ return; ++ case 4: ++ val = snd_soc_component_read(rt5645->component, RT5645_A_JD_CTRL1) & 0x0020; ++ break; ++ default: /* read rt5645 jd1_1 status */ ++ val = snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x1000; ++ break; + } +- if (btn_type == 0)/* button release */ +- report = rt5645->jack_type; +- else { +- mod_timer(&rt5645->btn_check_timer, +- msecs_to_jiffies(100)); +- } +- } else { +- /* jack out */ +- report = 0; +- snd_soc_component_update_bits(rt5645->component, +- RT5645_INT_IRQ_ST, 0x1, 0x0); +- rt5645_jack_detect(rt5645->component, 0); +- } + +- mutex_unlock(&rt5645->jd_mutex); ++ if (!val && rt5645->jack_type == 0) { /* jack in */ ++ report = rt5645_jack_detect(rt5645->component, 1); ++ } else if (!val && rt5645->jack_type == SND_JACK_HEADSET) { ++ /* for push button and jack out */ ++ btn_type = 0; ++ if (snd_soc_component_read(rt5645->component, RT5645_INT_IRQ_ST) & 0x4) { ++ /* button pressed */ ++ report = SND_JACK_HEADSET; ++ btn_type = rt5645_button_detect(rt5645->component); ++ /* ++ * rt5650 can report three kinds of button behavior, ++ * one click, double click and hold. However, ++ * currently we will report button pressed/released ++ * event. So all the three button behaviors are ++ * treated as button pressed. ++ */ ++ switch (btn_type) { ++ case 0x8000: ++ case 0x4000: ++ case 0x2000: ++ report |= SND_JACK_BTN_0; ++ break; ++ case 0x1000: ++ case 0x0800: ++ case 0x0400: ++ report |= SND_JACK_BTN_1; ++ break; ++ case 0x0200: ++ case 0x0100: ++ case 0x0080: ++ report |= SND_JACK_BTN_2; ++ break; ++ case 0x0040: ++ case 0x0020: ++ case 0x0010: ++ report |= SND_JACK_BTN_3; ++ break; ++ case 0x0000: /* unpressed */ ++ break; ++ default: ++ dev_err(rt5645->component->dev, ++ "Unexpected button code 0x%04x\n", ++ btn_type); ++ break; ++ } ++ } ++ if (btn_type == 0)/* button release */ ++ report = rt5645->jack_type; ++ else ++ mod_timer(&rt5645->btn_check_timer, ++ msecs_to_jiffies(100)); ++ } else { ++ /* jack out */ ++ report = 0; ++ snd_soc_component_update_bits(rt5645->component, ++ RT5645_INT_IRQ_ST, 0x1, 0x0); ++ rt5645_jack_detect(rt5645->component, 0); ++ } ++ } + + snd_soc_jack_report(rt5645->hp_jack, report, SND_JACK_HEADPHONE); + snd_soc_jack_report(rt5645->mic_jack, report, SND_JACK_MICROPHONE); +@@ -3522,9 +3520,15 @@ static int rt5645_suspend(struct snd_soc_component *component) + static int rt5645_resume(struct snd_soc_component *component) + { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + return 0; + } +@@ -4335,9 +4339,15 @@ static int rt5645_sys_suspend(struct device *dev) + static int rt5645_sys_resume(struct device *dev) + { + struct rt5645_priv *rt5645 = dev_get_drvdata(dev); ++ int ret; + + regcache_cache_only(rt5645->regmap, false); +- regcache_sync(rt5645->regmap); ++ ret = regcache_sync(rt5645->regmap); ++ if (ret) { ++ regcache_cache_only(rt5645->regmap, true); ++ regcache_mark_dirty(rt5645->regmap); ++ return ret; ++ } + + if (rt5645->hp_jack) { + rt5645->jack_type = 0; +diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c +--- a/sound/soc/codecs/rt5665.c ++++ b/sound/soc/codecs/rt5665.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -1208,7 +1209,7 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + usleep_range(10000, 15000); + } + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + val = snd_soc_component_read(rt5665->component, RT5665_AJD1_CTRL) & 0x0010; + if (!val) { +@@ -1274,8 +1275,6 @@ static void rt5665_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5665->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5665->jd_check_work); +- +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static const char * const rt5665_clk_sync[] = { +@@ -4564,7 +4563,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + { + int value, count; + +- mutex_lock(&rt5665->calibrate_mutex); ++ guard(mutex)(&rt5665->calibrate_mutex); + + regcache_cache_bypass(rt5665->regmap, true); + +@@ -4601,7 +4600,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("HP Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4620,7 +4620,8 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + pr_err("MONO Calibration Failure\n"); + regmap_write(rt5665->regmap, RT5665_RESET, 0); + regcache_cache_bypass(rt5665->regmap, false); +- goto out_unlock; ++ rt5665->calibration_done = true; ++ return; + } + + count++; +@@ -4635,9 +4636,7 @@ static void rt5665_calibrate(struct rt5665_priv *rt5665) + regmap_write(rt5665->regmap, RT5665_BIAS_CUR_CTRL_8, 0xa602); + regmap_write(rt5665->regmap, RT5665_ASRC_8, 0x0120); + +-out_unlock: + rt5665->calibration_done = true; +- mutex_unlock(&rt5665->calibrate_mutex); + } + + static void rt5665_calibrate_handler(struct work_struct *work) +diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c +--- a/sound/soc/codecs/rt5668.c ++++ b/sound/soc/codecs/rt5668.c +@@ -6,6 +6,7 @@ + * Author: Bard Liao + */ + ++#include + #include + #include + #include +@@ -986,7 +987,7 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + return; + } + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + val = snd_soc_component_read(rt5668->component, RT5668_AJD1_CTRL) + & RT5668_JDH_RS_MASK; +@@ -1053,8 +1054,6 @@ static void rt5668_jack_detect_handler(struct work_struct *work) + schedule_delayed_work(&rt5668->jd_check_work, 0); + else + cancel_delayed_work_sync(&rt5668->jd_check_work); +- +- mutex_unlock(&rt5668->calibrate_mutex); + } + + static const struct snd_kcontrol_new rt5668_snd_controls[] = { +@@ -2356,7 +2355,7 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + { + int value, count; + +- mutex_lock(&rt5668->calibrate_mutex); ++ guard(mutex)(&rt5668->calibrate_mutex); + + rt5668_reset(rt5668->regmap); + regmap_write(rt5668->regmap, RT5668_PWR_ANLG_1, 0xa2bf); +@@ -2400,9 +2399,6 @@ static void rt5668_calibrate(struct rt5668_priv *rt5668) + /* restore settings */ + regmap_write(rt5668->regmap, RT5668_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5668->regmap, RT5668_PWR_DIG_1, 0x0000); +- +- mutex_unlock(&rt5668->calibrate_mutex); +- + } + + static int rt5668_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c +--- a/sound/soc/codecs/rt5677-spi.c ++++ b/sound/soc/codecs/rt5677-spi.c +@@ -6,6 +6,7 @@ + * Author: Oder Chiou + */ + ++#include + #include + #include + #include +@@ -133,9 +134,8 @@ static int rt5677_spi_hw_params( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = substream; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -147,9 +147,8 @@ static int rt5677_spi_hw_free( + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + rt5677_dsp->substream = NULL; +- mutex_unlock(&rt5677_dsp->dma_lock); + + return 0; + } +@@ -311,17 +310,17 @@ static void rt5677_spi_copy_work(struct work_struct *work) + int ret = 0; + + /* Ensure runtime->dma_area buffer does not go away while copying. */ +- mutex_lock(&rt5677_dsp->dma_lock); ++ guard(mutex)(&rt5677_dsp->dma_lock); + if (!rt5677_dsp->substream) { + dev_err(rt5677_dsp->dev, "No pcm substream\n"); +- goto done; ++ return; + } + + runtime = rt5677_dsp->substream->runtime; + + if (rt5677_spi_mic_write_offset(&mic_write_offset)) { + dev_err(rt5677_dsp->dev, "No mic_write_offset\n"); +- goto done; ++ return; + } + + /* If this is the first time that we've asked for streaming data after +@@ -355,7 +354,7 @@ static void rt5677_spi_copy_work(struct work_struct *work) + ret = rt5677_spi_copy(rt5677_dsp, copy_bytes); + if (ret) { + dev_err(rt5677_dsp->dev, "Copy failed %d\n", ret); +- goto done; ++ return; + } + rt5677_dsp->avail_bytes += copy_bytes; + if (rt5677_dsp->avail_bytes >= period_bytes) { +@@ -367,8 +366,6 @@ static void rt5677_spi_copy_work(struct work_struct *work) + + delay = bytes_to_frames(runtime, period_bytes) / runtime->rate; + schedule_delayed_work(&rt5677_dsp->copy_work, secs_to_jiffies(delay)); +-done: +- mutex_unlock(&rt5677_dsp->dma_lock); + } + + static int rt5677_spi_pcm_new(struct snd_soc_component *component, +@@ -507,10 +504,8 @@ int rt5677_spi_read(u32 addr, void *rxbuf, size_t len) + header[3] = ((addr + offset) & 0x0000ff00) >> 8; + header[4] = ((addr + offset) & 0x000000ff) >> 0; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); +- ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + + /* Copy data back to caller buffer */ + rt5677_spi_reverse(cb + offset, len - offset, body, t[1].len); +@@ -564,9 +559,8 @@ int rt5677_spi_write(u32 addr, const void *txbuf, size_t len) + offset += t.len; + t.len += RT5677_SPI_HEADER + 1; + +- mutex_lock(&spi_mutex); +- status |= spi_sync(g_spi, &m); +- mutex_unlock(&spi_mutex); ++ scoped_guard(mutex, &spi_mutex) ++ status |= spi_sync(g_spi, &m); + } + return status; + } +@@ -591,10 +585,10 @@ void rt5677_spi_hotword_detected(void) + return; + } + +- mutex_lock(&rt5677_dsp->dma_lock); +- dev_info(rt5677_dsp->dev, "Hotword detected\n"); +- rt5677_dsp->new_hotword = true; +- mutex_unlock(&rt5677_dsp->dma_lock); ++ scoped_guard(mutex, &rt5677_dsp->dma_lock) { ++ dev_info(rt5677_dsp->dev, "Hotword detected\n"); ++ rt5677_dsp->new_hotword = true; ++ } + + schedule_delayed_work(&rt5677_dsp->copy_work, 0); + } +diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c +--- a/sound/soc/codecs/rt5677.c ++++ b/sound/soc/codecs/rt5677.c +@@ -6,6 +6,8 @@ + * Author: Oder Chiou + */ + ++#include ++#include + #include + #include + #include +@@ -563,46 +565,43 @@ static int rt5677_dsp_mode_i2c_write_addr(struct rt5677_priv *rt5677, + struct snd_soc_component *component = rt5677->component; + int ret; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, + value >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set data msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, + value & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set data lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + opcode); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -622,36 +621,33 @@ static int rt5677_dsp_mode_i2c_read_addr( + int ret; + unsigned int msb, lsb; + +- mutex_lock(&rt5677->dsp_cmd_lock); ++ guard(mutex)(&rt5677->dsp_cmd_lock); + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_MSB, + addr >> 16); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr msb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_ADDR_LSB, + addr & 0xffff); + if (ret < 0) { + dev_err(component->dev, "Failed to set addr lsb value: %d\n", ret); +- goto err; ++ return ret; + } + + ret = regmap_write(rt5677->regmap_physical, RT5677_DSP_I2C_OP_CODE, + 0x0002); + if (ret < 0) { + dev_err(component->dev, "Failed to set op code value: %d\n", ret); +- goto err; ++ return ret; + } + + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_MSB, &msb); + regmap_read(rt5677->regmap_physical, RT5677_DSP_I2C_DATA_LSB, &lsb); + *value = (msb << 16) | lsb; + +-err: +- mutex_unlock(&rt5677->dsp_cmd_lock); +- + return ret; + } + +@@ -853,11 +849,11 @@ static int rt5677_parse_and_load_dsp(struct rt5677_priv *rt5677, const u8 *buf, + + static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + { +- const struct firmware *fwp; + struct device *dev = rt5677->component->dev; +- int ret = 0; ++ int ret; + + /* Load dsp firmware from rt5677_elf_vad file */ ++ const struct firmware *fwp __free(firmware) = NULL; + ret = request_firmware(&fwp, "rt5677_elf_vad", dev); + if (ret) { + dev_err(dev, "Request rt5677_elf_vad failed %d\n", ret); +@@ -865,9 +861,7 @@ static int rt5677_load_dsp_from_file(struct rt5677_priv *rt5677) + } + dev_info(dev, "Requested rt5677_elf_vad (%zu)\n", fwp->size); + +- ret = rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); +- release_firmware(fwp); +- return ret; ++ return rt5677_parse_and_load_dsp(rt5677, fwp->data, fwp->size); + } + + static int rt5677_set_dsp_vad(struct snd_soc_component *component, bool on) +@@ -940,21 +934,20 @@ static void rt5677_dsp_work(struct work_struct *work) + activity = false; + + /* Don't turn off the DSP while handling irqs */ +- mutex_lock(&rt5677->irq_lock); +- /* Set DSP CPU to Stop */ +- regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, +- RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); ++ scoped_guard(mutex, &rt5677->irq_lock) { ++ /* Set DSP CPU to Stop */ ++ regmap_update_bits(rt5677->regmap, RT5677_PWR_DSP1, ++ RT5677_PWR_DSP_CPU, RT5677_PWR_DSP_CPU); + +- rt5677_set_dsp_mode(rt5677, false); ++ rt5677_set_dsp_mode(rt5677, false); + +- /* Disable and clear VAD interrupt */ +- regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); ++ /* Disable and clear VAD interrupt */ ++ regmap_write(rt5677->regmap, RT5677_VAD_CTRL1, 0x2184); + +- /* Set GPIO1 pin back to be IRQ output for jack detect */ +- regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, +- RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); +- +- mutex_unlock(&rt5677->irq_lock); ++ /* Set GPIO1 pin back to be IRQ output for jack detect */ ++ regmap_update_bits(rt5677->regmap, RT5677_GPIO_CTRL1, ++ RT5677_GPIO1_PIN_MASK, RT5677_GPIO1_PIN_IRQ); ++ } + } + } + +@@ -4767,6 +4760,21 @@ static int rt5677_gpio_direction_in(struct gpio_chip *chip, unsigned offset) + return rt5677_update_gpio_bits(rt5677, offset, m, v); + } + ++static int rt5677_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) ++{ ++ struct rt5677_priv *rt5677 = gpiochip_get_data(chip); ++ unsigned int shift = RT5677_GPIOx_DIR_SFT + (offset % 5) * 3; ++ unsigned int bank = offset / 5; ++ unsigned int reg = bank ? RT5677_GPIO_CTRL3 : RT5677_GPIO_CTRL2; ++ int ret; ++ ++ ret = regmap_test_bits(rt5677->regmap, reg, BIT(shift)); ++ if (ret < 0) ++ return ret; ++ ++ return ret ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; ++} ++ + /* + * Configures the GPIO as + * 0 - floating +@@ -4834,6 +4842,7 @@ static int rt5677_to_irq(struct gpio_chip *chip, unsigned offset) + static const struct gpio_chip rt5677_template_chip = { + .label = RT5677_DRV_NAME, + .owner = THIS_MODULE, ++ .get_direction = rt5677_gpio_get_direction, + .direction_output = rt5677_gpio_direction_out, + .set = rt5677_gpio_set, + .direction_input = rt5677_gpio_direction_in, +@@ -4980,11 +4989,11 @@ static int rt5677_read(void *context, unsigned int reg, unsigned int *val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_read(rt5677, RT5677_PRIV_DATA, val); ++ } + } else { + rt5677_dsp_mode_i2c_read(rt5677, reg, val); + } +@@ -5002,12 +5011,12 @@ static int rt5677_write(void *context, unsigned int reg, unsigned int val) + + if (rt5677->is_dsp_mode) { + if (reg > 0xff) { +- mutex_lock(&rt5677->dsp_pri_lock); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, +- reg & 0xff); +- rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, +- val); +- mutex_unlock(&rt5677->dsp_pri_lock); ++ scoped_guard(mutex, &rt5677->dsp_pri_lock) { ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_INDEX, ++ reg & 0xff); ++ rt5677_dsp_mode_i2c_write(rt5677, RT5677_PRIV_DATA, ++ val); ++ } + } else { + rt5677_dsp_mode_i2c_write(rt5677, reg, val); + } +@@ -5399,7 +5408,7 @@ static void rt5677_resume_irq_check(struct work_struct *work) + * Without this explicit check, unplug the headset right after suspend + * starts, then after resume the headset is still shown as plugged in. + */ +- mutex_lock(&rt5677->irq_lock); ++ guard(mutex)(&rt5677->irq_lock); + for (i = 0; i < RT5677_IRQ_NUM; i++) { + if (rt5677->irq_en & rt5677_irq_descs[i].enable_mask) { + virq = irq_find_mapping(rt5677->domain, i); +@@ -5407,7 +5416,6 @@ static void rt5677_resume_irq_check(struct work_struct *work) + handle_nested_irq(virq); + } + } +- mutex_unlock(&rt5677->irq_lock); + } + + static void rt5677_irq_bus_lock(struct irq_data *data) +diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c +--- a/sound/soc/codecs/rt5682-sdw.c ++++ b/sound/soc/codecs/rt5682-sdw.c +@@ -6,6 +6,7 @@ + // Author: Oder Chiou + // + ++#include + #include + #include + #include +@@ -410,7 +411,9 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + if (rt5682->first_hw_init) { + regcache_cache_bypass(rt5682->regmap, false); + regcache_mark_dirty(rt5682->regmap); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) ++ goto err_sync; + + /* volatile registers */ + regmap_update_bits(rt5682->regmap, RT5682_CBJ_CTRL_2, +@@ -472,8 +475,16 @@ static int rt5682_io_init(struct device *dev, struct sdw_slave *slave) + /* Mark Slave initialization complete */ + rt5682->hw_init = true; + rt5682->first_hw_init = true; ++ goto out; ++ ++err_sync: ++ regcache_cache_bypass(rt5682->regmap, false); ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); + + err_nodev: ++out: + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete: %d\n", __func__, ret); +@@ -660,12 +671,11 @@ static int rt5682_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt5682->disable_irq_lock); ++ guard(mutex)(&rt5682->disable_irq_lock); + if (status->control_port & 0x4 && !rt5682->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt5682->jack_detect_work, msecs_to_jiffies(rt5682->irq_work_delay_time)); + } +- mutex_unlock(&rt5682->disable_irq_lock); + + return 0; + } +@@ -736,11 +746,11 @@ static int rt5682_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt5682->disable_irq_lock); +- rt5682->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt5682->disable_irq_lock); ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ rt5682->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -760,12 +770,12 @@ static int rt5682_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt5682->disable_irq_lock); +- if (rt5682->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt5682->disable_irq = false; ++ scoped_guard(mutex, &rt5682->disable_irq_lock) { ++ if (rt5682->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt5682->disable_irq = false; ++ } + } +- mutex_unlock(&rt5682->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT5682_PROBE_TIMEOUT); +@@ -776,7 +786,13 @@ static int rt5682_dev_resume(struct device *dev) + + regcache_cache_only(rt5682->sdw_regmap, false); + regcache_cache_only(rt5682->regmap, false); +- regcache_sync(rt5682->regmap); ++ ret = regcache_sync(rt5682->regmap); ++ if (ret) { ++ regcache_cache_only(rt5682->sdw_regmap, true); ++ regcache_cache_only(rt5682->regmap, true); ++ regcache_mark_dirty(rt5682->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c +--- a/sound/soc/codecs/rt5682.c ++++ b/sound/soc/codecs/rt5682.c +@@ -6,6 +6,7 @@ + // Author: Bard Liao + // + ++#include + #include + #include + #include +@@ -3125,7 +3126,7 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + { + int value, count; + +- mutex_lock(&rt5682->calibrate_mutex); ++ guard(mutex)(&rt5682->calibrate_mutex); + + rt5682_reset(rt5682); + regmap_write(rt5682->regmap, RT5682_I2C_CTRL, 0x000f); +@@ -3175,8 +3176,6 @@ void rt5682_calibrate(struct rt5682_priv *rt5682) + regmap_write(rt5682->regmap, RT5682_CALIB_ADC_CTRL, 0x2005); + regmap_write(rt5682->regmap, RT5682_STO1_ADC_MIXER, 0xc0c4); + regmap_write(rt5682->regmap, RT5682_CAL_REC, 0x0c0c); +- +- mutex_unlock(&rt5682->calibrate_mutex); + } + EXPORT_SYMBOL_GPL(rt5682_calibrate); + +diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c +--- a/sound/soc/codecs/rt5682s.c ++++ b/sound/soc/codecs/rt5682s.c +@@ -6,6 +6,7 @@ + // Author: Derek Fang + // + ++#include + #include + #include + #include +@@ -648,7 +649,7 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + { + struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); + +- mutex_lock(&rt5682s->sar_mutex); ++ guard(mutex)(&rt5682s->sar_mutex); + + switch (mode) { + case SAR_PWR_SAVING: +@@ -695,8 +696,6 @@ static void rt5682s_sar_power_mode(struct snd_soc_component *component, int mode + dev_err(component->dev, "Invalid SAR Power mode: %d\n", mode); + break; + } +- +- mutex_unlock(&rt5682s->sar_mutex); + } + + static void rt5682s_enable_push_button_irq(struct snd_soc_component *component) +@@ -2534,7 +2533,7 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return -EINVAL; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, + RT5682S_PWR_VREF2 | RT5682S_PWR_FV2 | RT5682S_PWR_MB, +@@ -2556,8 +2555,6 @@ static int rt5682s_wclk_prepare(struct clk_hw *hw) + + rt5682s->wclk_enabled = 1; + +- mutex_unlock(&rt5682s->wclk_mutex); +- + return 0; + } + +@@ -2570,7 +2567,7 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + if (!rt5682s_clk_check(rt5682s)) + return; + +- mutex_lock(&rt5682s->wclk_mutex); ++ guard(mutex)(&rt5682s->wclk_mutex); + + if (!rt5682s->jack_type) + snd_soc_component_update_bits(component, RT5682S_PWR_ANLG_1, +@@ -2585,8 +2582,6 @@ static void rt5682s_wclk_unprepare(struct clk_hw *hw) + rt5682s_set_pllb_power(rt5682s, 0); + + rt5682s->wclk_enabled = 0; +- +- mutex_unlock(&rt5682s->wclk_mutex); + } + + static unsigned long rt5682s_wclk_recalc_rate(struct clk_hw *hw, +@@ -2997,7 +2992,7 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + { + unsigned int count, value; + +- mutex_lock(&rt5682s->calibrate_mutex); ++ guard(mutex)(&rt5682s->calibrate_mutex); + + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0xaa80); + usleep_range(15000, 20000); +@@ -3034,8 +3029,6 @@ static void rt5682s_calibrate(struct rt5682s_priv *rt5682s) + regmap_write(rt5682s->regmap, RT5682S_PWR_DIG_1, 0x00c0); + regmap_write(rt5682s->regmap, RT5682S_PWR_ANLG_1, 0x0800); + regmap_write(rt5682s->regmap, RT5682S_GLB_CLK, 0x0000); +- +- mutex_unlock(&rt5682s->calibrate_mutex); + } + + static const struct regmap_config rt5682s_regmap = { +diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c +--- a/sound/soc/codecs/rt700-sdw.c ++++ b/sound/soc/codecs/rt700-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,12 +416,11 @@ static int rt700_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt700->disable_irq_lock); ++ guard(mutex)(&rt700->disable_irq_lock); + if (status->control_port & 0x4 && !rt700->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt700->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt700->disable_irq_lock); + + return 0; + } +@@ -499,11 +499,11 @@ static int rt700_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt700->disable_irq_lock); +- rt700->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt700->disable_irq_lock); ++ scoped_guard(mutex, &rt700->disable_irq_lock) { ++ rt700->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c +--- a/sound/soc/codecs/rt711-sdca-sdw.c ++++ b/sound/soc/codecs/rt711-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -415,13 +416,13 @@ static int rt711_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711_sdca->disable_irq_lock); +- rt711_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt711_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt711_sdca->disable_irq_lock) { ++ rt711_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -443,13 +444,15 @@ static int rt711_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711-sdca.c b/sound/soc/codecs/rt711-sdca.c +--- a/sound/soc/codecs/rt711-sdca.c ++++ b/sound/soc/codecs/rt711-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -450,7 +451,7 @@ static void rt711_sdca_btn_check_handler(struct work_struct *work) + + static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + { +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + if (rt711->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -515,8 +516,6 @@ static void rt711_sdca_jack_init(struct rt711_sdca_priv *rt711) + + dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt711->calibrate_mutex); + } + + static int rt711_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c +--- a/sound/soc/codecs/rt711-sdw.c ++++ b/sound/soc/codecs/rt711-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -422,12 +423,11 @@ static int rt711_interrupt_callback(struct sdw_slave *slave, + dev_dbg(&slave->dev, + "%s control_port_stat=%x", __func__, status->control_port); + +- mutex_lock(&rt711->disable_irq_lock); ++ guard(mutex)(&rt711->disable_irq_lock); + if (status->control_port & 0x4 && !rt711->disable_irq) { + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(250)); + } +- mutex_unlock(&rt711->disable_irq_lock); + + return 0; + } +@@ -509,11 +509,11 @@ static int rt711_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt711->disable_irq_lock); +- rt711->disable_irq = true; +- ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, +- SDW_SCP_INT1_IMPL_DEF, 0); +- mutex_unlock(&rt711->disable_irq_lock); ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ rt711->disable_irq = true; ++ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, ++ SDW_SCP_INT1_IMPL_DEF, 0); ++ } + + if (ret < 0) { + /* log but don't prevent suspend from happening */ +@@ -535,12 +535,12 @@ static int rt711_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt711->disable_irq_lock); +- if (rt711->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); +- rt711->disable_irq = false; ++ scoped_guard(mutex, &rt711->disable_irq_lock) { ++ if (rt711->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_INTMASK1, SDW_SCP_INT1_IMPL_DEF); ++ rt711->disable_irq = false; ++ } + } +- mutex_unlock(&rt711->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT711_PROBE_TIMEOUT); +diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c +--- a/sound/soc/codecs/rt711.c ++++ b/sound/soc/codecs/rt711.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -89,24 +90,24 @@ static int rt711_calibration(struct rt711_priv *rt711) + struct regmap *regmap = rt711->regmap; + int ret = 0; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + dev = regmap_get_device(regmap); + + /* Calibration manual mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL, +- 0xf, 0x0); ++ 0xf, 0x0); + + /* trigger */ + rt711_index_update_bits(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, +- RT711_DAC_DC_CALI_TRIGGER); ++ RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, ++ RT711_DAC_DC_CALI_TRIGGER); + + /* wait for calibration process */ + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + + while (val & RT711_DAC_DC_CALI_TRIGGER) { + if (loop >= 500) { +@@ -119,16 +120,15 @@ static int rt711_calibration(struct rt711_priv *rt711) + + usleep_range(10000, 11000); + rt711_index_read(regmap, RT711_VENDOR_CALI, +- RT711_DAC_DC_CALI_CTL1, &val); ++ RT711_DAC_DC_CALI_CTL1, &val); + } + + /* depop mode */ + rt711_index_update_bits(regmap, RT711_VENDOR_REG, +- RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); ++ RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL); + + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + + dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret); + return ret; +@@ -362,24 +362,24 @@ static void rt711_jack_init(struct rt711_priv *rt711) + { + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rt711->component); + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + /* power on */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0); + + if (rt711->hs_jack) { + /* unsolicited response & IRQ control */ + regmap_write(rt711->regmap, +- RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); ++ RT711_SET_MIC2_UNSOLICITED_ENABLE, 0x82); + regmap_write(rt711->regmap, +- RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); ++ RT711_SET_HP_UNSOLICITED_ENABLE, 0x81); + regmap_write(rt711->regmap, +- RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); ++ RT711_SET_INLINE_UNSOLICITED_ENABLE, 0x83); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x10, 0x2420); ++ 0x10, 0x2420); + rt711_index_write(rt711->regmap, RT711_VENDOR_REG, +- 0x19, 0x2e11); ++ 0x19, 0x2e11); + + switch (rt711->jd_src) { + case RT711_JD1: +@@ -449,8 +449,7 @@ static void rt711_jack_init(struct rt711_priv *rt711) + /* power off */ + if (snd_soc_dapm_get_bias_level(dapm) <= SND_SOC_BIAS_STANDBY) + regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + } + + static int rt711_set_jack_detect(struct snd_soc_component *component, +@@ -511,7 +510,7 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + unsigned int read_ll, read_rl; + int i; + +- mutex_lock(&rt711->calibrate_mutex); ++ guard(mutex)(&rt711->calibrate_mutex); + + /* Can't use update bit function, so read the original value first */ + addr_h = mc->reg; +@@ -599,7 +598,6 @@ static int rt711_set_amp_gain_put(struct snd_kcontrol *kcontrol, + regmap_write(rt711->regmap, + RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3); + +- mutex_unlock(&rt711->calibrate_mutex); + return 0; + } + +@@ -908,11 +906,11 @@ static int rt711_set_bias_level(struct snd_soc_component *component, + break; + + case SND_SOC_BIAS_STANDBY: +- mutex_lock(&rt711->calibrate_mutex); +- regmap_write(rt711->regmap, +- RT711_SET_AUDIO_POWER_STATE, +- AC_PWRST_D3); +- mutex_unlock(&rt711->calibrate_mutex); ++ scoped_guard(mutex, &rt711->calibrate_mutex) { ++ regmap_write(rt711->regmap, ++ RT711_SET_AUDIO_POWER_STATE, ++ AC_PWRST_D3); ++ } + break; + + default: +diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c +--- a/sound/soc/codecs/rt712-sdca-dmic.c ++++ b/sound/soc/codecs/rt712-sdca-dmic.c +@@ -912,10 +912,23 @@ static int rt712_sdca_dmic_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) ++ goto err_sync; ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) ++ goto err_sync; ++ + return 0; ++ ++err_sync: ++ regcache_cache_only(rt712->regmap, true); ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_mark_dirty(rt712->regmap); ++ regcache_mark_dirty(rt712->mbq_regmap); ++ return ret; + } + + static const struct dev_pm_ops rt712_sdca_dmic_pm = { +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -427,13 +428,13 @@ static int rt712_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt712_sdca->disable_irq_lock); +- rt712_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt712_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt712_sdca->disable_irq_lock) { ++ rt712_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -455,14 +456,15 @@ static int rt712_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt712->disable_irq_lock); +- if (rt712->disable_irq == true) { +- +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt712->disable_irq = false; ++ scoped_guard(mutex, &rt712->disable_irq_lock) { ++ if (rt712->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt712->disable_irq = false; ++ } + } +- mutex_unlock(&rt712->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT712_PROBE_TIMEOUT); +@@ -472,9 +474,20 @@ static int rt712_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt712->regmap, false); +- regcache_sync(rt712->regmap); ++ ret = regcache_sync(rt712->regmap); ++ if (ret) { ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt712->mbq_regmap, false); +- regcache_sync(rt712->mbq_regmap); ++ ret = regcache_sync(rt712->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt712->mbq_regmap, true); ++ regcache_cache_only(rt712->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -7,6 +7,7 @@ + // + + #include ++#include + #include + #include + #include +@@ -403,7 +404,7 @@ static void rt712_sdca_btn_check_handler(struct work_struct *work) + + static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + { +- mutex_lock(&rt712->calibrate_mutex); ++ guard(mutex)(&rt712->calibrate_mutex); + + if (rt712->hs_jack) { + /* Enable HID1 event & set button RTC mode */ +@@ -450,8 +451,6 @@ static void rt712_sdca_jack_init(struct rt712_sdca_priv *rt712) + + dev_dbg(&rt712->slave->dev, "in %s disable\n", __func__); + } +- +- mutex_unlock(&rt712->calibrate_mutex); + } + + static int rt712_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -466,13 +467,13 @@ static int rt721_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt721_sdca->disable_irq_lock); +- rt721_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt721_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt721_sdca->disable_irq_lock) { ++ rt721_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -494,13 +495,15 @@ static int rt721_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt721->disable_irq_lock); +- if (rt721->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt721->disable_irq = false; ++ scoped_guard(mutex, &rt721->disable_irq_lock) { ++ if (rt721->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt721->disable_irq = false; ++ } + } +- mutex_unlock(&rt721->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT721_PROBE_TIMEOUT); +@@ -510,9 +513,20 @@ static int rt721_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt721->regmap, false); +- regcache_sync(rt721->regmap); ++ ret = regcache_sync(rt721->regmap); ++ if (ret) { ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + regcache_cache_only(rt721->mbq_regmap, false); +- regcache_sync(rt721->mbq_regmap); ++ ret = regcache_sync(rt721->mbq_regmap); ++ if (ret) { ++ regcache_cache_only(rt721->mbq_regmap, true); ++ regcache_cache_only(rt721->regmap, true); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -5,7 +5,7 @@ + // Copyright(c) 2024 Realtek Semiconductor Corp. + // + // +- ++#include + #include + #include + #include +@@ -289,7 +289,7 @@ static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) + + static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + { +- mutex_lock(&rt721->calibrate_mutex); ++ guard(mutex)(&rt721->calibrate_mutex); + if (rt721->hs_jack) { + sdw_write_no_pm(rt721->slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0); +@@ -309,7 +309,6 @@ static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x4000, 0x4000); + } +- mutex_unlock(&rt721->calibrate_mutex); + } + + static int rt721_sdca_set_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c +--- a/sound/soc/codecs/rt722-sdca-sdw.c ++++ b/sound/soc/codecs/rt722-sdca-sdw.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -517,13 +518,13 @@ static int rt722_sdca_dev_system_suspend(struct device *dev) + * deferred work completes and before the parent disables + * interrupts on the link + */ +- mutex_lock(&rt722_sdca->disable_irq_lock); +- rt722_sdca->disable_irq = true; +- ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, +- SDW_SCP_SDCA_INTMASK_SDCA_0, 0); +- ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_8, 0); +- mutex_unlock(&rt722_sdca->disable_irq_lock); ++ scoped_guard(mutex, &rt722_sdca->disable_irq_lock) { ++ rt722_sdca->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8, 0); ++ } + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ +@@ -545,13 +546,15 @@ static int rt722_sdca_dev_resume(struct device *dev) + return 0; + + if (!slave->unattach_request) { +- mutex_lock(&rt722->disable_irq_lock); +- if (rt722->disable_irq == true) { +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, SDW_SCP_SDCA_INTMASK_SDCA_0); +- sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, SDW_SCP_SDCA_INTMASK_SDCA_8); +- rt722->disable_irq = false; ++ scoped_guard(mutex, &rt722->disable_irq_lock) { ++ if (rt722->disable_irq) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK1, ++ SDW_SCP_SDCA_INTMASK_SDCA_0); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK2, ++ SDW_SCP_SDCA_INTMASK_SDCA_8); ++ rt722->disable_irq = false; ++ } + } +- mutex_unlock(&rt722->disable_irq_lock); + } + + ret = sdw_slave_wait_for_init(slave, RT722_PROBE_TIMEOUT); +@@ -561,7 +564,13 @@ static int rt722_sdca_dev_resume(struct device *dev) + } + + regcache_cache_only(rt722->regmap, false); +- regcache_sync(rt722->regmap); ++ ret = regcache_sync(rt722->regmap); ++ if (ret) { ++ regcache_cache_only(rt722->regmap, true); ++ regcache_mark_dirty(rt722->regmap); ++ return ret; ++ } ++ + return 0; + } + +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -6,6 +6,7 @@ + // + // + ++#include + #include + #include + #include +@@ -294,7 +295,7 @@ static void rt722_sdca_btn_check_handler(struct work_struct *work) + + static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + { +- mutex_lock(&rt722->calibrate_mutex); ++ guard(mutex)(&rt722->calibrate_mutex); + if (rt722->hs_jack) { + /* set SCP_SDCA_IntMask1[0]=1 */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK1, +@@ -317,7 +318,6 @@ static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_HDA_CTL, + RT722_GE_RELATED_CTL2, 0x4000, 0x4000); + } +- mutex_unlock(&rt722->calibrate_mutex); + } + + static int rt722_sdca_set_jack_detect(struct snd_soc_component *component, +@@ -352,8 +352,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + static const char func_tag[] = "FUNC"; + static const char xu_tag[] = "XU"; + const char *dmi_vendor, *dmi_product, *dmi_sku; +- char *cae_filename; +- const struct firmware *cae_fw = NULL; + unsigned int cae_st_spk, cae_st_hp, cae_st_mic; + unsigned int func, value; + unsigned int combined_val; +@@ -385,7 +383,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + space = strchr(dmi_sku, ' '); + s_len = space ? space - dmi_sku : strlen(dmi_sku); + +- cae_filename = kasprintf(GFP_KERNEL, ++ char *cae_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, + "realtek/rt722/rt722_RAE_%.*s_%.*s_%.*s.dat", + v_len, dmi_vendor, + p_len, dmi_product, +@@ -399,8 +398,8 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regmap_write(rt722->regmap, RT722_MIC_CAE_PARAM39, 0x5f); + usleep_range(50000, 60000); + ++ const struct firmware *cae_fw __free(firmware) = NULL; + request_firmware(&cae_fw, cae_filename, dev); +- kfree(cae_filename); + if (!cae_fw) { + dev_err(dev, "%s: Failed to load CAE firmware\n", __func__); + return -ENOENT; +@@ -555,7 +554,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + regcache_cache_bypass(rt722->regmap, false); + rt722->cae_update_done = 1; + dev_dbg(dev, "%s: CAE FW update done.\n", __func__); +- release_firmware(cae_fw); + return 0; + + verify_abort: +@@ -565,7 +563,6 @@ static int rt722_cae_load(struct rt722_sdca_priv *rt722) + out_release: + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_REG, + RT722_MISC_CTRL1, 0x8000, 0x0000); +- release_firmware(cae_fw); + dev_err(dev, "%s: CAE FW update aborted (ret=%d).\n", __func__, ret); + return ret; + } +@@ -1906,6 +1903,9 @@ int rt722_sdca_io_init(struct device *dev, struct sdw_slave *slave) + rt722_sdca_amp_preset(rt722); + rt722_sdca_jack_preset(rt722); + ++ if (rt722->hs_jack && (!rt722->first_hw_init)) ++ rt722_sdca_jack_init(rt722); ++ + if (rt722->first_hw_init) { + regcache_cache_bypass(rt722->regmap, false); + regcache_mark_dirty(rt722->regmap); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -0,0 +1,339 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca-sdw.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt766-sdca-sdw.h" ++ ++#define RT766_PROBE_TIMEOUT 5000 ++ ++static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_MUTE_REG(UAJ, USER_FU41, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 1): ++ case RT766_MUTE_REG(UAJ, USER_FU36, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_PDE_REQ_REG(UAJ, PDE47): ++ case RT766_PDE_REQ_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_FUNC_STATUS_REG(UAJ): /* 0x40480000 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): /* 0x40481400 */ ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): /* 0x40481480 */ ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): /* 0x40600490 */ ++ case RT766_PDE_REQ_REG(MIC, PDE11): ++ case RT766_MUTE_REG(MIC, USER_FU113, 1): ++ case RT766_MUTE_REG(MIC, USER_FU113, 2): ++ case RT766_MUTE_REG(MIC, USER_FU113, 3): ++ case RT766_MUTE_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_FUNC_STATUS_REG(MIC): /* 0x40880000 */ ++ case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): /* 0x40881500 */ ++ case RT766_FUNC_STATUS_REG(HID): /* 0x40c80000 */ ++ /* 0x40c80080 - 0x40c80098 */ ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_MUTE_REG(AMP, USER_FU21, 1): ++ case RT766_MUTE_REG(AMP, USER_FU21, 2): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ case RT766_PDE_REQ_REG(AMP, PDE23): ++ case RT766_FUNC_STATUS_REG(AMP): /* 0x41080000 */ ++ case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER): ++ case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): /* 0x41081980 */ ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool rt766_sdca_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: ++ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID: ++ case 0xc344 ... 0xc345: ++ case 0xc900: ++ case 0xc920: ++ case 0xd540 ... 0xd542: ++ case 0xf01e: ++ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1: ++ case 0x310100: ++ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END: ++ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END: ++ case RT766_FUNC_STATUS_REG(UAJ): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE47): ++ case RT766_PDE_ACTUAL_REG(UAJ, PDE34): ++ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE): ++ case RT766_FUNC_STATUS_REG(MIC): ++ case RT766_PDE_ACTUAL_REG(MIC, PDE11): ++ case RT766_FUNC_STATUS_REG(HID): ++ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ... ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH): ++ case RT766_FUNC_STATUS_REG(AMP): ++ case RT766_PDE_ACTUAL_REG(AMP, PDE23): ++ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static int rt766_sdca_mbq_size(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU41, 2): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 1): ++ case RT766_VOLUME_REG(UAJ, USER_FU36, 2): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1): ++ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 1): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 2): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 3): ++ case RT766_VOLUME_REG(MIC, USER_FU113, 4): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 1): ++ case RT766_VOLUME_REG(AMP, USER_FU21, 2): ++ return 2; ++ default: ++ return 1; ++ } ++} ++ ++static const struct regmap_sdw_mbq_cfg rt766_sdca_mbq_cfg = { ++ .mbq_size = rt766_sdca_mbq_size, ++}; ++ ++static const struct regmap_config rt766_sdca_regmap = { ++ .reg_bits = 32, ++ .val_bits = 16, ++ .readable_reg = rt766_sdca_readable_register, ++ .volatile_reg = rt766_sdca_volatile_register, ++ .reg_defaults = rt766_sdca_defaults, ++ .num_reg_defaults = ARRAY_SIZE(rt766_sdca_defaults), ++ .max_register = SDW_SDCA_MAX_REGISTER, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int rt766_sdca_update_status(struct sdw_slave *slave, ++ enum sdw_slave_status status) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(&slave->dev); ++ ++ if (status == SDW_SLAVE_UNATTACHED) ++ rt766->hw_init = false; ++ ++ if (status == SDW_SLAVE_ATTACHED) { ++ if (rt766->hs_jack) { ++ /* ++ * Due to the SCP_SDCA_INTMASK will be cleared by any reset, and then ++ * if the device attached again, we will need to set the setting back. ++ * It could avoid losing the jack detection interrupt. ++ * This also could sync with the cache value as the rt766_sdca_jack_init set. ++ */ ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24); ++ } ++ } ++ ++ /* ++ * Perform initialization only if slave status is present and ++ * hw_init flag is false ++ */ ++ if (rt766->hw_init || status != SDW_SLAVE_ATTACHED) ++ return 0; ++ ++ /* perform I/O transfers required for Slave initialization */ ++ return rt766_sdca_io_init(&slave->dev, slave); ++} ++ ++static int rt766_sdca_read_prop(struct sdw_slave *slave) ++{ ++ struct sdw_slave_prop *prop = &slave->prop; ++ int ret; ++ ++ ret = sdw_slave_read_prop(slave); ++ if (ret < 0) ++ return ret; ++ ++ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; ++ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; ++ /* ++ * SDCA interrupts are routed through SoundWire domain IRQ. ++ */ ++ prop->use_domain_irq = true; ++ ++ return 0; ++} ++ ++static const struct sdw_slave_ops rt766_sdca_slave_ops = { ++ .read_prop = rt766_sdca_read_prop, ++ .update_status = rt766_sdca_update_status, ++}; ++ ++static int rt766_sdca_sdw_probe(struct sdw_slave *slave, ++ const struct sdw_device_id *id) ++{ ++ struct regmap *regmap; ++ ++ /* Regmap Initialization */ ++ regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, ++ &rt766_sdca_regmap, &rt766_sdca_mbq_cfg); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ return rt766_sdca_init(&slave->dev, regmap, slave); ++} ++ ++static void rt766_sdca_sdw_remove(struct sdw_slave *slave) ++{ ++ pm_runtime_disable(&slave->dev); ++} ++ ++static const struct sdw_device_id rt766_sdca_id[] = { ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ {}, ++}; ++MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); ++ ++static int rt766_sdca_dev_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, true); ++ return 0; ++} ++ ++static int rt766_sdca_dev_system_suspend(struct device *dev) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ int ret1, ret2; ++ ++ if (!rt766->hw_init) ++ return 0; ++ ++ /* ++ * prevent new interrupts from being handled after the ++ * deferred work completes and before the parent disables ++ * interrupts on the link ++ */ ++ mutex_lock(&rt766->disable_irq_lock); ++ rt766->disable_irq = true; ++ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK3, ++ SDW_SCP_SDCA_INTMASK_SDCA_16, 0); ++ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK4, ++ SDW_SCP_SDCA_INTMASK_SDCA_24, 0); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ if (ret1 < 0 || ret2 < 0) { ++ /* log but don't prevent suspend from happening */ ++ dev_dbg(&slave->dev, "%s: could not disable SDCA interrupts\n:", __func__); ++ } ++ ++ return rt766_sdca_dev_suspend(dev); ++} ++ ++static int rt766_sdca_dev_resume(struct device *dev) ++{ ++ struct sdw_slave *slave = dev_to_sdw_dev(dev); ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ int ret; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ if (!slave->unattach_request) { ++ mutex_lock(&rt766->disable_irq_lock); ++ if (rt766->disable_irq == true) { ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK3, SDW_SCP_SDCA_INTMASK_SDCA_16); ++ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK4, SDW_SCP_SDCA_INTMASK_SDCA_24); ++ rt766->disable_irq = false; ++ } ++ mutex_unlock(&rt766->disable_irq_lock); ++ goto regmap_sync; ++ } ++ ++ ret = sdw_slave_wait_for_init(slave, RT766_PROBE_TIMEOUT); ++ if (ret) { ++ sdw_show_ping_status(slave->bus, true); ++ return ret; ++ } ++ ++regmap_sync: ++ regcache_cache_only(rt766->regmap, false); ++ ret = regcache_sync(rt766->regmap); ++ if (ret) { ++ regcache_cache_only(rt766->regmap, true); ++ regcache_mark_dirty(rt766->regmap); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops rt766_sdca_pm = { ++ SYSTEM_SLEEP_PM_OPS(rt766_sdca_dev_system_suspend, rt766_sdca_dev_resume) ++ RUNTIME_PM_OPS(rt766_sdca_dev_suspend, rt766_sdca_dev_resume, NULL) ++}; ++ ++static struct sdw_driver rt766_sdca_sdw_driver = { ++ .driver = { ++ .name = "rt766-sdca", ++ .pm = pm_ptr(&rt766_sdca_pm), ++ }, ++ .probe = rt766_sdca_sdw_probe, ++ .remove = rt766_sdca_sdw_remove, ++ .ops = &rt766_sdca_slave_ops, ++ .id_table = rt766_sdca_id, ++}; ++module_sdw_driver(rt766_sdca_sdw_driver); ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca-sdw.h b/sound/soc/codecs/rt766-sdca-sdw.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca-sdw.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca-sdw.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_SDW_H__ ++#define __RT766_SDW_H__ ++ ++#include ++#include ++ ++static const struct reg_default rt766_sdca_defaults[] = { ++ /* 0x40400289 - 0x4040028a */ ++ { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 }, ++ /* 0x40400291 - 0x40400292 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 }, ++ /* 0x40400789 - 0x4040078a */ ++ { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 }, ++ { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 }, ++ /* 0x40400791 - 0x40400792 */ ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 }, ++ { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 }, /* 0x40401408 */ ++ { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 }, /* 0x40401488 */ ++ { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480080 */ ++ { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40480880 */ ++ /* 0x40600259 - 0x4060025a */ ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 }, ++ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 }, ++ { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 }, /* 0x40600488 */ ++ ++ { RT766_PDE_REQ_REG(MIC, PDE11), 0x03 }, /* 0x40801508 */ ++ /* 0x40801809 - 0x4080180c */ ++ { RT766_MUTE_REG(MIC, USER_FU113, 1), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 2), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 3), 0x01 }, ++ { RT766_MUTE_REG(MIC, USER_FU113, 4), 0x01 }, ++ /* 0x40801811 - 0x40801814 */ ++ { RT766_VOLUME_REG(MIC, USER_FU113, 1), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 2), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 3), 0x0000 }, ++ { RT766_VOLUME_REG(MIC, USER_FU113, 4), 0x0000 }, ++ { RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x40880900 */ ++ ++ /* 0x41000189 - 0x4100018a */ ++ { RT766_MUTE_REG(AMP, USER_FU21, 1), 0x01 }, ++ { RT766_MUTE_REG(AMP, USER_FU21, 2), 0x01 }, ++ /* 0x41000191 - 0x41000192 */ ++ { RT766_VOLUME_REG(AMP, USER_FU21, 1), 0x0000 }, ++ { RT766_VOLUME_REG(AMP, USER_FU21, 2), 0x0000 }, ++ { RT766_PDE_REQ_REG(AMP, PDE23), 0x03 }, /* 0x41001988 */ ++ { RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0x00 }, /* 0x41080200 */ ++ { RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 }, /* 0x41081080 */ ++ ++}; ++ ++#endif /* __RT766_SDW_H__ */ +diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.c +@@ -0,0 +1,1378 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++// ++// rt766-sdca.c -- rt766 SDCA ALSA SoC audio driver ++// ++// Copyright(c) 2026 Realtek Semiconductor Corp. ++// ++// ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rt766-sdca.h" ++#include "rt-sdw-common.h" ++ ++static int rt766_sdca_btn_detect(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ unsigned char *buf = NULL; ++ unsigned int offset, owner, length; ++ unsigned int det_mode, idx, val; ++ int ret; ++ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ /* get current UMP message owner */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), ++ &owner); ++ if (ret < 0) ++ goto io_error; ++ ++ /* if owner is device then there is no button event from device */ ++ if (owner == 1) ++ return 0; ++ ++ if (det_mode) { ++ /* read UMP message length */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH), ++ &length); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ /* read UMP message offset */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGEOFFSET), ++ &offset); ++ if (ret < 0) ++ goto _end_btn_det_; ++ ++ buf = devm_kzalloc(&rt766->slave->dev, length, GFP_KERNEL); ++ if (!buf) { ++ dev_err(&rt766->slave->dev, "%s: alloc buf failed\n", __func__); ++ goto _end_btn_det_; ++ } ++ ++ for (idx = 0; idx < length; idx++) { ++ ret = regmap_read(rt766->regmap, ++ RT766_BUF_ADDR_HID1 + offset + idx, &val); ++ if (ret < 0) ++ goto _end_btn_det_; ++ buf[idx] = val & 0xff; ++ } ++ ++ if (rt766->hid) ++ hid_input_report(rt766->hid, HID_INPUT_REPORT, ++ buf, length, 1); ++ } ++ ++_end_btn_det_: ++ if (buf) ++ devm_kfree(&rt766->slave->dev, buf); ++ ++ /* Host is owner, so set back to device */ ++ if (owner == 0) { ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), 0x01); ++ } ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_btn_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_btn_detect(interrupt); ++ mutex_unlock(&rt766->disable_irq_lock); ++ return IRQ_HANDLED; ++} ++ ++static int rt766_sdca_headset_detect(struct rt766_sdca_priv *rt766) ++{ ++ unsigned int det_mode; ++ int ret; ++ ++ /* get detected_mode */ ++ ret = regmap_read(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE), ++ &det_mode); ++ if (ret < 0) ++ goto io_error; ++ ++ switch (det_mode) { ++ case 0x00: ++ rt766->jack_type = 0; ++ break; ++ case 0x03: ++ rt766->jack_type = SND_JACK_HEADPHONE; ++ break; ++ case 0x05: ++ rt766->jack_type = SND_JACK_HEADSET; ++ break; ++ } ++ ++ /* write selected_mode */ ++ if (det_mode) { ++ ret = regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), ++ det_mode); ++ if (ret < 0) ++ goto io_error; ++ } ++ ++ dev_dbg(&rt766->slave->dev, ++ "%s, detected_mode=0x%x\n", __func__, det_mode); ++ ++ return 0; ++ ++io_error: ++ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); ++ return ret; ++} ++ ++static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) ++{ ++ struct sdca_interrupt *interrupt = data; ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ if (!rt766->hs_jack) ++ return IRQ_HANDLED; ++ ++ if (!rt766->component->card || !rt766->component->card->instantiated) ++ return IRQ_HANDLED; ++ ++ mutex_lock(&rt766->disable_irq_lock); ++ if (!rt766->disable_irq) ++ rt766_sdca_headset_detect(rt766); ++ mutex_unlock(&rt766->disable_irq_lock); ++ ++ dev_dbg(&rt766->slave->dev, ++ "in %s, jack_type=%d\n", __func__, rt766->jack_type); ++ ++ snd_soc_jack_report(rt766->hs_jack, rt766->jack_type, SND_JACK_HEADSET); ++ return IRQ_HANDLED; ++} ++ ++static void rt766_sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct rt766_sdca_priv *rt766 = interrupt->priv; ++ ++ hid_destroy_device(rt766->hid); ++} ++ ++static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766, ++ struct sdca_function_data *function, ++ struct snd_soc_component *component, ++ struct sdca_interrupt_info *info, ++ bool enabled) ++{ ++ struct device *dev = &rt766->slave->dev; ++ struct sdca_interrupt *interrupt; ++ struct sdca_control *control; ++ struct sdca_entity *entity; ++ irq_handler_t handler; ++ int i, j, irq, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ for (j = 0; j < entity->num_controls; j++) { ++ control = &entity->controls[j]; ++ irq = control->interrupt_position; ++ ++ switch (SDCA_CTL_TYPE(entity->type, control->sel)) { ++ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): ++ handler = rt766_sdca_irq_jd_handler; ++ break; ++ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ handler = rt766_sdca_irq_btn_handler; ++ break; ++ default: ++ continue; ++ } ++ ++ interrupt = &info->irqs[irq]; ++ ++ if (enabled) { ++ ret = sdca_irq_data_populate(dev, rt766->regmap, component, ++ function, entity, control, ++ interrupt); ++ if (ret) ++ return ret; ++ ++ if (handler == rt766_sdca_irq_btn_handler) { ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ ++ interrupt->free_priv = rt766_sdca_destroy_hid_device; ++ rt766->hid = interrupt->priv; ++ } ++ ++ interrupt->priv = rt766; ++ ret = sdca_irq_request(dev, info, irq, interrupt->name, ++ handler, interrupt); ++ if (ret) { ++ dev_err(dev, "failed to request irq %s: %d\n", ++ interrupt->name, ret); ++ sdca_irq_cleanup_late(dev, function, info); ++ return ret; ++ } ++ dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name); ++ } else { ++ sdca_irq_cleanup_late(dev, function, info); ++ dev_dbg(dev, "Freeing IRQ %d\n", irq); ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, ++ struct snd_soc_jack *hs_jack, void *data) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ if (!rt766->uaj_func_data) { ++ dev_err(&rt766->slave->dev, "The SDCA UAJ function is not supported.\n"); ++ return -EINVAL; ++ } ++ ++ rt766->hs_jack = hs_jack; ++ ++ if (!rt766->first_hw_init) ++ return 0; ++ ++ ret = pm_runtime_resume_and_get(component->dev); ++ if (ret < 0) { ++ if (ret != -EACCES) { ++ dev_err(component->dev, "%s: failed to resume %d\n", __func__, ret); ++ return ret; ++ } ++ ++ /* pm_runtime not enabled yet */ ++ dev_dbg(component->dev, "%s: skipping jack init for now\n", __func__); ++ return 0; ++ } ++ ++ /* disable interrupts if hs_jack is not set */ ++ if (!rt766->hs_jack) { ++ if (rt766->uaj_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ rt766->component, rt766->irq_info, false); ++ ++ if (rt766->hid_func_data) ++ rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ rt766->component, rt766->irq_info, false); ++ } ++ ++ pm_runtime_put_autosuspend(component->dev); ++ ++ return 0; ++} ++ ++static int rt766_sdca_set_fu_ctl(struct rt766_sdca_priv *rt766, int func_num, int fu_num) ++{ ++ unsigned int fu01_reg, fu02_reg; ++ unsigned int ch_01, ch_02; ++ unsigned int ch_mute; ++ unsigned int fu_reg; ++ int err, i; ++ ++ switch (fu_num) { ++ case RT766_SDCA_ENT_USER_FU41: ++ ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU36: ++ ch_01 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU21: ++ ch_01 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_l_mute) ? 0x01 : 0x00; ++ ch_02 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_r_mute) ? 0x01 : 0x00; ++ break; ++ case RT766_SDCA_ENT_USER_FU113: ++ for (i = 0; i < ARRAY_SIZE(rt766->fu113_mixer_mute); i++) { ++ ch_mute = (rt766->fu113_dapm_mute || rt766->fu113_mixer_mute[i]) ? 0x01 : 0x00; ++ fu_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1) + i; ++ err = regmap_write(rt766->regmap, fu_reg, ch_mute); ++ if (err < 0) ++ return err; ++ } ++ return 0; ++ } ++ ++ fu01_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 1); ++ fu02_reg = SDW_SDCA_CTL(func_num, fu_num, SDCA_CTL_FU_MUTE, 2); ++ err = regmap_write(rt766->regmap, fu01_reg, ch_01); ++ if (err < 0) ++ return err; ++ err = regmap_write(rt766->regmap, fu02_reg, ch_02); ++ if (err < 0) ++ return err; ++ ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu41_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu41_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu41_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu41_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu41_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu41_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu41_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu36_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu36_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu36_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu36_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu36_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu36_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu36_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu36_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu21_playback_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ ucontrol->value.integer.value[0] = !rt766->fu21_mixer_l_mute; ++ ucontrol->value.integer.value[1] = !rt766->fu21_mixer_r_mute; ++ return 0; ++} ++ ++static int rt766_sdca_fu21_playback_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err; ++ ++ if (rt766->fu21_mixer_l_mute == !ucontrol->value.integer.value[0] && ++ rt766->fu21_mixer_r_mute == !ucontrol->value.integer.value[1]) ++ return 0; ++ ++ rt766->fu21_mixer_l_mute = !ucontrol->value.integer.value[0]; ++ rt766->fu21_mixer_r_mute = !ucontrol->value.integer.value[1]; ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ if (err < 0) ++ return err; ++ ++ return 1; ++} ++ ++static int rt766_sdca_fu113_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu113_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu113_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ const unsigned int interval_offset = 0xc0; ++ unsigned int regvalue, ctl, i; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value); ++ ctl = p->max - (((0x1e00 - regvalue) & 0xffff) / interval_offset); ++ ++ ucontrol->value.integer.value[i] = ctl; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ const unsigned int interval_offset = 0xc0; ++ unsigned int gain_val[4]; ++ unsigned int i, changed = 0; ++ unsigned int regvalue[4]; ++ int err; ++ ++ /* check all channels */ ++ for (i = 0; i < p->count; i++) { ++ regmap_read(rt766->regmap, p->reg_base + i, ®value[i]); ++ ++ gain_val[i] = ucontrol->value.integer.value[i]; ++ if (gain_val[i] > p->max) ++ gain_val[i] = p->max; ++ ++ gain_val[i] = 0x1e00 - ((p->max - gain_val[i]) * interval_offset); ++ gain_val[i] &= 0xffff; ++ ++ if (regvalue[i] != gain_val[i]) ++ changed = 1; ++ } ++ ++ if (!changed) ++ return 0; ++ ++ for (i = 0; i < p->count; i++) { ++ err = regmap_write(rt766->regmap, p->reg_base + i, gain_val[i]); ++ if (err < 0) ++ dev_err(&rt766->slave->dev, "0x%08x can't be set\n", p->reg_base + i); ++ } ++ ++ return changed; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ unsigned int i; ++ ++ for (i = 0; i < 4; i++) ++ ucontrol->value.integer.value[i] = !rt766->fu113_mixer_mute[i]; ++ return 0; ++} ++ ++static int rt766_sdca_dmic_fu113_capture_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ int err, changed = 0, i; ++ ++ for (i = 0; i < 4; i++) { ++ if (rt766->fu113_mixer_mute[i] != !ucontrol->value.integer.value[i]) ++ changed = 1; ++ rt766->fu113_mixer_mute[i] = !ucontrol->value.integer.value[i]; ++ } ++ ++ err = rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_USER_FU113); ++ if (err < 0) ++ return err; ++ return changed; ++} ++ ++static int rt766_sdca_fu41_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu41_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu41_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU41); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event, int func_num, int pde_num, const char *pde_ent) ++{ ++ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdca_function_data *func_data; ++ unsigned char ps0 = 0x0, ps3 = 0x3; ++ const struct sdca_entity *entity; ++ unsigned int pde_req_reg; ++ int from_ps, to_ps; ++ int ret; ++ ++ pde_req_reg = SDW_SDCA_CTL(func_num, pde_num, SDCA_CTL_PDE_REQUESTED_PS, 0); ++ ++ switch (func_num) { ++ case RT766_FUNC_NUM_UAJ: ++ func_data = rt766->uaj_func_data; ++ break; ++ case RT766_FUNC_NUM_AMP: ++ func_data = rt766->sa_func_data; ++ break; ++ case RT766_FUNC_NUM_MIC: ++ func_data = rt766->sm_func_data; ++ break; ++ default: ++ dev_err(component->dev, "%s: unsupported func_num %d\n", ++ __func__, func_num); ++ return -EINVAL; ++ } ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ regmap_write(rt766->regmap, pde_req_reg, ps0); ++ from_ps = ps3; ++ to_ps = ps0; ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ regmap_write(rt766->regmap, pde_req_reg, ps3); ++ from_ps = ps0; ++ to_ps = ps3; ++ break; ++ } ++ ++ entity = sdca_find_entity_by_label(func_data, pde_ent); ++ if (!entity) { ++ dev_err(component->dev, "%s: failed to find entity %s\n", ++ __func__, pde_ent); ++ return -EINVAL; ++ } ++ ++ ret = sdca_asoc_pde_poll_actual_ps(rt766->regmap, ++ func_num, ++ pde_num, ++ from_ps, to_ps, ++ entity->pde.max_delay, ++ entity->pde.num_max_delay); ++ if (ret) ++ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ __func__, from_ps, to_ps, ret); ++ ++ return ret; ++} ++ ++static int rt766_sdca_pde47_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE47, "PDE 47"); ++} ++ ++static int rt766_sdca_fu36_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu36_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu36_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_USER_FU36); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde34_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_UAJ, RT766_SDCA_ENT_PDE34, "PDE 34"); ++} ++ ++static int rt766_sdca_fu21_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *component = ++ snd_soc_dapm_to_component(w->dapm); ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ switch (event) { ++ case SND_SOC_DAPM_POST_PMU: ++ rt766->fu21_dapm_mute = false; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ case SND_SOC_DAPM_PRE_PMD: ++ rt766->fu21_dapm_mute = true; ++ rt766_sdca_set_fu_ctl(rt766, RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_USER_FU21); ++ break; ++ } ++ return 0; ++} ++ ++static int rt766_sdca_pde23_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_AMP, RT766_SDCA_ENT_PDE23, "PDE 23"); ++} ++ ++static int rt766_sdca_pde11_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ return rt766_sdca_pde_event(w, kcontrol, event, ++ RT766_FUNC_NUM_MIC, RT766_SDCA_ENT_PDE11, "PDE 11"); ++} ++ ++static int rt766_dmic_fu_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ struct rt_sdca_dmic_kctrl_priv *p = ++ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value; ++ ++ if (p->max == 1) ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; ++ else ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; ++ uinfo->count = p->count; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = p->max; ++ return 0; ++} ++ ++static const char * const rt766_rx_data_ch_select[] = { ++ "L,R", ++ "R,L", ++ "L,L", ++ "R,R", ++ "L,L+R", ++ "R,L+R", ++ "L+R,L", ++ "L+R,R", ++ "L+R,L+R", ++}; ++ ++static SOC_ENUM_SINGLE_DECL(rt766_rx_data_ch_enum, ++ RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0, ++ rt766_rx_data_ch_select); ++ ++static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -9525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(spk_vol_tlv, -6525, 75, 0); ++static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); ++static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, -200, 200, 0); ++ ++#define RT766_P75DB_STEP 0xC0 /* 0.75 dB in Q7.8 format */ ++#define RT766_2DB_STEP 0x200 /* 2 dB in Q7.8 format */ ++#define RT766_HP_VOL_MIN (-127) /* -95.25 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MIN (-23) /* -17.25 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MIN (-1) /* -2 dB / 2 dB step */ ++#define RT766_SPK_VOL_MIN (-87) /* -65.25 dB / 0.75 dB step */ ++#define RT766_P_VOL_MAX 0 /* 0 dB / 0.75 dB step */ ++#define RT766_HS_VOL_MAX 40 /* 30 dB / 0.75 dB step */ ++#define RT766_HS_BOOST_VOL_MAX 20 /* 40 dB / 2 dB step */ ++ ++static const struct snd_kcontrol_new rt766_sdca_controls[] = { ++ SOC_DOUBLE_EXT("FU41 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu41_playback_get, rt766_sdca_fu41_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU41 Playback Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU41, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU41, 2), ++ RT766_HP_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, hp_vol_tlv), ++ SOC_DOUBLE_EXT("FU36 Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu36_capture_get, rt766_sdca_fu36_capture_put), ++ SDCA_DOUBLE_Q78_TLV("FU36 Capture Volume", ++ RT766_VOLUME_REG(UAJ, USER_FU36, 1), ++ RT766_VOLUME_REG(UAJ, USER_FU36, 2), ++ RT766_HS_VOL_MIN, RT766_HS_VOL_MAX, RT766_P75DB_STEP, mic_vol_tlv), ++ SDCA_DOUBLE_Q78_TLV("FU33 Boost Volume", ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), ++ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), ++ RT766_HS_BOOST_VOL_MIN, RT766_HS_BOOST_VOL_MAX, RT766_2DB_STEP, boost_vol_tlv), ++ ++ SOC_DOUBLE_EXT("FU21 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0, ++ rt766_sdca_fu21_playback_get, rt766_sdca_fu21_playback_put), ++ SDCA_DOUBLE_Q78_TLV("FU21 Playback Volume", ++ RT766_VOLUME_REG(AMP, USER_FU21, 1), ++ RT766_VOLUME_REG(AMP, USER_FU21, 2), ++ RT766_SPK_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, spk_vol_tlv), ++ SOC_ENUM("RX Channel Select", rt766_rx_data_ch_enum), ++ ++ RT_SDCA_FU_CTRL("FU113 Capture Switch", ++ RT766_MUTE_REG(MIC, USER_FU113, 1), 1, 1, 4, rt766_dmic_fu_info, ++ rt766_sdca_dmic_fu113_capture_get, rt766_sdca_dmic_fu113_capture_put), ++ RT_SDCA_EXT_TLV("FU113 Capture Volume", ++ RT766_VOLUME_REG(MIC, USER_FU113, 1), ++ rt766_sdca_dmic_set_gain_get, rt766_sdca_dmic_set_gain_put, ++ 4, 0x3f, mic_vol_tlv, rt766_dmic_fu_info), ++}; ++ ++static const struct snd_soc_dapm_widget rt766_sdca_dapm_widgets[] = { ++ /* UAJ */ ++ SND_SOC_DAPM_OUTPUT("HP"), ++ SND_SOC_DAPM_INPUT("MIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 47", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde47_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 34", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde34_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_DAC_E("FU 41", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu41_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 36", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu36_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("DP12TX", "DP12 Capture", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* AMP */ ++ SND_SOC_DAPM_OUTPUT("SPOL"), ++ SND_SOC_DAPM_OUTPUT("SPOR"), ++ SND_SOC_DAPM_DAC_E("FU 21", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu21_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_SUPPLY("PDE 23", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde23_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), ++ ++ /* DMIC */ ++ SND_SOC_DAPM_INPUT("DMIC1"), ++ SND_SOC_DAPM_INPUT("DMIC2"), ++ SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0, ++ rt766_sdca_pde11_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_ADC_E("FU 113", NULL, SND_SOC_NOPM, 0, 0, ++ rt766_sdca_fu113_event, ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_AIF_OUT("DP8TX", "DP8 Capture", 0, SND_SOC_NOPM, 0, 0), ++}; ++ ++static const struct snd_soc_dapm_route rt766_sdca_audio_map[] = { ++ { "FU 41", NULL, "DP3RX" }, ++ { "DP12TX", NULL, "FU 36" }, ++ { "FU 36", NULL, "PDE 34" }, ++ { "FU 36", NULL, "MIC2" }, ++ { "HP", NULL, "PDE 47" }, ++ { "HP", NULL, "FU 41" }, ++ ++ { "FU 21", NULL, "DP1RX" }, ++ { "FU 21", NULL, "PDE 23" }, ++ { "SPOL", NULL, "FU 21" }, ++ { "SPOR", NULL, "FU 21" }, ++ ++ {"DP8TX", NULL, "FU 113"}, ++ {"FU 113", NULL, "PDE 11"}, ++ {"FU 113", NULL, "DMIC1"}, ++ {"FU 113", NULL, "DMIC2"}, ++}; ++ ++static int rt766_sdca_probe(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct device *dev = &rt766->slave->dev; ++ int ret; ++ ++ rt766->component = component; ++ ++ ret = pm_runtime_resume(component->dev); ++ if (ret < 0 && ret != -EACCES) ++ return ret; ++ ++ if (rt766->uaj_func_data) { ++ dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq); ++ ++ rt766->irq_info = devm_sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq); ++ if (IS_ERR(rt766->irq_info)) ++ return PTR_ERR(rt766->irq_info); ++ ++ ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ ++ if (rt766->hid_func_data) { ++ ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, ++ component, rt766->irq_info, true); ++ if (ret < 0) { ++ dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static void rt766_sdca_remove(struct snd_soc_component *component) ++{ ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ ++ sdca_irq_cleanup_late(component->dev, rt766->uaj_func_data, rt766->irq_info); ++ sdca_irq_cleanup_late(component->dev, rt766->hid_func_data, rt766->irq_info); ++} ++ ++static const struct snd_soc_component_driver soc_sdca_dev_rt766 = { ++ .probe = rt766_sdca_probe, ++ .remove = rt766_sdca_remove, ++ .controls = rt766_sdca_controls, ++ .num_controls = ARRAY_SIZE(rt766_sdca_controls), ++ .dapm_widgets = rt766_sdca_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(rt766_sdca_dapm_widgets), ++ .dapm_routes = rt766_sdca_audio_map, ++ .num_dapm_routes = ARRAY_SIZE(rt766_sdca_audio_map), ++ .set_jack = rt766_sdca_set_jack_detect, ++ .endianness = 1, ++}; ++ ++static int rt766_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, ++ int direction) ++{ ++ snd_soc_dai_dma_data_set(dai, direction, sdw_stream); ++ ++ return 0; ++} ++ ++static void rt766_sdca_shutdown(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ snd_soc_dai_set_dma_data(dai, substream, NULL); ++} ++ ++static int rt766_sdca_pcm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_config stream_config; ++ struct sdw_port_config port_config; ++ enum sdw_data_direction direction; ++ struct sdw_stream_runtime *sdw_stream; ++ unsigned int sampling_rate; ++ int retval, port; ++ ++ dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); ++ sdw_stream = snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!sdw_stream) ++ return -EINVAL; ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); ++ ++ /* SoundWire specific configuration */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ direction = SDW_DATA_DIR_RX; ++ if (dai->id == RT766_AIF1) ++ port = 3; ++ else if (dai->id == RT766_AIF2) ++ port = 1; ++ else ++ return -EINVAL; ++ } else { ++ direction = SDW_DATA_DIR_TX; ++ if (dai->id == RT766_AIF1) ++ port = 12; ++ else if (dai->id == RT766_AIF3) ++ port = 8; ++ else ++ return -EINVAL; ++ } ++ ++ port_config.num = port; ++ retval = sdw_stream_add_slave(rt766->slave, &stream_config, ++ &port_config, 1, sdw_stream); ++ if (retval) { ++ dev_err(dai->dev, "%s: Unable to configure port\n", __func__); ++ return retval; ++ } ++ ++ if (params_channels(params) > 16) { ++ dev_err(component->dev, "%s: Unsupported channels %d\n", ++ __func__, params_channels(params)); ++ return -EINVAL; ++ } ++ ++ /* sampling rate configuration */ ++ switch (params_rate(params)) { ++ case 44100: ++ sampling_rate = RT766_SDCA_RATE_44100HZ; ++ break; ++ case 48000: ++ sampling_rate = RT766_SDCA_RATE_48000HZ; ++ break; ++ case 96000: ++ sampling_rate = RT766_SDCA_RATE_96000HZ; ++ break; ++ case 192000: ++ sampling_rate = RT766_SDCA_RATE_192000HZ; ++ break; ++ default: ++ dev_err(component->dev, "%s: Rate %d is not supported\n", ++ __func__, params_rate(params)); ++ return -EINVAL; ++ } ++ ++ /* set sampling frequency */ ++ switch (dai->id) { ++ case RT766_AIF1: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF2: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ case RT766_AIF3: ++ regmap_write(rt766->regmap, ++ RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), ++ sampling_rate); ++ break; ++ default: ++ dev_err(component->dev, "%s: Wrong DAI id\n", __func__); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int rt766_sdca_pcm_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct snd_soc_component *component = dai->component; ++ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component); ++ struct sdw_stream_runtime *sdw_stream = ++ snd_soc_dai_get_dma_data(dai, substream); ++ ++ if (!rt766->slave) ++ return -EINVAL; ++ ++ sdw_stream_remove_slave(rt766->slave, sdw_stream); ++ return 0; ++} ++ ++#define RT766_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ ++ SNDRV_PCM_RATE_192000) ++#define RT766_DAC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) ++#define RT766_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ ++ SNDRV_PCM_FMTBIT_S32_LE) ++ ++static const struct snd_soc_dai_ops rt766_sdca_ops = { ++ .hw_params = rt766_sdca_pcm_hw_params, ++ .hw_free = rt766_sdca_pcm_hw_free, ++ .set_stream = rt766_sdca_set_sdw_stream, ++ .shutdown = rt766_sdca_shutdown, ++}; ++ ++static struct snd_soc_dai_driver rt766_sdca_dai[] = { ++ { ++ .name = "rt766-sdca-aif1", ++ .id = RT766_AIF1, ++ .playback = { ++ .stream_name = "DP3 Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .capture = { ++ .stream_name = "DP12 Capture", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ .symmetric_rate = 1, ++ }, ++ { ++ .name = "rt766-sdca-aif2", ++ .id = RT766_AIF2, ++ .playback = { ++ .stream_name = "DP1 Playback", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_DAC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ }, ++ { ++ .name = "rt766-sdca-aif3", ++ .id = RT766_AIF3, ++ .capture = { ++ .stream_name = "DP8 Capture", ++ .channels_min = 1, ++ .channels_max = 4, ++ .rates = RT766_STEREO_RATES, ++ .formats = RT766_ADC_FORMATS, ++ }, ++ .ops = &rt766_sdca_ops, ++ } ++}; ++ ++static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function, ++ const char *label) ++{ ++ struct snd_soc_pcm_stream stream; ++ struct sdca_entity *entity; ++ int i, ret; ++ ++ for (i = 0; i < function->num_entities; i++) { ++ entity = &function->entities[i]; ++ ++ if (strcmp(entity->label, label)) ++ continue; ++ ++ /* Can't check earlier as only terminals have an iot member. */ ++ if (!entity->iot.is_dataport) ++ continue; ++ ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, &stream); ++ if (ret < 0) { ++ dev_dbg(dev, "%s: failed to parse rates for entity %s\n", ++ __func__, entity->label); ++ return 0; ++ } ++ ++ dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, stream.rates); ++ } ++ ++ return stream.rates; ++} ++ ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave) ++{ ++ struct sdca_function_data *func_data_ptr; ++ struct snd_soc_dai_driver *dai_drv; ++ struct rt766_sdca_priv *rt766; ++ unsigned int rates; ++ int ret; ++ int i; ++ ++ rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL); ++ if (!rt766) ++ return -ENOMEM; ++ ++ dev_set_drvdata(dev, rt766); ++ rt766->slave = slave; ++ rt766->regmap = regmap; ++ ++ regcache_cache_only(rt766->regmap, true); ++ ++ ret = devm_mutex_init(dev, &rt766->disable_irq_lock); ++ if (ret < 0) { ++ dev_err(dev, "Failed to initialize mutex\n"); ++ return ret; ++ } ++ ++ /* ++ * Mark hw_init to false ++ * HW init will be performed when device reports present ++ */ ++ rt766->hw_init = false; ++ rt766->first_hw_init = false; ++ rt766->fu41_dapm_mute = true; ++ rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false; ++ rt766->fu36_dapm_mute = true; ++ rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true; ++ rt766->fu21_dapm_mute = true; ++ rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false; ++ rt766->fu113_dapm_mute = true; ++ rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] = ++ rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true; ++ ++ dai_drv = devm_kzalloc(dev, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai), GFP_KERNEL); ++ if (!dai_drv) { ++ dev_err(dev, "Failed to allocate memory for DAI driver\n"); ++ ret = -ENOMEM; ++ goto _sdw_init_err_; ++ } ++ memcpy(dai_drv, rt766_sdca_dai, sizeof(struct snd_soc_dai_driver) * ARRAY_SIZE(rt766_sdca_dai)); ++ ++ /* get SDCA function data */ ++ dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions); ++ for (i = 0; i < slave->sdca_data.num_functions; i++) { ++ func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL); ++ if (!func_data_ptr) { ++ dev_err(dev, "Failed to allocate memory for function data\n"); ++ ret = -ENOMEM; ++ goto _free_dai_drv_; ++ } ++ ++ func_data_ptr->desc = &slave->sdca_data.function[i]; ++ ret = sdca_parse_function(dev, func_data_ptr); ++ if (ret) { ++ devm_kfree(dev, func_data_ptr); ++ goto _free_dai_drv_; ++ } ++ dev_dbg(dev, "Function type=%d, num_entities=%d", ++ slave->sdca_data.function[i].type, func_data_ptr->num_entities); ++ ++ switch (slave->sdca_data.function[i].type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ rt766->uaj_func_data = func_data_ptr; ++ /* ++ * Some machines may only support a subset of the sample rates supported by the codec. ++ * Therefore, we need to parse the supported sample rates from the DisCo table and ++ * configure them in the DAI. If the DisCo table does not provide sample rate information, ++ * we will fall back to the default supported rates defined in the codec driver. ++ */ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].playback.rates = rates; ++ ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36"); ++ if (rates) ++ dai_drv[RT766_DAI_UAJ].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ rt766->sa_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21"); ++ if (rates) ++ dai_drv[RT766_DAI_AMP].playback.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ rt766->sm_func_data = func_data_ptr; ++ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113"); ++ if (rates) ++ dai_drv[RT766_DAI_MIC].capture.rates = rates; ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ rt766->hid_func_data = func_data_ptr; ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ slave->sdca_data.function[i].type); ++ } ++ } ++ ++ ret = devm_snd_soc_register_component(dev, ++ &soc_sdca_dev_rt766, dai_drv, ARRAY_SIZE(rt766_sdca_dai)); ++ if (ret < 0) ++ goto _free_dai_drv_; ++ ++ /* set autosuspend parameters */ ++ pm_runtime_set_autosuspend_delay(dev, 3000); ++ pm_runtime_use_autosuspend(dev); ++ ++ /* make sure the device does not suspend immediately */ ++ pm_runtime_mark_last_busy(dev); ++ ++ pm_runtime_enable(dev); ++ dev_dbg(dev, "%s\n", __func__); ++ return 0; ++ ++_free_dai_drv_: ++ if (dai_drv) ++ devm_kfree(dev, dai_drv); ++ ++_sdw_init_err_: ++ return ret; ++} ++ ++static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_function_data *func_data) ++{ ++ struct device *dev = &rt766->slave->dev; ++ unsigned int func_status_reg; ++ unsigned int func_status; ++ int ret; ++ ++ switch (func_data->desc->type) { ++ case SDCA_FUNCTION_TYPE_UAJ: ++ func_status_reg = RT766_FUNC_STATUS_REG(UAJ); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_AMP: ++ func_status_reg = RT766_FUNC_STATUS_REG(AMP); ++ break; ++ case SDCA_FUNCTION_TYPE_SMART_MIC: ++ func_status_reg = RT766_FUNC_STATUS_REG(MIC); ++ break; ++ case SDCA_FUNCTION_TYPE_HID: ++ func_status_reg = RT766_FUNC_STATUS_REG(HID); ++ break; ++ default: ++ dev_dbg(dev, "Unexpected SDCA function type found: %d", ++ func_data->desc->type); ++ return -EINVAL; ++ } ++ ++ regmap_read(rt766->regmap, func_status_reg, &func_status); ++ dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); ++ ++ if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { ++ ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); ++ if (ret) { ++ dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); ++ goto _func_init_err_; ++ } ++ ++ regmap_write(rt766->regmap, func_status_reg, ++ SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION); ++ } ++ ++ return 0; ++ ++_func_init_err_: ++ dev_err(dev, "%s: %s init writes failed, err=%d", __func__, func_data->desc->name, ret); ++ return ret; ++} ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave) ++{ ++ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev); ++ unsigned int val; ++ ++ rt766->disable_irq = false; ++ ++ if (rt766->hw_init) ++ return 0; ++ ++ regcache_cache_only(rt766->regmap, false); ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, true); ++ } else { ++ /* ++ * PM runtime status is marked as 'active' only when a Slave reports as Attached ++ */ ++ ++ /* update count of parent 'active' children */ ++ pm_runtime_set_active(&slave->dev); ++ } ++ ++ pm_runtime_get_noresume(&slave->dev); ++ ++ regmap_read(rt766->regmap, RT766_BOND_LATCH_ID, &val); ++ dev_dbg(&slave->dev, "%s bond ID=0x%x (%s)\n", __func__, val, (val == 0x1) ? "RT767" : "RT766"); ++ ++ /* check function status and initialize if needed */ ++ if (rt766->uaj_func_data) ++ rt766_func_initialize(rt766, rt766->uaj_func_data); ++ if (rt766->sa_func_data) ++ rt766_func_initialize(rt766, rt766->sa_func_data); ++ if (rt766->sm_func_data) ++ rt766_func_initialize(rt766, rt766->sm_func_data); ++ if (rt766->hid_func_data) ++ rt766_func_initialize(rt766, rt766->hid_func_data); ++ ++ if (rt766->first_hw_init) { ++ regcache_cache_bypass(rt766->regmap, false); ++ regcache_mark_dirty(rt766->regmap); ++ } else { ++ rt766->first_hw_init = true; ++ } ++ ++ /* Mark Slave initialization complete */ ++ rt766->hw_init = true; ++ ++ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); ++ ++ pm_runtime_put_autosuspend(&slave->dev); ++ ++ return 0; ++} ++ ++MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver"); ++MODULE_AUTHOR("Shuming Fan "); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/rt766-sdca.h +@@ -0,0 +1,140 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * rt766-sdca.h -- RT766 SDCA ALSA SoC audio driver header ++ * ++ * Copyright(c) 2026 Realtek Semiconductor Corp. ++ */ ++ ++#ifndef __RT766_H__ ++#define __RT766_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct rt766_sdca_priv { ++ struct regmap *regmap; ++ struct snd_soc_component *component; ++ struct sdw_slave *slave; ++ bool hw_init; ++ bool first_hw_init; ++ struct snd_soc_jack *hs_jack; ++ struct mutex disable_irq_lock; /* SDCA irq lock protection */ ++ bool disable_irq; ++ int jack_type; ++ bool fu41_dapm_mute; ++ bool fu41_mixer_l_mute; ++ bool fu41_mixer_r_mute; ++ bool fu113_dapm_mute; ++ bool fu113_mixer_mute[4]; ++ bool fu21_dapm_mute; ++ bool fu21_mixer_l_mute; ++ bool fu21_mixer_r_mute; ++ bool fu36_dapm_mute; ++ bool fu36_mixer_l_mute; ++ bool fu36_mixer_r_mute; ++ struct sdca_function_data *uaj_func_data; ++ struct sdca_function_data *sm_func_data; ++ struct sdca_function_data *sa_func_data; ++ struct sdca_function_data *hid_func_data; ++ struct sdca_interrupt_info *irq_info; ++ struct hid_device *hid; ++}; ++ ++/* vendor registers */ ++#define RT766_VERSION_ID 0xc404 ++#define RT766_DEV_ID1 0xc405 ++#define RT766_DEV_ID0 0xc406 ++#define RT766_BOND_LATCH_ID 0xc407 ++ ++#define RT766_HP_POWER_STATE 0x1000004 ++#define RT766_HP_FSM_CTL2_1 0x100000d ++ ++/* MCU Patch address */ ++#define RT766_MCU_PATCH_ADDR1_START 0x10010000 ++#define RT766_MCU_PATCH_ADDR1_END 0x10011fff ++#define RT766_MCU_PATCH_ADDR2_START 0x10020000 ++#define RT766_MCU_PATCH_ADDR2_END 0x10023fff ++ ++/* Buffer address for HID */ ++#define RT766_BUF_ADDR_HID1 0x44030000 ++#define RT766_BUF_ADDR_HID2 0x44030020 ++ ++/* SDCA (Channel) */ ++#define RT766_CH_1 0x01 ++#define RT766_CH_2 0x02 ++#define RT766_CH_3 0x03 ++#define RT766_CH_4 0x04 ++ ++/* RT766 SDCA Control - function number */ ++#define RT766_FUNC_NUM_UAJ 0x01 ++#define RT766_FUNC_NUM_MIC 0x02 ++#define RT766_FUNC_NUM_HID 0x03 ++#define RT766_FUNC_NUM_AMP 0x04 ++ ++/* RT766 SDCA entity */ ++#define RT766_SDCA_ENT_0 0x00 ++#define RT766_SDCA_ENT_HID101 0x01 ++#define RT766_SDCA_ENT_GE49 0x49 ++#define RT766_SDCA_ENT_USER_FU41 0x05 ++#define RT766_SDCA_ENT_USER_FU36 0x0f ++#define RT766_SDCA_ENT_USER_FU21 0x03 ++#define RT766_SDCA_ENT_USER_FU113 0x30 ++#define RT766_SDCA_ENT_PDE23 0x33 ++#define RT766_SDCA_ENT_PDE47 0x28 ++#define RT766_SDCA_ENT_PDE11 0x2a ++#define RT766_SDCA_ENT_PDE34 0x29 ++#define RT766_SDCA_ENT_CS41 0x01 ++#define RT766_SDCA_ENT_CS36 0x11 ++#define RT766_SDCA_ENT_CS113 0x12 ++#define RT766_SDCA_ENT_CS21 0x21 ++#define RT766_SDCA_ENT_PLATFORM_FU33 0x44 ++#define RT766_SDCA_ENT_PPU21 0x04 ++ ++/* sample frequency index */ ++#define RT766_SDCA_RATE_44100HZ 0x08 ++#define RT766_SDCA_RATE_48000HZ 0x09 ++#define RT766_SDCA_RATE_96000HZ 0x0b ++#define RT766_SDCA_RATE_192000HZ 0x0d ++ ++/* SDCA Register macros */ ++#define RT766_MUTE_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_MUTE, RT766_CH_##ch) ++ ++#define RT766_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_CHANNEL_VOLUME, RT766_CH_##ch) ++ ++#define RT766_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_GAIN, RT766_CH_##ch) ++ ++#define RT766_PDE_REQ_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_REQUESTED_PS, 0) ++ ++#define RT766_PDE_ACTUAL_REG(func, pde) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_ACTUAL_PS, 0) ++ ++#define RT766_FUNC_STATUS_REG(func) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0) ++ ++#define RT766_SDCA_CTL(func, ent, ctl) \ ++ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##ent, ctl, 0) ++ ++enum { ++ RT766_AIF1, ++ RT766_AIF2, ++ RT766_AIF3, ++}; ++ ++enum { ++ RT766_DAI_UAJ, ++ RT766_DAI_AMP, ++ RT766_DAI_MIC, ++}; ++ ++int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave); ++int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave); ++#endif /* __RT766_H__ */ +diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c +--- a/sound/soc/codecs/rt9120.c ++++ b/sound/soc/codecs/rt9120.c +@@ -606,12 +606,19 @@ static int rt9120_runtime_suspend(struct device *dev) + static int rt9120_runtime_resume(struct device *dev) + { + struct rt9120_data *data = dev_get_drvdata(dev); ++ int ret; + + if (data->pwdnn_gpio) { + gpiod_set_value(data->pwdnn_gpio, 1); + msleep(RT9120_CHIPON_WAITMS); + regcache_cache_only(data->regmap, false); +- regcache_sync(data->regmap); ++ ret = regcache_sync(data->regmap); ++ if (ret) { ++ regcache_cache_only(data->regmap, true); ++ regcache_mark_dirty(data->regmap); ++ gpiod_set_value(data->pwdnn_gpio, 0); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/sigmadsp.c b/sound/soc/codecs/sigmadsp.c +--- a/sound/soc/codecs/sigmadsp.c ++++ b/sound/soc/codecs/sigmadsp.c +@@ -5,6 +5,7 @@ + * Copyright 2009-2014 Analog Devices Inc. + */ + ++#include + #include + #include + #include +@@ -135,7 +136,7 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + uint8_t *data; + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + data = ucontrol->value.bytes.data; + +@@ -148,8 +149,6 @@ static int sigmadsp_ctrl_put(struct snd_kcontrol *kcontrol, + ctrl->cached = true; + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -160,7 +159,7 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + struct sigmadsp *sigmadsp = snd_kcontrol_chip(kcontrol); + int ret = 0; + +- mutex_lock(&sigmadsp->lock); ++ guard(mutex)(&sigmadsp->lock); + + if (!ctrl->cached) { + ret = sigmadsp_read(sigmadsp, ctrl->addr, ctrl->cache, +@@ -174,8 +173,6 @@ static int sigmadsp_ctrl_get(struct snd_kcontrol *kcontrol, + ctrl->num_bytes); + } + +- mutex_unlock(&sigmadsp->lock); +- + return ret; + } + +@@ -487,7 +484,7 @@ static void devm_sigmadsp_release(struct device *dev, void *res) + static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + { + const struct sigma_firmware_header *ssfw_head; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + u32 crc; + +@@ -495,7 +492,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + ret = request_firmware(&fw, name, sigmadsp->dev); + if (ret) { + pr_debug("%s: request_firmware() failed with %i\n", __func__, ret); +- goto done; ++ return ret; + } + + /* then verify the header */ +@@ -509,13 +506,13 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + */ + if (fw->size < sizeof(*ssfw_head) || fw->size >= 0x4000000) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid size\n"); +- goto done; ++ return -EINVAL; + } + + ssfw_head = (void *)fw->data; + if (memcmp(ssfw_head->magic, SIGMA_MAGIC, ARRAY_SIZE(ssfw_head->magic))) { + dev_err(sigmadsp->dev, "Failed to load firmware: Invalid magic\n"); +- goto done; ++ return -EINVAL; + } + + crc = crc32(0, fw->data + sizeof(*ssfw_head), +@@ -524,7 +521,7 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (crc != le32_to_cpu(ssfw_head->crc)) { + dev_err(sigmadsp->dev, "Failed to load firmware: Wrong crc checksum: expected %x got %x\n", + le32_to_cpu(ssfw_head->crc), crc); +- goto done; ++ return -EINVAL; + } + + switch (ssfw_head->version) { +@@ -545,9 +542,6 @@ static int sigmadsp_firmware_load(struct sigmadsp *sigmadsp, const char *name) + if (ret) + sigmadsp_firmware_release(sigmadsp); + +-done: +- release_firmware(fw); +- + return ret; + } + +@@ -677,10 +671,10 @@ static void sigmadsp_activate_ctrl(struct sigmadsp *sigmadsp, + return; + changed = snd_ctl_activate_id(card, &ctrl->kcontrol->id, active); + if (active && changed > 0) { +- mutex_lock(&sigmadsp->lock); +- if (ctrl->cached) +- sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); +- mutex_unlock(&sigmadsp->lock); ++ scoped_guard(mutex, &sigmadsp->lock) { ++ if (ctrl->cached) ++ sigmadsp_ctrl_write(sigmadsp, ctrl, ctrl->cache); ++ } + } + } + +diff --git a/sound/soc/codecs/sma1303.c b/sound/soc/codecs/sma1303.c +--- a/sound/soc/codecs/sma1303.c ++++ b/sound/soc/codecs/sma1303.c +@@ -1777,6 +1777,8 @@ static void sma1303_i2c_remove(struct i2c_client *client) + struct sma1303_priv *sma1303 = + (struct sma1303_priv *) i2c_get_clientdata(client); + ++ if (sma1303->attr_grp) ++ sysfs_remove_group(sma1303->kobj, sma1303->attr_grp); + cancel_delayed_work_sync(&sma1303->check_fault_work); + } + +diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c +--- a/sound/soc/codecs/sma1307.c ++++ b/sound/soc/codecs/sma1307.c +@@ -1690,7 +1690,7 @@ static void sma1307_check_fault_worker(struct work_struct *work) + + static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *file) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int size, offset, num_mode; + int ret; + +@@ -1703,22 +1703,18 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil + return; + } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { + dev_err(sma1307->dev, "%s: Invalid file\n", __func__); +- release_firmware(fw); + sma1307->set.status = false; + return; + } + + int *data __free(kfree) = kzalloc(fw->size, GFP_KERNEL); + if (!data) { +- release_firmware(fw); + sma1307->set.status = false; + return; + } + size = fw->size >> 2; + memcpy(data, fw->data, fw->size); + +- release_firmware(fw); +- + /* HEADER */ + sma1307->set.header_size = SMA1307_SETTING_HEADER_SIZE; + sma1307->set.checksum = data[sma1307->set.header_size - 2]; +diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c +--- a/sound/soc/codecs/sta32x.c ++++ b/sound/soc/codecs/sta32x.c +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -269,9 +270,9 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta32x->coeff_lock); ++ guard(mutex)(&sta32x->coeff_lock); + + /* preserve reserved bits in STA32X_CFUD */ + regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); +@@ -283,24 +284,20 @@ static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + + regmap_write(sta32x->regmap, STA32X_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; ++ + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta32x->regmap, STA32X_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta32x->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c +--- a/sound/soc/codecs/sta350.c ++++ b/sound/soc/codecs/sta350.c +@@ -16,6 +16,7 @@ + + #define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + ++#include + #include + #include + #include +@@ -306,9 +307,9 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud, val; +- int i, ret = 0; ++ int i; + +- mutex_lock(&sta350->coeff_lock); ++ guard(mutex)(&sta350->coeff_lock); + + /* preserve reserved bits in STA350_CFUD */ + regmap_read(sta350->regmap, STA350_CFUD, &cfud); +@@ -320,24 +321,19 @@ static int sta350_coefficient_get(struct snd_kcontrol *kcontrol, + regmap_write(sta350->regmap, STA350_CFUD, cfud); + + regmap_write(sta350->regmap, STA350_CFADDR2, index); +- if (numcoef == 1) { ++ if (numcoef == 1) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x04); +- } else if (numcoef == 5) { ++ else if (numcoef == 5) + regmap_write(sta350->regmap, STA350_CFUD, cfud | 0x08); +- } else { +- ret = -EINVAL; +- goto exit_unlock; +- } ++ else ++ return -EINVAL; + + for (i = 0; i < 3 * numcoef; i++) { + regmap_read(sta350->regmap, STA350_B1CF1 + i, &val); + ucontrol->value.bytes.data[i] = val; + } + +-exit_unlock: +- mutex_unlock(&sta350->coeff_lock); +- +- return ret; ++ return 0; + } + + static int sta350_coefficient_put(struct snd_kcontrol *kcontrol, +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -88,6 +88,9 @@ + #define TAC_FW_FILE_HDR 20 + #define TAC_MAX_FW_CHUNKS 512 + ++#define TAC_UAJ_PREP_CONNECTED 0xff ++#define TAC_UAJ_PREP_DISCONNECTED 0xdf ++ + struct tac_fw_hdr { + u32 size; + u32 version_offset; +@@ -138,6 +141,7 @@ struct tac5xx2_prv { + bool hw_init; + bool first_hw_init_done; + u32 part_id; ++ u32 rev_id; + struct snd_soc_jack *hs_jack; + int jack_type; + /* Custom fw binary. UMP File Download is not used. */ +@@ -261,7 +265,6 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 0, 0x78), 0x0}, + {TAC_REG_SDW(0, 0, 0x7b), 0x0}, + {TAC_REG_SDW(0, 0, 0x7c), 0xd0}, +- {TAC_REG_SDW(0, 0, 0x7d), 0x0}, + {TAC_REG_SDW(0, 0, 0x7e), 0x0}, + {TAC_REG_SDW(0, 1, 0x1), 0x0}, + {TAC_REG_SDW(0, 1, 0x2), 0x0}, +@@ -507,18 +510,6 @@ static const struct regmap_config tac_regmap = { + .use_single_write = true, + }; + +-/* Check if device has DSP algo that needs status monitoring */ +-static bool tac_has_dsp_algo(struct tac5xx2_prv *tac_dev) +-{ +- switch (tac_dev->part_id) { +- case 0x5682: +- case 0x2883: +- return true; +- default: +- return false; +- } +-} +- + /* Check if device has UAJ (Universal Audio Jack) support */ + static bool tac_has_uaj_support(struct tac5xx2_prv *tac_dev) + { +@@ -800,7 +791,7 @@ static int tac_sdw_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, pde_entity, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, pde_entity, + SDCA_PDE_PS3, SDCA_PDE_PS0, NULL, 0); + if (ret) + dev_err(tac_dev->dev, "failed to transition func %d, pde %d from PS3 -> PS0, err=%d\n", +@@ -847,7 +838,7 @@ static int tac_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + return ret; + } + +- ret = sdca_asoc_pde_poll_actual_ps(tac_dev->dev, tac_dev->regmap, function_id, ++ ret = sdca_asoc_pde_poll_actual_ps(tac_dev->regmap, function_id, + pde_entity, SDCA_PDE_PS0, SDCA_PDE_PS3, + NULL, 0); + if (ret) +@@ -942,6 +933,7 @@ static int tac5xx2_sdca_button_detect(struct tac5xx2_prv *tac_dev) + static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + { + int val, ret; ++ u8 jack_prep; + + ret = regmap_read(tac_dev->regmap, + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_GE35, +@@ -951,6 +943,8 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + return ret; + } + ++ jack_prep = TAC_UAJ_PREP_CONNECTED; ++ + switch (val) { + case 4: + tac_dev->jack_type = SND_JACK_MICROPHONE; +@@ -964,6 +958,7 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + case 0: + default: + tac_dev->jack_type = 0; ++ jack_prep = TAC_UAJ_PREP_DISCONNECTED; + break; + } + +@@ -973,26 +968,44 @@ static int tac5xx2_sdca_headset_detect(struct tac5xx2_prv *tac_dev) + if (ret) + dev_err(tac_dev->dev, "Failed to update the jack type to device"); + ++ /* ++ * When uaj is uplugged and booted, we end up with the channel prepare ++ * timeout error for the uaj ports. This writes allows the channel prepare ++ * to succeed when the uaj is not plugged in. ++ */ ++ if (tac_dev->rev_id >= 0x30) { ++ ret = regmap_write(tac_dev->regmap, TAC_REG_SDW(0, 3, 127), jack_prep); ++ if (ret) ++ dev_warn(tac_dev->dev, "Failed to write jack_prep register: %d\n", ret); ++ } ++ + return 0; + } + + static int tac5xx2_jack_init(struct tac5xx2_prv *tac_dev) + { ++ u32 jd_int_mask, hid_int_mask; + int ret = 0; + ++ if (tac_dev->rev_id >= 0x30) { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_12; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_17; ++ } else { ++ jd_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_11; ++ hid_int_mask = SDW_SCP_SDCA_INTMASK_SDCA_16; ++ } ++ + if (!tac_dev->hs_jack) + goto disable_interrupts; + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, +- SDW_SCP_SDCA_INTMASK_SDCA_11); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK2, jd_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register jack detection interrupt: %d\n", ret); + goto disable_interrupts; + } + +- ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, +- SDW_SCP_SDCA_INTMASK_SDCA_16); ++ ret = regmap_write(tac_dev->regmap, SDW_SCP_SDCA_INTMASK3, hid_int_mask); + if (ret) { + dev_err(tac_dev->dev, + "Failed to register for button detect interrupt: %d\n", ret); +@@ -1051,6 +1064,7 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + unsigned int sdca_int2, sdca_int3, jack_report_mask = 0; + struct tac5xx2_prv *tac_dev = dev_get_drvdata(&slave->dev); + struct device *dev = &slave->dev; ++ u32 headset_detect_chk, hid_detect_chk; + int btn_type = 0; + int ret = 0; + +@@ -1081,14 +1095,22 @@ static int tac_interrupt_callback(struct sdw_slave *slave, + dev_dbg(dev, "SDCA_INT2: 0x%02x, SDCA_INT3: 0x%02x\n", + sdca_int2, sdca_int3); + +- if (sdca_int2 & SDW_SCP_SDCA_INT_SDCA_11) { ++ if (tac_dev->rev_id >= 0x30) { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_12; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_17; ++ } else { ++ headset_detect_chk = SDW_SCP_SDCA_INT_SDCA_11; ++ hid_detect_chk = SDW_SCP_SDCA_INT_SDCA_16; ++ } ++ ++ if (sdca_int2 & headset_detect_chk) { + ret = tac5xx2_sdca_headset_detect(tac_dev); + if (ret < 0) + goto clear; + jack_report_mask |= SND_JACK_HEADSET; + } + +- if (sdca_int3 & SDW_SCP_SDCA_INT_SDCA_16) { ++ if (sdca_int3 & hid_detect_chk) { + btn_type = tac5xx2_sdca_button_detect(tac_dev); + if (btn_type < 0) + btn_type = 0; +@@ -1695,6 +1717,15 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + ++ if (!tac_dev->rev_id) { ++ ret = regmap_read(tac_dev->regmap, TAC_REV_ID, &tac_dev->rev_id); ++ if (ret) { ++ dev_err(tac_dev->dev, "failed to rev id, err=%d\n", ret); ++ goto io_init_err; ++ } ++ dev_dbg(tac_dev->dev, "detected rev_id 0x%x", tac_dev->rev_id); ++ } ++ + if (tac_dev->fw_files && tac_dev->fw_file_cnt > 0) { + ret = tac_download_fw_to_hw(tac_dev); + if (ret) { +@@ -1940,7 +1971,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + "failed to allocate %s function data", + func_name); + function_data->desc = &peripheral->sdca_data.function[i]; +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + *func_ptr = function_data; + else +@@ -1959,6 +1990,7 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->hw_init = false; + tac_dev->first_hw_init_done = false; + tac_dev->part_id = id->part_id; ++ tac_dev->rev_id = 0x0; + dev_set_drvdata(dev, tac_dev); + + regmap = devm_regmap_init_sdw_mbq_cfg(&peripheral->dev, peripheral, +@@ -1972,17 +2004,13 @@ static s32 tac_sdw_probe(struct sdw_slave *peripheral, + tac_dev->jack_type = 0; + init_completion(&tac_dev->fw_caching_complete); + +- if (tac_has_dsp_algo(tac_dev)) { +- tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, +- sizeof(tac_dev->fw_binaryname)); ++ tac_generate_fw_name(peripheral, tac_dev->fw_binaryname, ++ sizeof(tac_dev->fw_binaryname)); + +- ret = tac_load_and_cache_firmware_async(tac_dev); +- if (ret) { +- complete_all(&tac_dev->fw_caching_complete); +- dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); +- } +- } else { ++ ret = tac_load_and_cache_firmware_async(tac_dev); ++ if (ret) { + complete_all(&tac_dev->fw_caching_complete); ++ dev_dbg(dev, "failed to load fw: %d, use rom mode\n", ret); + } + + ret = tac_init(tac_dev); +diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c +--- a/sound/soc/codecs/tas2552.c ++++ b/sound/soc/codecs/tas2552.c +@@ -495,13 +495,21 @@ static int tas2552_runtime_suspend(struct device *dev) + static int tas2552_runtime_resume(struct device *dev) + { + struct tas2552_data *tas2552 = dev_get_drvdata(dev); ++ int ret; + + gpiod_set_value_cansleep(tas2552->enable_gpio, 1); + + tas2552_sw_shutdown(tas2552, 0); + + regcache_cache_only(tas2552->regmap, false); +- regcache_sync(tas2552->regmap); ++ ret = regcache_sync(tas2552->regmap); ++ if (ret) { ++ regcache_cache_only(tas2552->regmap, true); ++ regcache_mark_dirty(tas2552->regmap); ++ tas2552_sw_shutdown(tas2552, 1); ++ gpiod_set_value_cansleep(tas2552->enable_gpio, 0); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c +--- a/sound/soc/codecs/tas2781-comlib-i2c.c ++++ b/sound/soc/codecs/tas2781-comlib-i2c.c +@@ -6,6 +6,7 @@ + // + // Author: Shenghao Ding + ++#include + #include + #include + #include +@@ -342,7 +343,7 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + /* Codec Lock Hold to ensure that codec_probe and firmware parsing and + * loading do not simultaneously execute. + */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + + if (tas_priv->name_prefix) + scnprintf(tas_priv->rca_binaryname, 64, "%s-%sRCA%d.bin", +@@ -360,8 +361,6 @@ int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n", + ret); + +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); + return ret; + } + EXPORT_SYMBOL_GPL(tascodec_init); +diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c +--- a/sound/soc/codecs/tas2781-fmwlib.c ++++ b/sound/soc/codecs/tas2781-fmwlib.c +@@ -2253,7 +2253,7 @@ int tas2781_load_calibration(void *context, char *file_name, + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); +- const struct firmware *fw_entry = NULL; ++ const struct firmware *fw_entry __free(firmware) = NULL; + struct tasdevice_fw *tas_fmw; + struct firmware fmw; + int offset = 0; +@@ -2263,60 +2263,50 @@ int tas2781_load_calibration(void *context, char *file_name, + if (ret) { + dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", + __func__, file_name); +- goto out; ++ return ret; + } + + if (!fw_entry->size) { + dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", + __func__, (unsigned long)fw_entry->size); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + fmw.size = fw_entry->size; + fmw.data = fw_entry->data; + + tas_fmw = tasdev->cali_data_fmw = kzalloc_obj(struct tasdevice_fw); +- if (!tasdev->cali_data_fmw) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!tasdev->cali_data_fmw) ++ return -ENOMEM; ++ + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, "fw_parse_header EXIT!\n"); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_variable_hdr_cal(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, + "%s: fw_parse_variable_header_cal EXIT!\n", __func__); +- ret = offset; +- goto out; ++ return -EINVAL; + } + offset = fw_parse_program_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_program_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_configuration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_configuration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + offset = fw_parse_calibration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_calibration_data EXIT!\n"); +- ret = offset; +- goto out; ++ return offset; + } + +-out: +- release_firmware(fw_entry); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2409,7 +2399,7 @@ static int tasdevice_dspfw_ready(const struct firmware *fmw, + int tasdevice_dsp_parser(void *context) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; +- const struct firmware *fw_entry; ++ const struct firmware *fw_entry __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, +@@ -2417,15 +2407,10 @@ int tasdevice_dsp_parser(void *context) + if (ret) { + dev_err(tas_priv->dev, "%s: load %s error\n", __func__, + tas_priv->coef_binaryname); +- goto out; ++ return ret; + } + +- ret = tasdevice_dspfw_ready(fw_entry, tas_priv); +- release_firmware(fw_entry); +- fw_entry = NULL; +- +-out: +- return ret; ++ return tasdevice_dspfw_ready(fw_entry, tas_priv); + } + EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, "SND_SOC_TAS2781_FMWLIB"); + +@@ -2807,11 +2792,12 @@ int tasdevice_prmg_load(void *context, int prm_no) + } + EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_load, "SND_SOC_TAS2781_FMWLIB"); + +-void tasdevice_tuning_switch(void *context, int state) ++void tasdevice_tuning_switch(void *context, int state, bool is_cap) + { + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; +- int profile_cfg_id = tas_priv->rcabin.profile_cfg_id; ++ int profile_cfg_id = is_cap ? tas_priv->rcabin.capture_profile_id : ++ tas_priv->rcabin.profile_cfg_id; + + /* + * Only RCA-based Playback can still work with no dsp program running +@@ -2828,7 +2814,6 @@ void tasdevice_tuning_switch(void *context, int state) + if (state == 0) { + if (tas_fmw && tas_priv->cur_prog < tas_fmw->nr_programs) { + /* dsp mode or tuning mode */ +- profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + tasdevice_select_tuningprm_cfg(tas_priv, + tas_priv->cur_prog, tas_priv->cur_conf, + profile_cfg_id); +diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c +--- a/sound/soc/codecs/tas2781-i2c.c ++++ b/sound/soc/codecs/tas2781-i2c.c +@@ -13,6 +13,7 @@ + // Author: Kevin Lu + // + ++#include + #include + #include + #include +@@ -617,6 +618,7 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + { + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); ++ int i; + + guard(mutex)(&priv->codec_lock); + if (priv->chip_id == TAS2563) +@@ -624,6 +626,14 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, + else + tas2781_calib_stop_put(priv); + ++ /* ++ * Set reloading-firmware flag after calibration, the flag will work ++ * during next playback, then set to the program id after reloading ++ * firmware. ++ */ ++ for (i = 0; i < priv->ndev; i++) ++ priv->tasdevice[i].cur_prog = -1; ++ + return 1; + } + +@@ -843,12 +853,12 @@ static int tasdevice_digital_gain_get( + unsigned char data[4]; + int ret; + +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- goto out; ++ return ret; + } + + target = get_unaligned_be32(&data[0]); +@@ -868,8 +878,7 @@ static int tasdevice_digital_gain_get( + /* find out the member same as or closer to the current volume */ + ucontrol->value.integer.value[0] = + abs(target - ar_l) <= abs(target - ar_r) ? l : r; +-out: +- mutex_unlock(&tas_dev->codec_lock); ++ + return 0; + } + +@@ -882,29 +891,26 @@ static int tasdevice_digital_gain_put( + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_dev = snd_soc_component_get_drvdata(codec); + int vol = ucontrol->value.integer.value[0]; +- int status = 0, max = mc->max, rc = 1; ++ int status = 0, max = mc->max; + int i, ret; + unsigned int reg = mc->reg; + unsigned int volrd, volwr; + unsigned char data[4]; + + vol = clamp(vol, 0, max); +- mutex_lock(&tas_dev->codec_lock); ++ guard(mutex)(&tas_dev->codec_lock); + /* Read the primary device */ + ret = tasdevice_dev_bulk_read(tas_dev, 0, reg, data, 4); + if (ret) { + dev_err(tas_dev->dev, "%s, get AMP vol error\n", __func__); +- rc = -1; +- goto out; ++ return -1; + } + + volrd = get_unaligned_be32(&data[0]); + volwr = get_unaligned_be32(tas_dev->dvc_tlv_table[vol]); + +- if (volrd == volwr) { +- rc = 0; +- goto out; +- } ++ if (volrd == volwr) ++ return 0; + + for (i = 0; i < tas_dev->ndev; i++) { + ret = tasdevice_dev_bulk_write(tas_dev, i, reg, +@@ -918,10 +924,9 @@ static int tasdevice_digital_gain_put( + } + + if (status) +- rc = -1; +-out: +- mutex_unlock(&tas_dev->codec_lock); +- return rc; ++ return -1; ++ ++ return 1; + } + + static const struct snd_kcontrol_new tasdevice_cali_controls[] = { +@@ -995,6 +1000,57 @@ static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + return ret; + } + ++/** ++ * tasdevice_set_capture_profile_id - Set runtime capture profile index via ++ * ALSA control ++ * @kcontrol: ALSA kcontrol handle that triggers this operation ++ * @ucontrol: User space control value carrying the new profile index ++ * ++ * This mixer control handler validates the user-provided capture profile ID ++ * against the maximum valid index parsed from the loaded DSP firmware, ++ * then updates the runtime stored capture profile ID only if the new value ++ * differs from the current active one. It will immediately return -EINVAL ++ * if the submitted profile ID falls outside the valid range, including the ++ * edge case that no valid configuration blocks are detected in firmware. ++ * ++ * No actual DSP register write is performed in this handler. The updated ++ * profile ID will be applied to the hardware when the next ALSA capture ++ * stream starts up. Caller does not need to take extra codec lock here, ++ * as the ALSA control core already guarantees serialized execution. ++ * ++ * Return: 1 if profile ID value was changed, 0 if no modification needed, ++ * -EINVAL if the input profile ID is out of valid range ++ */ ++static int tasdevice_set_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int user_prof_id = ucontrol->value.integer.value[0]; ++ unsigned int max_valid_id; ++ int ret = 0; ++ ++ /* ++ * Align valid range with the bound defined in ++ * tasdevice_info_profile() ++ */ ++ max_valid_id = tas_priv->rcabin.ncfgs - 1; ++ ++ /* ++ * Reject invalid input including zero total configuration edge ++ * case ++ */ ++ if (tas_priv->rcabin.ncfgs == 0 || user_prof_id > max_valid_id) ++ return -EINVAL; ++ ++ if (tas_priv->rcabin.capture_profile_id != user_prof_id) { ++ tas_priv->rcabin.capture_profile_id = user_prof_id; ++ ret = 1; ++ } ++ ++ return ret; ++} ++ + static int tasdevice_info_active_num(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) + { +@@ -1075,6 +1131,41 @@ static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + return 0; + } + ++/** ++ * tasdevice_get_capture_profile_id - Report current active capture profile ++ * ID to user space ++ * @kcontrol: ALSA kcontrol structure passed from ALSA core ++ * @ucontrol: User-space control element value buffer to write the result back ++ * ++ * This function ensures the returned profile ID is always clamped inside the ++ * valid range advertised by the info callback, preventing accidental invalid ++ * values from being exposed to applications even if internal driver state is ++ * temporarily inconsistent. ++ * ++ * Returns 0 on successful fill of the control value, no error conditions ++ * are defined for this getter callback. ++ */ ++static int tasdevice_get_capture_profile_id(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ unsigned int max_valid_id, current_prof_id; ++ ++ max_valid_id = tas_priv->rcabin.ncfgs > 0 ? ++ (tas_priv->rcabin.ncfgs - 1U) : 0; ++ ++ /* ++ * Cast current profile id to unsigned to match type with max_valid_id, ++ * avoid signedness mismatch; ++ */ ++ current_prof_id = (unsigned int)tas_priv->rcabin.capture_profile_id; ++ /* Prevent underflow when there are no loaded capture profiles. */ ++ ucontrol->value.integer.value[0] = min(current_prof_id, max_valid_id); ++ ++ return 0; ++} ++ + static int tasdevice_get_chip_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) + { +@@ -1117,6 +1208,41 @@ static int tasdevice_create_control(struct tasdevice_priv *tas_priv) + ret = snd_soc_add_component_controls(tas_priv->codec, + prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); + ++ mix_index = 0; ++ switch (tas_priv->chip_id) { ++ case TAS2563: ++ case TAS2568: ++ case TAS2570: ++ case TAS2572: ++ case TAS2573: ++ case TAS2574: ++ case TAS2781: ++ prof_ctrls = devm_kcalloc(tas_priv->dev, nr_controls, ++ sizeof(prof_ctrls[0]), GFP_KERNEL); ++ if (!prof_ctrls) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* Create a mixer item for selecting the capture profile */ ++ name = devm_kstrdup(tas_priv->dev, "Speaker Capture Profile Id", ++ GFP_KERNEL); ++ if (!name) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ prof_ctrls[mix_index].name = name; ++ prof_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ prof_ctrls[mix_index].info = tasdevice_info_profile; ++ prof_ctrls[mix_index].get = tasdevice_get_capture_profile_id; ++ prof_ctrls[mix_index].put = tasdevice_set_capture_profile_id; ++ mix_index++; ++ ++ ret = snd_soc_add_component_controls(tas_priv->codec, ++ prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); ++ break; ++ } ++ + out: + return ret; + } +@@ -1330,6 +1456,43 @@ static void alpa_cali_update(struct bulk_reg_val *p, + p->val_len = 4; + } + ++static int create_tas2781_cali_start_ktrl(struct tasdevice_priv ++ *priv, struct snd_kcontrol_new *cali_ctrl) ++{ ++ struct soc_bytes_ext *ext_cali_start; ++ char *cali_start_name; ++ ++ ext_cali_start = devm_kzalloc(priv->dev, ++ sizeof(*ext_cali_start), GFP_KERNEL); ++ if (!ext_cali_start) ++ return -ENOMEM; ++ ++ cali_start_name = devm_kstrdup(priv->dev, ++ "Calibration Start", GFP_KERNEL); ++ if (!cali_start_name) ++ return -ENOMEM; ++ /* ++ * package structure for tas2781 ftc start: ++ * Pkg len (1 byte) ++ * Reg id (1 byte, constant 'r') ++ * book, page, register for pilot threshold, pilot tone ++ * and sine gain (12 bytes) ++ * for (i = 0; i < Device-Sum; i++) { ++ * Device #i index_info (1 byte) ++ * Sine gain for Device #i (8 bytes) ++ * } ++ */ ++ ext_cali_start->max = 14 + priv->ndev * 9; ++ cali_ctrl->name = cali_start_name; ++ cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; ++ cali_ctrl->info = snd_soc_bytes_info_ext; ++ cali_ctrl->put = tas2781_calib_start_put; ++ cali_ctrl->get = tasdev_nop_get; ++ cali_ctrl->private_value = (unsigned long)ext_cali_start; ++ ++ return 0; ++} ++ + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + { + struct calidata *cali_data = &priv->cali_data; +@@ -1448,37 +1611,11 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) + */ + cali_data->data[0] = 0xff; + if (priv->chip_id == TAS2781) { +- struct soc_bytes_ext *ext_cali_start; +- char *cali_start_name; +- +- ext_cali_start = devm_kzalloc(priv->dev, +- sizeof(*ext_cali_start), GFP_KERNEL); +- if (!ext_cali_start) +- return -ENOMEM; +- +- cali_start_name = devm_kstrdup(priv->dev, +- "Calibration Start", GFP_KERNEL); +- if (!cali_start_name) +- return -ENOMEM; +- /* +- * package structure for tas2781 ftc start: +- * Pkg len (1 byte) +- * Reg id (1 byte, constant 'r') +- * book, page, register for pilot threshold, pilot tone +- * and sine gain (12 bytes) +- * for (i = 0; i < Device-Sum; i++) { +- * Device #i index_info (1 byte) +- * Sine gain for Device #i (8 bytes) +- * } +- */ +- ext_cali_start->max = 14 + priv->ndev * 9; +- cali_ctrls[i].name = cali_start_name; +- cali_ctrls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; +- cali_ctrls[i].info = snd_soc_bytes_info_ext; +- cali_ctrls[i].put = tas2781_calib_start_put; +- cali_ctrls[i].get = tasdev_nop_get; +- cali_ctrls[i].private_value = (unsigned long)ext_cali_start; ++ rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); ++ if (rc != 0) ++ return rc; + i++; ++ + } + + return snd_soc_add_component_controls(priv->codec, cali_ctrls, +@@ -1765,13 +1902,25 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int state = 0; + +- /* Codec Lock Hold */ +- mutex_lock(&tas_priv->codec_lock); ++ guard(mutex)(&tas_priv->codec_lock); + if (event == SND_SOC_DAPM_PRE_PMD) + state = 1; +- tasdevice_tuning_switch(tas_priv, state); +- /* Codec Lock Release*/ +- mutex_unlock(&tas_priv->codec_lock); ++ tasdevice_tuning_switch(tas_priv, state, false); ++ ++ return 0; ++} ++ ++static int tasdevice_capture_dapm_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *kcontrol, int event) ++{ ++ struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm); ++ struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); ++ int state = 0; ++ ++ guard(mutex)(&tas_priv->codec_lock); ++ if (event == SND_SOC_DAPM_PRE_PMD) ++ state = 1; ++ tasdevice_tuning_switch(tas_priv, state, true); + + return 0; + } +@@ -1779,7 +1928,7 @@ static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT_E("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM, +- 0, 0, tasdevice_dapm_event, ++ 0, 0, tasdevice_capture_dapm_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SPK("SPK", tasdevice_dapm_event), + SND_SOC_DAPM_OUTPUT("OUT"), +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -12,6 +12,7 @@ + // Author: Baojun Xu + // Author: Kevin Lu + ++#include + #include + #include + #include +@@ -494,6 +495,56 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) + return tas2783_sdca_mbq_size(dev, reg) > 0; + } + ++static bool tas2783_writeable_register(struct device *dev, unsigned int reg) ++{ ++ /* ++ * The Latency Control of every Entity, together with the Power Domain ++ * actual state and the protection status, is read-only. They are ++ * listed in tas2783_reg_default[] with a placeholder value, so without ++ * this a regcache_sync() would try to write them back and the ++ * peripheral would reject the transaction, aborting the sync. ++ */ ++ switch (reg) { ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): ++ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): ++ return false; ++ ++ default: ++ return tas2783_sdca_mbq_size(dev, reg) > 0; ++ } ++} ++ + static bool tas2783_volatile_register(struct device *dev, u32 reg) + { + switch (reg) { +@@ -515,6 +566,7 @@ static const struct regmap_config tas_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = tas2783_readable_register, ++ .writeable_reg = tas2783_writeable_register, + .volatile_reg = tas2783_volatile_register, + .reg_defaults = tas2783_reg_default, + .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), +@@ -692,12 +744,12 @@ static s32 tas2783_update_calibdata(struct tas2783_prv *tas_dev) + return 0; + } + +- mutex_lock(&tas_dev->calib_lock); +- ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, +- tas_dev->cali_data.read_sz); +- if (!ret) +- tas2783_set_calib_params_to_device(tas_dev, tmp_val); +- mutex_unlock(&tas_dev->calib_lock); ++ scoped_guard(mutex, &tas_dev->calib_lock) { ++ ret = tas2783_validate_calibdata(tas_dev, tas_dev->cali_data.data, ++ tas_dev->cali_data.read_sz); ++ if (!ret) ++ tas2783_set_calib_params_to_device(tas_dev, tmp_val); ++ } + + return ret; + } +@@ -926,22 +978,23 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + dev_err(tas_dev->dev, + "clear latch failed, err=%d", ret); + +- mutex_lock(&tas_dev->pde_lock); +- /* +- * Sometimes, there is error returned during power on. +- * So added retry logic to ensure power on so that +- * port prepare succeeds +- */ +- do { +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_ON); +- if (!ret) +- break; +- usleep_range(2000, 2200); +- } while (retry--); +- mutex_unlock(&tas_dev->pde_lock); ++ scoped_guard(mutex, &tas_dev->pde_lock) { ++ /* ++ * Sometimes, there is error returned during power on. ++ * So added retry logic to ensure power on so that ++ * port prepare succeeds ++ */ ++ do { ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (!ret) ++ break; ++ usleep_range(2000, 2200); ++ } while (retry--); ++ } ++ + if (ret) + return ret; + +@@ -965,7 +1018,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { +- s32 ret; + struct snd_soc_component *component = dai->component; + struct tas2783_prv *tas_dev = + snd_soc_component_get_drvdata(component); +@@ -974,14 +1026,11 @@ static s32 tas_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + + sdw_stream_remove_slave(tas_dev->sdw_peripheral, sdw_stream); + +- mutex_lock(&tas_dev->pde_lock); +- ret = regmap_write(tas_dev->regmap, +- SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, +- TAS2783_SDCA_CTL_REQ_POW_STATE, 0), +- TAS2783_SDCA_POW_STATE_OFF); +- mutex_unlock(&tas_dev->pde_lock); +- +- return ret; ++ guard(mutex)(&tas_dev->pde_lock); ++ return regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_OFF); + } + + static const struct snd_soc_dai_ops tas_dai_ops = { +@@ -1349,7 +1398,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, + function_data->desc = &peripheral->sdca_data.function[i]; + + /* Parse the function */ +- ret = sdca_parse_function(dev, peripheral, function_data); ++ ret = sdca_parse_function(dev, function_data); + if (!ret) + tas_dev->sa_func_data = function_data; + else +diff --git a/sound/soc/codecs/tas5805m.c b/sound/soc/codecs/tas5805m.c +--- a/sound/soc/codecs/tas5805m.c ++++ b/sound/soc/codecs/tas5805m.c +@@ -12,6 +12,7 @@ + // + // It has been simplified a little and reworked for the 5.x ALSA SoC API. + ++#include + #include + #include + #include +@@ -230,10 +231,9 @@ static int tas5805m_vol_get(struct snd_kcontrol *kcontrol, + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + ucontrol->value.integer.value[0] = tas5805m->vol[0]; + ucontrol->value.integer.value[1] = tas5805m->vol[1]; +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -249,13 +249,12 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); +- int ret = 0; + + if (!(volume_is_valid(ucontrol->value.integer.value[0]) && + volume_is_valid(ucontrol->value.integer.value[1]))) + return -EINVAL; + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->vol[0] != ucontrol->value.integer.value[0] || + tas5805m->vol[1] != ucontrol->value.integer.value[1]) { + tas5805m->vol[0] = ucontrol->value.integer.value[0]; +@@ -265,11 +264,10 @@ static int tas5805m_vol_put(struct snd_kcontrol *kcontrol, + tas5805m->is_powered); + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- ret = 1; ++ return 1; + } +- mutex_unlock(&tas5805m->lock); + +- return ret; ++ return 0; + } + + static const struct snd_kcontrol_new tas5805m_snd_controls[] = { +@@ -332,7 +330,7 @@ static void do_work(struct work_struct *work) + + dev_dbg(&tas5805m->i2c->dev, "DSP startup\n"); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + /* We mustn't issue any I2C transactions until the I2S + * clock is stable. Furthermore, we must allow a 5ms + * delay after the first set of register writes to +@@ -345,7 +343,6 @@ static void do_work(struct work_struct *work) + + tas5805m->is_powered = true; + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + } + + static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, +@@ -362,7 +359,7 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + dev_dbg(component->dev, "DSP shutdown\n"); + cancel_work_sync(&tas5805m->work); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + if (tas5805m->is_powered) { + tas5805m->is_powered = false; + +@@ -379,7 +376,6 @@ static int tas5805m_dac_event(struct snd_soc_dapm_widget *w, + + regmap_write(rm, REG_DEVICE_CTRL_2, DCTRL2_MODE_HIZ); + } +- mutex_unlock(&tas5805m->lock); + } + + return 0; +@@ -414,14 +410,13 @@ static int tas5805m_mute(struct snd_soc_dai *dai, int mute, int direction) + struct tas5805m_priv *tas5805m = + snd_soc_component_get_drvdata(component); + +- mutex_lock(&tas5805m->lock); ++ guard(mutex)(&tas5805m->lock); + dev_dbg(component->dev, "set mute=%d (is_powered=%d)\n", + mute, tas5805m->is_powered); + + tas5805m->is_muted = mute; + if (tas5805m->is_powered) + tas5805m_refresh(tas5805m); +- mutex_unlock(&tas5805m->lock); + + return 0; + } +@@ -462,7 +457,6 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + struct tas5805m_priv *tas5805m; + char filename[128]; + const char *config_name; +- const struct firmware *fw; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &tas5805m_regmap); +@@ -507,24 +501,20 @@ static int tas5805m_i2c_probe(struct i2c_client *i2c) + + snprintf(filename, sizeof(filename), "tas5805m_dsp_%s.bin", + config_name); ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, filename, dev); + if (ret) + return ret; + + if ((fw->size < 2) || (fw->size & 1)) { + dev_err(dev, "firmware is invalid\n"); +- release_firmware(fw); + return -EINVAL; + } + + tas5805m->dsp_cfg_len = fw->size; + tas5805m->dsp_cfg_data = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL); +- if (!tas5805m->dsp_cfg_data) { +- release_firmware(fw); ++ if (!tas5805m->dsp_cfg_data) + return -ENOMEM; +- } +- +- release_firmware(fw); + + /* Do the first part of the power-on here, while we can expect + * the I2S interface to be quiet. We must raise PDN# and then +diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c +--- a/sound/soc/codecs/tlv320aic26.c ++++ b/sound/soc/codecs/tlv320aic26.c +@@ -320,8 +320,14 @@ static int aic26_probe(struct snd_soc_component *component) + return 0; + } + ++static void aic26_remove(struct snd_soc_component *component) ++{ ++ device_remove_file(component->dev, &dev_attr_keyclick); ++} ++ + static const struct snd_soc_component_driver aic26_soc_component_dev = { + .probe = aic26_probe, ++ .remove = aic26_remove, + .controls = aic26_snd_controls, + .num_controls = ARRAY_SIZE(aic26_snd_controls), + .dapm_widgets = tlv320aic26_dapm_widgets, +diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c +--- a/sound/soc/codecs/tlv320aic31xx.c ++++ b/sound/soc/codecs/tlv320aic31xx.c +@@ -1720,18 +1720,14 @@ static int tlv320dac3100_fw_load(struct aic31xx_priv *aic31xx, + static int tlv320dac3100_load_coeffs(struct aic31xx_priv *aic31xx, + const char *fw_name) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, fw_name, aic31xx->dev); + if (ret) + return ret; + +- ret = tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); +- +- release_firmware(fw); +- +- return ret; ++ return tlv320dac3100_fw_load(aic31xx, fw->data, fw->size); + } + + static int aic31xx_i2c_probe(struct i2c_client *i2c) +diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c +--- a/sound/soc/codecs/tlv320aic32x4-clk.c ++++ b/sound/soc/codecs/tlv320aic32x4-clk.c +@@ -1,5 +1,5 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Clock Tree for the Texas Instruments TLV320AIC32x4 + * + * Copyright 2019 Annaliese McDermond +@@ -9,6 +9,7 @@ + + #include + #include ++#include + #include + #include + +@@ -57,7 +58,7 @@ static void clk_aic32x4_pll_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + + regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLLEN, 0); ++ AIC32X4_PLLEN, 0); + } + + static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) +@@ -75,7 +76,7 @@ static int clk_aic32x4_pll_is_prepared(struct clk_hw *hw) + } + + static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + /* Change to use regmap_bulk_read? */ + unsigned int val; +@@ -106,19 +107,19 @@ static int clk_aic32x4_pll_get_muldiv(struct clk_aic32x4 *pll, + } + + static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, +- struct clk_aic32x4_pll_muldiv *settings) ++ struct clk_aic32x4_pll_muldiv *settings) + { + int ret; + /* Change to use regmap_bulk_write for some if not all? */ + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_R_MASK, settings->r); ++ AIC32X4_PLL_R_MASK, settings->r); + if (ret < 0) + return ret; + + ret = regmap_update_bits(pll->regmap, AIC32X4_PLLPR, +- AIC32X4_PLL_P_MASK, +- settings->p << AIC32X4_PLL_P_SHIFT); ++ AIC32X4_PLL_P_MASK, ++ settings->p << AIC32X4_PLL_P_SHIFT); + if (ret < 0) + return ret; + +@@ -136,23 +137,21 @@ static int clk_aic32x4_pll_set_muldiv(struct clk_aic32x4 *pll, + return 0; + } + +-static unsigned long clk_aic32x4_pll_calc_rate( +- struct clk_aic32x4_pll_muldiv *settings, +- unsigned long parent_rate) ++static unsigned long clk_aic32x4_pll_calc_rate(struct clk_aic32x4_pll_muldiv *settings, ++ unsigned long parent_rate) + { + u64 rate; + /* + * We scale j by 10000 to account for the decimal part of P and divide + * it back out later. + */ +- rate = (u64) parent_rate * settings->r * +- ((settings->j * 10000) + settings->d); ++ rate = (u64)parent_rate * settings->r * ((settings->j * 10000) + settings->d); + +- return (unsigned long) DIV_ROUND_UP_ULL(rate, settings->p * 10000); ++ return (unsigned long)DIV_ROUND_UP_ULL(rate, settings->p * 10000); + } + + static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, +- unsigned long rate, unsigned long parent_rate) ++ unsigned long rate, unsigned long parent_rate) + { + u64 multiplier; + +@@ -165,14 +164,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + * of the multiplier. This is because we can't do floating point + * math in the kernel. + */ +- multiplier = (u64) rate * settings->p * 10000; ++ multiplier = (u64)rate * settings->p * 10000; + do_div(multiplier, parent_rate); + + /* + * J can't be over 64, so R can scale this. + * R can't be greater than 4. + */ +- settings->r = ((u32) multiplier / 640000) + 1; ++ settings->r = ((u32)multiplier / 640000) + 1; + if (settings->r > 4) + return -1; + do_div(multiplier, settings->r); +@@ -184,14 +183,14 @@ static int clk_aic32x4_pll_calc_muldiv(struct clk_aic32x4_pll_muldiv *settings, + return -1; + + /* Figure out the integer part, J, and the fractional part, D. */ +- settings->j = (u32) multiplier / 10000; +- settings->d = (u32) multiplier % 10000; ++ settings->j = (u32)multiplier / 10000; ++ settings->d = (u32)multiplier % 10000; + + return 0; + } + + static unsigned long clk_aic32x4_pll_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -220,8 +219,8 @@ static int clk_aic32x4_pll_determine_rate(struct clk_hw *hw, + } + + static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, +- unsigned long rate, +- unsigned long parent_rate) ++ unsigned long rate, ++ unsigned long parent_rate) + { + struct clk_aic32x4 *pll = to_clk_aic32x4(hw); + struct clk_aic32x4_pll_muldiv settings; +@@ -236,7 +235,7 @@ static int clk_aic32x4_pll_set_rate(struct clk_hw *hw, + return ret; + + /* 10ms is the delay to wait before the clocks are stable */ +- msleep(10); ++ usleep_range(10000, 20000); + + return 0; + } +@@ -261,7 +260,6 @@ static u8 clk_aic32x4_pll_get_parent(struct clk_hw *hw) + return (val & AIC32X4_PLL_CLKIN_MASK) >> AIC32X4_PLL_CLKIN_SHIFT; + } + +- + static const struct clk_ops aic32x4_pll_ops = { + .prepare = clk_aic32x4_pll_prepare, + .unprepare = clk_aic32x4_pll_unprepare, +@@ -311,11 +309,11 @@ static void clk_aic32x4_div_unprepare(struct clk_hw *hw) + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + + regmap_update_bits(div->regmap, div->reg, +- AIC32X4_DIVEN, 0); ++ AIC32X4_DIVEN, 0); + } + + static int clk_aic32x4_div_set_rate(struct clk_hw *hw, unsigned long rate, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + u8 divisor; +@@ -342,7 +340,7 @@ static int clk_aic32x4_div_determine_rate(struct clk_hw *hw, + } + + static unsigned long clk_aic32x4_div_recalc_rate(struct clk_hw *hw, +- unsigned long parent_rate) ++ unsigned long parent_rate) + { + struct clk_aic32x4 *div = to_clk_aic32x4(hw); + unsigned int val; +@@ -399,7 +397,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + { + .name = "pll", + .parent_names = +- (const char* []) { "mclk", "bclk", "gpio", "din" }, ++ (const char *[]) { "mclk", "bclk", "gpio", "din" }, + .num_parents = 4, + .ops = &aic32x4_pll_ops, + .reg = 0, +@@ -414,28 +412,28 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }, + { + .name = "ndac", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NDAC, + }, + { + .name = "mdac", +- .parent_names = (const char * []) { "ndac" }, ++ .parent_names = (const char *[]) { "ndac" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MDAC, + }, + { + .name = "nadc", +- .parent_names = (const char * []) { "codec_clkin" }, ++ .parent_names = (const char *[]) { "codec_clkin" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_NADC, + }, + { + .name = "madc", +- .parent_names = (const char * []) { "nadc" }, ++ .parent_names = (const char *[]) { "nadc" }, + .num_parents = 1, + .ops = &aic32x4_div_ops, + .reg = AIC32X4_MADC, +@@ -451,7 +449,7 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { + }; + + static struct clk *aic32x4_register_clk(struct device *dev, +- struct aic32x4_clkdesc *desc) ++ struct aic32x4_clkdesc *desc) + { + struct clk_init_data init; + struct clk_aic32x4 *priv; +@@ -464,8 +462,8 @@ static struct clk *aic32x4_register_clk(struct device *dev, + init.flags = 0; + + priv = devm_kzalloc(dev, sizeof(struct clk_aic32x4), GFP_KERNEL); +- if (priv == NULL) +- return (struct clk *) -ENOMEM; ++ if (!priv) ++ return ERR_PTR(-ENOMEM); + + priv->dev = dev; + priv->hw.init = &init; +diff --git a/sound/soc/codecs/tlv320aic32x4-i2c.c b/sound/soc/codecs/tlv320aic32x4-i2c.c +--- a/sound/soc/codecs/tlv320aic32x4-i2c.c ++++ b/sound/soc/codecs/tlv320aic32x4-i2c.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,17 +15,23 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_i2c_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 8; +- config.val_bits = 8; ++ regmap = devm_regmap_init_i2c(i2c, &aic32x4_i2c_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_i2c(i2c, &config); + type = (uintptr_t)i2c_get_match_data(i2c); + + return aic32x4_probe(&i2c->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4-spi.c b/sound/soc/codecs/tlv320aic32x4-spi.c +--- a/sound/soc/codecs/tlv320aic32x4-spi.c ++++ b/sound/soc/codecs/tlv320aic32x4-spi.c +@@ -1,11 +1,10 @@ +-/* SPDX-License-Identifier: GPL-2.0 +- * ++// SPDX-License-Identifier: GPL-2.0 ++/* + * Copyright 2011-2019 NW Digital Radio + * + * Author: Annaliese McDermond + * + * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27. +- * + */ + + #include +@@ -16,19 +15,25 @@ + + #include "tlv320aic32x4.h" + ++static const struct regmap_config aic32x4_spi_regmap_config = { ++ .reg_bits = 7, ++ .pad_bits = 1, ++ .val_bits = 8, ++ .read_flag_mask = 0x01, ++ .max_register = AIC32X4_REFPOWERUP, ++ .ranges = aic32x4_regmap_pages, ++ .num_ranges = 1, ++}; ++ + static int aic32x4_spi_probe(struct spi_device *spi) + { + struct regmap *regmap; +- struct regmap_config config; + enum aic32x4_type type; + +- config = aic32x4_regmap_config; +- config.reg_bits = 7; +- config.pad_bits = 1; +- config.val_bits = 8; +- config.read_flag_mask = 0x01; ++ regmap = devm_regmap_init_spi(spi, &aic32x4_spi_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); + +- regmap = devm_regmap_init_spi(spi, &config); + type = (uintptr_t)spi_get_device_match_data(spi); + + return aic32x4_probe(&spi->dev, regmap, type); +diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c +--- a/sound/soc/codecs/tlv320aic32x4.c ++++ b/sound/soc/codecs/tlv320aic32x4.c +@@ -1,7 +1,5 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + /* +- * linux/sound/soc/codecs/tlv320aic32x4.c +- * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin +@@ -28,7 +26,6 @@ + #include + #include + #include +-#include + + #include "tlv320aic32x4.h" + +@@ -45,7 +42,8 @@ struct aic32x4_priv { + struct regulator *supply_dv; + struct regulator *supply_av; + +- struct aic32x4_setup_data *setup; ++ unsigned int gpio_func[5]; ++ + struct device *dev; + enum aic32x4_type type; + +@@ -75,7 +73,7 @@ static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, + }; + + static int mic_bias_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *kcontrol, int event) ++ struct snd_kcontrol *kcontrol, int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + +@@ -83,25 +81,23 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, + case SND_SOC_DAPM_POST_PMU: + /* Change Mic Bias Registor */ + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, +- AIC32X4_MICBIAS_LDOIN | +- AIC32X4_MICBIAS_2075V); +- printk(KERN_DEBUG "%s: Mic Bias will be turned ON\n", __func__); ++ AIC32x4_MICBIAS_MASK, ++ AIC32X4_MICBIAS_LDOIN | ++ AIC32X4_MICBIAS_2075V); ++ dev_dbg(component->dev, "Mic Bias will be turned ON\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_update_bits(component, AIC32X4_MICBIAS, +- AIC32x4_MICBIAS_MASK, 0); +- printk(KERN_DEBUG "%s: Mic Bias will be turned OFF\n", +- __func__); ++ AIC32x4_MICBIAS_MASK, 0); ++ dev_dbg(component->dev, "Mic Bias will be turned OFF\n"); + break; + } + + return 0; + } + +- + static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -114,7 +110,7 @@ static int aic32x4_get_mfp1_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -123,8 +119,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_DOUTCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP2 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP2 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -142,7 +137,7 @@ static int aic32x4_set_mfp2_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -155,7 +150,7 @@ static int aic32x4_get_mfp3_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -164,8 +159,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_MISOCTL); + gpio_check = (val & AIC32X4_MFP_GPIO_ENABLED); + if (gpio_check != AIC32X4_MFP_GPIO_ENABLED) { +- printk(KERN_ERR "%s: MFP4 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP4 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -183,7 +177,7 @@ static int aic32x4_set_mfp4_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -195,7 +189,7 @@ static int aic32x4_get_mfp5_gpio(struct snd_kcontrol *kcontrol, + }; + + static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) ++ struct snd_ctl_elem_value *ucontrol) + { + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + u8 val; +@@ -204,8 +198,7 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + val = snd_soc_component_read(component, AIC32X4_GPIOCTL); + gpio_check = (val & AIC32X4_MFP5_GPIO_OUTPUT); + if (gpio_check != AIC32X4_MFP5_GPIO_OUTPUT) { +- printk(KERN_ERR "%s: MFP5 is not configure as a GPIO output\n", +- __func__); ++ dev_err(component->dev, "MFP5 is not configure as a GPIO output\n"); + return -EINVAL; + } + +@@ -222,25 +215,31 @@ static int aic32x4_set_mfp5_gpio(struct snd_kcontrol *kcontrol, + return 0; + }; + +-static const struct snd_kcontrol_new aic32x4_mfp1[] = { +- SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp2[] = { +- SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp3[] = { +- SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp4[] = { +- SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), +-}; +- +-static const struct snd_kcontrol_new aic32x4_mfp5[] = { +- SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, +- aic32x4_set_mfp5_gpio), ++static const struct { ++ unsigned int reg; ++ struct snd_kcontrol_new ctrl; ++} aic32x4_mfp_cfg[] = { ++ { ++ .reg = AIC32X4_DINCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP1 GPIO", 0, aic32x4_get_mfp1_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_DOUTCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP2 GPIO", 0, NULL, aic32x4_set_mfp2_gpio), ++ }, ++ { ++ .reg = AIC32X4_SCLKCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP3 GPIO", 0, aic32x4_get_mfp3_gpio, NULL), ++ }, ++ { ++ .reg = AIC32X4_MISOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP4 GPIO", 0, NULL, aic32x4_set_mfp4_gpio), ++ }, ++ { ++ .reg = AIC32X4_GPIOCTL, ++ .ctrl = SOC_SINGLE_BOOL_EXT("MFP5 GPIO", 0, aic32x4_get_mfp5_gpio, ++ aic32x4_set_mfp5_gpio), ++ }, + }; + + /* 0dB min, 0.5dB steps */ +@@ -270,53 +269,42 @@ static SOC_ENUM_SINGLE_DECL(r_ptm_enum, AIC32X4_RPLAYBACK, 2, ptm_text); + + static const struct snd_kcontrol_new aic32x4_snd_controls[] = { + SOC_DOUBLE_R_S_TLV("PCM Playback Volume", AIC32X4_LDACVOL, +- AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), ++ AIC32X4_RDACVOL, 0, -0x7f, 0x30, 7, 0, tlv_pcm), + SOC_ENUM("DAC Left Playback PowerTune Switch", l_ptm_enum), + SOC_ENUM("DAC Right Playback PowerTune Switch", r_ptm_enum), + SOC_DOUBLE_R_S_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_HPRGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R_S_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, +- tlv_driver_gain), ++ AIC32X4_LORGAIN, 0, -0x6, 0x1d, 5, 0, tlv_driver_gain), + SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, +- AIC32X4_HPRGAIN, 6, 0x01, 1), ++ AIC32X4_HPRGAIN, 6, 0x01, 1), + SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN, +- AIC32X4_LORGAIN, 6, 0x01, 1), ++ AIC32X4_LORGAIN, 6, 0x01, 1), + SOC_ENUM("LO Playback Common Mode Switch", lo_cm_enum), + SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 7, 0x01, 1), ++ AIC32X4_RMICPGAVOL, 7, 0x01, 1), + + SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), + SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0), + + SOC_DOUBLE_R_S_TLV("ADC Level Volume", AIC32X4_LADCVOL, +- AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), ++ AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol), + SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL, +- AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), ++ AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + + SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0), + SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0), +- SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 4, 0x07, 0), +- SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, +- 0, 0x03, 0), +- SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 6, 0x03, 0), +- SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, +- 1, 0x1F, 0), +- SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, +- 0, 0x7F, 0), +- SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, +- 3, 0x1F, 0), +- SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, +- 0, 0x1F, 0), +- SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, +- 0, 0x0F, 0), ++ SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1, 4, 0x07, 0), ++ SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1, 0, 0x03, 0), ++ SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2, 6, 0x03, 0), ++ SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2, 1, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3, 0, 0x7F, 0), ++ SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5, 3, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6, 0, 0x1F, 0), ++ SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7, 0, 0x0F, 0), + }; + + static const struct snd_kcontrol_new hpl_output_mixer_controls[] = { +@@ -354,21 +342,27 @@ static SOC_ENUM_SINGLE_DECL(in3r_lpga_n_enum, AIC32X4_LMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L L+ Switch", in1l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L L+ Switch", in2l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L L+ Switch", in3l_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in1r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R L+ Switch", in1r_lpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cml_to_lmixer_controls[] = { + SOC_DAPM_ENUM("CM_L L- Switch", cml_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R L- Switch", in2r_lpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_lmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R L- Switch", in3r_lpga_n_enum), + }; +@@ -385,21 +379,27 @@ static SOC_ENUM_SINGLE_DECL(in3l_rpga_n_enum, AIC32X4_RMICPGANIN, 2, resistor_te + static const struct snd_kcontrol_new in1r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_R R+ Switch", in1r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_R R+ Switch", in2r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in3r_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_R R+ Switch", in3r_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new in2l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN2_L R+ Switch", in2l_rpga_p_enum), + }; ++ + static const struct snd_kcontrol_new cmr_to_rmixer_controls[] = { + SOC_DAPM_ENUM("CM_R R- Switch", cmr_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in1l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN1_L R- Switch", in1l_rpga_n_enum), + }; ++ + static const struct snd_kcontrol_new in3l_to_rmixer_controls[] = { + SOC_DAPM_ENUM("IN3_L R- Switch", in3l_rpga_n_enum), + }; +@@ -428,38 +428,38 @@ static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0), + SND_SOC_DAPM_MUX("IN1_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_rmixer_controls), ++ in1r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_rmixer_controls), ++ in2r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_rmixer_controls), ++ in3r_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Right Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_rmixer_controls), ++ in2l_to_rmixer_controls), + SND_SOC_DAPM_MUX("CM_R to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cmr_to_rmixer_controls), ++ cmr_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN1_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_rmixer_controls), ++ in1l_to_rmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Right Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_rmixer_controls), ++ in3l_to_rmixer_controls), + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0), + SND_SOC_DAPM_MUX("IN1_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1l_to_lmixer_controls), ++ in1l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in2l_to_lmixer_controls), ++ in2l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_L to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in3l_to_lmixer_controls), ++ in3l_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN1_R to Left Mixer Positive Resistor", SND_SOC_NOPM, 0, 0, +- in1r_to_lmixer_controls), ++ in1r_to_lmixer_controls), + SND_SOC_DAPM_MUX("CM_L to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- cml_to_lmixer_controls), ++ cml_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN2_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in2r_to_lmixer_controls), ++ in2r_to_lmixer_controls), + SND_SOC_DAPM_MUX("IN3_R to Left Mixer Negative Resistor", SND_SOC_NOPM, 0, 0, +- in3r_to_lmixer_controls), ++ in3r_to_lmixer_controls), + + SND_SOC_DAPM_SUPPLY("Mic Bias", AIC32X4_MICBIAS, 6, 0, mic_bias_event, +- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), ++ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_POST("ADC Reset", aic32x4_reset_adc), + +@@ -575,7 +575,7 @@ static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + {"IN3_R to Left Mixer Negative Resistor", "40 kOhm", "IN3_R"}, + }; + +-static const struct regmap_range_cfg aic32x4_regmap_pages[] = { ++const struct regmap_range_cfg aic32x4_regmap_pages[] = { + { + .selector_reg = 0, + .selector_mask = 0xff, +@@ -585,13 +585,7 @@ static const struct regmap_range_cfg aic32x4_regmap_pages[] = { + .range_max = AIC32X4_REFPOWERUP, + }, + }; +- +-const struct regmap_config aic32x4_regmap_config = { +- .max_register = AIC32X4_REFPOWERUP, +- .ranges = aic32x4_regmap_pages, +- .num_ranges = ARRAY_SIZE(aic32x4_regmap_pages), +-}; +-EXPORT_SYMBOL(aic32x4_regmap_config); ++EXPORT_SYMBOL_GPL(aic32x4_regmap_pages); + + static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +@@ -624,7 +618,7 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: +- printk(KERN_ERR "aic32x4: invalid clock provider\n"); ++ dev_err(component->dev, "invalid clock provider\n"); + return -EINVAL; + } + +@@ -651,19 +645,20 @@ static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) + AIC32X4_IFACE1_DATATYPE_SHIFT); + break; + default: +- printk(KERN_ERR "aic32x4: invalid DAI interface format\n"); ++ dev_err(component->dev, "invalid DAI interface format\n"); + return -EINVAL; + } + + aic32x4->fmt = fmt; + + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATATYPE_MASK | +- AIC32X4_IFACE1_MASTER_MASK, iface_reg_1); ++ AIC32X4_IFACE1_DATATYPE_MASK | ++ AIC32X4_IFACE1_MASTER_MASK, ++ iface_reg_1); + snd_soc_component_update_bits(component, AIC32X4_IFACE2, +- AIC32X4_DATA_OFFSET_MASK, iface_reg_2); ++ AIC32X4_DATA_OFFSET_MASK, iface_reg_2); + snd_soc_component_update_bits(component, AIC32X4_IFACE3, +- AIC32X4_BCLKINV_MASK, iface_reg_3); ++ AIC32X4_BCLKINV_MASK, iface_reg_3); + + return 0; + } +@@ -676,14 +671,13 @@ static int aic32x4_set_aosr(struct snd_soc_component *component, u8 aosr) + static int aic32x4_set_dosr(struct snd_soc_component *component, u16 dosr) + { + snd_soc_component_write(component, AIC32X4_DOSRMSB, dosr >> 8); +- snd_soc_component_write(component, AIC32X4_DOSRLSB, +- (dosr & 0xff)); ++ snd_soc_component_write(component, AIC32X4_DOSRLSB, dosr & 0xff); + + return 0; + } + + static int aic32x4_set_processing_blocks(struct snd_soc_component *component, +- u8 r_block, u8 p_block) ++ u8 r_block, u8 p_block) + { + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + +@@ -703,6 +697,45 @@ static int aic32x4_set_processing_blocks(struct snd_soc_component *component, + return 0; + } + ++static int aic32x4_configure_rate(struct snd_soc_component *component, ++ unsigned int rate, u8 *aosr, u8 *adc_rc, ++ u8 *dac_rc, u8 *dosr_inc) ++{ ++ struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); ++ u8 prb_rx, prb_tx; ++ ++ if (rate <= 48000) { ++ *aosr = 128; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 8; ++ prb_rx = 1; ++ prb_tx = 1; ++ } else if (rate <= 96000) { ++ *aosr = 64; ++ *adc_rc = 6; ++ *dac_rc = 8; ++ *dosr_inc = 4; ++ prb_rx = 1; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 9; ++ } else if (rate == 192000) { ++ *aosr = 32; ++ *adc_rc = 3; ++ *dac_rc = 4; ++ *dosr_inc = 2; ++ prb_rx = 13; ++ prb_tx = (aic32x4->type == AIC32X4_TYPE_TAS2505) ? 1 : 19; ++ } else { ++ dev_err(component->dev, "Sampling rate %u not supported\n", rate); ++ return -EINVAL; ++ } ++ ++ if (aic32x4->type == AIC32X4_TYPE_TAS2505) ++ prb_rx = 0; ++ ++ return aic32x4_set_processing_blocks(component, prb_rx, prb_tx); ++} ++ + static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned int sample_rate, unsigned int channels, + unsigned int bit_depth) +@@ -717,7 +750,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + unsigned long adc_clock_rate, dac_clock_rate; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "pll" }, + { .id = "nadc" }, + { .id = "madc" }, +@@ -729,37 +762,11 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + if (ret) + return ret; + +- if (sample_rate <= 48000) { +- aosr = 128; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 8; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 1); +- } else if (sample_rate <= 96000) { +- aosr = 64; +- adc_resource_class = 6; +- dac_resource_class = 8; +- dosr_increment = 4; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 1, 9); +- } else if (sample_rate == 192000) { +- aosr = 32; +- adc_resource_class = 3; +- dac_resource_class = 4; +- dosr_increment = 2; +- if (aic32x4->type == AIC32X4_TYPE_TAS2505) +- aic32x4_set_processing_blocks(component, 0, 1); +- else +- aic32x4_set_processing_blocks(component, 13, 19); +- } else { +- dev_err(component->dev, "Sampling rate not supported\n"); +- return -EINVAL; +- } ++ ret = aic32x4_configure_rate(component, sample_rate, &aosr, ++ &adc_resource_class, &dac_resource_class, ++ &dosr_increment); ++ if (ret) ++ return ret; + + /* PCM over I2S is always 2-channel */ + if ((aic32x4->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) +@@ -781,50 +788,39 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, + (min_mdac * dosr * sample_rate); + for (mdac = min_mdac; mdac <= 128; ++mdac) { + for (ndac = max_ndac; ndac > 0; --ndac) { +- dac_clock_rate = ndac * mdac * dosr * +- sample_rate; +- if (dac_clock_rate == adc_clock_rate) { +- if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) +- continue; ++ dac_clock_rate = ndac * mdac * dosr * sample_rate; ++ if (dac_clock_rate != adc_clock_rate) ++ continue; + +- clk_set_rate(clocks[0].clk, +- dac_clock_rate); ++ if (clk_round_rate(clocks[0].clk, dac_clock_rate) == 0) ++ continue; + +- clk_set_rate(clocks[1].clk, +- sample_rate * aosr * +- madc); +- clk_set_rate(clocks[2].clk, +- sample_rate * aosr); +- aic32x4_set_aosr(component, +- aosr); ++ clk_set_rate(clocks[0].clk, dac_clock_rate); + +- clk_set_rate(clocks[3].clk, +- sample_rate * dosr * +- mdac); +- clk_set_rate(clocks[4].clk, +- sample_rate * dosr); +- aic32x4_set_dosr(component, +- dosr); ++ clk_set_rate(clocks[1].clk, sample_rate * aosr * madc); ++ clk_set_rate(clocks[2].clk, sample_rate * aosr); ++ aic32x4_set_aosr(component, aosr); + +- clk_set_rate(clocks[5].clk, +- sample_rate * channels * +- bit_depth); ++ clk_set_rate(clocks[3].clk, sample_rate * dosr * mdac); ++ clk_set_rate(clocks[4].clk, sample_rate * dosr); ++ aic32x4_set_dosr(component, dosr); + +- return 0; +- } ++ clk_set_rate(clocks[5].clk, ++ sample_rate * channels * bit_depth); ++ ++ return 0; + } + } + } + } + +- dev_err(component->dev, +- "Could not set clocks to support sample rate.\n"); ++ dev_err(component->dev, "Could not set clocks to support sample rate.\n"); + return -EINVAL; + } + + static int aic32x4_hw_params(struct snd_pcm_substream *substream, +- struct snd_pcm_hw_params *params, +- struct snd_soc_dai *dai) ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { + struct snd_soc_component *component = dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); +@@ -837,24 +833,20 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + + switch (params_physical_width(params)) { + case 16: +- iface1_reg |= (AIC32X4_WORD_LEN_16BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_16BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 20: +- iface1_reg |= (AIC32X4_WORD_LEN_20BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 24: +- iface1_reg |= (AIC32X4_WORD_LEN_24BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + case 32: +- iface1_reg |= (AIC32X4_WORD_LEN_32BITS << +- AIC32X4_IFACE1_DATALEN_SHIFT); ++ iface1_reg |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_IFACE1_DATALEN_SHIFT); + break; + } + snd_soc_component_update_bits(component, AIC32X4_IFACE1, +- AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); ++ AIC32X4_IFACE1_DATALEN_MASK, iface1_reg); + + if (params_channels(params) == 1) { + dacsetup_reg = AIC32X4_RDAC2LCHN | AIC32X4_LDAC2LCHN; +@@ -865,7 +857,7 @@ static int aic32x4_hw_params(struct snd_pcm_substream *substream, + dacsetup_reg = AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN; + } + snd_soc_component_update_bits(component, AIC32X4_DACSETUP, +- AIC32X4_DAC_CHAN_MASK, dacsetup_reg); ++ AIC32X4_DAC_CHAN_MASK, dacsetup_reg); + + return 0; + } +@@ -875,7 +867,7 @@ static int aic32x4_mute(struct snd_soc_dai *dai, int mute, int direction) + struct snd_soc_component *component = dai->component; + + snd_soc_component_update_bits(component, AIC32X4_DACMUTE, +- AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); ++ AIC32X4_MUTEON, mute ? AIC32X4_MUTEON : 0); + + return 0; + } +@@ -886,7 +878,7 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "madc" }, + { .id = "mdac" }, + { .id = "bdiv" }, +@@ -955,44 +947,14 @@ static void aic32x4_setup_gpios(struct snd_soc_component *component) + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); + + /* setup GPIO functions */ +- /* MFP1 */ +- if (aic32x4->setup->gpio_func[0] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DINCTL, +- aic32x4->setup->gpio_func[0]); +- snd_soc_add_component_controls(component, aic32x4_mfp1, +- ARRAY_SIZE(aic32x4_mfp1)); +- } ++ BUILD_BUG_ON(ARRAY_SIZE(aic32x4->gpio_func) != ARRAY_SIZE(aic32x4_mfp_cfg)); ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) { ++ if (aic32x4->gpio_func[i] == AIC32X4_MFPX_DEFAULT_VALUE) ++ continue; + +- /* MFP2 */ +- if (aic32x4->setup->gpio_func[1] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_DOUTCTL, +- aic32x4->setup->gpio_func[1]); +- snd_soc_add_component_controls(component, aic32x4_mfp2, +- ARRAY_SIZE(aic32x4_mfp2)); +- } +- +- /* MFP3 */ +- if (aic32x4->setup->gpio_func[2] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_SCLKCTL, +- aic32x4->setup->gpio_func[2]); +- snd_soc_add_component_controls(component, aic32x4_mfp3, +- ARRAY_SIZE(aic32x4_mfp3)); +- } +- +- /* MFP4 */ +- if (aic32x4->setup->gpio_func[3] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_MISOCTL, +- aic32x4->setup->gpio_func[3]); +- snd_soc_add_component_controls(component, aic32x4_mfp4, +- ARRAY_SIZE(aic32x4_mfp4)); +- } +- +- /* MFP5 */ +- if (aic32x4->setup->gpio_func[4] != AIC32X4_MFPX_DEFAULT_VALUE) { +- snd_soc_component_write(component, AIC32X4_GPIOCTL, +- aic32x4->setup->gpio_func[4]); +- snd_soc_add_component_controls(component, aic32x4_mfp5, +- ARRAY_SIZE(aic32x4_mfp5)); ++ snd_soc_component_write(component, aic32x4_mfp_cfg[i].reg, ++ aic32x4->gpio_func[i]); ++ snd_soc_add_component_controls(component, &aic32x4_mfp_cfg[i].ctrl, 1); + } + } + +@@ -1002,7 +964,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1013,8 +975,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1022,7 +983,7 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Power platform configuration */ + if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { + snd_soc_component_write(component, AIC32X4_MICBIAS, +- AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); ++ AIC32X4_MICBIAS_LDOIN | AIC32X4_MICBIAS_2075V); + } + if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) + snd_soc_component_write(component, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE); +@@ -1041,16 +1002,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + /* Mic PGA routing */ + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_IN2R_10K); ++ AIC32X4_LMICPGANIN_IN2R_10K); + else + snd_soc_component_write(component, AIC32X4_LMICPGANIN, +- AIC32X4_LMICPGANIN_CM1L_10K); ++ AIC32X4_LMICPGANIN_CM1L_10K); + if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_IN1L_10K); ++ AIC32X4_RMICPGANIN_IN1L_10K); + else + snd_soc_component_write(component, AIC32X4_RMICPGANIN, +- AIC32X4_RMICPGANIN_CM1R_10K); ++ AIC32X4_RMICPGANIN_CM1R_10K); + + /* + * Workaround: for an unknown reason, the ADC needs to be powered up +@@ -1058,8 +1019,8 @@ static int aic32x4_component_probe(struct snd_soc_component *component) + * a HW BUG or some kind of behavior not documented in the datasheet. + */ + tmp_reg = snd_soc_component_read(component, AIC32X4_ADCSETUP); +- snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg | +- AIC32X4_LADC_EN | AIC32X4_RADC_EN); ++ snd_soc_component_write(component, AIC32X4_ADCSETUP, ++ tmp_reg | AIC32X4_LADC_EN | AIC32X4_RADC_EN); + snd_soc_component_write(component, AIC32X4_ADCSETUP, tmp_reg); + + /* +@@ -1102,13 +1063,13 @@ static const struct snd_kcontrol_new aic32x4_tas2505_snd_controls[] = { + SOC_ENUM("DAC Playback PowerTune Switch", l_ptm_enum), + + SOC_SINGLE_TLV("HP Driver Gain Volume", +- AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), ++ AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE("HP DAC Playback Switch", AIC32X4_HPLGAIN, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Driver Playback Volume", +- TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), ++ TAS2505_SPKVOL1, 0, 0x74, 1, tlv_tas_driver_gain), + SOC_SINGLE_TLV("Speaker Amplifier Playback Volume", +- TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), ++ TAS2505_SPKVOL2, 4, 5, 0, tlv_amp_vol), + + SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0), + }; +@@ -1144,11 +1105,12 @@ static const struct snd_soc_dapm_route aic32x4_tas2505_dapm_routes[] = { + static struct snd_soc_dai_driver aic32x4_tas2505_dai = { + .name = "tas2505-hifi", + .playback = { +- .stream_name = "Playback", +- .channels_min = 1, +- .channels_max = 2, +- .rates = SNDRV_PCM_RATE_8000_96000, +- .formats = AIC32X4_FORMATS,}, ++ .stream_name = "Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = AIC32X4_FORMATS, ++ }, + .ops = &aic32x4_ops, + .symmetric_rate = 1, + }; +@@ -1159,7 +1121,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + u32 tmp_reg; + int ret; + +- static struct clk_bulk_data clocks[] = { ++ struct clk_bulk_data clocks[] = { + { .id = "codec_clkin" }, + { .id = "pll" }, + { .id = "bdiv" }, +@@ -1170,8 +1132,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + if (ret) + return ret; + +- if (aic32x4->setup) +- aic32x4_setup_gpios(component); ++ aic32x4_setup_gpios(component); + + clk_set_parent(clocks[0].clk, clocks[1].clk); + clk_set_parent(clocks[2].clk, clocks[3].clk); +@@ -1192,7 +1153,7 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AIC32X4_CMMODE, tmp_reg); + + /* +- * Enable the fast charging feature and ensure the needed 40ms ellapsed ++ * Enable the fast charging feature and ensure the needed 40ms elapsed + * before using the analog circuits. + */ + snd_soc_component_write(component, TAS2505_REFPOWERUP, +@@ -1218,17 +1179,10 @@ static const struct snd_soc_component_driver soc_component_dev_aic32x4_tas2505 = + .endianness = 1, + }; + +-static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, +- struct device_node *np) ++static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, struct device_node *np) + { +- struct aic32x4_setup_data *aic32x4_setup; + int ret; + +- aic32x4_setup = devm_kzalloc(aic32x4->dev, sizeof(*aic32x4_setup), +- GFP_KERNEL); +- if (!aic32x4_setup) +- return -ENOMEM; +- + ret = of_property_match_string(np, "clock-names", "mclk"); + if (ret < 0) + return -EINVAL; +@@ -1245,9 +1199,11 @@ static int aic32x4_parse_dt(struct aic32x4_priv *aic32x4, + gpiod_set_consumer_name(aic32x4->rstn_gpio, "tlv320aic32x4_rstn"); + } + +- if (of_property_read_u32_array(np, "aic32x4-gpio-func", +- aic32x4_setup->gpio_func, 5) >= 0) +- aic32x4->setup = aic32x4_setup; ++ for (int i = 0; i < ARRAY_SIZE(aic32x4->gpio_func); i++) ++ aic32x4->gpio_func[i] = AIC32X4_MFPX_DEFAULT_VALUE; ++ of_property_read_u32_array(np, "aic32x4-gpio-func", ++ aic32x4->gpio_func, ARRAY_SIZE(aic32x4->gpio_func)); ++ + return 0; + } + +@@ -1266,7 +1222,7 @@ static void aic32x4_disable_regulators(struct aic32x4_priv *aic32x4) + } + + static int aic32x4_setup_regulators(struct device *dev, +- struct aic32x4_priv *aic32x4) ++ struct aic32x4_priv *aic32x4) + { + int ret = 0; + +@@ -1356,12 +1312,8 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + struct device_node *np = dev->of_node; + int ret; + +- if (IS_ERR(regmap)) +- return PTR_ERR(regmap); +- +- aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), +- GFP_KERNEL); +- if (aic32x4 == NULL) ++ aic32x4 = devm_kzalloc(dev, sizeof(struct aic32x4_priv), GFP_KERNEL); ++ if (!aic32x4) + return -ENOMEM; + + aic32x4->dev = dev; +@@ -1405,11 +1357,13 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, + switch (aic32x4->type) { + case AIC32X4_TYPE_TAS2505: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4_tas2505, &aic32x4_tas2505_dai, 1); ++ &soc_component_dev_aic32x4_tas2505, ++ &aic32x4_tas2505_dai, 1); + break; + default: + ret = devm_snd_soc_register_component(dev, +- &soc_component_dev_aic32x4, &aic32x4_dai, 1); ++ &soc_component_dev_aic32x4, ++ &aic32x4_dai, 1); + } + + if (ret) { +diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h +--- a/sound/soc/codecs/tlv320aic32x4.h ++++ b/sound/soc/codecs/tlv320aic32x4.h +@@ -3,7 +3,6 @@ + * tlv320aic32x4.h + */ + +- + #ifndef _TLV320AIC32X4_H + #define _TLV320AIC32X4_H + +@@ -16,7 +15,7 @@ enum aic32x4_type { + AIC32X4_TYPE_TAS2505, + }; + +-extern const struct regmap_config aic32x4_regmap_config; ++extern const struct regmap_range_cfg aic32x4_regmap_pages[]; + int aic32x4_probe(struct device *dev, struct regmap *regmap, + enum aic32x4_type type); + void aic32x4_remove(struct device *dev); +@@ -24,7 +23,7 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + + /* tlv320aic32x4 register space (in decimal to match datasheet) */ + +-#define AIC32X4_REG(page, reg) ((page * 128) + reg) ++#define AIC32X4_REG(page, reg) (((page) * 128) + (reg)) + + #define AIC32X4_PSEL AIC32X4_REG(0, 0) + +@@ -234,4 +233,31 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); + #define AIC32X4_MAX_CODEC_CLKIN_FREQ 110000000 + #define AIC32X4_MAX_PLL_CLKIN 20000000 + ++#define AIC32X4_PWR_MICBIAS_2075_LDOIN 0x00000001 ++#define AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE 0x00000002 ++#define AIC32X4_PWR_AIC32X4_LDO_ENABLE 0x00000004 ++#define AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36 0x00000008 ++#define AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED 0x00000010 ++ ++#define AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K 0x00000001 ++#define AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K 0x00000002 ++ ++/* GPIO API */ ++#define AIC32X4_MFPX_DEFAULT_VALUE 0xff ++ ++#define AIC32X4_MFP1_DIN_DISABLED 0 ++#define AIC32X4_MFP1_DIN_ENABLED 0x2 ++#define AIC32X4_MFP1_GPIO_IN 0x4 ++ ++#define AIC32X4_MFP2_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP2_GPIO_OUT_HIGH 0x1 ++ ++#define AIC32X4_MFP_GPIO_ENABLED 0x4 ++ ++#define AIC32X4_MFP5_GPIO_DISABLED 0x0 ++#define AIC32X4_MFP5_GPIO_INPUT 0x8 ++#define AIC32X4_MFP5_GPIO_OUTPUT 0xc ++#define AIC32X4_MFP5_GPIO_OUT_LOW 0x0 ++#define AIC32X4_MFP5_GPIO_OUT_HIGH 0x1 ++ + #endif /* _TLV320AIC32X4_H */ +diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c +--- a/sound/soc/codecs/tlv320dac33.c ++++ b/sound/soc/codecs/tlv320dac33.c +@@ -7,6 +7,7 @@ + * Copyright: (C) 2009 Nokia Corporation + */ + ++#include + #include + #include + #include +@@ -236,13 +237,10 @@ static int dac33_write_locked(struct snd_soc_component *component, unsigned int + unsigned int value) + { + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- int ret; + +- mutex_lock(&dac33->mutex); +- ret = dac33_write(component, reg, value); +- mutex_unlock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + +- return ret; ++ return dac33_write(component, reg, value); + } + + #define DAC33_I2C_ADDR_AUTOINC 0x80 +@@ -365,13 +363,13 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); + int ret = 0; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_dbg(component->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); +- goto exit; ++ return ret; + } + + if (power) { +@@ -380,7 +378,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to enable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + if (dac33->reset_gpiod) { +@@ -388,7 +386,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -400,7 +398,7 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret < 0) { + dev_err(&dac33->i2c->dev, + "Failed to set reset GPIO: %d\n", ret); +- goto exit; ++ return ret; + } + } + +@@ -409,14 +407,12 @@ static int dac33_hard_power(struct snd_soc_component *component, int power) + if (ret != 0) { + dev_err(component->dev, + "Failed to disable supplies: %d\n", ret); +- goto exit; ++ return ret; + } + + dac33->chip_power = 0; + } + +-exit: +- mutex_unlock(&dac33->mutex); + return ret; + } + +@@ -659,7 +655,6 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; + unsigned int delay; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: +@@ -667,10 +662,10 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + DAC33_THRREG(dac33->nsample)); + + /* Take the timestamps */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- dac33->t_stamp1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); ++ dac33->t_stamp1 = dac33->t_stamp2; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(dac33->alarm_threshold)); +@@ -682,11 +677,11 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + break; + case DAC33_FIFO_MODE7: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- /* Move back the timestamp with drain time */ +- dac33->t_stamp1 -= dac33->mode7_us_to_lthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); ++ /* Move back the timestamp with drain time */ ++ dac33->t_stamp1 -= dac33->mode7_us_to_lthr; ++ } + + dac33_write16(component, DAC33_PREFILL_MSB, + DAC33_THRREG(DAC33_MODE7_MARGIN)); +@@ -704,14 +699,12 @@ static inline void dac33_prefill_handler(struct tlv320dac33_priv *dac33) + static inline void dac33_playback_handler(struct tlv320dac33_priv *dac33) + { + struct snd_soc_component *component = dac33->component; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_MODE1: + /* Take the timestamp */ +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp2 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp2 = ktime_to_us(ktime_get()); + + dac33_write16(component, DAC33_NSAMPLE_MSB, + DAC33_THRREG(dac33->nsample)); +@@ -735,7 +728,7 @@ static void dac33_work(struct work_struct *work) + dac33 = container_of(work, struct tlv320dac33_priv, work); + component = dac33->component; + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + switch (dac33->state) { + case DAC33_PREFILL: + dac33->state = DAC33_PLAYBACK; +@@ -757,18 +750,15 @@ static void dac33_work(struct work_struct *work) + dac33_write(component, DAC33_FIFO_CTRL_A, reg); + break; + } +- mutex_unlock(&dac33->mutex); + } + + static irqreturn_t dac33_interrupt_handler(int irq, void *dev) + { + struct snd_soc_component *component = dev; + struct tlv320dac33_priv *dac33 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + +- spin_lock_irqsave(&dac33->lock, flags); +- dac33->t_stamp1 = ktime_to_us(ktime_get()); +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) ++ dac33->t_stamp1 = ktime_to_us(ktime_get()); + + /* Do not schedule the workqueue in Mode7 */ + if (dac33->fifo_mode != DAC33_FIFO_MODE7) +@@ -902,14 +892,13 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- mutex_lock(&dac33->mutex); ++ guard(mutex)(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ +- mutex_unlock(&dac33->mutex); + return 0; + } + +@@ -1053,8 +1042,6 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, + break; + } + +- mutex_unlock(&dac33->mutex); +- + return 0; + } + +@@ -1156,21 +1143,20 @@ static snd_pcm_sframes_t dac33_dai_delay( + unsigned int time_delta, uthr; + int samples_out, samples_in, samples; + snd_pcm_sframes_t delay = 0; +- unsigned long flags; + + switch (dac33->fifo_mode) { + case DAC33_FIFO_BYPASS: + break; + case DAC33_FIFO_MODE1: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- t1 = dac33->t_stamp2; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ t1 = dac33->t_stamp2; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t1) +- goto out; ++ return 0; + + if (t0 > t1) { + /* +@@ -1230,23 +1216,22 @@ static snd_pcm_sframes_t dac33_dai_delay( + } + break; + case DAC33_FIFO_MODE7: +- spin_lock_irqsave(&dac33->lock, flags); +- t0 = dac33->t_stamp1; +- uthr = dac33->uthr; +- spin_unlock_irqrestore(&dac33->lock, flags); ++ scoped_guard(spinlock_irqsave, &dac33->lock) { ++ t0 = dac33->t_stamp1; ++ uthr = dac33->uthr; ++ } + t_now = ktime_to_us(ktime_get()); + + /* We have not started to fill the FIFO yet, delay is 0 */ + if (!t0) +- goto out; ++ return 0; + + if (t_now <= t0) { + /* + * Either the timestamps are messed or equal. Report + * maximum delay + */ +- delay = uthr; +- goto out; ++ return uthr; + } + + time_delta = t_now - t0; +@@ -1287,7 +1272,7 @@ static snd_pcm_sframes_t dac33_dai_delay( + dac33->fifo_mode); + break; + } +-out: ++ + return delay; + } + +diff --git a/sound/soc/codecs/tscs42xx.c b/sound/soc/codecs/tscs42xx.c +--- a/sound/soc/codecs/tscs42xx.c ++++ b/sound/soc/codecs/tscs42xx.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -210,25 +211,21 @@ static int power_up_audio_plls(struct snd_soc_component *component) + return ret; + } + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, mask, val); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL on (%d)\n", ret); +- goto exit; ++ return ret; + } + + if (!plls_locked(component)) { + dev_err(component->dev, "Failed to lock plls\n"); + ret = -ENOMSG; +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int power_down_audio_plls(struct snd_soc_component *component) +@@ -236,28 +233,24 @@ static int power_down_audio_plls(struct snd_soc_component *component) + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL1, + RV_PLLCTL1C_PDB_PLL1_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + ret = snd_soc_component_update_bits(component, R_PLLCTL1C, + RM_PLLCTL1C_PDB_PLL2, + RV_PLLCTL1C_PDB_PLL2_DISABLE); + if (ret < 0) { + dev_err(component->dev, "Failed to turn PLL off (%d)\n", ret); +- goto exit; ++ return ret; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- return ret; ++ return 0; + } + + static int coeff_ram_get(struct snd_kcontrol *kcontrol, +@@ -269,13 +262,11 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + (struct coeff_ram_ctl *)kcontrol->private_value; + struct soc_bytes_ext *params = &ctl->bytes_ext; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + memcpy(ucontrol->value.bytes.data, + &tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], params->max); + +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- + return 0; + } + +@@ -290,14 +281,14 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + unsigned int coeff_cnt = params->max / COEFF_SIZE; + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + tscs42xx->coeff_ram_synced = false; + + memcpy(&tscs42xx->coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs42xx->pll_lock); ++ guard(mutex)(&tscs42xx->pll_lock); + + if (plls_locked(component)) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, +@@ -305,18 +296,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->pll_lock); +- +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + /* Input L Capture Route */ +@@ -385,21 +370,17 @@ static int dac_event(struct snd_soc_dapm_widget *w, + struct tscs42xx *tscs42xx = snd_soc_component_get_drvdata(component); + int ret; + +- mutex_lock(&tscs42xx->coeff_ram_lock); ++ guard(mutex)(&tscs42xx->coeff_ram_lock); + + if (!tscs42xx->coeff_ram_synced) { + ret = write_coeff_ram(component, tscs42xx->coeff_ram, 0x00, + COEFF_RAM_COEFF_COUNT); + if (ret < 0) +- goto exit; ++ return ret; + tscs42xx->coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs42xx->coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static const struct snd_soc_dapm_widget tscs42xx_dapm_widgets[] = { +@@ -926,12 +907,10 @@ static int setup_sample_rate(struct snd_soc_component *component, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->samplerate = rate; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +@@ -1253,12 +1232,10 @@ static int tscs42xx_set_dai_bclk_ratio(struct snd_soc_dai *codec_dai, + return ret; + } + +- mutex_lock(&tscs42xx->audio_params_lock); ++ guard(mutex)(&tscs42xx->audio_params_lock); + + tscs42xx->bclk_ratio = ratio; + +- mutex_unlock(&tscs42xx->audio_params_lock); +- + return 0; + } + +diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c +--- a/sound/soc/codecs/tscs454.c ++++ b/sound/soc/codecs/tscs454.c +@@ -4,6 +4,7 @@ + // Author: Steven Eckhoff + + #include ++#include + #include + #include + #include +@@ -329,12 +330,10 @@ static int coeff_ram_get(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); +- +- memcpy(ucontrol->value.bytes.data, +- &coeff_ram[ctl->addr * COEFF_SIZE], params->max); +- +- mutex_unlock(coeff_ram_lock); ++ scoped_guard(mutex, coeff_ram_lock) { ++ memcpy(ucontrol->value.bytes.data, ++ &coeff_ram[ctl->addr * COEFF_SIZE], params->max); ++ } + + return 0; + } +@@ -428,15 +427,15 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + return -EINVAL; + } + +- mutex_lock(coeff_ram_lock); ++ guard(mutex)(coeff_ram_lock); + + *coeff_ram_synced = false; + + memcpy(&coeff_ram[ctl->addr * COEFF_SIZE], + ucontrol->value.bytes.data, params->max); + +- mutex_lock(&tscs454->pll1.lock); +- mutex_lock(&tscs454->pll2.lock); ++ guard(mutex)(&tscs454->pll1.lock); ++ guard(mutex)(&tscs454->pll2.lock); + + val = snd_soc_component_read(component, R_PLLSTAT); + if (val) { /* PLLs locked */ +@@ -446,18 +445,12 @@ static int coeff_ram_put(struct snd_kcontrol *kcontrol, + if (ret < 0) { + dev_err(component->dev, + "Failed to flush coeff ram cache (%d)\n", ret); +- goto exit; ++ return ret; + } + *coeff_ram_synced = true; + } + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->pll2.lock); +- mutex_unlock(&tscs454->pll1.lock); +- mutex_unlock(coeff_ram_lock); +- +- return ret; ++ return 0; + } + + static inline int coeff_ram_sync(struct snd_soc_component *component, +@@ -465,41 +458,35 @@ static inline int coeff_ram_sync(struct snd_soc_component *component, + { + int ret; + +- mutex_lock(&tscs454->dac_ram.lock); +- if (!tscs454->dac_ram.synced) { +- ret = write_coeff_ram(component, tscs454->dac_ram.cache, +- R_DACCRS, R_DACCRADD, R_DACCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->dac_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->dac_ram.lock) { ++ if (!tscs454->dac_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->dac_ram.cache, ++ R_DACCRS, R_DACCRADD, R_DACCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->dac_ram.lock); + +- mutex_lock(&tscs454->spk_ram.lock); +- if (!tscs454->spk_ram.synced) { +- ret = write_coeff_ram(component, tscs454->spk_ram.cache, +- R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->spk_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->spk_ram.lock) { ++ if (!tscs454->spk_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->spk_ram.cache, ++ R_SPKCRS, R_SPKCRADD, R_SPKCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->spk_ram.lock); + +- mutex_lock(&tscs454->sub_ram.lock); +- if (!tscs454->sub_ram.synced) { +- ret = write_coeff_ram(component, tscs454->sub_ram.cache, +- R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, +- 0x00, COEFF_RAM_COEFF_COUNT); +- if (ret < 0) { +- mutex_unlock(&tscs454->sub_ram.lock); +- return ret; ++ scoped_guard(mutex, &tscs454->sub_ram.lock) { ++ if (!tscs454->sub_ram.synced) { ++ ret = write_coeff_ram(component, tscs454->sub_ram.cache, ++ R_SUBCRS, R_SUBCRADD, R_SUBCRWDL, ++ 0x00, COEFF_RAM_COEFF_COUNT); ++ if (ret < 0) ++ return ret; + } + } +- mutex_unlock(&tscs454->sub_ram.lock); + + return 0; + } +@@ -658,16 +645,14 @@ static int set_sysclk(struct snd_soc_component *component) + + static inline void reserve_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users++; +- mutex_unlock(&pll->lock); + } + + static inline void free_pll(struct pll *pll) + { +- mutex_lock(&pll->lock); ++ guard(mutex)(&pll->lock); + pll->users--; +- mutex_unlock(&pll->lock); + } + + static int pll_connected(struct snd_soc_dapm_widget *source, +@@ -679,15 +664,13 @@ static int pll_connected(struct snd_soc_dapm_widget *source, + int users; + + if (strstr(source->name, "PLL 1")) { +- mutex_lock(&tscs454->pll1.lock); +- users = tscs454->pll1.users; +- mutex_unlock(&tscs454->pll1.lock); ++ scoped_guard(mutex, &tscs454->pll1.lock) ++ users = tscs454->pll1.users; + dev_dbg(component->dev, "%s(): PLL 1 users = %d\n", __func__, + users); + } else { +- mutex_lock(&tscs454->pll2.lock); +- users = tscs454->pll2.users; +- mutex_unlock(&tscs454->pll2.lock); ++ scoped_guard(mutex, &tscs454->pll2.lock) ++ users = tscs454->pll2.users; + dev_dbg(component->dev, "%s(): PLL 2 users = %d\n", __func__, + users); + } +@@ -806,7 +789,7 @@ static inline int aif_free(struct snd_soc_component *component, + { + struct tscs454 *tscs454 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d\n", __func__, aif->id); + +@@ -829,8 +812,6 @@ static inline int aif_free(struct snd_soc_component *component, + free_pll(tscs454->internal_rate.pll); + } + +- mutex_unlock(&tscs454->aifs_status_lock); +- + return 0; + } + +@@ -3174,7 +3155,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + unsigned int val; + int ret; + +- mutex_lock(&tscs454->aifs_status_lock); ++ guard(mutex)(&tscs454->aifs_status_lock); + + dev_dbg(component->dev, "%s(): aif %d fs = %u\n", __func__, + aif->id, fs); +@@ -3207,14 +3188,14 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + ret = set_aif_fs(component, aif->id, fs); + if (ret < 0) { + dev_err(component->dev, "Failed to set aif fs (%d)\n", ret); +- goto exit; ++ return ret; + } + + ret = set_aif_sample_format(component, params_format(params), aif->id); + if (ret < 0) { + dev_err(component->dev, + "Failed to set aif sample format (%d)\n", ret); +- goto exit; ++ return ret; + } + + set_aif_status_active(&tscs454->aifs_status, aif->id, +@@ -3223,11 +3204,7 @@ static int tscs454_hw_params(struct snd_pcm_substream *substream, + dev_dbg(component->dev, "Set aif %d active. Streams status is 0x%x\n", + aif->id, tscs454->aifs_status.streams); + +- ret = 0; +-exit: +- mutex_unlock(&tscs454->aifs_status_lock); +- +- return ret; ++ return 0; + } + + static int tscs454_hw_free(struct snd_pcm_substream *substream, +diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c +--- a/sound/soc/codecs/twl6040.c ++++ b/sound/soc/codecs/twl6040.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + struct twl6040_data *priv = snd_soc_component_get_drvdata(component); + int status; + +- mutex_lock(&priv->mutex); ++ guard(mutex)(&priv->mutex); + + /* Sync status */ + status = twl6040_read(component, TWL6040_REG_STATUS); +@@ -281,8 +282,6 @@ static void twl6040_hs_jack_report(struct snd_soc_component *component, + snd_soc_jack_report(jack, report, report); + else + snd_soc_jack_report(jack, 0, report); +- +- mutex_unlock(&priv->mutex); + } + + void twl6040_hs_jack_detect(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/uda1342.c b/sound/soc/codecs/uda1342.c +--- a/sound/soc/codecs/uda1342.c ++++ b/sound/soc/codecs/uda1342.c +@@ -308,9 +308,16 @@ static int uda1342_suspend(struct device *dev) + static int uda1342_resume(struct device *dev) + { + struct uda1342_priv *uda1342 = dev_get_drvdata(dev); ++ int ret; + ++ regcache_cache_only(uda1342->regmap, false); + regcache_mark_dirty(uda1342->regmap); +- regcache_sync(uda1342->regmap); ++ ret = regcache_sync(uda1342->regmap); ++ if (ret) { ++ regcache_cache_only(uda1342->regmap, true); ++ regcache_mark_dirty(uda1342->regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c +--- a/sound/soc/codecs/wcd9335.c ++++ b/sound/soc/codecs/wcd9335.c +@@ -5010,9 +5010,9 @@ static int wcd9335_power_on_reset(struct wcd9335_codec *wcd) + */ + usleep_range(600, 650); + +- gpiod_set_value(wcd->reset_gpio, 1); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 1); + msleep(20); +- gpiod_set_value(wcd->reset_gpio, 0); ++ gpiod_set_value_cansleep(wcd->reset_gpio, 0); + msleep(20); + + return 0; +diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c +--- a/sound/soc/codecs/wcd934x.c ++++ b/sound/soc/codecs/wcd934x.c +@@ -1265,13 +1265,10 @@ static int wcd934x_set_sido_input_src(struct wcd934x_codec *wcd, int sido_src) + + static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- +- if (++wcd->sysclk_users != 1) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (++wcd->sysclk_users != 1) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_ANA_BIAS, + WCD934X_ANA_BIAS_EN_MASK, +@@ -1328,12 +1325,10 @@ static int wcd934x_enable_ana_bias_and_sysclk(struct wcd934x_codec *wcd) + + static int wcd934x_disable_ana_bias_and_syclk(struct wcd934x_codec *wcd) + { +- mutex_lock(&wcd->sysclk_mutex); +- if (--wcd->sysclk_users != 0) { +- mutex_unlock(&wcd->sysclk_mutex); +- return 0; ++ scoped_guard(mutex, &wcd->sysclk_mutex) { ++ if (--wcd->sysclk_users != 0) ++ return 0; + } +- mutex_unlock(&wcd->sysclk_mutex); + + regmap_update_bits(wcd->regmap, WCD934X_CLK_SYS_MCLK_PRG, + WCD934X_EXT_CLK_BUF_EN_MASK | +@@ -2384,7 +2379,7 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + __func__, micb_num); + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + + switch (req) { + case MICB_PULLUP_ENABLE: +@@ -2446,8 +2441,6 @@ static int wcd934x_micbias_control(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&wcd934x->micb_lock); +- + return 0; + } + +@@ -2488,7 +2481,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd934x_codec *wcd934x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -2506,7 +2499,7 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd934x->micb_lock); ++ guard(mutex)(&wcd934x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2521,15 +2514,11 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD934X_MICB_VAL_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD934X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2550,9 +2539,8 @@ static int wcd934x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd934x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd934x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +@@ -5899,10 +5887,8 @@ static int wcd934x_codec_probe(struct platform_device *pdev) + } + + static const struct platform_device_id wcd934x_driver_id[] = { +- { +- .name = "wcd934x-codec", +- }, +- {}, ++ { .name = "wcd934x-codec" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, wcd934x_driver_id); + +diff --git a/sound/soc/codecs/wcd937x-sdw.c b/sound/soc/codecs/wcd937x-sdw.c +--- a/sound/soc/codecs/wcd937x-sdw.c ++++ b/sound/soc/codecs/wcd937x-sdw.c +@@ -1084,10 +1084,16 @@ static int wcd937x_sdw_runtime_suspend(struct device *dev) + static int wcd937x_sdw_runtime_resume(struct device *dev) + { + struct wcd937x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c +--- a/sound/soc/codecs/wcd937x.c ++++ b/sound/soc/codecs/wcd937x.c +@@ -2,6 +2,7 @@ + // Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + + #include ++#include + #include + #include + #include +@@ -1056,7 +1057,7 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + return -EINVAL; + } + +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + switch (req) { + case MICB_PULLUP_ENABLE: + wcd937x->pullup_ref[micb_index]++; +@@ -1136,7 +1137,6 @@ static int wcd937x_micbias_control(struct snd_soc_component *component, + } + break; + } +- mutex_unlock(&wcd937x->micb_lock); + + return 0; + } +@@ -1460,7 +1460,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd937x_priv *wcd937x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1475,7 +1475,7 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd937x->micb_lock); ++ guard(mutex)(&wcd937x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -1490,15 +1490,11 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD937X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD937X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -1519,9 +1515,8 @@ static int wcd937x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd937x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd937x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd938x-sdw.c b/sound/soc/codecs/wcd938x-sdw.c +--- a/sound/soc/codecs/wcd938x-sdw.c ++++ b/sound/soc/codecs/wcd938x-sdw.c +@@ -1245,10 +1245,16 @@ static int wcd938x_sdw_runtime_suspend(struct device *dev) + static int wcd938x_sdw_runtime_resume(struct device *dev) + { + struct wcd938x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + pm_runtime_mark_last_busy(dev); +diff --git a/sound/soc/codecs/wcd938x.c b/sound/soc/codecs/wcd938x.c +--- a/sound/soc/codecs/wcd938x.c ++++ b/sound/soc/codecs/wcd938x.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0-only + // Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + ++#include + #include + #include + #include +@@ -1976,7 +1977,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + int req_volt, int micb_num) + { + struct wcd938x_priv *wcd938x = snd_soc_component_get_drvdata(component); +- int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en, ret = 0; ++ int cur_vout_ctl, req_vout_ctl, micb_reg, micb_en; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1994,7 +1995,7 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd938x->micb_lock); ++ guard(mutex)(&wcd938x->micb_lock); + /* + * If requested micbias voltage is same as current micbias + * voltage, then just return. Otherwise, adjust voltage as +@@ -2009,15 +2010,11 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD938X_MICB_VOUT_MASK); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = -EINVAL; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return -EINVAL; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + if (micb_en == WCD938X_MICB_ENABLE) + snd_soc_component_write_field(component, micb_reg, +@@ -2038,9 +2035,8 @@ static int wcd938x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + */ + usleep_range(2000, 2100); + } +-exit: +- mutex_unlock(&wcd938x->micb_lock); +- return ret; ++ ++ return 0; + } + + static int wcd938x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wcd939x-sdw.c b/sound/soc/codecs/wcd939x-sdw.c +--- a/sound/soc/codecs/wcd939x-sdw.c ++++ b/sound/soc/codecs/wcd939x-sdw.c +@@ -1431,10 +1431,16 @@ static int wcd939x_sdw_runtime_suspend(struct device *dev) + static int wcd939x_sdw_runtime_resume(struct device *dev) + { + struct wcd939x_sdw_priv *wcd = dev_get_drvdata(dev); ++ int ret; + + if (wcd->regmap) { + regcache_cache_only(wcd->regmap, false); +- regcache_sync(wcd->regmap); ++ ret = regcache_sync(wcd->regmap); ++ if (ret) { ++ regcache_cache_only(wcd->regmap, true); ++ regcache_mark_dirty(wcd->regmap); ++ return ret; ++ } + } + + return 0; +diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c +--- a/sound/soc/codecs/wcd939x.c ++++ b/sound/soc/codecs/wcd939x.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1923,7 +1924,6 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(component); + unsigned int micb_reg, cur_vout_ctl, micb_en; + int req_vout_ctl; +- int ret = 0; + + switch (micb_num) { + case MIC_BIAS_1: +@@ -1941,7 +1941,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + default: + return -EINVAL; + } +- mutex_lock(&wcd939x->micb_lock); ++ guard(mutex)(&wcd939x->micb_lock); + + /* + * If requested micbias voltage is same as current micbias +@@ -1957,15 +1957,11 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + WCD939X_MICB_VOUT_CTL); + + req_vout_ctl = wcd_get_micb_vout_ctl_val(component->dev, req_volt); +- if (req_vout_ctl < 0) { +- ret = req_vout_ctl; +- goto exit; +- } ++ if (req_vout_ctl < 0) ++ return req_vout_ctl; + +- if (cur_vout_ctl == req_vout_ctl) { +- ret = 0; +- goto exit; +- } ++ if (cur_vout_ctl == req_vout_ctl) ++ return 0; + + dev_dbg(component->dev, "%s: micb_num: %d, cur_mv: %d, req_mv: %d, micb_en: %d\n", + __func__, micb_num, WCD_VOUT_CTL_TO_MICB(cur_vout_ctl), +@@ -1990,9 +1986,7 @@ static int wcd939x_mbhc_micb_adjust_voltage(struct snd_soc_component *component, + usleep_range(2000, 2100); + } + +-exit: +- mutex_unlock(&wcd939x->micb_lock); +- return ret; ++ return 0; + } + + static int wcd939x_mbhc_micb_ctrl_threshold_mic(struct snd_soc_component *component, +diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c +--- a/sound/soc/codecs/wm0010.c ++++ b/sound/soc/codecs/wm0010.c +@@ -9,6 +9,7 @@ + * Scott Ling + */ + ++#include + #include + #include + #include +@@ -148,13 +149,11 @@ static const char *wm0010_state_to_str(enum wm0010_state state) + static void wm0010_halt(struct snd_soc_component *component) + { + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- unsigned long flags; + enum wm0010_state state; + + /* Fetch the wm0010 state */ +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + switch (state) { + case WM0010_POWER_OFF: +@@ -173,9 +172,8 @@ static void wm0010_halt(struct snd_soc_component *component) + break; + } + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- wm0010->state = WM0010_POWER_OFF; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ wm0010->state = WM0010_POWER_OFF; + } + + struct wm0010_boot_xfer { +@@ -190,11 +188,9 @@ struct wm0010_boot_xfer { + static void wm0010_mark_boot_failure(struct wm0010_priv *wm0010) + { + enum wm0010_state state; +- unsigned long flags; + +- spin_lock_irqsave(&wm0010->irq_lock, flags); +- state = wm0010->state; +- spin_unlock_irqrestore(&wm0010->irq_lock, flags); ++ scoped_guard(spinlock_irqsave, &wm0010->irq_lock) ++ state = wm0010->state; + + dev_err(wm0010->dev, "Failed to transition from `%s' state to `%s' state\n", + wm0010_state_to_str(state), wm0010_state_to_str(state + 1)); +@@ -337,7 +333,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + struct wm0010_boot_xfer *xfer; + int ret; + DECLARE_COMPLETION_ONSTACK(done); +- const struct firmware *fw; + const struct dfw_binrec *rec; + const struct dfw_inforec *inforec; + u64 *img; +@@ -346,6 +341,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + INIT_LIST_HEAD(&xfer_list); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, name, component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request application(%s): %d\n", +@@ -364,16 +360,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* First record should be INFO */ + if (rec->command != DFW_CMD_INFO) { + dev_err(component->dev, "First record not INFO\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + if (inforec->info_version != INFO_VERSION) { + dev_err(component->dev, + "Unsupported version (%02d) of INFO record\r\n", + inforec->info_version); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + dev_dbg(component->dev, "Version v%02d INFO record found\r\n", +@@ -382,8 +376,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + /* Check it's a DSP file */ + if (dsp != DEVICE_ID_WM0010) { + dev_err(component->dev, "Not a WM0010 firmware file.\r\n"); +- ret = -EINVAL; +- goto abort; ++ return -EINVAL; + } + + /* Skip the info record as we don't need to send it */ +@@ -408,14 +401,14 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.rx_buf = out; + + img = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; +- goto abort1; ++ goto abort; + } + xfer->t.tx_buf = img; + +@@ -453,13 +446,13 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + ret = spi_async(spi, &xfer->m); + if (ret != 0) { + dev_err(component->dev, "Write failed: %d\n", ret); +- goto abort1; ++ goto abort; + } + + if (wm0010->boot_failed) { + dev_dbg(component->dev, "Boot fail!\n"); + ret = -EINVAL; +- goto abort1; ++ goto abort; + } + } + +@@ -467,7 +460,7 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + + ret = 0; + +-abort1: ++abort: + while (!list_empty(&xfer_list)) { + xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer, + list); +@@ -477,8 +470,6 @@ static int wm0010_firmware_load(const char *name, struct snd_soc_component *comp + kfree(xfer); + } + +-abort: +- release_firmware(fw); + return ret; + } + +@@ -486,14 +477,12 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + { + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); +- const struct firmware *fw; + struct spi_message m; + struct spi_transfer t; +- u32 *img; +- u8 *out; + int i; + int ret = 0; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request stage2 loader: %d\n", +@@ -504,17 +493,15 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); + + /* Copy to local buffer first as vmalloc causes problems for dma */ +- img = kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); +- if (!img) { +- ret = -ENOMEM; +- goto abort2; +- } ++ u32 *img __free(kfree) = ++ kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); ++ if (!img) ++ return -ENOMEM; + +- out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA); +- if (!out) { +- ret = -ENOMEM; +- goto abort1; +- } ++ u8 *out __free(kfree) = ++ kzalloc(fw->size, GFP_KERNEL | GFP_DMA); ++ if (!out) ++ return -ENOMEM; + + spi_message_init(&m); + memset(&t, 0, sizeof(t)); +@@ -531,7 +518,7 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + ret = spi_sync(spi, &m); + if (ret != 0) { + dev_err(component->dev, "Initial download failed: %d\n", ret); +- goto abort; ++ return ret; + } + + /* Look for errors from the boot ROM */ +@@ -540,18 +527,11 @@ static int wm0010_stage2_load(struct snd_soc_component *component) + dev_err(component->dev, "Boot ROM error: %x in %d\n", + out[i], i); + wm0010_mark_boot_failure(wm0010); +- ret = -EBUSY; +- goto abort; ++ return -EBUSY; + } + } +-abort: +- kfree(out); +-abort1: +- kfree(img); +-abort2: +- release_firmware(fw); + +- return ret; ++ return 0; + } + + static int wm0010_boot(struct snd_soc_component *component) +@@ -734,9 +714,8 @@ static int wm0010_set_bias_level(struct snd_soc_component *component, + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_PREPARE) { +- mutex_lock(&wm0010->lock); +- wm0010_halt(component); +- mutex_unlock(&wm0010->lock); ++ scoped_guard(mutex, &wm0010->lock) ++ wm0010_halt(component); + } + break; + case SND_SOC_BIAS_OFF: +@@ -832,9 +811,8 @@ static irqreturn_t wm0010_irq(int irq, void *data) + case WM0010_OUT_OF_RESET: + case WM0010_BOOTROM: + case WM0010_STAGE2: +- spin_lock(&wm0010->irq_lock); +- complete(&wm0010->boot_completion); +- spin_unlock(&wm0010->irq_lock); ++ scoped_guard(spinlock, &wm0010->irq_lock) ++ complete(&wm0010->boot_completion); + return IRQ_HANDLED; + default: + return IRQ_NONE; +diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c +--- a/sound/soc/codecs/wm2000.c ++++ b/sound/soc/codecs/wm2000.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -612,20 +613,15 @@ static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int anc_active = ucontrol->value.integer.value[0]; +- int ret; + + if (anc_active > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->anc_active = anc_active; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, +@@ -645,20 +641,15 @@ static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); + unsigned int val = ucontrol->value.integer.value[0]; +- int ret; + + if (val > 1) + return -EINVAL; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + wm2000->spk_ena = val; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_kcontrol_new wm2000_controls[] = { +@@ -676,9 +667,8 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wm2000_priv *wm2000 = dev_get_drvdata(component->dev); +- int ret; + +- mutex_lock(&wm2000->lock); ++ guard(mutex)(&wm2000->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + wm2000->anc_eng_ena = 1; +@@ -686,11 +676,7 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + if (SND_SOC_DAPM_EVENT_OFF(event)) + wm2000->anc_eng_ena = 0; + +- ret = wm2000_anc_set_mode(wm2000); +- +- mutex_unlock(&wm2000->lock); +- +- return ret; ++ return wm2000_anc_set_mode(wm2000); + } + + static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { +@@ -810,7 +796,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + struct wm2000_priv *wm2000; + struct wm2000_platform_data *pdata; + const char *filename; +- const struct firmware *fw = NULL; ++ const struct firmware *fw __free(firmware) = NULL; + int ret, i; + unsigned int reg; + u16 id; +@@ -828,7 +814,7 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + ret = PTR_ERR(wm2000->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); +- goto out; ++ return ret; + } + + for (i = 0; i < WM2000_NUM_SUPPLIES; i++) +@@ -922,9 +908,6 @@ static int wm2000_i2c_probe(struct i2c_client *i2c) + + err_supplies: + regulator_bulk_disable(WM2000_NUM_SUPPLIES, wm2000->supplies); +- +-out: +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c +--- a/sound/soc/codecs/wm2200.c ++++ b/sound/soc/codecs/wm2200.c +@@ -2461,7 +2461,15 @@ static int wm2200_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm2200->regmap, false); +- regcache_sync(wm2200->regmap); ++ ret = regcache_sync(wm2200->regmap); ++ if (ret) { ++ regcache_cache_only(wm2200->regmap, true); ++ regcache_mark_dirty(wm2200->regmap); ++ gpiod_set_value_cansleep(wm2200->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm2200->core_supplies), ++ wm2200->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c +--- a/sound/soc/codecs/wm5100.c ++++ b/sound/soc/codecs/wm5100.c +@@ -2659,7 +2659,15 @@ static int wm5100_runtime_resume(struct device *dev) + } + + regcache_cache_only(wm5100->regmap, false); +- regcache_sync(wm5100->regmap); ++ ret = regcache_sync(wm5100->regmap); ++ if (ret) { ++ regcache_cache_only(wm5100->regmap, true); ++ regcache_mark_dirty(wm5100->regmap); ++ gpiod_set_value_cansleep(wm5100->ldo_ena, 0); ++ regulator_bulk_disable(ARRAY_SIZE(wm5100->core_supplies), ++ wm5100->core_supplies); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c +--- a/sound/soc/codecs/wm5102.c ++++ b/sound/soc/codecs/wm5102.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -667,10 +668,9 @@ static int wm5102_out_comp_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + put_unaligned_be16(arizona->dac_comp_coeff, + ucontrol->value.bytes.data); +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -681,16 +681,14 @@ static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + uint16_t dac_comp_coeff = get_unaligned_be16(ucontrol->value.bytes.data); +- int ret = 0; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_coeff != dac_comp_coeff) { + arizona->dac_comp_coeff = dac_comp_coeff; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, +@@ -699,9 +697,8 @@ static int wm5102_out_comp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + ucontrol->value.integer.value[0] = arizona->dac_comp_enabled; +- mutex_unlock(&arizona->dac_comp_lock); + + return 0; + } +@@ -712,19 +709,17 @@ static int wm5102_out_comp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct arizona *arizona = dev_get_drvdata(component->dev->parent); + struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; +- int ret = 0; + + if (ucontrol->value.integer.value[0] > mc->max) + return -EINVAL; + +- mutex_lock(&arizona->dac_comp_lock); ++ guard(mutex)(&arizona->dac_comp_lock); + if (arizona->dac_comp_enabled != ucontrol->value.integer.value[0]) { + arizona->dac_comp_enabled = ucontrol->value.integer.value[0]; +- ret = 1; ++ return 1; + } +- mutex_unlock(&arizona->dac_comp_lock); + +- return ret; ++ return 0; + } + + static const char * const wm5102_osr_text[] = { +diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c +--- a/sound/soc/codecs/wm8731.c ++++ b/sound/soc/codecs/wm8731.c +@@ -10,6 +10,7 @@ + * Based on wm8753.c by Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -110,22 +111,20 @@ static int wm8731_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8731_priv *wm8731 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8731->lock); ++ guard(mutex)(&wm8731->lock); + if (wm8731->deemph != deemph) { + wm8731->deemph = deemph; + + wm8731_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8731->lock); + +- return ret; ++ return 0; + } + + static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -458,22 +459,20 @@ static int wm8903_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8903_priv *wm8903 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; +- int ret = 0; + + if (deemph > 1) + return -EINVAL; + +- mutex_lock(&wm8903->lock); ++ guard(mutex)(&wm8903->lock); + if (wm8903->deemph != deemph) { + wm8903->deemph = deemph; + + wm8903_set_deemph(component); + +- ret = 1; ++ return 1; + } +- mutex_unlock(&wm8903->lock); + +- return ret; ++ return 0; + } + + /* ALSA can only do steps of .01dB */ +diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c +--- a/sound/soc/codecs/wm8940.c ++++ b/sound/soc/codecs/wm8940.c +@@ -333,7 +333,7 @@ static const struct snd_soc_dapm_route wm8940_dapm_routes[] = { + {"ADC", NULL, "Boost Mixer"}, + }; + +-#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0); ++#define wm8940_reset(c) snd_soc_component_write(c, WM8940_SOFTRESET, 0) + + static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c +--- a/sound/soc/codecs/wm8958-dsp2.c ++++ b/sound/soc/codecs/wm8958-dsp2.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -864,9 +865,8 @@ static void wm8958_enh_eq_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "ENH_EQ", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->enh_eq = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -876,9 +876,8 @@ static void wm8958_mbc_vss_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC+VSS", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc_vss = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +@@ -888,9 +887,8 @@ static void wm8958_mbc_loaded(const struct firmware *fw, void *context) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + + if (fw && (wm8958_dsp2_fw(component, "MBC", fw, true) == 0)) { +- mutex_lock(&wm8994->fw_lock); ++ guard(mutex)(&wm8994->fw_lock); + wm8994->mbc = fw; +- mutex_unlock(&wm8994->fw_lock); + } + } + +diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c +--- a/sound/soc/codecs/wm8962.c ++++ b/sound/soc/codecs/wm8962.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1564,11 +1565,10 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); + int old = wm8962->dsp2_ena; +- int ret = 0; + int dsp2_running = snd_soc_component_read(component, WM8962_DSP2_POWER_MANAGEMENT) & + WM8962_DSP2_ENA; + +- mutex_lock(&wm8962->dsp2_ena_lock); ++ guard(mutex)(&wm8962->dsp2_ena_lock); + + if (ucontrol->value.integer.value[0]) + wm8962->dsp2_ena |= 1 << shift; +@@ -1576,9 +1576,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962->dsp2_ena &= ~(1 << shift); + + if (wm8962->dsp2_ena == old) +- goto out; +- +- ret = 1; ++ return 0; + + if (dsp2_running) { + if (wm8962->dsp2_ena) +@@ -1587,10 +1585,7 @@ static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + wm8962_dsp2_stop(component); + } + +-out: +- mutex_unlock(&wm8962->dsp2_ena_lock); +- +- return ret; ++ return 1; + } + + /* The VU bits for the headphones are in a different register to the mute +@@ -3937,7 +3932,9 @@ static int wm8962_runtime_resume(struct device *dev) + WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, + 0); + +- regcache_sync(wm8962->regmap); ++ ret = regcache_sync(wm8962->regmap); ++ if (ret) ++ goto cache_sync_err; + + regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP, + WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA, +@@ -3955,6 +3952,11 @@ static int wm8962_runtime_resume(struct device *dev) + + return 0; + ++cache_sync_err: ++ regcache_cache_only(wm8962->regmap, true); ++ regcache_mark_dirty(wm8962->regmap); ++ regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies), ++ wm8962->supplies); + disable_clock: + clk_disable_unprepare(wm8962->pdata.mclk); + return ret; +diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c +--- a/sound/soc/codecs/wm8994.c ++++ b/sound/soc/codecs/wm8994.c +@@ -7,6 +7,7 @@ + * Author: Mark Brown + */ + ++#include + #include + #include + #include +@@ -766,7 +767,7 @@ static void active_reference(struct snd_soc_component *component) + { + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount++; + +@@ -775,8 +776,6 @@ static void active_reference(struct snd_soc_component *component) + + /* If we're using jack detection go into audio mode */ + wm1811_jackdet_set_mode(component, WM1811_JACKDET_MODE_AUDIO); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void active_dereference(struct snd_soc_component *component) +@@ -784,7 +783,7 @@ static void active_dereference(struct snd_soc_component *component) + struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); + u16 mode; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->active_refcount--; + +@@ -800,8 +799,6 @@ static void active_dereference(struct snd_soc_component *component) + + wm1811_jackdet_set_mode(component, mode); + } +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static int clk_sys_event(struct snd_soc_dapm_widget *w, +@@ -3704,7 +3701,7 @@ static void wm8958_open_circuit_work(struct work_struct *work) + open_circuit_work.work); + struct device *dev = wm8994->wm8994->dev; + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + wm1811_micd_stop(wm8994->hubs.component); + +@@ -3718,8 +3715,6 @@ static void wm8958_open_circuit_work(struct work_struct *work) + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); +- +- mutex_unlock(&wm8994->accdet_lock); + } + + static void wm8958_mic_id(void *data, u16 status) +@@ -3777,7 +3772,7 @@ static void wm1811_mic_work(struct work_struct *work) + struct snd_soc_component *component = wm8994->hubs.component; + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + +- pm_runtime_get_sync(component->dev); ++ guard(pm_runtime_active)(component->dev); + + /* If required for an external cap force MICBIAS on */ + if (control->pdata.jd_ext_cap) { +@@ -3785,7 +3780,7 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_dapm_sync(dapm); + } + +- mutex_lock(&wm8994->accdet_lock); ++ guard(mutex)(&wm8994->accdet_lock); + + dev_dbg(component->dev, "Starting mic detection\n"); + +@@ -3803,10 +3798,6 @@ static void wm1811_mic_work(struct work_struct *work) + snd_soc_component_update_bits(component, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm1811_jackdet_irq(int irq, void *data) +@@ -4026,15 +4017,10 @@ static void wm8958_mic_work(struct work_struct *work) + mic_complete_work.work); + struct snd_soc_component *component = wm8994->hubs.component; + +- pm_runtime_get_sync(component->dev); +- +- mutex_lock(&wm8994->accdet_lock); ++ guard(pm_runtime_active)(component->dev); ++ guard(mutex)(&wm8994->accdet_lock); + + wm8994->mic_id_cb(wm8994->mic_id_cb_data, wm8994->mic_status); +- +- mutex_unlock(&wm8994->accdet_lock); +- +- pm_runtime_put(component->dev); + } + + static irqreturn_t wm8958_mic_irq(int irq, void *data) +diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c +--- a/sound/soc/codecs/wm9712.c ++++ b/sound/soc/codecs/wm9712.c +@@ -6,6 +6,7 @@ + * Author: Liam Girdwood + */ + ++#include + #include + #include + #include +@@ -229,7 +230,7 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = 1 << shift; + +- mutex_lock(&wm9712->lock); ++ guard(mutex)(&wm9712->lock); + old = wm9712->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9712->hp_mixer[mixer] |= mask; +@@ -251,8 +252,6 @@ static int wm9712_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9712->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c +--- a/sound/soc/codecs/wm9713.c ++++ b/sound/soc/codecs/wm9713.c +@@ -11,6 +11,7 @@ + * o Support for DAPM + */ + ++#include + #include + #include + #include +@@ -238,7 +239,7 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + shift = mc->shift & 0xff; + mask = (1 << shift); + +- mutex_lock(&wm9713->lock); ++ guard(mutex)(&wm9713->lock); + old = wm9713->hp_mixer[mixer]; + if (ucontrol->value.integer.value[0]) + wm9713->hp_mixer[mixer] |= mask; +@@ -260,8 +261,6 @@ static int wm9713_hp_mixer_put(struct snd_kcontrol *kcontrol, + &update); + } + +- mutex_unlock(&wm9713->lock); +- + return change; + } + +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -348,7 +348,6 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct wm_adsp *dsp = snd_soc_component_get_drvdata(component); +- int ret = 1; + + if (ucontrol->value.enumerated.item[0] == dsp[e->shift_l].fw) + return 0; +@@ -356,16 +355,14 @@ int wm_adsp_fw_put(struct snd_kcontrol *kcontrol, + if (ucontrol->value.enumerated.item[0] >= WM_ADSP_NUM_FW) + return -EINVAL; + +- mutex_lock(&dsp[e->shift_l].cs_dsp.pwr_lock); ++ guard(mutex)(&dsp[e->shift_l].cs_dsp.pwr_lock); + + if (dsp[e->shift_l].cs_dsp.booted || !list_empty(&dsp[e->shift_l].compr_list)) +- ret = -EBUSY; ++ return -EBUSY; + else + dsp[e->shift_l].fw = ucontrol->value.enumerated.item[0]; + +- mutex_unlock(&dsp[e->shift_l].cs_dsp.pwr_lock); +- +- return ret; ++ return 1; + } + EXPORT_SYMBOL_GPL(wm_adsp_fw_put); + +@@ -450,15 +447,13 @@ static int wm_coeff_put_acked(struct snd_kcontrol *kctl, + if (val == 0) + return 0; /* 0 means no event */ + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + if (cs_ctl->enabled) + ret = cs_dsp_coeff_write_acked_control(cs_ctl, val); + else + ret = -EPERM; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + if (ret < 0) + return ret; + +@@ -486,15 +481,13 @@ static int wm_coeff_tlv_get(struct snd_kcontrol *kctl, + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + int ret = 0; + +- mutex_lock(&cs_ctl->dsp->pwr_lock); ++ guard(mutex)(&cs_ctl->dsp->pwr_lock); + + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, cs_ctl->cache, size); + + if (!ret && copy_to_user(bytes, cs_ctl->cache, size)) + ret = -EFAULT; + +- mutex_unlock(&cs_ctl->dsp->pwr_lock); +- + return ret; + } + +@@ -694,10 +687,9 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, + struct cs_dsp_coeff_ctl *cs_ctl; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + cs_ctl = cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); + + if (ret < 0) + return ret; +@@ -709,14 +701,10 @@ EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); + int wm_adsp_read_ctl(struct wm_adsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) + { +- int ret; ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); +- ret = cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), ++ return cs_dsp_coeff_read_ctrl(cs_dsp_get_ctl(&dsp->cs_dsp, name, type, alg), + 0, buf, len); +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_read_ctl); + +@@ -1270,38 +1258,32 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + { + struct wm_adsp_compr *compr, *tmp; + struct snd_soc_pcm_runtime *rtd = stream->private_data; +- int ret = 0; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (wm_adsp_fw[dsp->fw].num_caps == 0) { + adsp_err(dsp, "%s: Firmware does not support compressed API\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -ENXIO; +- goto out; ++ return -ENXIO; + } + + if (wm_adsp_fw[dsp->fw].compr_direction != stream->direction) { + adsp_err(dsp, "%s: Firmware does not support stream direction\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + list_for_each_entry(tmp, &dsp->compr_list, list) { + if (!strcmp(tmp->name, snd_soc_rtd_to_codec(rtd, 0)->name)) { + adsp_err(dsp, "%s: Only a single stream supported per dai\n", + snd_soc_rtd_to_codec(rtd, 0)->name); +- ret = -EBUSY; +- goto out; ++ return -EBUSY; + } + } + + compr = kzalloc_obj(*compr); +- if (!compr) { +- ret = -ENOMEM; +- goto out; +- } ++ if (!compr) ++ return -ENOMEM; + + compr->dsp = dsp; + compr->stream = stream; +@@ -1311,10 +1293,7 @@ int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream) + + stream->runtime->private_data = compr; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- +- return ret; ++ return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_open); + +@@ -1324,7 +1303,7 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + struct wm_adsp_compr *compr = stream->runtime->private_data; + struct wm_adsp *dsp = compr->dsp; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + wm_adsp_compr_detach(compr); + list_del(&compr->list); +@@ -1332,8 +1311,6 @@ int wm_adsp_compr_free(struct snd_soc_component *component, + kfree(compr->raw_buf); + kfree(compr); + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return 0; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_free); +@@ -1741,7 +1718,7 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + + compr_dbg(compr, "Trigger: %d\n", cmd); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -1777,8 +1754,6 @@ int wm_adsp_compr_trigger(struct snd_soc_component *component, + break; + } + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_trigger); +@@ -1907,21 +1882,20 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + + compr_dbg(compr, "Pointer request\n"); + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + buf = compr->buf; + + if (dsp->fatal_error || !buf || buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); +- ret = -EIO; +- goto out; ++ return -EIO; + } + + if (buf->avail < wm_adsp_compr_frag_words(compr)) { + ret = wm_adsp_buffer_update_avail(buf); + if (ret < 0) { + compr_err(compr, "Error reading avail: %d\n", ret); +- goto out; ++ return ret; + } + + /* +@@ -1934,14 +1908,14 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + if (buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); +- goto out; ++ return ret; + } + + ret = wm_adsp_buffer_reenable_irq(buf); + if (ret < 0) { + compr_err(compr, "Failed to re-enable buffer IRQ: %d\n", + ret); +- goto out; ++ return ret; + } + } + } +@@ -1950,9 +1924,6 @@ int wm_adsp_compr_pointer(struct snd_soc_component *component, + tstamp->copied_total += buf->avail * CS_DSP_DATA_WORD_SIZE; + tstamp->sampling_rate = compr->sample_rate; + +-out: +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_pointer); +@@ -2063,15 +2034,13 @@ int wm_adsp_compr_copy(struct snd_soc_component *component, + struct wm_adsp *dsp = compr->dsp; + int ret; + +- mutex_lock(&dsp->cs_dsp.pwr_lock); ++ guard(mutex)(&dsp->cs_dsp.pwr_lock); + + if (stream->direction == SND_COMPRESS_CAPTURE) + ret = wm_adsp_compr_read(compr, buf, count); + else + ret = -ENOTSUPP; + +- mutex_unlock(&dsp->cs_dsp.pwr_lock); +- + return ret; + } + EXPORT_SYMBOL_GPL(wm_adsp_compr_copy); +diff --git a/sound/soc/codecs/wsa881x.c b/sound/soc/codecs/wsa881x.c +--- a/sound/soc/codecs/wsa881x.c ++++ b/sound/soc/codecs/wsa881x.c +@@ -1178,7 +1178,13 @@ static int wsa881x_runtime_resume(struct device *dev) + } + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ gpiod_direction_output(wsa881x->sd_n, 1); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c +--- a/sound/soc/codecs/wsa883x.c ++++ b/sound/soc/codecs/wsa883x.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -1237,9 +1238,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = true; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = true; + + switch (wsa883x->dev_mode) { + case RECEIVER: +@@ -1290,9 +1290,8 @@ static int wsa883x_spkr_event(struct snd_soc_dapm_widget *w, + WSA883X_GLOBAL_PA_EN_MASK, 0); + snd_soc_component_write_field(component, WSA883X_PDM_WD_CTL, + WSA883X_PDM_EN_MASK, 0); +- mutex_lock(&wsa883x->sp_lock); +- wsa883x->pa_on = false; +- mutex_unlock(&wsa883x->sp_lock); ++ scoped_guard(mutex, &wsa883x->sp_lock) ++ wsa883x->pa_on = false; + break; + } + return 0; +@@ -1696,9 +1695,15 @@ static int wsa883x_runtime_suspend(struct device *dev) + static int wsa883x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c +--- a/sound/soc/codecs/wsa884x.c ++++ b/sound/soc/codecs/wsa884x.c +@@ -1701,9 +1701,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = true; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = true; + + wsa884x_spkr_post_pmu(component, wsa884x); + +@@ -1717,9 +1716,8 @@ static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, + 0x0); + +- mutex_lock(&wsa884x->sp_lock); +- wsa884x->pa_on = false; +- mutex_unlock(&wsa884x->sp_lock); ++ scoped_guard(mutex, &wsa884x->sp_lock) ++ wsa884x->pa_on = false; + break; + } + +@@ -2147,9 +2145,15 @@ static int wsa884x_runtime_suspend(struct device *dev) + static int wsa884x_runtime_resume(struct device *dev) + { + struct regmap *regmap = dev_get_regmap(dev, NULL); ++ int ret; + + regcache_cache_only(regmap, false); +- regcache_sync(regmap); ++ ret = regcache_sync(regmap); ++ if (ret) { ++ regcache_cache_only(regmap, true); ++ regcache_mark_dirty(regmap); ++ return ret; ++ } + + return 0; + } +diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/codecs/wsa885x.c +@@ -0,0 +1,1533 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. ++ */ ++ ++/* WSA885X codec driver */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Control Registers - Audio Processing */ ++#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S 0x0000 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK 0x0004 ++#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX 0x0140 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID 0x0208 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX 0x0240 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER 0x0340 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0 0x4405 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1 0x4406 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB 0x4409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB 0x6409 ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB 0x440a ++#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB 0x640a ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS 0x0a04 ++#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS 0x0a40 ++#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE 0x0b10 ++#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX 0x0e40 ++ ++/* Analog Top Registers - Power and Clock Control */ ++#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d ++#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG 0x8024 ++#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG 0x8025 ++#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL 0x8034 ++ ++/* Analog PLL Registers */ ++#define WSA885X_ANA_PLL_DIV_CTL_0 0x8090 ++#define WSA885X_ANA_PLL_DIV_CTL_1 0x8091 ++#define WSA885X_ANA_TOP_PLL_VCO_CTL 0x8092 ++#define WSA885X_ANA_TOP_PLL_LOOPFILT_0 0x8093 ++#define WSA885X_ANA_TOP_PLL_OVRD_CTL 0x8098 ++#define WSA885X_ANA_TOP_PLL_STATUS_0 0x809a ++#define WSA885X_ANA_TOP_PLL_STATUS_1 0x809b ++ ++/* Analog Boost Control Registers */ ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL2 0x805b ++#define WSA885X_ANA_TOP_BOOST_STB_CTRL3 0x805c ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2 0x805e ++#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3 0x805f ++#define WSA885X_ANA_TOP_BOOST_MISC 0x8063 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2 0x8065 ++#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4 0x8067 ++ ++/* Analog IV Sense ADC Registers */ ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2 0x80ca ++#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3 0x80cb ++#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL 0x80cc ++#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2 0x80d0 ++ ++/* Analog Speaker Power Stage Registers */ ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3 0x8108 ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3 0x810b ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3 0x810e ++#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3 0x8111 ++#define WSA885X_ANA_TOP_SPK_TOP_SPARE3 0x813c ++#define WSA885X_SPK_TOP_LF_CH1_CTRL11 0x811c ++#define WSA885X_SPK_TOP_LF_CH1_TUNE1 0x811d ++#define WSA885X_SPK_TOP_LF_CH2_TUNE1 0x8129 ++#define WSA885X_SPK_TOP_LF_CH1_CTRL9 0x811a ++#define WSA885X_SPK_TOP_LF_CH2_CTRL9 0x8126 ++#define WSA885X_SPK_TOP_LF_CH2_CTRL11 0x8128 ++#define WSA885X_SPK_TOP_COMMON_CTRL2 0x8102 ++#define WSA885X_SPK_TOP_COMMON_TUNE1 0x8103 ++#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1 0x80ba ++#define WSA885X_DIG_CTRL0_TOP_CLK_CFG 0x8418 ++#define WSA885X_DIG_CTRL0_SDCA_COMMIT 0x8419 ++#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE 0x841a ++#define WSA885X_DIG_CTRL0_SYS_CLK_SEL 0x841b ++#define WSA885X_DIG_CTRL0_CDC_CLK_CTL 0x841c ++#define WSA885X_DIG_CTRL0_PA_FSM_CTL 0x8420 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0 0x8423 ++#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1 0x8424 ++#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1 0x842b ++#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1 0x8435 ++#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL 0x8458 ++#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL 0x8470 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1 0x84b4 ++#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1 0x84b7 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1 0x84A0 ++#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1 0x84A4 ++ ++/* Digital Control 1 Registers - I2S/TDM Interface */ ++#define WSA885X_DIG_CTRL1_I2S_CTL0 0x85A0 ++#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX 0x85A2 ++#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX 0x85A3 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9 ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX 0x85AA ++#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX 0x85AB ++#define WSA885X_DIG_CTRL1_I2S_RESET_CTL 0x85AE ++ ++/* CDC RX Path Registers - Audio Data Path */ ++#define WSA885X_CDC_RX0_RX_PATH_CFG0 0x8601 ++#define WSA885X_CDC_RX0_RX_PATH_CFG1 0x8602 ++#define WSA885X_CDC_RX0_RX_PATH_CTL 0x8606 ++#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613 ++#define WSA885X_CDC_RX1_RX_PATH_CFG0 0x8621 ++#define WSA885X_CDC_RX1_RX_PATH_CFG1 0x8622 ++#define WSA885X_CDC_RX1_RX_PATH_CTL 0x8626 ++#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633 ++ ++/* CDC Compander Registers - Dynamic Range Control */ ++#define WSA885X_CDC_COMPANDER0_CTL0 0x8640 ++#define WSA885X_CDC_COMPANDER0_CTL7 0x8647 ++#define WSA885X_CDC_COMPANDER1_CTL0 0x8660 ++#define WSA885X_CDC_COMPANDER1_CTL7 0x8667 ++ ++/* CDC Speaker Protection Registers - IV Sense */ ++#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1 ++#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1 ++#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9 ++#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9 ++ ++/* CDC Class-H Registers - Headroom Control */ ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL1 0x86CD ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL0 0x86CC ++#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0 0x86C7 ++#define WSA885X_CDC_CLSH_CLSH_V_HD_PA 0x86C3 ++#define WSA885X_CDC_CLSH_V1P8_BP_CTL2 0x86CE ++ ++/* Driver Constants */ ++#define WSA885X_CLK_RATE_FIXED 73728000 ++#define WSA885X_NUM_REGS 0x03 ++ ++/* Interrupt Registers */ ++#define WSA885X_INTR_STATUS0 0x8584 ++#define WSA885X_INTR_MASK0 0x8581 ++#define WSA885X_INTR_CLEAR0 0x8587 ++ ++/* Power and PA FSM Control Registers */ ++#define WSA885X_PA0_FSM_CTL0 0x842A ++#define WSA885X_PA1_FSM_CTL0 0x8434 ++ ++/* Digital Control GPIO and Interrupt Registers */ ++#define WSA885X_DIG_CTRL1_PIN_CT 0x8510 ++#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL 0x8518 ++#define WSA885X_DIG_CTRL1_INTR_MODE 0x8580 ++ ++#define WSA885X_I2S_CTL0_PCM_RATE_MASK GENMASK(4, 1) ++#define WSA885X_I2S_CTL0_ENABLE_MASK BIT(0) ++#define WSA885X_I2S_CTL0_PCM_RATE(v) \ ++ FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v)) ++#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ 0x0 ++#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ 0x1 ++#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ 0x2 ++#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ 0x3 ++#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ 0x4 ++#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5 ++#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6 ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v)) ++#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK GENMASK(2, 0) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK GENMASK(6, 4) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v)) ++#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v) \ ++ FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v)) ++#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK BIT(0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1) ++#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8 \ ++ FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3) ++#define WSA885X_I2S_TDM_CH_TX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_TX_CH1_EN BIT(1) ++#define WSA885X_I2S_TDM_CH_TX_CH2_EN BIT(2) ++#define WSA885X_I2S_TDM_CH_TX_CH3_EN BIT(3) ++#define WSA885X_I2S_TDM_CH_RX_CH0_EN BIT(0) ++#define WSA885X_I2S_TDM_CH_RX_CH3_EN BIT(3) ++#define WSA885X_I2S_RESET_CTL_RESET_MASK BIT(0) ++#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2) ++#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3) ++#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK BIT(2) ++ ++#define WSA885X_I2S_TX_SLOT_ISENSE0 0x1 ++#define WSA885X_I2S_TX_SLOT_ISENSE1 0x2 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE0 0x5 ++#define WSA885X_I2S_TX_SLOT_CUR_SENSE1 0x6 ++ ++/* RX Sample Rate Index Values - Audio Playback Path */ ++#define WSA885X_RX_RATE_8000HZ 0x00 ++#define WSA885X_RX_RATE_16000HZ 0x01 ++#define WSA885X_RX_RATE_32000HZ 0x02 ++#define WSA885X_RX_RATE_44100HZ 0x03 ++#define WSA885X_RX_RATE_48000HZ 0x04 ++#define WSA885X_RX_RATE_96000HZ 0x05 ++#define WSA885X_RX_RATE_192000HZ 0x06 ++#define WSA885X_RX_RATE_384000HZ 0x07 ++ ++/* VI Sample Rate Index Values - Voltage/Current Sensing Path */ ++#define WSA885X_VI_RATE_8000HZ 0x00 ++#define WSA885X_VI_RATE_16000HZ 0x01 ++#define WSA885X_VI_RATE_44100HZ 0x02 ++#define WSA885X_VI_RATE_48000HZ 0x03 ++#define WSA885X_VI_RATE_96000HZ 0x04 ++#define WSA885X_VI_RATE_22050HZ 0x05 ++#define WSA885X_VI_RATE_24000HZ 0x06 ++#define WSA885X_VI_RATE_192000HZ 0x07 ++#define WSA885X_VI_RATE_384000HZ 0x08 ++ ++/* Channel Configuration Masks */ ++#define WSA885X_CHANNEL_STEREO 0x03 ++#define WSA885X_CHANNEL_MONO_LEFT 0x01 ++#define WSA885X_CHANNEL_MONO_RIGHT 0x02 ++ ++#define WSA885X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ ++ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 | \ ++ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) ++ ++#define WSA885X_PLL_LOCK_BIT BIT(0) ++ ++#define WSA885X_FU21_VOL_STEPS 124 ++#define WSA885X_USAGE_MODE_MAX 8 ++static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0); ++ ++static bool wsa885x_is_valid_rx_slot_mask(u32 mask) ++{ ++ return mask == WSA885X_CHANNEL_MONO_LEFT || ++ mask == WSA885X_CHANNEL_MONO_RIGHT || ++ mask == WSA885X_CHANNEL_STEREO; ++} ++ ++static const char *const wsa885x_supply_name[] = { ++ "vdd-1p8", ++ "vdd-io", ++}; ++ ++enum { ++ WSA885X_BATT_1S = 1, ++ WSA885X_BATT_2S, ++}; ++ ++enum { ++ WSA885X_IRQ_INT_SAF2WAR = 0, ++ WSA885X_IRQ_INT_WAR2SAF, ++ WSA885X_IRQ_INT_DISABLE, ++ WSA885X_IRQ_INT_PA0_OCP, ++ WSA885X_IRQ_INT_PA1_OCP, ++ WSA885X_IRQ_INT_CLIP0, ++ WSA885X_IRQ_INT_CLIP1, ++ WSA885X_IRQ_INT_CLK_WD, ++ WSA885X_IRQ_INT_INTR_GPIO1_PIN, ++ WSA885X_IRQ_INT_INTR_GPIO2_PIN, ++ WSA885X_IRQ_INT_UVLO, ++ WSA885X_IRQ_INT_BOP, ++ WSA885X_IRQ_INT_PA0_FSM_ERR, ++ WSA885X_IRQ_INT_PA1_FSM_ERR, ++ WSA885X_IRQ_INT_MAIN_FSM_ERR, ++ WSA885X_IRQ_INT_PCM_DATA0_WD, ++ WSA885X_IRQ_INT_PCM_DATA1_WD, ++ WSA885X_IRQ_INT_PCM_DATA0_DC, ++ WSA885X_IRQ_INT_PCM_DATA1_DC, ++ WSA885X_IRQ_INT_PLL_UNLOCKED, ++ WSA885X_IRQ_INT_PROT_MODE_CHANGE, ++ WSA885X_IRQ_INT_PB_CLOCK_VALID, ++ WSA885X_IRQ_INT_SENSE_CLOCK_VALID, ++ WSA885X_IRQ_MAX, ++}; ++ ++struct wsa885x_priv { ++ struct i2c_client *client; ++ struct regmap *regmap; ++ struct device *dev; ++ struct snd_soc_component *component; ++ struct gpio_desc *sd_n; ++ struct reset_control *sd_reset; ++ u32 usage_mode; ++ u32 rx_slot_mask; ++ u32 batt_conf; ++ int stereo_vol_db; ++ struct mutex state_lock; /* protects mutable control state */ ++}; ++ ++struct wsa885x_reg_update { ++ unsigned int reg; ++ unsigned int mask; ++ unsigned int val; ++}; ++ ++static const struct regmap_range_cfg wsa885x_regmap_ranges[] = { ++ { ++ .range_min = 0, ++ .range_max = 0x88ff, ++ .selector_reg = 0x0, ++ .selector_mask = 0xFF, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 0x100, ++ }, ++}; ++ ++static const struct reg_default wsa885x_codec_reg_defaults[] = { ++ {WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03}, ++ {WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03}, ++ {WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19}, ++ {WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22}, ++ {WSA885X_ANA_PLL_DIV_CTL_0, 0x0c}, ++ {WSA885X_ANA_PLL_DIV_CTL_1, 0x50}, ++ {WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00}, ++ {WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4}, ++ {WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03}, ++ {WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5}, ++ {WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13}, ++ {WSA885X_ANA_TOP_BOOST_MISC, 0x79}, ++ {WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00}, ++ {WSA885X_SPK_TOP_COMMON_CTRL2, 0x08}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00}, ++ {WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09}, ++ {WSA885X_SPK_TOP_COMMON_TUNE1, 0x03}, ++ {WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00}, ++ {WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40}, ++ {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff}, ++ {WSA885X_ANA_TOP_PLL_STATUS_0, 0x00}, ++ {WSA885X_ANA_TOP_PLL_STATUS_1, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00}, ++ {WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4}, ++ {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9}, ++ {WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00}, ++ {WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00}, ++ {WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00}, ++ {WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00}, ++ {WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05}, ++ {WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00}, ++ {WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45}, ++ {WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f}, ++ {WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01}, ++ {WSA885X_DIG_CTRL1_I2S_CTL0, 0x06}, ++ {WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00}, ++ {WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, WSA885X_I2S_TDM_CH_RX_CH3_EN}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX0_RX_PATH_CTL, 0x24}, ++ {WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89}, ++ {WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64}, ++ {WSA885X_CDC_RX1_RX_PATH_CTL, 0x04}, ++ {WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER0_CTL7, 0x2a}, ++ {WSA885X_CDC_COMPANDER1_CTL0, 0x01}, ++ {WSA885X_CDC_COMPANDER1_CTL7, 0x2a}, ++ {WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c}, ++ {WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d}, ++ {WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03}, ++ {WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05}, ++}; ++ ++static void wsa885x_multi_update_bits(struct regmap *regmap, ++ const struct wsa885x_reg_update *updates, ++ size_t num_updates) ++{ ++ size_t i; ++ ++ for (i = 0; i < num_updates; i++) ++ regmap_update_bits(regmap, updates[i].reg, ++ updates[i].mask, updates[i].val); ++} ++ ++static void wsa885x_toggle_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++} ++ ++static void wsa885x_pulse_irq_bit(struct wsa885x_priv *wsa885x, ++ unsigned int reg, unsigned int mask) ++{ ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++ regmap_update_bits(wsa885x->regmap, reg, mask, mask); ++ regmap_update_bits(wsa885x->regmap, reg, mask, 0); ++} ++ ++static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val) ++{ ++ if (!slot_num_val) ++ return -EINVAL; ++ ++ switch (slots) { ++ case 2: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2; ++ return 0; ++ case 4: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4; ++ return 0; ++ case 8: ++ *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 }, ++ { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 }, ++ }; ++ unsigned int slot_num_val; ++ int ret; ++ ++ if (!regmap) ++ return -EINVAL; ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) ++ return ret; ++ ++ regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs)); ++ ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, ++ slot_num_val); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK, ++ WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK); ++ regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH0_EN); ++ regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN, ++ WSA885X_I2S_TDM_CH_TX_CH1_EN); ++ ++ return 0; ++} ++ ++static int wsa885x_wait_for_pll_lock(struct wsa885x_priv *wsa885x) ++{ ++ unsigned int status = 0; ++ int cnt = 0, ret = 0; ++ ++ do { ++ usleep_range(1000, 1100); ++ ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret); ++ return ret; ++ } ++ ++ if (status & WSA885X_PLL_LOCK_BIT) ++ return 0; ++ } while (++cnt < 20); ++ ++ dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status); ++ return -ETIMEDOUT; ++} ++ ++static int wsa885x_2s_conf(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 }, ++ { WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d }, ++ { WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static const struct reg_sequence wsa885x_reg_init[] = { ++ { WSA885X_CDC_RX0_RX_PATH_CTL, 0x24 }, ++ { WSA885X_CDC_RX1_RX_PATH_CTL, 0x24 }, ++ { WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01 }, ++ { WSA885X_CDC_COMPANDER0_CTL0, 0x01 }, ++ { WSA885X_CDC_COMPANDER1_CTL0, 0x01 }, ++ { WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14 }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0f }, ++ { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x4f }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x02 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00 }, ++ { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG1, 0x60 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa5 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x85 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0c }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0e }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x0c }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01 }, ++ { WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x88 }, ++ { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x82 }, ++ { WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x34 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x41 }, ++ { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0x7f }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c }, ++ { WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d }, ++ { WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03 }, ++ { WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05 }, ++ { WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x20 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0x45 }, ++ { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0x45 }, ++ { WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x35 }, ++ { WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x21 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc7 }, ++ { WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x11 }, ++ { WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0x80 }, ++ { WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x08 }, ++ { WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x47 }, ++ { WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x47 }, ++ { WSA885X_CDC_COMPANDER0_CTL7, 0x34 }, ++ { WSA885X_CDC_COMPANDER1_CTL7, 0x34 }, ++ { WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x79 }, ++}; ++ ++static int wsa885x_hw_init(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e }, ++ { WSA885X_DIG_CTRL1_INTR_MODE, 0x01 }, ++ { WSA885X_DIG_CTRL1_PIN_CT, 0x04 }, ++ }; ++ int ret; ++ ++ ret = regmap_multi_reg_write(wsa885x->regmap, wsa885x_reg_init, ++ ARRAY_SIZE(wsa885x_reg_init)); ++ if (ret) ++ return ret; ++ ++ if (wsa885x->batt_conf == WSA885X_BATT_2S) { ++ ret = wsa885x_2s_conf(wsa885x); ++ if (ret) ++ return ret; ++ } ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_unmask_interrupts(struct wsa885x_priv *wsa885x) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_INTR_MASK0, 0x00 }, ++ { WSA885X_INTR_MASK0 + 1, 0x00 }, ++ { WSA885X_INTR_MASK0 + 2, 0xf8 }, ++ }; ++ ++ return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++} ++ ++static int wsa885x_wait_for_pde_state(struct wsa885x_priv *wsa885x, int ps) ++{ ++ unsigned int act_ps = 0, clock_valid = 0; ++ int rc = 0, cnt = 0; ++ ++ if (ps < 0 || ps > 3) ++ return -EINVAL; ++ ++ do { ++ usleep_range(1000, 1500); ++ rc = regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, ++ &act_ps); ++ if (rc) { ++ dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc); ++ return rc; ++ } ++ if (act_ps == ps) ++ return 0; ++ } while (++cnt < 5); ++ ++ if (regmap_read(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, ++ &clock_valid)) ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid read failed\n", ++ ps, act_ps); ++ else ++ dev_err(wsa885x->dev, ++ "PDE power state %d request failed, actual_ps %d, clock_valid:%d\n", ++ ps, act_ps, clock_valid); ++ ++ return -ETIMEDOUT; ++} ++ ++static void wsa885x_program_stereo_volume(struct wsa885x_priv *wsa885x, ++ int stereo_vol_db, bool commit) ++{ ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, ++ (u8)(s8)stereo_vol_db); ++ regmap_write(wsa885x->regmap, ++ WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00); ++ ++ if (commit) ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++} ++ ++static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct wsa885x_priv *wsa885x; ++ u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ switch (params_rate(params)) { ++ case 8000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ; ++ break; ++ case 16000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ; ++ break; ++ case 32000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 44100: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ; ++ break; ++ case 48000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; ++ break; ++ case 88200: ++ case 96000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ; ++ break; ++ case 176400: ++ case 192000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ; ++ break; ++ case 352800: ++ case 384000: ++ pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ; ++ cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ; ++ cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ; ++ break; ++ default: ++ dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params)); ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_PCM_RATE_MASK | ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) | ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, ++ cs21_sample_rate_idx); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, ++ cs24_sample_rate_idx); ++ ++ mutex_lock(&wsa885x->state_lock); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_slot_mask, ++ unsigned int rx_slot_mask, int slots, ++ int slot_width) ++{ ++ static const struct wsa885x_reg_update stereo_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, ++ WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ static const struct wsa885x_reg_update mono_left_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, ++ }; ++ static const struct wsa885x_reg_update mono_right_updates[] = { ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) }, ++ { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, ++ WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ unsigned int slot_num_val; ++ u32 mask; ++ int ret; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); ++ if (ret) { ++ dev_err(wsa885x->dev, "%s: unsupported slot count %d\n", ++ __func__, slots); ++ return ret; ++ } ++ ++ if (rx_slot_mask && !wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) { ++ dev_err(wsa885x->dev, ++ "%s: unsupported rx_slot_mask 0x%x\n", ++ __func__, rx_slot_mask); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (rx_slot_mask) ++ wsa885x->rx_slot_mask = rx_slot_mask; ++ else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mask = wsa885x->rx_slot_mask; ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, ++ WSA885X_I2S_RESET_CTL_RESET_MASK); ++ ++ if (mask == WSA885X_CHANNEL_STEREO) { ++ wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates, ++ ARRAY_SIZE(stereo_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN, ++ WSA885X_I2S_TDM_CH_TX_CH2_EN); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN, ++ WSA885X_I2S_TDM_CH_TX_CH3_EN); ++ } else if (mask == WSA885X_CHANNEL_MONO_LEFT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates, ++ ARRAY_SIZE(mono_left_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } else if (mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates, ++ ARRAY_SIZE(mono_right_updates)); ++ ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); ++ if (ret) ++ goto exit_unlock; ++ } ++ ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, ++ WSA885X_I2S_CTL0_ENABLE_MASK, ++ WSA885X_I2S_CTL0_ENABLE_MASK); ++ regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, ++ WSA885X_I2S_RESET_CTL_RESET_MASK, 0); ++ ++ ret = 0; ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, ++ unsigned int freq, int dir) ++{ ++ static const struct reg_sequence pll_prep[] = { ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 }, ++ { WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 }, ++ { WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 }, ++ { WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence pll_cleanup[] = { ++ { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, ++ { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 }, ++ { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ u32 pll_div; ++ int ret = 0; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (!freq) ++ return -EINVAL; ++ if (WSA885X_CLK_RATE_FIXED % freq) ++ return -EINVAL; ++ pll_div = WSA885X_CLK_RATE_FIXED / freq; ++ if (pll_div > 0xff) ++ return -EINVAL; ++ ++ regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep)); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02); ++ ++ ret = wsa885x_wait_for_pll_lock(wsa885x); ++ if (ret) { ++ dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n"); ++ regmap_multi_reg_write(wsa885x->regmap, pll_cleanup, ++ ARRAY_SIZE(pll_cleanup)); ++ return ret; ++ } ++ ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); ++ regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); ++ ++ return 0; ++} ++ ++static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream) ++{ ++ static const struct reg_sequence mute_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 }, ++ }; ++ static const struct reg_sequence mute_commit_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_prep_head_regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_prep_tail_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 }, ++ }; ++ static const struct reg_sequence unmute_volume_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_commit_regs[] = { ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 }, ++ }; ++ static const struct reg_sequence unmute_finish_regs[] = { ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 }, ++ { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 }, ++ { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ int ret = 0, ps0 = 0, ps3 = 3; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ ret = -EINVAL; ++ goto exit_unlock; ++ } ++ ++ if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ ++ if (mute) { ++ regmap_multi_reg_write(wsa885x->regmap, mute_regs, ++ ARRAY_SIZE(mute_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps3); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "PS3 transition failed: %d\n", ret); ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs, ++ ARRAY_SIZE(mute_commit_regs)); ++ } ++ } else { ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs, ++ ARRAY_SIZE(unmute_prep_head_regs)); ++ regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, ++ wsa885x->usage_mode); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs, ++ ARRAY_SIZE(unmute_prep_tail_regs)); ++ wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs, ++ ARRAY_SIZE(unmute_volume_regs)); ++ regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs, ++ ARRAY_SIZE(unmute_commit_regs)); ++ ret = wsa885x_wait_for_pde_state(wsa885x, ps0); ++ if (ret) ++ goto exit_unlock; ++ ++ if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN | ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH3_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01); ++ } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) { ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, ++ WSA885X_I2S_TDM_CH_RX_CH0_EN); ++ regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02); ++ } ++ ++ regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs, ++ ARRAY_SIZE(unmute_finish_regs)); ++ } ++ ++exit_unlock: ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return ret; ++} ++ ++static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ static const struct reg_sequence regs[] = { ++ { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, ++ }; ++ struct wsa885x_priv *wsa885x; ++ ++ wsa885x = snd_soc_component_get_drvdata(dai->component); ++ ++ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) ++ return 0; ++ ++ mutex_lock(&wsa885x->state_lock); ++ regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dai_ops wsa885x_dai_ops = { ++ .hw_params = wsa885x_codec_hw_params, ++ .set_tdm_slot = wsa885x_codec_set_tdm_slot, ++ .set_sysclk = wsa885x_codec_set_sysclk, ++ .mute_stream = wsa885x_codec_mute_stream, ++ .hw_free = wsa885x_codec_hw_free, ++}; ++ ++static struct snd_soc_dai_driver wsa885x_dai[] = { ++ { ++ .name = "wsa885x_dai_drv", ++ .playback = { ++ .stream_name = "WSA885X TDM Playback", ++ .channels_min = 1, ++ .channels_max = 2, ++ .rates = WSA885X_RATES, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | ++ SNDRV_PCM_FMTBIT_S32_LE, ++ }, ++ .ops = &wsa885x_dai_ops, ++ }, ++}; ++ ++static void wsa885x_reset_assert(void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (wsa885x->sd_reset) ++ reset_control_assert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 1); ++} ++ ++static void wsa885x_reset_deassert(struct wsa885x_priv *wsa885x) ++{ ++ if (wsa885x->sd_reset) ++ reset_control_deassert(wsa885x->sd_reset); ++ else ++ gpiod_direction_output(wsa885x->sd_n, 0); ++} ++ ++static int wsa885x_get_reset(struct device *dev, struct wsa885x_priv *wsa885x) ++{ ++ wsa885x->sd_reset = devm_reset_control_get_optional_shared(dev, NULL); ++ if (IS_ERR(wsa885x->sd_reset)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_reset), ++ "Failed to get reset\n"); ++ else if (wsa885x->sd_reset) ++ return 0; ++ ++ wsa885x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); ++ if (IS_ERR(wsa885x->sd_n)) ++ return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n), ++ "Shutdown Control GPIO not found\n"); ++ ++ return 0; ++} ++ ++static bool wsa885x_volatile_register(struct device *dev, unsigned int reg) ++{ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_DIG_CTRL0_SDCA_COMMIT: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_INTR_CLEAR0: ++ case WSA885X_INTR_CLEAR0 + 1: ++ case WSA885X_INTR_CLEAR0 + 2: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++static bool wsa885x_readable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg == WSA885X_INTR_CLEAR0 || ++ reg == WSA885X_INTR_CLEAR0 + 1 || ++ reg == WSA885X_INTR_CLEAR0 + 2) ++ return false; ++ return reg <= 0x88ff; ++} ++ ++static bool wsa885x_writeable_register(struct device *dev, unsigned int reg) ++{ ++ if (reg > 0x88ff) ++ return false; ++ ++ switch (reg) { ++ case WSA885X_ANA_TOP_PLL_STATUS_0: ++ case WSA885X_ANA_TOP_PLL_STATUS_1: ++ case WSA885X_INTR_STATUS0: ++ case WSA885X_INTR_STATUS0 + 1: ++ case WSA885X_INTR_STATUS0 + 2: ++ case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: ++ case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: ++ return false; ++ default: ++ return true; ++ } ++} ++ ++static const struct regmap_config wsa885x_regmap_cfg = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x88FF, ++ .ranges = wsa885x_regmap_ranges, ++ .num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges), ++ .reg_defaults = wsa885x_codec_reg_defaults, ++ .num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults), ++ .volatile_reg = wsa885x_volatile_register, ++ .writeable_reg = wsa885x_writeable_register, ++ .readable_reg = wsa885x_readable_register, ++ .cache_type = REGCACHE_MAPLE, ++ .use_single_read = true, ++ .use_single_write = true, ++}; ++ ++static int wsa885x_component_probe(struct snd_soc_component *component) ++{ ++ struct wsa885x_priv *wsa885x = ++ snd_soc_component_get_drvdata(component); ++ int ret; ++ ++ wsa885x->component = component; ++ snd_soc_component_init_regmap(component, wsa885x->regmap); ++ ++ ret = wsa885x_hw_init(wsa885x); ++ if (ret) ++ return ret; ++ ++ return wsa885x_unmask_interrupts(wsa885x); ++} ++ ++static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ int val; ++ ++ mutex_lock(&wsa885x->state_lock); ++ val = wsa885x->stereo_vol_db + 84; ++ mutex_unlock(&wsa885x->state_lock); ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = val; ++ return 0; ++} ++ ++static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ long val; ++ int stereo_vol_db; ++ ++ val = ucontrol->value.integer.value[0]; ++ ++ if (val < 0 || val > WSA885X_FU21_VOL_STEPS) { ++ dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val); ++ return -EINVAL; ++ } ++ ++ stereo_vol_db = (int)val - 84; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->stereo_vol_db == stereo_vol_db) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x_program_stereo_volume(wsa885x, stereo_vol_db, true); ++ wsa885x->stereo_vol_db = stereo_vol_db; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_usage_modes_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { ++ mutex_unlock(&wsa885x->state_lock); ++ return -ERANGE; ++ } ++ ++ ucontrol->value.integer.value[0] = wsa885x->usage_mode; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 0; ++} ++ ++static int wsa885x_usage_modes_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 val = ucontrol->value.integer.value[0]; ++ ++ if (val > WSA885X_USAGE_MODE_MAX) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->usage_mode == val) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->usage_mode = val; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static int wsa885x_rx_slot_mask_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask; ++ ++ mutex_lock(&wsa885x->state_lock); ++ mask = wsa885x->rx_slot_mask; ++ mutex_unlock(&wsa885x->state_lock); ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -ERANGE; ++ ++ ucontrol->value.integer.value[0] = mask; ++ ++ return 0; ++} ++ ++static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); ++ struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); ++ u32 mask = ucontrol->value.integer.value[0]; ++ ++ if (!wsa885x_is_valid_rx_slot_mask(mask)) ++ return -EINVAL; ++ ++ mutex_lock(&wsa885x->state_lock); ++ if (wsa885x->rx_slot_mask == mask) { ++ mutex_unlock(&wsa885x->state_lock); ++ return 0; ++ } ++ ++ wsa885x->rx_slot_mask = mask; ++ mutex_unlock(&wsa885x->state_lock); ++ ++ return 1; ++} ++ ++static const struct snd_kcontrol_new wsa885x_snd_controls[] = { ++ SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0, ++ wsa885x_usage_modes_get, ++ wsa885x_usage_modes_put), ++ ++ SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM, ++ 0, WSA885X_FU21_VOL_STEPS, 0, ++ wsa885x_stereo_gain_offset_get, ++ wsa885x_stereo_gain_offset_put, ++ wsa885x_fu21_digital_gain), ++ ++ SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0, ++ wsa885x_rx_slot_mask_get, ++ wsa885x_rx_slot_mask_put), ++}; ++ ++static const struct snd_soc_component_driver wsa885x_component = { ++ .name = "wsa885x", ++ .probe = wsa885x_component_probe, ++ .controls = wsa885x_snd_controls, ++ .num_controls = ARRAY_SIZE(wsa885x_snd_controls), ++}; ++ ++static irqreturn_t wsa885x_handle_irq(int irq_idx, void *data) ++{ ++ struct wsa885x_priv *wsa885x = data; ++ ++ if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX) ++ return IRQ_NONE; ++ ++ switch (irq_idx) { ++ case WSA885X_IRQ_INT_SAF2WAR: ++ case WSA885X_IRQ_INT_WAR2SAF: ++ case WSA885X_IRQ_INT_DISABLE: ++ case WSA885X_IRQ_INT_INTR_GPIO1_PIN: ++ case WSA885X_IRQ_INT_INTR_GPIO2_PIN: ++ case WSA885X_IRQ_INT_PA0_OCP: ++ case WSA885X_IRQ_INT_PA1_OCP: ++ case WSA885X_IRQ_INT_CLIP0: ++ case WSA885X_IRQ_INT_CLIP1: ++ case WSA885X_IRQ_INT_CLK_WD: ++ case WSA885X_IRQ_INT_BOP: ++ case WSA885X_IRQ_INT_UVLO: ++ case WSA885X_IRQ_INT_PCM_DATA0_DC: ++ case WSA885X_IRQ_INT_PCM_DATA1_DC: ++ case WSA885X_IRQ_INT_PLL_UNLOCKED: ++ case WSA885X_IRQ_INT_PROT_MODE_CHANGE: ++ case WSA885X_IRQ_INT_PB_CLOCK_VALID: ++ case WSA885X_IRQ_INT_SENSE_CLOCK_VALID: ++ break; ++ case WSA885X_IRQ_INT_PCM_DATA0_WD: ++ case WSA885X_IRQ_INT_PCM_DATA1_WD: ++ if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD) ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ else ++ wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1, ++ WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); ++ break; ++ case WSA885X_IRQ_INT_PA0_FSM_ERR: ++ case WSA885X_IRQ_INT_PA1_FSM_ERR: ++ case WSA885X_IRQ_INT_MAIN_FSM_ERR: ++ if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0, ++ WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) { ++ wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0, ++ WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t wsa885x_interrupt_handler(int irq, void *data) ++{ ++ static const unsigned int status_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_STATUS0, ++ WSA885X_INTR_STATUS0 + 1, ++ WSA885X_INTR_STATUS0 + 2, ++ }; ++ static const unsigned int clear_reg[WSA885X_NUM_REGS] = { ++ WSA885X_INTR_CLEAR0, ++ WSA885X_INTR_CLEAR0 + 1, ++ WSA885X_INTR_CLEAR0 + 2, ++ }; ++ unsigned int status[WSA885X_NUM_REGS] = { 0 }; ++ struct wsa885x_priv *wsa885x = data; ++ irqreturn_t handled = IRQ_NONE; ++ irqreturn_t irq_ret; ++ int i, bit, ret, irq_num; ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]); ++ if (ret) { ++ dev_err(wsa885x->dev, ++ "Failed to read status_reg[%d] (0x%x): %d\n", ++ i, status_reg[i], ret); ++ status[i] = 0; ++ continue; ++ } ++ } ++ ++ for (i = 0; i < WSA885X_NUM_REGS; i++) { ++ for (bit = 0; bit < 8; bit++) { ++ if (status[i] & BIT(bit)) { ++ irq_num = i * 8 + bit; ++ regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit)); ++ regmap_write(wsa885x->regmap, clear_reg[i], 0); ++ if (irq_num >= WSA885X_IRQ_MAX) { ++ dev_warn_ratelimited(wsa885x->dev, ++ "Unexpected IRQ bit %d (reg %d)\n", ++ bit, i); ++ handled = IRQ_HANDLED; ++ continue; ++ } ++ irq_ret = wsa885x_handle_irq(irq_num, wsa885x); ++ if (irq_ret == IRQ_HANDLED) ++ handled = IRQ_HANDLED; ++ } ++ } ++ } ++ return handled; ++} ++ ++static int wsa885x_register_irq(struct wsa885x_priv *wsa885x) ++{ ++ if (!wsa885x->client->irq) ++ return dev_err_probe(wsa885x->dev, -EINVAL, ++ "IRQ is not configured\n"); ++ ++ return devm_request_threaded_irq(wsa885x->dev, wsa885x->client->irq, NULL, ++ wsa885x_interrupt_handler, ++ IRQF_ONESHOT, ++ dev_name(wsa885x->dev), wsa885x); ++} ++ ++static int wsa885x_probe(struct i2c_client *client) ++{ ++ struct wsa885x_priv *wsa885x; ++ const struct snd_soc_component_driver *component_driver = &wsa885x_component; ++ const char *battery_config; ++ unsigned int i; ++ int ret; ++ struct device *dev = &client->dev; ++ ++ wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL); ++ if (!wsa885x) ++ return -ENOMEM; ++ ++ wsa885x->client = client; ++ wsa885x->dev = dev; ++ wsa885x->stereo_vol_db = -84; ++ wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; ++ mutex_init(&wsa885x->state_lock); ++ wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg); ++ ++ if (IS_ERR(wsa885x->regmap)) ++ return PTR_ERR(wsa885x->regmap); ++ ++ ret = device_property_read_string(dev, "qcom,battery-config", ++ &battery_config); ++ if (ret) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "1s")) { ++ wsa885x->batt_conf = WSA885X_BATT_1S; ++ } else if (!strcmp(battery_config, "2s")) { ++ wsa885x->batt_conf = WSA885X_BATT_2S; ++ } else { ++ return dev_err_probe(dev, -EINVAL, ++ "Invalid battery config %s (expected 1s or 2s)\n", ++ battery_config); ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(wsa885x_supply_name); i++) { ++ ret = devm_regulator_get_enable(dev, wsa885x_supply_name[i]); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "Failed to enable regulator %s\n", ++ wsa885x_supply_name[i]); ++ } ++ ++ ret = wsa885x_get_reset(dev, wsa885x); ++ if (ret) ++ return ret; ++ ++ wsa885x_reset_deassert(wsa885x); ++ usleep_range(5000, 5500); ++ ++ ret = devm_add_action_or_reset(dev, wsa885x_reset_assert, wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n"); ++ ++ i2c_set_clientdata(client, wsa885x); ++ ++ ret = wsa885x_register_irq(wsa885x); ++ if (ret) ++ return dev_err_probe(dev, ret, "wsa885x irq registration failed\n"); ++ ++ ret = devm_snd_soc_register_component(dev, component_driver, ++ wsa885x_dai, ++ ARRAY_SIZE(wsa885x_dai)); ++ if (ret) ++ return dev_err_probe(dev, ret, "Codec component registration failed\n"); ++ ++ return 0; ++} ++ ++static const struct of_device_id wsa885x_dt_match[] = { ++ { ++ .compatible = "qcom,wsa8855", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, wsa885x_dt_match); ++ ++static const struct i2c_device_id wsa885x_id[] = { ++ { ++ .name = "wsa885x", ++ .driver_data = 0, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, wsa885x_id); ++ ++static struct i2c_driver wsa885x_driver = { ++ .driver = { ++ .name = "wsa885x", ++ .of_match_table = wsa885x_dt_match, ++ }, ++ .probe = wsa885x_probe, ++ .id_table = wsa885x_id, ++}; ++ ++module_i2c_driver(wsa885x_driver); ++ ++MODULE_DESCRIPTION("ASoC WSA885X Stereo Smart PA Codec Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c +--- a/sound/soc/codecs/zl38060.c ++++ b/sound/soc/codecs/zl38060.c +@@ -162,7 +162,7 @@ static int zl38_fw_send_xaddr(struct regmap *regmap, const void *data) + static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + { + const struct ihex_binrec *rec; +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + u32 addr; + u16 len; + int err; +@@ -180,7 +180,7 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + return err; + err = zl38_fw_enter_boot_mode(regmap); + if (err) +- goto out; ++ return err; + rec = (const struct ihex_binrec *)fw->data; + while (rec) { + addr = be32_to_cpu(rec->addr); +@@ -195,15 +195,12 @@ static int zl38_load_firmware(struct device *dev, struct regmap *regmap) + err = -EINVAL; + } + if (err) +- goto out; ++ return err; + /* next ! */ + rec = ihex_next_binrec(rec); + } +- err = zl38_fw_go(regmap); + +-out: +- release_firmware(fw); +- return err; ++ return zl38_fw_go(regmap); + } + + +diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c +--- a/sound/soc/dwc/dwc-i2s.c ++++ b/sound/soc/dwc/dwc-i2s.c +@@ -955,7 +955,9 @@ static int dw_i2s_probe(struct platform_device *pdev) + } + + irq = platform_get_irq_optional(pdev, 0); +- if (irq >= 0) { ++ if (irq == -EPROBE_DEFER) ++ return irq; ++ if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { +diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c +--- a/sound/soc/fsl/fsl-asoc-card.c ++++ b/sound/soc/fsl/fsl-asoc-card.c +@@ -69,7 +69,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + + /** + * struct codec_priv - CODEC private data +- * @mclk: Main clock of the CODEC + * @mclk_freq: Clock rate of MCLK + * @free_freq: Clock rate of MCLK for hw_free() + * @mclk_id: MCLK (or main clock) id for set_sysclk() +@@ -80,7 +79,6 @@ static const struct snd_pcm_hw_constraint_list cs42888_channel_constraints = { + * to stay within PLL frequency limits + */ + struct codec_priv { +- struct clk *mclk; + unsigned long mclk_freq; + unsigned long free_freq; + u32 mclk_id; +@@ -109,12 +107,58 @@ struct cpu_priv { + u32 slot_num; + }; + ++struct fsl_asoc_card_priv; ++ ++/* ++ * struct fsl_asoc_card_pdata - per-compatible static card description ++ * @sysclk_dir: initial CPU SYSCLK direction override (0 = leave default IN) ++ * @sysclk_ratio: SYSCLK ratio on sample rate (0 = not used) ++ * @slot_width: TDM slot width (0 = not TDM) ++ * @codec_dai_name: name of the codec DAI ++ * @codec_mclk_id: MCLK id passed to set_sysclk() for the codec ++ * @codec_fll_id: FLL id; only valid when has_pll is true ++ * @codec_pll_id: PLL id; only valid when has_pll is true ++ * @codec_pll_ratio_s24: PLL output ratio for S24_LE ++ * @has_pll: codec uses PLL/FLL; codec_fll_id and codec_pll_id are valid ++ * @dai_fmt: DAI format flags ++ * @playback_only: restrict card to playback direction ++ * @capture_only: restrict card to capture direction ++ * @dapm_routes: DAPM route table override ++ * @num_dapm_routes: number of entries in dapm_routes ++ * @exclude_format: PCM format bitmask excluded (for SAI + WM8960/WM8962) ++ * @codec_init: codec-specific init run after mclk_freq is populated ++ * @probe_init: optional DT-driven init run at end of probe() (e.g. SPDIF codec discovery) ++ */ ++struct fsl_asoc_card_pdata { ++ u32 sysclk_dir[2]; ++ u32 sysclk_ratio[2]; ++ u32 slot_width; ++ const char *codec_dai_name; ++ u32 codec_mclk_id; ++ int codec_fll_id; ++ int codec_pll_id; ++ int codec_pll_ratio_s24; ++ bool has_pll; ++ bool playback_only; ++ bool capture_only; ++ u32 dai_fmt; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ u64 exclude_format; ++ int (*codec_init)(struct fsl_asoc_card_priv *priv); ++ int (*probe_init)(struct device_node *codec_np[], ++ struct device_node *cpu_np, ++ const char *codec_dai_name[], ++ struct fsl_asoc_card_priv *priv); ++}; ++ + /** + * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data + * @dai_link: DAI link structure including normal one and DPCM link + * @hp_jack: Headphone Jack structure + * @mic_jack: Microphone Jack structure + * @pdev: platform device pointer ++ * @pdata: pointer to the per-compatible card platform data + * @codec_priv: CODEC private data + * @cpu_priv: CPU private data + * @card: ASoC card structure +@@ -135,6 +179,7 @@ struct fsl_asoc_card_priv { + struct simple_util_jack hp_jack; + struct simple_util_jack mic_jack; + struct platform_device *pdev; ++ const struct fsl_asoc_card_pdata *pdata; + struct codec_priv codec_priv[2]; + struct cpu_priv cpu_priv; + struct snd_soc_card card; +@@ -609,6 +654,192 @@ static int fsl_asoc_card_spdif_init(struct device_node *codec_np[], + return 0; + } + ++static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ unsigned long mclk_freq = priv->codec_priv[0].mclk_freq; ++ ++ /* ++ * Set CPU sysclk frequency from codec MCLK only if not already ++ * set by the CPU DAI init (e.g. ESAI extal clock takes precedence). ++ */ ++ if (!priv->cpu_priv.sysclk_freq[TX]) ++ priv->cpu_priv.sysclk_freq[TX] = mclk_freq; ++ if (!priv->cpu_priv.sysclk_freq[RX]) ++ priv->cpu_priv.sysclk_freq[RX] = mclk_freq; ++ ++ priv->constraint_channels = &cs42888_channel_constraints; ++ if (mclk_freq % 12288000 == 0) ++ priv->constraint_rates = &cs42888_rate_48k_constraints; ++ else if (mclk_freq % 11289600 == 0) ++ priv->constraint_rates = &cs42888_rate_44k_constraints; ++ else ++ dev_warn(&priv->pdev->dev, ++ "Unknown MCLK frequency %lu, no rate constraints\n", ++ mclk_freq); ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_wm8958_codec_init(struct fsl_asoc_card_priv *priv) ++{ ++ priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; ++ return 0; ++} ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs42888_pdata = { ++ .codec_dai_name = "cs42888", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .slot_width = 32, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++ .codec_init = fsl_asoc_card_cs42888_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_cs427x_pdata = { ++ .codec_dai_name = "cs4271-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = CS427x_SYSCLK_MCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_sgtl5000_pdata = { ++ .codec_dai_name = "sgtl5000", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = SGTL5000_SYSCLK, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic32x4_pdata = { ++ .codec_dai_name = "tlv320aic32x4-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_tlv320aic31xx_pdata = { ++ .codec_dai_name = "tlv320dac31xx-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .sysclk_dir = { SND_SOC_CLOCK_OUT, SND_SOC_CLOCK_OUT }, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8962_pdata = { ++ .codec_dai_name = "wm8962", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8962_SYSCLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8962_SYSCLK_FLL, ++ .codec_pll_id = WM8962_FLL, ++ /* ++ * WM8962 has same BCLK generation limitations as WM8960. ++ * See WM8960 section for detailed explanation. ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8960_pdata = { ++ .codec_dai_name = "wm8960-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .has_pll = true, ++ .codec_fll_id = WM8960_SYSCLK_AUTO, ++ .codec_pll_id = WM8960_SYSCLK_AUTO, ++ /* ++ * WM8960 in master mode cannot generate exact 1.92 MHz BCLK ++ * required for S20_3LE (48kHz x 2ch x 20bit). Closest available ++ * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. ++ * ++ * In SAI master mode, SAI derive BCLK from MCLK using integer ++ * dividers only. S20_3LE requires non-integer divider ratios ++ * with standard MCLK frequencies. For example, 48kHz stereo ++ * needs 1.920 MHz BCLK, which requires a divider of 6.4 from ++ * 12.288 MHz MCLK (not an integer). ++ */ ++ .exclude_format = SNDRV_PCM_FMTBIT_S20_3LE, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_ac97_pdata = { ++ .codec_dai_name = "ac97-hifi", ++ .dai_fmt = SND_SOC_DAIFMT_AC97, ++ .dapm_routes = audio_map_ac97, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_ac97), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_mqs_pdata = { ++ .codec_dai_name = "fsl-mqs-dai", ++ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBC_CFC | ++ SND_SOC_DAIFMT_NB_NF, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8524_pdata = { ++ .codec_dai_name = "wm8524-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ /* RX=0, TX=1: set TX (index 1) to CLOCK_OUT, RX stays at default IN */ ++ .sysclk_dir = { 0, SND_SOC_CLOCK_OUT }, ++ .sysclk_ratio = { 0, 256 }, ++ .slot_width = 32, ++ .playback_only = true, ++ .dapm_routes = audio_map_tx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_tx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_si476x_pdata = { ++ .codec_dai_name = "si476x-codec", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBC_CFC, ++ .dapm_routes = audio_map_rx, ++ .num_dapm_routes = ARRAY_SIZE(audio_map_rx), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8958_pdata = { ++ .codec_dai_name = "wm8994-aif1", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8994_FLL_SRC_MCLK1, ++ .has_pll = true, ++ .codec_fll_id = WM8994_SYSCLK_FLL1, ++ .codec_pll_id = WM8994_FLL1, ++ .codec_init = fsl_asoc_card_wm8958_codec_init, ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_nau8822_pdata = { ++ .codec_dai_name = "nau8822-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = NAU8822_CLK_MCLK, ++ .has_pll = true, ++ .codec_fll_id = NAU8822_CLK_PLL, ++ .codec_pll_id = NAU8822_CLK_PLL, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_wm8904_pdata = { ++ .codec_dai_name = "wm8904-hifi", ++ .dai_fmt = DAI_FMT_BASE | SND_SOC_DAIFMT_CBP_CFP, ++ .codec_mclk_id = WM8904_FLL_MCLK, ++ .has_pll = true, ++ .codec_fll_id = WM8904_CLK_FLL, ++ .codec_pll_id = WM8904_FLL_MCLK, ++ .codec_pll_ratio_s24 = 192, ++ .dapm_routes = audio_map, ++ .num_dapm_routes = ARRAY_SIZE(audio_map), ++}; ++ ++static const struct fsl_asoc_card_pdata fsl_asoc_spdif_pdata = { ++ .codec_dai_name = "spdif", ++ .dai_fmt = DAI_FMT_BASE, ++ .probe_init = fsl_asoc_card_spdif_init, ++}; ++ + static int hp_jack_event(struct notifier_block *nb, unsigned long event, + void *data) + { +@@ -643,17 +874,173 @@ static struct notifier_block mic_jack_nb = { + .notifier_call = mic_jack_event, + }; + +-static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++/* ++ * fsl_asoc_card_init_cpu - configure CPU DAI-specific settings. ++ * ++ * Called from late_probe() when the CPU DAI component is guaranteed bound. ++ */ ++static int fsl_asoc_card_init_cpu(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) + { + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); +- struct snd_soc_pcm_runtime *rtd = list_first_entry( +- &card->rtd_list, struct snd_soc_pcm_runtime, list); ++ struct device_node *np = priv->pdev->dev.of_node; ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ const char *comp_drv_name = cpu_dai->component->driver->name; ++ struct device *dev = card->dev; ++ int ret; ++ ++ if (!strcmp(comp_drv_name, "fsl-ssi")) { ++ /* Only SSI needs to configure AUDMUX */ ++ ret = fsl_asoc_card_audmux_init(np, priv); ++ if (ret) { ++ dev_err(dev, "failed to init audmux\n"); ++ return ret; ++ } ++ } else if (!strcmp(comp_drv_name, "fsl-esai")) { ++ struct clk *esai_clk = clk_get(cpu_dai->dev, "extal"); ++ ++ if (!IS_ERR(esai_clk)) { ++ priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); ++ priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); ++ clk_put(esai_clk); ++ } else { ++ dev_warn(dev, "failed to get ESAI extal clock: %ld\n", PTR_ERR(esai_clk)); ++ } ++ ++ priv->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL; ++ priv->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL; ++ } else if (!strcmp(comp_drv_name, "fsl-sai")) { ++ priv->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1; ++ priv->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1; ++ ++ if (priv->pdata->exclude_format) ++ priv->exclude_format = priv->pdata->exclude_format; ++ } ++ ++ return 0; ++} ++ ++/* ++ * fsl_asoc_card_init_codecs - read codec MCLK rates and set codec sysclk. ++ * ++ * Called from late_probe() after all components are bound. ++ */ ++static int fsl_asoc_card_init_codecs(struct snd_soc_card *card, ++ struct snd_soc_pcm_runtime *rtd) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ const struct fsl_asoc_card_pdata *pdata = priv->pdata; + struct snd_soc_dai *codec_dai; + struct codec_priv *codec_priv; + struct device *dev = card->dev; + int codec_idx; + int ret; + ++ /* Read MCLK rate from each bound codec component */ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ struct clk *codec_clk = clk_get(codec_dai->component->dev, NULL); ++ ++ codec_priv = &priv->codec_priv[codec_idx]; ++ if (!IS_ERR(codec_clk)) { ++ codec_priv->mclk_freq = clk_get_rate(codec_clk); ++ clk_put(codec_clk); ++ } ++ } ++ ++ if (pdata->codec_init) { ++ ret = pdata->codec_init(priv); ++ if (ret) ++ return ret; ++ } ++ ++ for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { ++ codec_priv = &priv->codec_priv[codec_idx]; ++ ++ ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, ++ codec_priv->mclk_freq, SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) { ++ dev_err(dev, "failed to set sysclk in %s\n", __func__); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static void fsl_asoc_card_free_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ ++ if (priv->hp_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->hp_jack.jack, &hp_jack_nb); ++ snd_soc_jack_free_gpios(&priv->hp_jack.jack, 1, &priv->hp_jack.gpio); ++ priv->hp_jack.gpio.desc = NULL; ++ } ++ ++ if (priv->mic_jack.gpio.desc) { ++ snd_soc_jack_notifier_unregister(&priv->mic_jack.jack, &mic_jack_nb); ++ snd_soc_jack_free_gpios(&priv->mic_jack.jack, 1, &priv->mic_jack.gpio); ++ priv->mic_jack.gpio.desc = NULL; ++ } ++} ++ ++/* ++ * fsl_asoc_card_init_jack - register optional headphone and mic jacks. ++ * ++ * Called from late_probe() once per card bind cycle. ++ */ ++static int fsl_asoc_card_init_jack(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct device_node *np = priv->pdev->dev.of_node; ++ int ret; ++ ++ /* ++ * Properties "hp-det-gpios" and "mic-det-gpios" are optional. ++ * simple_util_init_jack() checks for the GPIO property and ++ * does nothing if it is absent. ++ */ ++ if (of_property_present(np, "hp-det-gpios") || ++ of_property_present(np, "hp-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->hp_jack, ++ 1, NULL, "Headphone Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); ++ } ++ ++ if (of_property_present(np, "mic-det-gpios") || ++ of_property_present(np, "mic-det-gpio") /* deprecated */) { ++ ret = simple_util_init_jack(card, &priv->mic_jack, ++ 0, NULL, "Mic Jack"); ++ if (ret) ++ return ret; ++ ++ snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); ++ } ++ ++ return 0; ++} ++ ++static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ++{ ++ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); ++ struct snd_soc_pcm_runtime *rtd; ++ int ret; ++ ++ /* Use the first rtd which carries the CPU+codec DAIs */ ++ rtd = list_first_entry(&card->rtd_list, ++ struct snd_soc_pcm_runtime, list); ++ ++ ret = fsl_asoc_card_init_jack(card); ++ if (ret) ++ goto jack_fail; ++ ++ ret = fsl_asoc_card_init_cpu(card, rtd); ++ if (ret) ++ goto jack_fail; ++ + if (fsl_asoc_card_is_ac97(priv)) { + #if IS_ENABLED(CONFIG_SND_AC97_CODEC) + struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; +@@ -671,19 +1058,20 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) + return 0; + } + +- for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) { +- codec_priv = &priv->codec_priv[codec_idx]; ++ ret = fsl_asoc_card_init_codecs(card, rtd); ++ if (ret) ++ goto jack_fail; + +- ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, +- codec_priv->mclk_freq, SND_SOC_CLOCK_IN); +- if (ret && ret != -ENOTSUPP) { +- dev_err(dev, "failed to set sysclk in %s\n", __func__); +- return ret; +- } ++ return 0; + +- if (!IS_ERR_OR_NULL(codec_priv->mclk)) +- clk_prepare_enable(codec_priv->mclk); +- } ++jack_fail: ++ fsl_asoc_card_free_jack(card); ++ return ret; ++} ++ ++static int fsl_asoc_card_card_remove(struct snd_soc_card *card) ++{ ++ fsl_asoc_card_free_jack(card); + + return 0; + } +@@ -697,12 +1085,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + struct platform_device *asrc_pdev = NULL; + struct device_node *bitclkprovider = NULL; + struct device_node *frameprovider = NULL; +- struct platform_device *cpu_pdev; + struct fsl_asoc_card_priv *priv; +- struct device *codec_dev[2] = { NULL, NULL }; ++ const struct fsl_asoc_card_pdata *pdata; + struct snd_soc_dai_link_component *dlc; +- const char *codec_dai_name[2]; +- const char *codec_dev_name[2]; ++ const char *codec_dai_name[2] = { NULL, NULL }; + u32 asrc_fmt = 0; + int codec_idx; + u32 width; +@@ -714,6 +1100,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + + priv->pdev = pdev; + ++ pdata = of_device_get_match_data(&pdev->dev); ++ if (!pdata) { ++ dev_err(&pdev->dev, "unknown Device Tree compatible\n"); ++ return -EINVAL; ++ } ++ priv->pdata = pdata; ++ + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + /* Give a chance to old DT bindings */ + if (!cpu_np) +@@ -726,52 +1119,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto fail; + } + +- cpu_pdev = of_find_device_by_node(cpu_np); +- if (!cpu_pdev) { +- ret = dev_err_probe(&pdev->dev, -EPROBE_DEFER, +- "failed to find CPU DAI device\n"); +- goto fail; +- } +- + codec_np[0] = of_parse_phandle(np, "audio-codec", 0); + codec_np[1] = of_parse_phandle(np, "audio-codec", 1); + +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_np[codec_idx]) { +- struct platform_device *codec_pdev; +- struct i2c_client *codec_i2c; +- +- codec_i2c = of_find_i2c_device_by_node(codec_np[codec_idx]); +- if (codec_i2c) { +- codec_dev[codec_idx] = &codec_i2c->dev; +- codec_dev_name[codec_idx] = codec_i2c->name; +- } +- if (!codec_dev[codec_idx]) { +- codec_pdev = of_find_device_by_node(codec_np[codec_idx]); +- if (codec_pdev) { +- codec_dev[codec_idx] = &codec_pdev->dev; +- codec_dev_name[codec_idx] = codec_pdev->name; +- } +- } +- } +- } +- + asrc_np = of_parse_phandle(np, "audio-asrc", 0); + if (asrc_np) + asrc_pdev = of_find_device_by_node(asrc_np); + +- /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ +- for (codec_idx = 0; codec_idx < 2; codec_idx++) { +- if (codec_dev[codec_idx]) { +- struct clk *codec_clk = clk_get(codec_dev[codec_idx], NULL); +- +- if (!IS_ERR(codec_clk)) { +- priv->codec_priv[codec_idx].mclk_freq = clk_get_rate(codec_clk); +- clk_put(codec_clk); +- } +- } +- } +- + /* Default sample rate and format, will be updated in hw_params() */ + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; +@@ -823,133 +1177,40 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + /* Diversify the card configurations */ +- if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { +- codec_dai_name[0] = "cs42888"; +- priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv[0].mclk_freq; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.slot_width = 32; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->constraint_channels = &cs42888_channel_constraints; +- if (priv->codec_priv[0].mclk_freq % 12288000 == 0) +- priv->constraint_rates = &cs42888_rate_48k_constraints; +- else if (priv->codec_priv[0].mclk_freq % 11289600 == 0) +- priv->constraint_rates = &cs42888_rate_44k_constraints; +- else +- dev_warn(&pdev->dev, "Unknown MCLK frequency %lu, no rate constraints\n", +- priv->codec_priv[0].mclk_freq); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) { +- codec_dai_name[0] = "cs4271-hifi"; +- priv->codec_priv[0].mclk_id = CS427x_SYSCLK_MCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { +- codec_dai_name[0] = "sgtl5000"; +- priv->codec_priv[0].mclk_id = SGTL5000_SYSCLK; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) { +- codec_dai_name[0] = "tlv320aic32x4-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic31xx")) { +- codec_dai_name[0] = "tlv320dac31xx-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; ++ priv->cpu_priv.sysclk_dir[TX] = pdata->sysclk_dir[TX]; ++ priv->cpu_priv.sysclk_dir[RX] = pdata->sysclk_dir[RX]; ++ priv->cpu_priv.sysclk_ratio[TX] = pdata->sysclk_ratio[TX]; ++ priv->cpu_priv.sysclk_ratio[RX] = pdata->sysclk_ratio[RX]; ++ priv->cpu_priv.slot_width = pdata->slot_width; ++ ++ codec_dai_name[0] = pdata->codec_dai_name; ++ priv->codec_priv[0].mclk_id = pdata->codec_mclk_id; ++ if (pdata->has_pll) { ++ priv->codec_priv[0].fll_id = pdata->codec_fll_id; ++ priv->codec_priv[0].pll_id = pdata->codec_pll_id; ++ } ++ if (pdata->codec_pll_ratio_s24) ++ priv->codec_priv[0].pll_ratio_s24 = pdata->codec_pll_ratio_s24; ++ ++ if (pdata->playback_only) { + priv->dai_link[1].playback_only = 1; + priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { +- codec_dai_name[0] = "wm8962"; +- priv->codec_priv[0].mclk_id = WM8962_SYSCLK_MCLK; +- priv->codec_priv[0].fll_id = WM8962_SYSCLK_FLL; +- priv->codec_priv[0].pll_id = WM8962_FLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8962 has same BCLK generation limitations as WM8960. +- * See WM8960 section for detailed explanation. +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) { +- codec_dai_name[0] = "wm8960-hifi"; +- priv->codec_priv[0].fll_id = WM8960_SYSCLK_AUTO; +- priv->codec_priv[0].pll_id = WM8960_SYSCLK_AUTO; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- /* +- * WM8960 in master mode cannot generate exact 1.92 MHz BCLK +- * required for S20_3LE (48kHz × 2ch × 20bit). Closest available +- * is 2.048 MHz (SYSCLK/6), which causes right channel corruption. +- * +- * In SAI master mode, SAI derive BCLK from MCLK using integer +- * dividers only. S20_3LE requires non-integer divider ratios +- * with standard MCLK frequencies. For example, 48kHz stereo +- * needs 1.920 MHz BCLK, which requires a divider of 6.4 from +- * 12.288 MHz MCLK (not an integer). +- */ +- if (of_node_name_eq(cpu_np, "sai")) +- priv->exclude_format = SNDRV_PCM_FMTBIT_S20_3LE; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) { +- codec_dai_name[0] = "ac97-hifi"; +- priv->dai_fmt = SND_SOC_DAIFMT_AC97; +- priv->card.dapm_routes = audio_map_ac97; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) { +- codec_dai_name[0] = "fsl-mqs-dai"; +- priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J | +- SND_SOC_DAIFMT_CBC_CFC | +- SND_SOC_DAIFMT_NB_NF; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) { +- codec_dai_name[0] = "wm8524-hifi"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->dai_link[1].playback_only = 1; +- priv->dai_link[2].playback_only = 1; +- priv->cpu_priv.slot_width = 32; +- priv->card.dapm_routes = audio_map_tx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx); +- priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; +- priv->cpu_priv.sysclk_ratio[TX] = 256; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) { +- codec_dai_name[0] = "si476x-codec"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC; +- priv->card.dapm_routes = audio_map_rx; +- priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) { +- codec_dai_name[0] = "wm8994-aif1"; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- priv->codec_priv[0].mclk_id = WM8994_FLL_SRC_MCLK1; +- priv->codec_priv[0].fll_id = WM8994_SYSCLK_FLL1; +- priv->codec_priv[0].pll_id = WM8994_FLL1; +- priv->codec_priv[0].free_freq = priv->codec_priv[0].mclk_freq; +- priv->card.dapm_routes = NULL; +- priv->card.num_dapm_routes = 0; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-nau8822")) { +- codec_dai_name[0] = "nau8822-hifi"; +- priv->codec_priv[0].mclk_id = NAU8822_CLK_MCLK; +- priv->codec_priv[0].fll_id = NAU8822_CLK_PLL; +- priv->codec_priv[0].pll_id = NAU8822_CLK_PLL; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- if (codec_dev[0]) +- priv->codec_priv[0].mclk = devm_clk_get(codec_dev[0], NULL); +- } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8904")) { +- codec_dai_name[0] = "wm8904-hifi"; +- priv->codec_priv[0].mclk_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].fll_id = WM8904_CLK_FLL; +- priv->codec_priv[0].pll_id = WM8904_FLL_MCLK; +- priv->codec_priv[0].pll_ratio_s24 = 192; +- priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP; +- } else if (of_device_is_compatible(np, "fsl,imx-audio-spdif")) { +- ret = fsl_asoc_card_spdif_init(codec_np, cpu_np, codec_dai_name, priv); ++ } ++ if (pdata->capture_only) { ++ priv->dai_link[1].capture_only = 1; ++ priv->dai_link[2].capture_only = 1; ++ } ++ ++ priv->dai_fmt = pdata->dai_fmt; ++ ++ priv->card.dapm_routes = pdata->dapm_routes; ++ priv->card.num_dapm_routes = pdata->num_dapm_routes; ++ ++ if (pdata->probe_init) { ++ ret = pdata->probe_init(codec_np, cpu_np, ++ codec_dai_name, priv); + if (ret) + goto asrc_fail; +- } else { +- dev_err(&pdev->dev, "unknown Device Tree compatible\n"); +- ret = -EINVAL; +- goto asrc_fail; + } + + /* +@@ -995,51 +1256,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + of_node_put(bitclkprovider); + of_node_put(frameprovider); + +- if (!fsl_asoc_card_is_ac97(priv) && !codec_dev[0] +- && codec_dai_name[0] != snd_soc_dummy_dlc.dai_name) { +- dev_dbg(&pdev->dev, "failed to find codec device\n"); +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- /* Common settings for corresponding Freescale CPU DAI driver */ +- if (of_node_name_eq(cpu_np, "ssi")) { +- /* Only SSI needs to configure AUDMUX */ +- ret = fsl_asoc_card_audmux_init(np, priv); +- if (ret) { +- dev_err(&pdev->dev, "failed to init audmux\n"); +- goto asrc_fail; +- } +- } else if (of_node_name_eq(cpu_np, "esai")) { +- struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal"); +- +- if (!IS_ERR(esai_clk)) { +- priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk); +- priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk); +- clk_put(esai_clk); +- } else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) { +- ret = -EPROBE_DEFER; +- goto asrc_fail; +- } +- +- priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; +- priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; +- } else if (of_node_name_eq(cpu_np, "sai")) { +- priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; +- priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; +- } +- + /* Initialize sound card */ + priv->card.dev = &pdev->dev; + priv->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&priv->card, "model"); + if (ret) { +- snprintf(priv->name, sizeof(priv->name), "%s-audio", +- fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name[0]); +- priv->card.name = priv->name; ++ /* ++ * "model" is required by the DT binding. Enforce it here so ++ * the driver fails with a clear message. ++ */ ++ dev_err(&pdev->dev, "Error parsing card name: %d\n", ret); ++ goto asrc_fail; + } + priv->card.dai_link = priv->dai_link; + priv->card.late_probe = fsl_asoc_card_late_probe; ++ priv->card.remove = fsl_asoc_card_card_remove; + priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); + +@@ -1146,39 +1377,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + goto asrc_fail; + } + +- /* +- * Properties "hp-det-gpios" and "mic-det-gpios" are optional, and +- * simple_util_init_jack() uses these properties for creating +- * Headphone Jack and Microphone Jack. +- * +- * The notifier is initialized in snd_soc_card_jack_new(), then +- * snd_soc_jack_notifier_register can be called. +- */ +- if (of_property_present(np, "hp-det-gpios") || +- of_property_present(np, "hp-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->hp_jack, +- 1, NULL, "Headphone Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb); +- } +- +- if (of_property_present(np, "mic-det-gpios") || +- of_property_present(np, "mic-det-gpio") /* deprecated */) { +- ret = simple_util_init_jack(&priv->card, &priv->mic_jack, +- 0, NULL, "Mic Jack"); +- if (ret) +- goto asrc_fail; +- +- snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb); +- } +- + asrc_fail: + of_node_put(asrc_np); + of_node_put(codec_np[0]); + of_node_put(codec_np[1]); +- put_device(&cpu_pdev->dev); + fail: + of_node_put(cpu_np); + +@@ -1186,21 +1388,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id fsl_asoc_card_dt_ids[] = { +- { .compatible = "fsl,imx-audio-ac97", }, +- { .compatible = "fsl,imx-audio-cs42888", }, +- { .compatible = "fsl,imx-audio-cs427x", }, +- { .compatible = "fsl,imx-audio-tlv320aic32x4", }, +- { .compatible = "fsl,imx-audio-tlv320aic31xx", }, +- { .compatible = "fsl,imx-audio-sgtl5000", }, +- { .compatible = "fsl,imx-audio-wm8962", }, +- { .compatible = "fsl,imx-audio-wm8960", }, +- { .compatible = "fsl,imx-audio-mqs", }, +- { .compatible = "fsl,imx-audio-wm8524", }, +- { .compatible = "fsl,imx-audio-si476x", }, +- { .compatible = "fsl,imx-audio-wm8958", }, +- { .compatible = "fsl,imx-audio-nau8822", }, +- { .compatible = "fsl,imx-audio-wm8904", }, +- { .compatible = "fsl,imx-audio-spdif", }, ++ { .compatible = "fsl,imx-audio-ac97", .data = &fsl_asoc_ac97_pdata }, ++ { .compatible = "fsl,imx-audio-cs42888", .data = &fsl_asoc_cs42888_pdata }, ++ { .compatible = "fsl,imx-audio-cs427x", .data = &fsl_asoc_cs427x_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic32x4", .data = &fsl_asoc_tlv320aic32x4_pdata }, ++ { .compatible = "fsl,imx-audio-tlv320aic31xx", .data = &fsl_asoc_tlv320aic31xx_pdata }, ++ { .compatible = "fsl,imx-audio-sgtl5000", .data = &fsl_asoc_sgtl5000_pdata }, ++ { .compatible = "fsl,imx-audio-wm8962", .data = &fsl_asoc_wm8962_pdata }, ++ { .compatible = "fsl,imx-audio-wm8960", .data = &fsl_asoc_wm8960_pdata }, ++ { .compatible = "fsl,imx-audio-mqs", .data = &fsl_asoc_mqs_pdata }, ++ { .compatible = "fsl,imx-audio-wm8524", .data = &fsl_asoc_wm8524_pdata }, ++ { .compatible = "fsl,imx-audio-si476x", .data = &fsl_asoc_si476x_pdata }, ++ { .compatible = "fsl,imx-audio-wm8958", .data = &fsl_asoc_wm8958_pdata }, ++ { .compatible = "fsl,imx-audio-nau8822", .data = &fsl_asoc_nau8822_pdata }, ++ { .compatible = "fsl,imx-audio-wm8904", .data = &fsl_asoc_wm8904_pdata }, ++ { .compatible = "fsl,imx-audio-spdif", .data = &fsl_asoc_spdif_pdata }, + {} + }; + MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); +diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c +--- a/sound/soc/fsl/fsl_asrc.c ++++ b/sound/soc/fsl/fsl_asrc.c +@@ -222,10 +222,9 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc = pair->asrc; + struct device *dev = &asrc->pdev->dev; +- unsigned long lock_flags; + int i, ret = 0; + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc->pair[i] != NULL) +@@ -250,8 +249,6 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) + pair->index = index; + } + +- spin_unlock_irqrestore(&asrc->lock, lock_flags); +- + return ret; + } + +@@ -265,19 +262,16 @@ static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) + { + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; +- unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + +- spin_lock_irqsave(&asrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&asrc->lock); + + asrc->channel_avail += pair->channels; + asrc->pair[index] = NULL; + pair->error = 0; +- +- spin_unlock_irqrestore(&asrc->lock, lock_flags); + } + + /** +diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h +--- a/sound/soc/fsl/fsl_asrc.h ++++ b/sound/soc/fsl/fsl_asrc.h +@@ -444,7 +444,7 @@ struct dma_block { + }; + + /** +- * fsl_asrc_soc_data: soc specific data ++ * struct fsl_asrc_soc_data - soc specific data + * + * @use_edma: using edma as dma device or not + * @channel_bits: width of ASRCNCR register for each pair +@@ -457,7 +457,7 @@ struct fsl_asrc_soc_data { + }; + + /** +- * fsl_asrc_pair_priv: ASRC Pair private data ++ * struct fsl_asrc_pair_priv - ASRC Pair private data + * + * @config: configuration profile + */ +@@ -466,7 +466,7 @@ struct fsl_asrc_pair_priv { + }; + + /** +- * fsl_asrc_priv: ASRC private data ++ * struct fsl_asrc_priv - ASRC private data + * + * @asrck_clk: clock sources to driver ASRC internal logic + * @soc: soc specific data +diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c +--- a/sound/soc/fsl/fsl_asrc_dma.c ++++ b/sound/soc/fsl/fsl_asrc_dma.c +@@ -219,7 +219,9 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, + */ + component_be = snd_soc_lookup_component_nolocked(dev_be, SND_DMAENGINE_PCM_DRV_NAME); + if (component_be) { +- be_chan = soc_component_to_pcm(component_be)->chan[substream->stream]; ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component_be); ++ ++ be_chan = pcm->chan[substream->stream]; + tmp_chan = be_chan; + } + if (!tmp_chan) { +diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c +--- a/sound/soc/fsl/fsl_audmix.c ++++ b/sound/soc/fsl/fsl_audmix.c +@@ -286,7 +286,6 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) + { + struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai); +- unsigned long lock_flags; + + /* Capture stream shall not be handled */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) +@@ -296,16 +295,14 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms |= BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms |= BIT(dai->driver->id); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&priv->lock, lock_flags); +- priv->tdms &= ~BIT(dai->driver->id); +- spin_unlock_irqrestore(&priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &priv->lock) ++ priv->tdms &= ~BIT(dai->driver->id); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c +--- a/sound/soc/fsl/fsl_dma.c ++++ b/sound/soc/fsl/fsl_dma.c +@@ -18,8 +18,6 @@ + #include + #include + #include +-#include +-#include + #include + #include + +@@ -824,9 +822,34 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np; + struct resource res; ++ void __iomem *channel; + const uint32_t *iprop; ++ int irq; + int ret; + ++ channel = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(channel)) ++ return PTR_ERR(channel); ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ ++ dma = devm_kzalloc(&pdev->dev, sizeof(*dma), GFP_KERNEL); ++ if (!dma) ++ return -ENOMEM; ++ ++ dma->dai.name = DRV_NAME; ++ dma->dai.open = fsl_dma_open; ++ dma->dai.close = fsl_dma_close; ++ dma->dai.hw_params = fsl_dma_hw_params; ++ dma->dai.hw_free = fsl_dma_hw_free; ++ dma->dai.pointer = fsl_dma_pointer; ++ dma->dai.pcm_new = fsl_dma_new; ++ ++ dma->channel = channel; ++ dma->irq = irq; ++ + /* Find the SSI node that points to us. */ + ssi_np = find_ssi_node(np); + if (!ssi_np) { +@@ -842,55 +865,19 @@ static int fsl_soc_dma_probe(struct platform_device *pdev) + return ret; + } + +- dma = kzalloc_obj(*dma); +- if (!dma) { +- of_node_put(ssi_np); +- return -ENOMEM; +- } +- +- dma->dai.name = DRV_NAME; +- dma->dai.open = fsl_dma_open; +- dma->dai.close = fsl_dma_close; +- dma->dai.hw_params = fsl_dma_hw_params; +- dma->dai.hw_free = fsl_dma_hw_free; +- dma->dai.pointer = fsl_dma_pointer; +- dma->dai.pcm_new = fsl_dma_new; +- + /* Store the SSI-specific information that we need */ + dma->ssi_stx_phys = res.start + REG_SSI_STX0; + dma->ssi_srx_phys = res.start + REG_SSI_SRX0; + + iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); ++ of_node_put(ssi_np); + if (iprop) + dma->ssi_fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + dma->ssi_fifo_depth = 8; + +- of_node_put(ssi_np); +- +- ret = devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register platform\n"); +- kfree(dma); +- return ret; +- } +- +- dma->channel = of_iomap(np, 0); +- dma->irq = irq_of_parse_and_map(np, 0); +- +- dev_set_drvdata(&pdev->dev, dma); +- +- return 0; +-} +- +-static void fsl_soc_dma_remove(struct platform_device *pdev) +-{ +- struct dma_object *dma = dev_get_drvdata(&pdev->dev); +- +- iounmap(dma->channel); +- irq_dispose_mapping(dma->irq); +- kfree(dma); ++ return devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0); + } + + static const struct of_device_id fsl_soc_dma_ids[] = { +@@ -905,7 +892,6 @@ static struct platform_driver fsl_soc_dma_driver = { + .of_match_table = fsl_soc_dma_ids, + }, + .probe = fsl_soc_dma_probe, +- .remove = fsl_soc_dma_remove, + }; + + module_platform_driver(fsl_soc_dma_driver); +diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h +--- a/sound/soc/fsl/fsl_dma.h ++++ b/sound/soc/fsl/fsl_dma.h +@@ -92,7 +92,7 @@ static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) + #define CCSR_DMA_ATR_SNOOP 0x00050000 + #define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +-/** ++/* + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List +@@ -107,7 +107,7 @@ struct fsl_dma_list_descriptor { + u8 res[8]; /* Reserved */ + } __attribute__ ((aligned(32), packed)); + +-/** ++/* + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor +diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c +--- a/sound/soc/fsl/fsl_easrc.c ++++ b/sound/soc/fsl/fsl_easrc.c +@@ -1025,7 +1025,6 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; + struct device *dev; +- unsigned long lock_flags; + int ret; + + if (!easrc) +@@ -1053,9 +1052,8 @@ static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id) + if (ret) + return ret; + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- ret = fsl_easrc_config_slot(easrc, ctx->index); +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ ret = fsl_easrc_config_slot(easrc, ctx->index); + if (ret) + return ret; + +@@ -1301,13 +1299,12 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *easrc = ctx->asrc; + struct device *dev; +- unsigned long lock_flags; + int ret = 0; + int i; + + dev = &easrc->pdev->dev; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) { + if (easrc->pair[i]) +@@ -1331,8 +1328,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + easrc->channel_avail -= channels; + } + +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- + return ret; + } + +@@ -1343,7 +1338,6 @@ static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx) + */ + static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + { +- unsigned long lock_flags; + struct fsl_asrc *easrc; + + if (!ctx) +@@ -1351,14 +1345,12 @@ static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx) + + easrc = ctx->asrc; + +- spin_lock_irqsave(&easrc->lock, lock_flags); ++ guard(spinlock_irqsave)(&easrc->lock); + + fsl_easrc_release_slot(easrc, ctx->index); + + easrc->channel_avail += ctx->channels; + easrc->pair[ctx->index] = NULL; +- +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + } + + /* +@@ -2292,15 +2284,13 @@ static int fsl_easrc_runtime_suspend(struct device *dev) + { + struct fsl_asrc *easrc = dev_get_drvdata(dev); + struct fsl_easrc_priv *easrc_priv = easrc->private; +- unsigned long lock_flags; + + regcache_cache_only(easrc->regmap, true); + + clk_disable_unprepare(easrc->mem_clk); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- easrc_priv->firmware_loaded = 0; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &easrc->lock) ++ easrc_priv->firmware_loaded = 0; + + return 0; + } +@@ -2311,7 +2301,6 @@ static int fsl_easrc_runtime_resume(struct device *dev) + struct fsl_easrc_priv *easrc_priv = easrc->private; + struct fsl_easrc_ctx_priv *ctx_priv; + struct fsl_asrc_pair *ctx; +- unsigned long lock_flags; + int ret; + int i; + +@@ -2323,13 +2312,11 @@ static int fsl_easrc_runtime_resume(struct device *dev) + regcache_mark_dirty(easrc->regmap); + regcache_sync(easrc->regmap); + +- spin_lock_irqsave(&easrc->lock, lock_flags); +- if (easrc_priv->firmware_loaded) { +- spin_unlock_irqrestore(&easrc->lock, lock_flags); +- goto skip_load; ++ scoped_guard(spinlock_irqsave, &easrc->lock) { ++ if (easrc_priv->firmware_loaded) ++ return 0; ++ easrc_priv->firmware_loaded = 1; + } +- easrc_priv->firmware_loaded = 1; +- spin_unlock_irqrestore(&easrc->lock, lock_flags); + + ret = fsl_easrc_get_firmware(easrc); + if (ret) { +@@ -2377,9 +2364,7 @@ static int fsl_easrc_runtime_resume(struct device *dev) + goto disable_mem_clk; + } + +-skip_load: + return 0; +- + disable_mem_clk: + clk_disable_unprepare(easrc->mem_clk); + return ret; +diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h +--- a/sound/soc/fsl/fsl_easrc.h ++++ b/sound/soc/fsl/fsl_easrc.h +@@ -584,7 +584,7 @@ struct fsl_easrc_slot { + }; + + /** +- * fsl_easrc_ctx_priv: EASRC context private data ++ * struct fsl_easrc_ctx_priv - EASRC context private data + * + * @in_params: input parameter + * @out_params: output parameter +@@ -625,7 +625,7 @@ struct fsl_easrc_ctx_priv { + }; + + /** +- * fsl_easrc_priv: EASRC private data ++ * struct fsl_easrc_priv - EASRC private data + * + * @slot: slot setting + * @firmware_hdr: the header of firmware +diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c +--- a/sound/soc/fsl/fsl_esai.c ++++ b/sound/soc/fsl/fsl_esai.c +@@ -709,10 +709,9 @@ static void fsl_esai_hw_reset(struct work_struct *work) + { + struct fsl_esai *esai_priv = container_of(work, struct fsl_esai, work); + bool tx = true, rx = false, enabled[2]; +- unsigned long lock_flags; + u32 tfcr, rfcr; + +- spin_lock_irqsave(&esai_priv->lock, lock_flags); ++ guard(spinlock_irqsave)(&esai_priv->lock); + /* Save the registers */ + regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr); + regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr); +@@ -750,8 +749,6 @@ static void fsl_esai_hw_reset(struct work_struct *work) + fsl_esai_trigger_start(esai_priv, tx); + if (enabled[rx]) + fsl_esai_trigger_start(esai_priv, rx); +- +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); + } + + static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, +@@ -759,7 +756,6 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + + esai_priv->channels[tx] = substream->runtime->channels; + +@@ -767,16 +763,14 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_start(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_start(esai_priv, tx); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +- spin_lock_irqsave(&esai_priv->lock, lock_flags); +- fsl_esai_trigger_stop(esai_priv, tx); +- spin_unlock_irqrestore(&esai_priv->lock, lock_flags); ++ scoped_guard(spinlock_irqsave, &esai_priv->lock) ++ fsl_esai_trigger_stop(esai_priv, tx); + break; + default: + return -EINVAL; +diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c +--- a/sound/soc/fsl/fsl_spdif.c ++++ b/sound/soc/fsl/fsl_spdif.c +@@ -853,17 +853,15 @@ static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +@@ -885,17 +883,15 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; +- unsigned long flags; + int ret = -EAGAIN; + +- spin_lock_irqsave(&ctrl->ctl_lock, flags); ++ guard(spinlock_irqsave)(&ctrl->ctl_lock); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + ret = 0; + } +- spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; + } +diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c +--- a/sound/soc/fsl/fsl_ssi.c ++++ b/sound/soc/fsl/fsl_ssi.c +@@ -1218,13 +1218,13 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + if (reg > 0x7f) + return; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 write clk_prepare_enable failed: %d\n", + ret); +- goto ret_unlock; ++ return; + } + + lreg = reg << 12; +@@ -1238,9 +1238,6 @@ static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + udelay(100); + + clk_disable_unprepare(fsl_ac97_data->clk); +- +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + } + + static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, +@@ -1252,12 +1249,12 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned int lreg; + int ret; + +- mutex_lock(&fsl_ac97_data->ac97_reg_lock); ++ guard(mutex)(&fsl_ac97_data->ac97_reg_lock); + + ret = clk_prepare_enable(fsl_ac97_data->clk); + if (ret) { + pr_err("ac97 read clk_prepare_enable failed: %d\n", ret); +- goto ret_unlock; ++ return val; + } + + lreg = (reg & 0x7f) << 12; +@@ -1272,8 +1269,6 @@ static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + + clk_disable_unprepare(fsl_ac97_data->clk); + +-ret_unlock: +- mutex_unlock(&fsl_ac97_data->ac97_reg_lock); + return val; + } + +diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c +--- a/sound/soc/fsl/fsl_xcvr.c ++++ b/sound/soc/fsl/fsl_xcvr.c +@@ -797,10 +797,9 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + { + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +- unsigned long lock_flags; + int ret = 0; + +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -812,7 +811,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DPTH_RESET(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to set DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -824,7 +823,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, "err updating isr %d\n", ret); +- goto release_lock; ++ return ret; + } + fallthrough; + case FSL_XCVR_MODE_SPDIF: +@@ -833,7 +832,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -844,14 +843,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL); + if (ret < 0) { + dev_err(dai->dev, "Error while setting IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + /* clear DPATH RESET */ +@@ -860,7 +859,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear DPATH RESET: %d\n", ret); +- goto release_lock; ++ return ret; + } + + break; +@@ -873,14 +872,14 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to disable DMA: %d\n", ret); +- goto release_lock; ++ return ret; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to clear IER0: %d\n", ret); +- goto release_lock; ++ return ret; + } + + if (tx) { +@@ -891,7 +890,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret); +- goto release_lock; ++ return ret; + } + if (xcvr->soc_data->spdif_only) + break; +@@ -905,7 +904,7 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + if (ret < 0) { + dev_err(dai->dev, + "Err updating ISR %d\n", ret); +- goto release_lock; ++ return ret; + } + break; + } +@@ -916,18 +915,16 @@ static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + break; + } + +-release_lock: +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + return ret; + } + + static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + { + struct device *dev = &xcvr->pdev->dev; +- const struct firmware *fw; + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; + u32 mask, val; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to request firmware.\n"); +@@ -939,7 +936,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ + if (rem > 16384) { + dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); +- release_firmware(fw); + return -ENOMEM; + } + +@@ -950,7 +946,7 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + if (ret < 0) { + dev_err(dev, "FW: failed to set page %d, err=%d\n", + page, ret); +- goto err_firmware; ++ return ret; + } + + off = page * size; +@@ -971,11 +967,6 @@ static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) + } + } + +-err_firmware: +- release_firmware(fw); +- if (ret < 0) +- return ret; +- + /* configure watermarks */ + mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; + val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); +@@ -1448,11 +1439,10 @@ static void reset_rx_work(struct work_struct *work) + { + struct fsl_xcvr *xcvr = container_of(work, struct fsl_xcvr, work_rst); + struct device *dev = &xcvr->pdev->dev; +- unsigned long lock_flags; + u32 ext_ctrl; + + dev_dbg(dev, "reset rx path\n"); +- spin_lock_irqsave(&xcvr->lock, lock_flags); ++ guard(spinlock_irqsave)(&xcvr->lock); + regmap_read(xcvr->regmap, FSL_XCVR_EXT_CTRL, &ext_ctrl); + + if (!(ext_ctrl & FSL_XCVR_EXT_CTRL_DMA_RD_DIS)) { +@@ -1469,7 +1459,6 @@ static void reset_rx_work(struct work_struct *work) + FSL_XCVR_EXT_CTRL_RX_DPTH_RESET, + 0); + } +- spin_unlock_irqrestore(&xcvr->lock, lock_flags); + } + + static irqreturn_t irq0_isr(int irq, void *devid) +diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c +--- a/sound/soc/fsl/imx-audio-rpmsg.c ++++ b/sound/soc/fsl/imx-audio-rpmsg.c +@@ -22,7 +22,6 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data; + struct rpmsg_info *info; + struct rpmsg_msg *msg; +- unsigned long flags; + + if (!rpmsg->rpmsg_pdev) + return 0; +@@ -37,21 +36,21 @@ static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + /* TYPE C is notification from M core */ + switch (r_msg->header.cmd) { + case TX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[TX], flags); +- msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[TX]; +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[TX]; ++ } + info->callback[TX](info->callback_param[TX]); + break; + case RX_PERIOD_DONE: +- spin_lock_irqsave(&info->lock[RX], flags); +- msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; +- msg->r_msg.param.buffer_tail = +- r_msg->param.buffer_tail; +- msg->r_msg.param.buffer_tail %= info->num_period[1]; +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; ++ msg->r_msg.param.buffer_tail = ++ r_msg->param.buffer_tail; ++ msg->r_msg.param.buffer_tail %= info->num_period[1]; ++ } + info->callback[RX](info->callback_param[RX]); + break; + default: +diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c +--- a/sound/soc/fsl/imx-pcm-rpmsg.c ++++ b/sound/soc/fsl/imx-pcm-rpmsg.c +@@ -39,10 +39,9 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + struct rpmsg_device *rpdev = info->rpdev; + int ret = 0; + +- mutex_lock(&info->msg_lock); ++ guard(mutex)(&info->msg_lock); + if (!rpdev) { + dev_err(info->dev, "rpmsg channel not ready\n"); +- mutex_unlock(&info->msg_lock); + return -EINVAL; + } + +@@ -55,15 +54,12 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + sizeof(struct rpmsg_s_msg)); + if (ret) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); +- mutex_unlock(&info->msg_lock); + return ret; + } + + /* No receive msg for TYPE_C command */ +- if (msg->s_msg.header.type == MSG_TYPE_C) { +- mutex_unlock(&info->msg_lock); ++ if (msg->s_msg.header.type == MSG_TYPE_C) + return 0; +- } + + /* wait response from rpmsg */ + ret = wait_for_completion_timeout(&info->cmd_complete, +@@ -71,7 +67,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + if (!ret) { + dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n", + msg->s_msg.header.cmd); +- mutex_unlock(&info->msg_lock); + return -ETIMEDOUT; + } + +@@ -100,8 +95,6 @@ static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd, + info->r_msg.param.resp); + +- mutex_unlock(&info->msg_lock); +- + return 0; + } + +@@ -109,14 +102,13 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + struct rpmsg_msg *msg, + struct rpmsg_info *info) + { +- unsigned long flags; + int ret = 0; + + /* + * Queue the work to workqueue. + * If the queue is full, drop the message. + */ +- spin_lock_irqsave(&info->wq_lock, flags); ++ guard(spinlock_irqsave)(&info->wq_lock); + if (info->work_write_index != info->work_read_index) { + int index = info->work_write_index; + +@@ -130,7 +122,6 @@ static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + info->msg_drop_count[substream->stream]++; + ret = -EPIPE; + } +- spin_unlock_irqrestore(&info->wq_lock, flags); + + return ret; + } +@@ -523,7 +514,6 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + snd_pcm_sframes_t avail; + struct timer_list *timer; + struct rpmsg_msg *msg; +- unsigned long flags; + int buffer_tail = 0; + int written_num; + +@@ -553,11 +543,11 @@ static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + msg->s_msg.param.buffer_tail = buffer_tail; + + /* The notification message is updated to latest */ +- spin_lock_irqsave(&info->lock[substream->stream], flags); +- memcpy(&info->notify[substream->stream], msg, +- sizeof(struct rpmsg_s_msg)); +- info->notify_updated[substream->stream] = true; +- spin_unlock_irqrestore(&info->lock[substream->stream], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[substream->stream]) { ++ memcpy(&info->notify[substream->stream], msg, ++ sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[substream->stream] = true; ++ } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_hw_avail(runtime); +@@ -641,7 +631,7 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + bool is_notification = false; + struct rpmsg_info *info; + struct rpmsg_msg msg; +- unsigned long flags; ++ bool updated; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + info = work_of_rpmsg->info; +@@ -652,25 +642,26 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + * enough data in M core side, need to let M core know + * data is updated immediately. + */ +- spin_lock_irqsave(&info->lock[TX], flags); +- if (info->notify_updated[TX]) { +- memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[TX] = false; +- spin_unlock_irqrestore(&info->lock[TX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[TX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[TX]) { ++ updated = info->notify_updated[TX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[TX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); + +- spin_lock_irqsave(&info->lock[RX], flags); +- if (info->notify_updated[RX]) { +- memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); +- info->notify_updated[RX] = false; +- spin_unlock_irqrestore(&info->lock[RX], flags); +- info->send_message(&msg, info); +- } else { +- spin_unlock_irqrestore(&info->lock[RX], flags); ++ scoped_guard(spinlock_irqsave, &info->lock[RX]) { ++ updated = info->notify_updated[RX]; ++ if (updated) { ++ memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); ++ info->notify_updated[RX] = false; ++ } + } ++ if (updated) ++ info->send_message(&msg, info); ++ + + /* Skip the notification message for it has been processed above */ + if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C && +@@ -682,10 +673,10 @@ static void imx_rpmsg_pcm_work(struct work_struct *work) + info->send_message(&work_of_rpmsg->msg, info); + + /* update read index */ +- spin_lock_irqsave(&info->wq_lock, flags); +- info->work_read_index++; +- info->work_read_index %= WORK_MAX_NUM; +- spin_unlock_irqrestore(&info->wq_lock, flags); ++ scoped_guard(spinlock_irqsave, &info->wq_lock) { ++ info->work_read_index++; ++ info->work_read_index %= WORK_MAX_NUM; ++ } + } + + static int imx_rpmsg_pcm_probe(struct platform_device *pdev) +@@ -819,9 +810,9 @@ static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = { + }; + + static const struct platform_device_id imx_rpmsg_pcm_id_table[] = { +- { .name = "rpmsg-audio-channel" }, +- { .name = "rpmsg-micfil-channel" }, +- { }, ++ { .name = "rpmsg-audio-channel" }, ++ { .name = "rpmsg-micfil-channel" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, imx_rpmsg_pcm_id_table); + +diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c +--- a/sound/soc/fsl/mpc5200_dma.c ++++ b/sound/soc/fsl/mpc5200_dma.c +@@ -77,18 +77,20 @@ static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) + { + struct psc_dma_stream *s = _psc_dma_stream; + +- spin_lock(&s->psc_dma->lock); +- /* For each finished period, dequeue the completed period buffer +- * and enqueue a new one in it's place. */ +- while (bcom_buffer_done(s->bcom_task)) { +- bcom_retrieve_buffer(s->bcom_task, NULL, NULL); ++ scoped_guard(spinlock, &s->psc_dma->lock) { ++ /* ++ * For each finished period, dequeue the completed period buffer ++ * and enqueue a new one in its place ++ */ ++ while (bcom_buffer_done(s->bcom_task)) { ++ bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + +- s->period_current = (s->period_current+1) % s->runtime->periods; +- s->period_count++; ++ s->period_current = (s->period_current+1) % s->runtime->periods; ++ s->period_count++; + +- psc_dma_bcom_enqueue_next_buffer(s); ++ psc_dma_bcom_enqueue_next_buffer(s); ++ } + } +- spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ +@@ -116,7 +118,6 @@ static int psc_dma_trigger(struct snd_soc_component *component, + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; +- unsigned long flags; + int i; + + switch (cmd) { +@@ -135,19 +136,18 @@ static int psc_dma_trigger(struct snd_soc_component *component, + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ +- spin_lock_irqsave(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); + +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); ++ for (i = 0; i < runtime->periods; i++) ++ if (!bcom_queue_full(s->bcom_task)) ++ psc_dma_bcom_enqueue_next_buffer(s); + +- for (i = 0; i < runtime->periods; i++) +- if (!bcom_queue_full(s->bcom_task)) +- psc_dma_bcom_enqueue_next_buffer(s); +- +- bcom_enable(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ bcom_enable(s->bcom_task); ++ } + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + +@@ -158,13 +158,13 @@ static int psc_dma_trigger(struct snd_soc_component *component, + substream->pstr->stream, s->period_count); + s->active = 0; + +- spin_lock_irqsave(&psc_dma->lock, flags); +- bcom_disable(s->bcom_task); +- if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) +- bcom_gen_bd_rx_reset(s->bcom_task); +- else +- bcom_gen_bd_tx_reset(s->bcom_task); +- spin_unlock_irqrestore(&psc_dma->lock, flags); ++ scoped_guard(spinlock_irqsave, &psc_dma->lock) { ++ bcom_disable(s->bcom_task); ++ if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) ++ bcom_gen_bd_rx_reset(s->bcom_task); ++ else ++ bcom_gen_bd_tx_reset(s->bcom_task); ++ } + + break; + +@@ -314,35 +314,29 @@ int mpc5200_audio_dma_create(struct platform_device *op) + { + phys_addr_t fifo; + struct psc_dma *psc_dma; +- struct resource res; ++ struct resource *res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; +- int ret; ++ ++ regs = devm_platform_get_and_ioremap_resource(op, 0, &res); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); + + /* Fetch the registers and IRQ of the PSC */ +- irq = irq_of_parse_and_map(op->dev.of_node, 0); +- if (of_address_to_resource(op->dev.of_node, 0, &res)) { +- dev_err(&op->dev, "Missing reg property\n"); +- return -ENODEV; +- } +- regs = devm_ioremap(&op->dev, res.start, resource_size(&res)); +- if (!regs) { +- dev_err(&op->dev, "Could not map registers\n"); +- return -ENODEV; +- } ++ irq = platform_get_irq(op, 0); ++ if (irq < 0) ++ return irq; + + /* Allocate and initialize the driver private data */ +- psc_dma = kzalloc_obj(*psc_dma); ++ psc_dma = devm_kzalloc(&op->dev, sizeof(*psc_dma), GFP_KERNEL); + if (!psc_dma) + return -ENOMEM; + + /* Get the PSC ID */ + prop = of_get_property(op->dev.of_node, "cell-index", &size); +- if (!prop || size < sizeof *prop) { +- ret = -ENODEV; +- goto out_free; +- } ++ if (!prop || size < sizeof *prop) ++ return -ENODEV; + + spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); +@@ -357,7 +351,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ +- fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); ++ fifo = res->start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = +@@ -365,8 +359,7 @@ int mpc5200_audio_dma_create(struct platform_device *op) + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); +- ret = -ENODEV; +- goto out_free; ++ return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ +@@ -399,16 +392,14 @@ int mpc5200_audio_dma_create(struct platform_device *op) + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + +- rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, ++ rc = devm_request_irq(&op->dev, psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); +- rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); +- rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, ++ rc |= devm_request_irq(&op->dev, psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); +- if (rc) { +- ret = -ENODEV; +- goto out_irq; +- } ++ if (rc) ++ return -ENODEV; + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); +@@ -416,13 +407,6 @@ int mpc5200_audio_dma_create(struct platform_device *op) + /* Tell the ASoC OF helpers about it */ + return devm_snd_soc_register_component(&op->dev, + &mpc5200_audio_dma_component, NULL, 0); +-out_irq: +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +-out_free: +- kfree(psc_dma); +- return ret; + } + EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +@@ -435,12 +419,6 @@ int mpc5200_audio_dma_destroy(struct platform_device *op) + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + +- /* Release irqs */ +- free_irq(psc_dma->irq, psc_dma); +- free_irq(psc_dma->capture.irq, &psc_dma->capture); +- free_irq(psc_dma->playback.irq, &psc_dma->playback); +- +- kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c +--- a/sound/soc/fsl/mpc5200_psc_ac97.c ++++ b/sound/soc/fsl/mpc5200_psc_ac97.c +@@ -30,14 +30,13 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + int status; + unsigned int val; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + +@@ -53,19 +52,16 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); +- mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + +- mutex_unlock(&psc_dma->mutex); + return (unsigned short) val; + } + +@@ -74,52 +70,46 @@ static void psc_ac97_write(struct snd_ac97 *ac97, + { + int status; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); +- goto out; ++ return; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); +- +- out: +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_warm_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); ++ guard(mutex)(&psc_dma->mutex); + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); +- +- mutex_unlock(&psc_dma->mutex); + } + + static void psc_ac97_cold_reset(struct snd_ac97 *ac97) + { + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + +- mutex_lock(&psc_dma->mutex); +- dev_dbg(psc_dma->dev, "cold reset\n"); ++ scoped_guard(mutex, &psc_dma->mutex) { ++ dev_dbg(psc_dma->dev, "cold reset\n"); + +- mpc5200_psc_ac97_gpio_reset(psc_dma->id); ++ mpc5200_psc_ac97_gpio_reset(psc_dma->id); + +- /* Notify the PSC that a reset has occurred */ +- out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); ++ /* Notify the PSC that a reset has occurred */ ++ out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); + +- /* Re-enable RX and TX */ +- out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); +- +- mutex_unlock(&psc_dma->mutex); ++ /* Re-enable RX and TX */ ++ out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); ++ } + + usleep_range(1000, 2000); + psc_ac97_warm_reset(ac97); +@@ -286,7 +276,7 @@ static int psc_ac97_of_probe(struct platform_device *op) + return rc; + } + +- rc = snd_soc_register_component(&op->dev, &psc_ac97_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_ac97_component, + psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); +@@ -312,8 +302,6 @@ static int psc_ac97_of_probe(struct platform_device *op) + static void psc_ac97_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); +- snd_soc_set_ac97_ops(NULL); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c +--- a/sound/soc/fsl/mpc5200_psc_i2s.c ++++ b/sound/soc/fsl/mpc5200_psc_i2s.c +@@ -79,9 +79,12 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * ++ * @cpu_dai: DAI runtime data pointer + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +@@ -101,7 +104,10 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * ++ * @cpu_dai: DAI runtime data pointer + * @format: one of SND_SOC_DAIFMT_xxx ++ * ++ * Returns: %0 on success or %-EINVAL on failure. + */ + static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + { +@@ -119,7 +125,7 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) + */ + + /** +- * psc_i2s_dai_template: template CPU Digital Audio Interface ++ * var psc_i2s_dai_ops - template CPU Digital Audio Interface + */ + static const struct snd_soc_dai_ops psc_i2s_dai_ops = { + .hw_params = psc_i2s_hw_params, +@@ -166,7 +172,7 @@ static int psc_i2s_of_probe(struct platform_device *op) + if (rc != 0) + return rc; + +- rc = snd_soc_register_component(&op->dev, &psc_i2s_component, ++ rc = devm_snd_soc_register_component(&op->dev, &psc_i2s_component, + psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); +@@ -213,7 +219,6 @@ static int psc_i2s_of_probe(struct platform_device *op) + static void psc_i2s_of_remove(struct platform_device *op) + { + mpc5200_audio_dma_destroy(op); +- snd_soc_unregister_component(&op->dev); + } + + /* Match table for of_platform binding */ +diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c +--- a/sound/soc/fsl/p1022_ds.c ++++ b/sound/soc/fsl/p1022_ds.c +@@ -73,6 +73,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_ds_machine_probe: initialize the board + * +@@ -82,8 +84,7 @@ struct machine_data { + */ + static int p1022_ds_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -122,8 +123,7 @@ static int p1022_ds_machine_probe(struct snd_soc_card *card) + static int p1022_ds_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -156,8 +156,7 @@ static int p1022_ds_startup(struct snd_pcm_substream *substream) + */ + static int p1022_ds_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -399,8 +398,7 @@ static int p1022_ds_probe(struct platform_device *pdev) + static void p1022_ds_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c +--- a/sound/soc/fsl/p1022_rdk.c ++++ b/sound/soc/fsl/p1022_rdk.c +@@ -79,6 +79,8 @@ struct machine_data { + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ + }; + ++#define card_to_mdata(_card) container_of(_card, struct machine_data, card) ++ + /** + * p1022_rdk_machine_probe - initialize the board + * @card: ASoC card instance +@@ -91,8 +93,7 @@ struct machine_data { + */ + static int p1022_rdk_machine_probe(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -134,8 +135,7 @@ static int p1022_rdk_machine_probe(struct snd_soc_card *card) + static int p1022_rdk_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct machine_data *mdata = +- container_of(rtd->card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret = 0; + +@@ -169,8 +169,7 @@ static int p1022_rdk_startup(struct snd_pcm_substream *substream) + */ + static int p1022_rdk_machine_remove(struct snd_soc_card *card) + { +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); +@@ -361,8 +360,7 @@ static int p1022_rdk_probe(struct platform_device *pdev) + static void p1022_rdk_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); +- struct machine_data *mdata = +- container_of(card, struct machine_data, card); ++ struct machine_data *mdata = card_to_mdata(card); + + snd_soc_unregister_card(card); + kfree(mdata); +diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c +--- a/sound/soc/generic/audio-graph-card.c ++++ b/sound/soc/generic/audio-graph-card.c +@@ -579,11 +579,11 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) + goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); + if (ret < 0) + goto end; + +@@ -594,6 +594,7 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -603,14 +604,13 @@ int audio_graph_parse_of(struct simple_util_priv *priv, struct device *dev) + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph_parse_of); + +diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c +--- a/sound/soc/generic/audio-graph-card2.c ++++ b/sound/soc/generic/audio-graph-card2.c +@@ -1303,11 +1303,11 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + struct graph2_custom_hooks *hooks) + { + struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; ++ int ret = -ENOMEM; + + struct link_info *li __free(kfree) = kzalloc_obj(*li); + if (!li) +- return -ENOMEM; ++ goto end; + + card->probe = graph_util_card_probe; + card->owner = THIS_MODULE; +@@ -1316,31 +1316,35 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if ((hooks) && (hooks)->hook_pre) { + ret = (hooks)->hook_pre(priv); + if (ret < 0) +- goto err; ++ goto end; + } + + ret = graph_for_each_link(priv, hooks, li, graph_count); + if (!li->link) + ret = -EINVAL; + if (ret < 0) +- goto err; ++ goto end; + + ret = simple_util_init_priv(priv, li); + if (ret < 0) +- goto err; ++ goto end; + + priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); + if (IS_ERR(priv->pa_gpio)) { + ret = PTR_ERR(priv->pa_gpio); + dev_err(dev, "failed to get amplifier gpio: %d\n", ret); +- goto err; ++ goto end; + } + +- ret = simple_util_parse_widgets(card, NULL); ++ ret = simple_util_parse_widgets(priv, NULL); + if (ret < 0) +- goto err; ++ goto end; + +- ret = simple_util_parse_routing(card, NULL); ++ ret = simple_util_parse_routing(priv, NULL); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, NULL); + if (ret < 0) + goto err; + +@@ -1349,6 +1353,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + if (ret < 0) + goto err; + ++ /* Card name should be set after graph_for_each_link() */ + ret = simple_util_parse_card_name(priv, NULL); + if (ret < 0) + goto err; +@@ -1363,15 +1368,13 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, + + simple_util_debug_info(priv); + +- ret = snd_soc_of_parse_aux_devs(card, "aux-devs"); +- if (ret < 0) +- goto err; +- + ret = devm_snd_soc_register_card(dev, card); + err: +- if (ret < 0) +- dev_err_probe(dev, ret, "parse error\n"); +- ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } ++end: + return graph_ret(priv, ret); + } + EXPORT_SYMBOL_GPL(audio_graph2_parse_of); +diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c +--- a/sound/soc/generic/simple-card-utils.c ++++ b/sound/soc/generic/simple-card-utils.c +@@ -216,6 +216,27 @@ int simple_util_set_dailink_name(struct simple_util_priv *priv, + } + EXPORT_SYMBOL_GPL(simple_util_set_dailink_name); + ++int simple_util_parse_property(struct simple_util_priv *priv, ++ int (*func)(struct snd_soc_card *card, const char *propname), ++ char *prefix, char *property) ++{ ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device_node *node = card->dev->of_node; ++ char prop[128]; ++ ++ if (!prefix) ++ prefix = ""; ++ ++ snprintf(prop, sizeof(prop), "%s%s", prefix, property); ++ ++ /* no property is not error */ ++ if (!of_property_present(node, prop)) ++ return 0; ++ ++ return func(card, prop); ++} ++EXPORT_SYMBOL_GPL(simple_util_parse_property); ++ + int simple_util_parse_card_name(struct simple_util_priv *priv, + char *prefix) + { +@@ -731,11 +752,12 @@ void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus, + } + EXPORT_SYMBOL_GPL(simple_util_canonicalize_cpu); + +-void simple_util_clean_reference(struct snd_soc_card *card) ++void simple_util_clean_reference(struct simple_util_priv *priv) + { + struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link_component *cpu; + struct snd_soc_dai_link_component *codec; ++ struct snd_soc_card *card = simple_priv_to_card(priv); + int i, j; + + for_each_card_prelinks(card, i, dai_link) { +@@ -747,57 +769,6 @@ void simple_util_clean_reference(struct snd_soc_card *card) + } + EXPORT_SYMBOL_GPL(simple_util_clean_reference); + +-int simple_util_parse_routing(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "routing"); +- +- if (!of_property_present(node, prop)) +- return 0; +- +- return snd_soc_of_parse_audio_routing(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_routing); +- +-int simple_util_parse_widgets(struct snd_soc_card *card, +- char *prefix) +-{ +- struct device_node *node = card->dev->of_node; +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets"); +- +- if (of_property_present(node, prop)) +- return snd_soc_of_parse_audio_simple_widgets(card, prop); +- +- /* no widgets is not error */ +- return 0; +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_widgets); +- +-int simple_util_parse_pin_switches(struct snd_soc_card *card, +- char *prefix) +-{ +- char prop[128]; +- +- if (!prefix) +- prefix = ""; +- +- snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches"); +- +- return snd_soc_of_parse_pin_switches(card, prop); +-} +-EXPORT_SYMBOL_GPL(simple_util_parse_pin_switches); +- + int simple_util_init_jack(struct snd_soc_card *card, + struct simple_util_jack *sjack, + int is_hp, char *prefix, +@@ -854,9 +825,9 @@ int simple_util_init_jack(struct snd_soc_card *card, + } + EXPORT_SYMBOL_GPL(simple_util_init_jack); + +-int simple_util_init_aux_jacks(struct simple_util_priv *priv, char *prefix) ++int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) + { +- struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + int found_jack_index = 0; + int type = 0; +@@ -1026,8 +997,9 @@ EXPORT_SYMBOL_GPL(simple_util_init_priv); + void simple_util_remove(struct platform_device *pdev) + { + struct snd_soc_card *card = platform_get_drvdata(pdev); ++ struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + +- simple_util_clean_reference(card); ++ simple_util_clean_reference(priv); + } + EXPORT_SYMBOL_GPL(simple_util_remove); + +diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c +--- a/sound/soc/generic/simple-card.c ++++ b/sound/soc/generic/simple-card.c +@@ -517,44 +517,6 @@ static int simple_populate_aux(struct simple_util_priv *priv) + return simple_ret(priv, ret); + } + +-static int simple_parse_of(struct simple_util_priv *priv, struct link_info *li) +-{ +- struct snd_soc_card *card = simple_priv_to_card(priv); +- int ret; +- +- ret = simple_util_parse_widgets(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_routing(card, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_pin_switches(card, PREFIX); +- if (ret < 0) +- goto end; +- +- /* Single/Muti DAI link(s) & New style of DT node */ +- memset(li, 0, sizeof(*li)); +- ret = simple_for_each_link(priv, li, +- simple_dai_link_of, +- simple_dai_link_of_dpcm); +- if (ret < 0) +- goto end; +- +- ret = simple_util_parse_card_name(priv, PREFIX); +- if (ret < 0) +- goto end; +- +- ret = simple_populate_aux(priv); +- if (ret < 0) +- goto end; +- +- ret = snd_soc_of_parse_aux_devs(card, PREFIX "aux-devs"); +-end: +- return simple_ret(priv, ret); +-} +- + static int simple_count_noml(struct simple_util_priv *priv, + struct device_node *np, + struct device_node *codec, +@@ -699,28 +661,19 @@ static int simple_soc_probe(struct snd_soc_card *card) + if (ret < 0) + goto end; + +- ret = simple_util_init_aux_jacks(priv, PREFIX); ++ ret = simple_util_init_aux_jacks(card, PREFIX); + end: + return simple_ret(priv, ret); + } + +-static int simple_probe(struct platform_device *pdev) ++static int simple_parse_of(struct simple_util_priv *priv) + { +- struct simple_util_priv *priv; +- struct device *dev = &pdev->dev; +- struct snd_soc_card *card; +- int ret; ++ struct snd_soc_card *card = simple_priv_to_card(priv); ++ struct device *dev = card->dev; ++ int ret = -EINVAL; + +- /* Allocate the private data and the DAI link array */ +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- card = simple_priv_to_card(priv); +- card->owner = THIS_MODULE; +- card->dev = dev; +- card->probe = simple_soc_probe; +- card->driver_name = "simple-card"; ++ if (!dev) ++ return simple_ret(priv, ret); + + ret = -ENOMEM; + struct link_info *li __free(kfree) = kzalloc_obj(*li); +@@ -739,25 +692,71 @@ static int simple_probe(struct platform_device *pdev) + if (ret < 0) + goto end; + +- ret = simple_parse_of(priv, li); +- if (ret < 0) { +- dev_err_probe(dev, ret, "parse error\n"); ++ ret = simple_util_parse_widgets(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_routing(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_pin_switches(priv, PREFIX); ++ if (ret < 0) ++ goto end; ++ ++ ret = simple_util_parse_aux_devs(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ /* Single/Muti DAI link(s) & New style of DT node */ ++ memset(li, 0, sizeof(*li)); ++ ret = simple_for_each_link(priv, li, ++ simple_dai_link_of, ++ simple_dai_link_of_dpcm); ++ if (ret < 0) ++ goto err; ++ ++ /* Card name should be set after simple_for_each_link() */ ++ ret = simple_util_parse_card_name(priv, PREFIX); ++ if (ret < 0) ++ goto err; ++ ++ ret = simple_populate_aux(priv); ++ if (ret < 0) + goto err; +- } + + snd_soc_card_set_drvdata(card, priv); + + simple_util_debug_info(priv); + + ret = devm_snd_soc_register_card(dev, card); +- if (ret < 0) +- goto err; +- +- return 0; + err: +- simple_util_clean_reference(card); ++ if (ret < 0) { ++ simple_util_clean_reference(priv); ++ return dev_err_probe(dev, ret, "parse error\n"); ++ } + end: +- return dev_err_probe(dev, ret, "parse error\n"); ++ return simple_ret(priv, ret); ++} ++ ++static int simple_probe(struct platform_device *pdev) ++{ ++ struct simple_util_priv *priv; ++ struct device *dev = &pdev->dev; ++ struct snd_soc_card *card; ++ ++ /* Allocate the private data and the DAI link array */ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ card = simple_priv_to_card(priv); ++ card->owner = THIS_MODULE; ++ card->dev = dev; ++ card->probe = simple_soc_probe; ++ card->driver_name = "simple-card"; ++ ++ return simple_parse_of(priv); + } + + static const struct of_device_id simple_of_match[] = { +diff --git a/sound/soc/intel/atom/sst-atom-controls.c b/sound/soc/intel/atom/sst-atom-controls.c +--- a/sound/soc/intel/atom/sst-atom-controls.c ++++ b/sound/soc/intel/atom/sst-atom-controls.c +@@ -14,6 +14,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -73,14 +74,10 @@ static int sst_fill_and_send_cmd(struct sst_data *drv, + u8 ipc_msg, u8 block, u8 task_id, u8 pipe_id, + void *cmd_data, u16 len) + { +- int ret; ++ guard(mutex)(&drv->lock); + +- mutex_lock(&drv->lock); +- ret = sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, +- task_id, pipe_id, cmd_data, len); +- mutex_unlock(&drv->lock); +- +- return ret; ++ return sst_fill_and_send_cmd_unlocked(drv, ipc_msg, block, ++ task_id, pipe_id, cmd_data, len); + } + + /* +@@ -167,7 +164,7 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + unsigned int val, mux; + u8 *map = is_tx ? sst_ssp_rx_map : sst_ssp_tx_map; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + val = 1 << ctl_no; + /* search which slot/channel has this bit set - there should be only one */ + for (mux = e->max; mux > 0; mux--) +@@ -175,7 +172,6 @@ static int sst_slot_get(struct snd_kcontrol *kcontrol, + break; + + ucontrol->value.enumerated.item[0] = mux; +- mutex_unlock(&drv->lock); + + dev_dbg(c->dev, "%s - %s map = %#x\n", + is_tx ? "tx channel" : "rx slot", +@@ -235,7 +231,7 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + if (mux > e->max - 1) + return -EINVAL; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + /* first clear all registers of this bit */ + for (i = 0; i < e->max; i++) + map[i] &= ~val; +@@ -244,7 +240,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + /* kctl set to 'none' and we reset the bits so send IPC */ + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -258,7 +253,6 @@ static int sst_slot_put(struct snd_kcontrol *kcontrol, + + ret = sst_check_and_send_slot_map(drv, kcontrol); + +- mutex_unlock(&drv->lock); + return ret; + } + +@@ -354,13 +348,12 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + struct sst_algo_control *bc = (void *)kcontrol->private_value; + + dev_dbg(cmpnt->dev, "control_name=%s\n", kcontrol->id.name); +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + switch (bc->type) { + case SST_ALGO_PARAMS: + memcpy(bc->params, ucontrol->value.bytes.data, bc->max); + break; + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- algo type:%d\n", + bc->type); + return -EINVAL; +@@ -368,7 +361,6 @@ static int sst_algo_control_set(struct snd_kcontrol *kcontrol, + /*if pipe is enabled, need to send the algo params from here*/ + if (bc->w && bc->w->power) + ret = sst_send_algo_cmd(drv, bc); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -475,7 +467,7 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + struct sst_gain_mixer_control *mc = (void *)kcontrol->private_value; + struct sst_gain_value *gv = mc->gain_val; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + + switch (mc->type) { + case SST_GAIN_TLV: +@@ -497,7 +489,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + break; + + default: +- mutex_unlock(&drv->lock); + dev_err(cmpnt->dev, "Invalid Input- gain type:%d\n", + mc->type); + return -EINVAL; +@@ -506,7 +497,6 @@ static int sst_gain_put(struct snd_kcontrol *kcontrol, + if (mc->w && mc->w->power) + ret = sst_send_gain_cmd(drv, gv, mc->task_id, + mc->pipe_id | mc->instance_id, mc->module_id, 0); +- mutex_unlock(&drv->lock); + + return ret; + } +@@ -521,10 +511,9 @@ static int sst_send_pipe_module_params(struct snd_soc_dapm_widget *w, + struct sst_data *drv = snd_soc_component_get_drvdata(c); + struct sst_ids *ids = w->priv; + +- mutex_lock(&drv->lock); ++ guard(mutex)(&drv->lock); + sst_find_and_send_pipe_algo(drv, w->name, ids); + sst_set_pipe_gain(ids, drv, 0); +- mutex_unlock(&drv->lock); + + return 0; + } +@@ -761,27 +750,29 @@ int sst_handle_vb_timer(struct snd_soc_dai *dai, bool enable) + return ret; + } + +- mutex_lock(&drv->lock); +- if (enable) +- timer_usage++; +- else +- timer_usage--; +- +- /* +- * Send the command only if this call is the first enable or last +- * disable +- */ +- if ((enable && (timer_usage == 1)) || +- (!enable && (timer_usage == 0))) { +- ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, +- SST_FLAG_BLOCKED, SST_TASK_SBA, 0, &cmd, +- sizeof(cmd.header) + cmd.header.length); +- if (ret && enable) { ++ scoped_guard(mutex, &drv->lock) { ++ if (enable) ++ timer_usage++; ++ else + timer_usage--; +- enable = false; ++ ++ /* ++ * Send the command only if this call is the first enable or last ++ * disable ++ */ ++ if ((enable && timer_usage == 1) || ++ (!enable && timer_usage == 0)) { ++ ret = sst_fill_and_send_cmd_unlocked(drv, SST_IPC_IA_CMD, ++ SST_FLAG_BLOCKED, ++ SST_TASK_SBA, 0, &cmd, ++ sizeof(cmd.header) + ++ cmd.header.length); ++ if (ret && enable) { ++ timer_usage--; ++ enable = false; ++ } + } + } +- mutex_unlock(&drv->lock); + + if (!enable) + sst->ops->power(sst->dev, false); +diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +--- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c ++++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c +@@ -11,6 +11,7 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +@@ -32,7 +33,7 @@ int sst_register_dsp(struct sst_device *dev) + return -EINVAL; + if (!try_module_get(dev->dev->driver->owner)) + return -ENODEV; +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + if (sst) { + dev_err(dev->dev, "we already have a device %s\n", sst->name); + module_put(dev->dev->driver->owner); +@@ -41,7 +42,7 @@ int sst_register_dsp(struct sst_device *dev) + } + dev_dbg(dev->dev, "registering device %s\n", dev->name); + sst = dev; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_register_dsp); +@@ -53,17 +54,15 @@ int sst_unregister_dsp(struct sst_device *dev) + if (dev != sst) + return -EINVAL; + +- mutex_lock(&sst_lock); ++ guard(mutex)(&sst_lock); + +- if (!sst) { +- mutex_unlock(&sst_lock); ++ if (!sst) + return -EIO; +- } + + module_put(sst->dev->driver->owner); + dev_dbg(dev->dev, "unreg %s\n", sst->name); + sst = NULL; +- mutex_unlock(&sst_lock); ++ + return 0; + } + EXPORT_SYMBOL_GPL(sst_unregister_dsp); +@@ -103,21 +102,14 @@ static int sst_media_digital_mute(struct snd_soc_dai *dai, int mute, int stream) + void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) + { +- unsigned long flags; +- spin_lock_irqsave(&stream->status_lock, flags); ++ guard(spinlock_irqsave)(&stream->status_lock); + stream->stream_status = state; +- spin_unlock_irqrestore(&stream->status_lock, flags); + } + + static inline int sst_get_stream_status(struct sst_runtime_stream *stream) + { +- int state; +- unsigned long flags; +- +- spin_lock_irqsave(&stream->status_lock, flags); +- state = stream->stream_status; +- spin_unlock_irqrestore(&stream->status_lock, flags); +- return state; ++ guard(spinlock_irqsave)(&stream->status_lock); ++ return stream->stream_status; + } + + static void sst_fill_alloc_params(struct snd_pcm_substream *substream, +@@ -244,12 +236,7 @@ static int sst_platform_alloc_stream(struct snd_pcm_substream *substream, + + stream->stream_info.str_id = str_params.stream_id; + +- ret_val = stream->ops->open(sst->dev, &str_params); +- if (ret_val <= 0) +- return ret_val; +- +- +- return ret_val; ++ return stream->ops->open(sst->dev, &str_params); + } + + static void sst_period_elapsed(void *arg) +@@ -304,7 +291,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + { + int ret_val = 0; + struct snd_pcm_runtime *runtime = substream->runtime; +- struct sst_runtime_stream *stream; ++ struct sst_runtime_stream *stream __free(kfree) = NULL; + + stream = kzalloc_obj(*stream); + if (!stream) +@@ -312,15 +299,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + spin_lock_init(&stream->status_lock); + + /* get the sst ops */ +- mutex_lock(&sst_lock); +- if (!sst || +- !try_module_get(sst->dev->driver->owner)) { +- dev_err(dai->dev, "no device available to run\n"); +- ret_val = -ENODEV; +- goto out_ops; ++ scoped_guard(mutex, &sst_lock) { ++ if (!sst || ++ !try_module_get(sst->dev->driver->owner)) { ++ dev_err(dai->dev, "no device available to run\n"); ++ return -ENODEV; ++ } ++ stream->ops = sst->ops; + } +- stream->ops = sst->ops; +- mutex_unlock(&sst_lock); + + stream->stream_info.str_id = 0; + +@@ -330,7 +316,7 @@ static int sst_media_open(struct snd_pcm_substream *substream, + + ret_val = power_up_sst(stream); + if (ret_val < 0) +- goto out_power_up; ++ return ret_val; + + /* + * Make sure the period to be multiple of 1ms to align the +@@ -347,12 +333,14 @@ static int sst_media_open(struct snd_pcm_substream *substream, + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS, 2); + +- return snd_pcm_hw_constraint_integer(runtime, +- SNDRV_PCM_HW_PARAM_PERIODS); +-out_ops: +- mutex_unlock(&sst_lock); +-out_power_up: +- kfree(stream); ++ ret_val = snd_pcm_hw_constraint_integer(runtime, ++ SNDRV_PCM_HW_PARAM_PERIODS); ++ ++ if (ret_val < 0) ++ return ret_val; ++ ++ stream = NULL; ++ + return ret_val; + } + +diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c +--- a/sound/soc/intel/atom/sst/sst_ipc.c ++++ b/sound/soc/intel/atom/sst/sst_ipc.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -180,9 +181,8 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + union interrupt_reg_mrfld isr; + union interrupt_reg_mrfld imr; + union ipc_header_mrfld clear_ipc; +- unsigned long irq_flags; + +- spin_lock_irqsave(&sst_drv_ctx->ipc_spin_lock, irq_flags); ++ guard(spinlock_irqsave)(&sst_drv_ctx->ipc_spin_lock); + imr.full = sst_shim_read64(sst_drv_ctx->shim, SST_IMRX); + isr.full = sst_shim_read64(sst_drv_ctx->shim, SST_ISRX); + +@@ -200,7 +200,6 @@ void intel_sst_clear_intr_mrfld(struct intel_sst_drv *sst_drv_ctx) + /* un mask busy interrupt */ + imr.part.busy_interrupt = 0; + sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, imr.full); +- spin_unlock_irqrestore(&sst_drv_ctx->ipc_spin_lock, irq_flags); + } + + +diff --git a/sound/soc/intel/atom/sst/sst_pvt.c b/sound/soc/intel/atom/sst/sst_pvt.c +--- a/sound/soc/intel/atom/sst/sst_pvt.c ++++ b/sound/soc/intel/atom/sst/sst_pvt.c +@@ -11,6 +11,7 @@ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ ++#include + #include + #include + #include +@@ -64,9 +65,8 @@ u64 sst_shim_read64(void __iomem *addr, int offset) + void sst_set_fw_state_locked( + struct intel_sst_drv *sst_drv_ctx, int sst_state) + { +- mutex_lock(&sst_drv_ctx->sst_lock); ++ guard(mutex)(&sst_drv_ctx->sst_lock); + sst_drv_ctx->sst_state = sst_state; +- mutex_unlock(&sst_drv_ctx->sst_lock); + } + + /* +@@ -302,18 +302,17 @@ int sst_assign_pvt_id(struct intel_sst_drv *drv) + { + int local; + +- spin_lock(&drv->block_lock); ++ guard(spinlock)(&drv->block_lock); + /* find first zero index from lsb */ + local = ffz(drv->pvt_id); + dev_dbg(drv->dev, "pvt_id assigned --> %d\n", local); + if (local >= SST_MAX_BLOCKS){ +- spin_unlock(&drv->block_lock); + dev_err(drv->dev, "PVT _ID error: no free id blocks "); + return -EINVAL; + } + /* toggle the index */ + change_bit(local, &drv->pvt_id); +- spin_unlock(&drv->block_lock); ++ + return local; + } + +diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c +--- a/sound/soc/intel/avs/apl.c ++++ b/sound/soc/intel/avs/apl.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -190,7 +191,7 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + { + struct avs_path *path; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Any gateway without buffer allocated in LP area disqualifies D0IX. */ + list_for_each_entry(path, &adev->path_list, node) { + struct avs_path_pipeline *ppl; +@@ -210,14 +211,11 @@ static bool avs_apl_lp_streaming(struct avs_dev *adev) + if (cfg->copier.dma_type == INVALID_OBJECT_ID) + continue; + +- if (!mod->gtw_attrs.lp_buffer_alloc) { +- spin_unlock(&adev->path_list_lock); ++ if (!mod->gtw_attrs.lp_buffer_alloc) + return false; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return true; + } +diff --git a/sound/soc/intel/avs/avs.h b/sound/soc/intel/avs/avs.h +--- a/sound/soc/intel/avs/avs.h ++++ b/sound/soc/intel/avs/avs.h +@@ -336,14 +336,13 @@ int avs_icl_load_basefw(struct avs_dev *adev, struct firmware *fw); + /* Soc component members */ + + struct avs_soc_component { +- struct snd_soc_component base; ++ struct snd_soc_component *base; + struct avs_tplg *tplg; + + struct list_head node; + }; + +-#define to_avs_soc_component(comp) \ +- container_of(comp, struct avs_soc_component, base) ++#define to_avs_soc_component(comp) snd_soc_component_to_priv(comp) + + extern const struct snd_soc_dai_ops avs_dai_fe_ops; + +diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c +--- a/sound/soc/intel/avs/boards/da7219.c ++++ b/sound/soc/intel/avs/boards/da7219.c +@@ -175,7 +175,6 @@ static int avs_create_dai_link(struct device *dev, int ssp_port, int tdm_slot, + if (!dl || !platform) + return -ENOMEM; + +- dl->name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); + dl->name = devm_kasprintf(dev, GFP_KERNEL, + AVS_STRING_FMT("SSP", "-Codec", ssp_port, tdm_slot)); + dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +@@ -259,10 +258,8 @@ static int avs_da7219_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_da7219_driver_ids[] = { +- { +- .name = "avs_da7219", +- }, +- {}, ++ { .name = "avs_da7219" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_da7219_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c +--- a/sound/soc/intel/avs/boards/dmic.c ++++ b/sound/soc/intel/avs/boards/dmic.c +@@ -104,10 +104,8 @@ static int avs_dmic_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_dmic_driver_ids[] = { +- { +- .name = "avs_dmic", +- }, +- {}, ++ { .name = "avs_dmic" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_dmic_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/es8336.c b/sound/soc/intel/avs/boards/es8336.c +--- a/sound/soc/intel/avs/boards/es8336.c ++++ b/sound/soc/intel/avs/boards/es8336.c +@@ -309,10 +309,8 @@ static int avs_es8336_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_es8336_driver_ids[] = { +- { +- .name = "avs_es8336", +- }, +- {}, ++ { .name = "avs_es8336" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_es8336_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -231,10 +231,8 @@ static int avs_hdaudio_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_hdaudio_driver_ids[] = { +- { +- .name = "avs_hdaudio", +- }, +- {}, ++ { .name = "avs_hdaudio" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_hdaudio_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/i2s_test.c b/sound/soc/intel/avs/boards/i2s_test.c +--- a/sound/soc/intel/avs/boards/i2s_test.c ++++ b/sound/soc/intel/avs/boards/i2s_test.c +@@ -107,10 +107,8 @@ static int avs_i2s_test_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_i2s_test_driver_ids[] = { +- { +- .name = "avs_i2s_test", +- }, +- {}, ++ { .name = "avs_i2s_test" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_i2s_test_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c +--- a/sound/soc/intel/avs/boards/max98357a.c ++++ b/sound/soc/intel/avs/boards/max98357a.c +@@ -136,10 +136,8 @@ static int avs_max98357a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98357a_driver_ids[] = { +- { +- .name = "avs_max98357a", +- }, +- {}, ++ { .name = "avs_max98357a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98357a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c +--- a/sound/soc/intel/avs/boards/max98373.c ++++ b/sound/soc/intel/avs/boards/max98373.c +@@ -191,10 +191,8 @@ static int avs_max98373_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98373_driver_ids[] = { +- { +- .name = "avs_max98373", +- }, +- {}, ++ { .name = "avs_max98373" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98373_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/max98927.c b/sound/soc/intel/avs/boards/max98927.c +--- a/sound/soc/intel/avs/boards/max98927.c ++++ b/sound/soc/intel/avs/boards/max98927.c +@@ -188,10 +188,8 @@ static int avs_max98927_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_max98927_driver_ids[] = { +- { +- .name = "avs_max98927", +- }, +- {}, ++ { .name = "avs_max98927" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_max98927_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c +--- a/sound/soc/intel/avs/boards/nau8825.c ++++ b/sound/soc/intel/avs/boards/nau8825.c +@@ -293,10 +293,8 @@ static int avs_nau8825_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_nau8825_driver_ids[] = { +- { +- .name = "avs_nau8825", +- }, +- {}, ++ { .name = "avs_nau8825" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_nau8825_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/pcm3168a.c b/sound/soc/intel/avs/boards/pcm3168a.c +--- a/sound/soc/intel/avs/boards/pcm3168a.c ++++ b/sound/soc/intel/avs/boards/pcm3168a.c +@@ -132,10 +132,8 @@ static int avs_pcm3168a_probe(struct platform_device *pdev) + } + + static const struct platform_device_id avs_pcm3168a_driver_ids[] = { +- { +- .name = "avs_pcm3168a", +- }, +- {}, ++ { .name = "avs_pcm3168a" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_pcm3168a_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/probe.c b/sound/soc/intel/avs/boards/probe.c +--- a/sound/soc/intel/avs/boards/probe.c ++++ b/sound/soc/intel/avs/boards/probe.c +@@ -64,7 +64,7 @@ static const struct platform_device_id avs_probe_mb_driver_ids[] = { + { + .name = "avs_probe_mb", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_probe_mb_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c +--- a/sound/soc/intel/avs/boards/rt274.c ++++ b/sound/soc/intel/avs/boards/rt274.c +@@ -261,7 +261,7 @@ static const struct platform_device_id avs_rt274_driver_ids[] = { + { + .name = "avs_rt274", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt274_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c +--- a/sound/soc/intel/avs/boards/rt286.c ++++ b/sound/soc/intel/avs/boards/rt286.c +@@ -231,7 +231,7 @@ static const struct platform_device_id avs_rt286_driver_ids[] = { + { + .name = "avs_rt286", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt286_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c +--- a/sound/soc/intel/avs/boards/rt298.c ++++ b/sound/soc/intel/avs/boards/rt298.c +@@ -250,7 +250,7 @@ static const struct platform_device_id avs_rt298_driver_ids[] = { + { + .name = "avs_rt298", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt298_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5514.c b/sound/soc/intel/avs/boards/rt5514.c +--- a/sound/soc/intel/avs/boards/rt5514.c ++++ b/sound/soc/intel/avs/boards/rt5514.c +@@ -178,7 +178,7 @@ static const struct platform_device_id avs_rt5514_driver_ids[] = { + { + .name = "avs_rt5514", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5514_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5640.c b/sound/soc/intel/avs/boards/rt5640.c +--- a/sound/soc/intel/avs/boards/rt5640.c ++++ b/sound/soc/intel/avs/boards/rt5640.c +@@ -252,7 +252,7 @@ static const struct platform_device_id avs_rt5640_driver_ids[] = { + { + .name = "avs_rt5640", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5640_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5663.c b/sound/soc/intel/avs/boards/rt5663.c +--- a/sound/soc/intel/avs/boards/rt5663.c ++++ b/sound/soc/intel/avs/boards/rt5663.c +@@ -249,7 +249,7 @@ static const struct platform_device_id avs_rt5663_driver_ids[] = { + { + .name = "avs_rt5663", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5663_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c +--- a/sound/soc/intel/avs/boards/rt5682.c ++++ b/sound/soc/intel/avs/boards/rt5682.c +@@ -325,7 +325,7 @@ static const struct platform_device_id avs_rt5682_driver_ids[] = { + { + .name = "avs_rt5682", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_rt5682_driver_ids); + +diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c +--- a/sound/soc/intel/avs/boards/ssm4567.c ++++ b/sound/soc/intel/avs/boards/ssm4567.c +@@ -180,7 +180,7 @@ static const struct platform_device_id avs_ssm4567_driver_ids[] = { + { + .name = "avs_ssm4567", + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, avs_ssm4567_driver_ids); + +diff --git a/sound/soc/intel/avs/control.c b/sound/soc/intel/avs/control.c +--- a/sound/soc/intel/avs/control.c ++++ b/sound/soc/intel/avs/control.c +@@ -27,7 +27,7 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + struct avs_path_pipeline *ppl; + struct avs_path_module *mod; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + list_for_each_entry(path, &adev->path_list, node) { + list_for_each_entry(ppl, &path->ppl_list, node) { + list_for_each_entry(mod, &ppl->mod_list, node) { +@@ -35,14 +35,11 @@ static struct avs_path_module *avs_get_volume_module(struct avs_dev *adev, u32 i + + if ((guid_equal(type, &AVS_PEAKVOL_MOD_UUID) || + guid_equal(type, &AVS_GAIN_MOD_UUID)) && +- mod->template->ctl_id == id) { +- spin_unlock(&adev->path_list_lock); ++ mod->template->ctl_id == id) + return mod; +- } + } + } + } +- spin_unlock(&adev->path_list_lock); + + return NULL; + } +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -14,6 +14,7 @@ + // foundation of this driver + // + ++#include + #include + #include + #include +@@ -273,7 +274,7 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + if (snd_hdac_bus_handle_stream_irq(bus, status, hdac_update_stream)) + ret = IRQ_HANDLED; + +- spin_lock_irq(&bus->reg_lock); ++ guard(spinlock_irq)(&bus->reg_lock); + /* Clear RIRB interrupt. */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { +@@ -283,7 +284,6 @@ static irqreturn_t avs_hda_interrupt(struct hdac_bus *bus) + ret = IRQ_HANDLED; + } + +- spin_unlock_irq(&bus->reg_lock); + return ret; + } + +diff --git a/sound/soc/intel/avs/debug.h b/sound/soc/intel/avs/debug.h +--- a/sound/soc/intel/avs/debug.h ++++ b/sound/soc/intel/avs/debug.h +@@ -9,6 +9,7 @@ + #ifndef __SOUND_SOC_INTEL_AVS_DEBUG_H + #define __SOUND_SOC_INTEL_AVS_DEBUG_H + ++#include + #include "messages.h" + #include "registers.h" + +@@ -26,14 +27,9 @@ struct avs_dev; + + static inline int avs_log_buffer_status_locked(struct avs_dev *adev, union avs_notify_msg *msg) + { +- unsigned long flags; +- int ret; ++ guard(spinlock_irqsave)(&adev->trace_lock); + +- spin_lock_irqsave(&adev->trace_lock, flags); +- ret = avs_dsp_op(adev, log_buffer_status, msg); +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- +- return ret; ++ return avs_dsp_op(adev, log_buffer_status, msg); + } + + struct avs_apl_log_buffer_layout { +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -6,9 +6,9 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include +-#include + #include + #include + #include +@@ -236,20 +236,15 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) { +- ret = -EBUSY; +- goto err; +- } ++ if (kfifo_initialized(&adev->trace_fifo)) ++ return -EBUSY; + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- goto err; ++ return ret; + + file->private_data = adev; + return 0; +-err: +- module_put(adev->dev->driver->owner); +- return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +@@ -257,24 +252,22 @@ static int strace_release(struct inode *inode, struct file *file) + union avs_notify_msg msg = AVS_NOTIFICATION(LOG_BUFFER_STATUS); + struct avs_dev *adev = file->private_data; + unsigned long resource_mask; +- unsigned long flags, i; ++ unsigned long i; + u32 num_cores; + + resource_mask = adev->logged_resources; + num_cores = adev->hw_cfg.dsp_cores; + +- spin_lock_irqsave(&adev->trace_lock, flags); ++ scoped_guard(spinlock_irqsave, &adev->trace_lock) { ++ /* Gather any remaining logs. */ ++ for_each_set_bit(i, &resource_mask, num_cores) { ++ msg.log.core = i; ++ avs_dsp_op(adev, log_buffer_status, &msg); ++ } + +- /* Gather any remaining logs. */ +- for_each_set_bit(i, &resource_mask, num_cores) { +- msg.log.core = i; +- avs_dsp_op(adev, log_buffer_status, &msg); ++ kfifo_free(&adev->trace_fifo); + } + +- kfifo_free(&adev->trace_fifo); +- +- spin_unlock_irqrestore(&adev->trace_lock, flags); +- + module_put(adev->dev->driver->owner); + return 0; + } +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -99,39 +100,39 @@ static void avs_dsp_recovery(struct avs_dev *adev) + unsigned int core_mask; + int ret; + +- mutex_lock(&adev->comp_list_mutex); +- /* disconnect all running streams */ +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct snd_soc_pcm_runtime *rtd; +- struct snd_soc_card *card; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ /* disconnect all running streams */ ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct snd_soc_pcm_runtime *rtd; ++ struct snd_soc_card *card; + +- card = acomp->base.card; +- if (!card) +- continue; +- +- for_each_card_rtds(card, rtd) { +- struct snd_pcm *pcm; +- int dir; +- +- pcm = rtd->pcm; +- if (!pcm || rtd->dai_link->no_pcm) ++ card = acomp->base->card; ++ if (!card) + continue; + +- for_each_pcm_streams(dir) { +- struct snd_pcm_substream *substream; ++ for_each_card_rtds(card, rtd) { ++ struct snd_pcm *pcm; ++ int dir; + +- substream = pcm->streams[dir].substream; +- if (!substream || !substream->runtime) ++ pcm = rtd->pcm; ++ if (!pcm || rtd->dai_link->no_pcm) + continue; + +- /* No need for _irq() as we are in nonatomic context. */ +- snd_pcm_stream_lock(substream); +- snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); +- snd_pcm_stream_unlock(substream); ++ for_each_pcm_streams(dir) { ++ struct snd_pcm_substream *substream; ++ ++ substream = pcm->streams[dir].substream; ++ if (!substream || !substream->runtime) ++ continue; ++ ++ /* No need for _irq() as we are in nonatomic context. */ ++ snd_pcm_stream_lock(substream); ++ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); ++ snd_pcm_stream_unlock(substream); ++ } + } + } + } +- mutex_unlock(&adev->comp_list_mutex); + + /* forcibly shutdown all cores */ + core_mask = GENMASK(adev->hw_cfg.dsp_cores - 1, 0); +@@ -397,7 +398,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + if (!ipc->ready) + return -EPERM; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); +@@ -412,7 +413,7 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + /* Same treatment as on exception, just stack_dump=0. */ + avs_dsp_exception_caught(adev, &msg); + } +- goto exit; ++ return ret; + } + + ret = ipc->rx.rsp.status; +@@ -436,8 +437,6 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + memcpy(reply->data, ipc->rx.data, reply->size); + } + +-exit: +- mutex_unlock(&ipc->msg_mutex); + return ret; + } + +@@ -501,7 +500,7 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + struct avs_ipc *ipc = adev->ipc; + int ret; + +- mutex_lock(&ipc->msg_mutex); ++ guard(mutex)(&ipc->msg_mutex); + + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, NULL); +@@ -522,8 +521,6 @@ static int avs_dsp_do_send_rom_msg(struct avs_dev *adev, struct avs_ipc_msg *req + dev_err(adev->dev, "%s (0x%08x 0x%08x) failed: %d\n", + name, request->glb.primary, request->glb.ext.val, ret); + +- mutex_unlock(&ipc->msg_mutex); +- + return ret; + } + +diff --git a/sound/soc/intel/avs/loader.c b/sound/soc/intel/avs/loader.c +--- a/sound/soc/intel/avs/loader.c ++++ b/sound/soc/intel/avs/loader.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -630,15 +631,15 @@ static int avs_load_firmware(struct avs_dev *adev, bool purge) + if (ret) + goto reenable_gating; + +- mutex_lock(&adev->comp_list_mutex); +- list_for_each_entry(acomp, &adev->comp_list, node) { +- struct avs_tplg *tplg = acomp->tplg; ++ scoped_guard(mutex, &adev->comp_list_mutex) { ++ list_for_each_entry(acomp, &adev->comp_list, node) { ++ struct avs_tplg *tplg = acomp->tplg; + +- ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); +- if (ret < 0) +- break; ++ ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs); ++ if (ret < 0) ++ break; ++ } + } +- mutex_unlock(&adev->comp_list_mutex); + + reenable_gating: + avs_hda_l1sen_enable(adev, true); +diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c +--- a/sound/soc/intel/avs/path.c ++++ b/sound/soc/intel/avs/path.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -69,16 +70,13 @@ avs_path_find_path(struct avs_dev *adev, const char *name, u32 template_id) + if (!template) + return NULL; + +- spin_lock(&adev->path_list_lock); ++ guard(spinlock)(&adev->path_list_lock); + /* Only one variant of given path template may be instantiated at a time. */ + list_for_each_entry(path, &adev->path_list, node) { +- if (path->template->owner == template) { +- spin_unlock(&adev->path_list_lock); ++ if (path->template->owner == template) + return path; +- } + } + +- spin_unlock(&adev->path_list_lock); + return NULL; + } + +@@ -1300,7 +1298,7 @@ void avs_path_free(struct avs_path *path) + struct avs_path *cpath, *csave; + struct avs_dev *adev = path->owner; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Free all condpaths this path spawned. */ + list_for_each_entry_safe(cpath, csave, &path->source_list, source_node) +@@ -1309,8 +1307,6 @@ void avs_path_free(struct avs_path *path) + avs_condpath_free(path->owner, cpath); + + avs_path_free_unlocked(path); +- +- mutex_unlock(&adev->path_mutex); + } + + struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, +@@ -1329,13 +1325,13 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + } + + /* Serialize path and its components creation. */ +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + /* Satisfy needs of avs_path_find_tplg(). */ +- mutex_lock(&adev->comp_list_mutex); ++ guard(mutex)(&adev->comp_list_mutex); + + path = avs_path_create_unlocked(adev, dma_id, variant); + if (IS_ERR(path)) +- goto exit; ++ return path; + + ret = avs_condpaths_walk_all(adev, path); + if (ret) { +@@ -1343,10 +1339,6 @@ struct avs_path *avs_path_create(struct avs_dev *adev, u32 dma_id, + path = ERR_PTR(ret); + } + +-exit: +- mutex_unlock(&adev->comp_list_mutex); +- mutex_unlock(&adev->path_mutex); +- + return path; + } + +@@ -1491,15 +1483,13 @@ static void avs_condpaths_pause(struct avs_dev *adev, struct avs_path *path) + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* If either source or sink stops, so do the attached conditional paths. */ + list_for_each_entry(cpath, &path->source_list, source_node) + avs_condpath_pause(adev, cpath); + list_for_each_entry(cpath, &path->sink_list, sink_node) + avs_condpath_pause(adev, cpath); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_pause(struct avs_path *path) +@@ -1555,7 +1545,7 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + { + struct avs_path *cpath; + +- mutex_lock(&adev->path_mutex); ++ guard(mutex)(&adev->path_mutex); + + /* Run conditional paths only if source and sink are both running. */ + list_for_each_entry(cpath, &path->source_list, source_node) +@@ -1567,8 +1557,6 @@ static void avs_condpaths_run(struct avs_dev *adev, struct avs_path *path, int t + if (cpath->source->state == AVS_PPL_STATE_RUNNING && + cpath->sink->state == AVS_PPL_STATE_RUNNING) + avs_condpath_run(adev, cpath, trigger); +- +- mutex_unlock(&adev->path_mutex); + } + + int avs_path_run(struct avs_path *path, int trigger) +diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c +--- a/sound/soc/intel/avs/pcm.c ++++ b/sound/soc/intel/avs/pcm.c +@@ -959,7 +959,7 @@ static const struct file_operations topology_name_fops = { + static int avs_component_load_libraries(struct avs_soc_component *acomp) + { + struct avs_tplg *tplg = acomp->tplg; +- struct avs_dev *adev = to_avs_dev(acomp->base.dev); ++ struct avs_dev *adev = to_avs_dev(acomp->base->dev); + int ret; + + if (!tplg->num_libs) +@@ -1393,25 +1393,28 @@ int avs_register_component(struct device *dev, const char *name, + struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) + { + struct avs_soc_component *acomp; +- int ret; ++ const char *comp_name; + + acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL); + if (!acomp) + return -ENOMEM; + +- acomp->base.name = devm_kstrdup(dev, name, GFP_KERNEL); +- if (!acomp->base.name) ++ acomp->base = snd_soc_component_alloc(dev); ++ if (!acomp->base) ++ return -ENOMEM; ++ ++ comp_name = devm_kstrdup(dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + + INIT_LIST_HEAD(&acomp->node); + + drv->use_dai_pcm_id = !obsolete_card_names; + +- ret = snd_soc_component_initialize(&acomp->base, drv, dev); +- if (ret < 0) +- return ret; ++ snd_soc_component_set_name(acomp->base, comp_name); ++ snd_soc_component_set_priv(acomp->base, acomp); + +- return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais); ++ return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + } + + static struct snd_soc_dai_driver dmic_cpu_dais[] = { +diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c +--- a/sound/soc/intel/avs/probes.c ++++ b/sound/soc/intel/avs/probes.c +@@ -296,19 +296,19 @@ static const struct snd_soc_component_driver avs_probe_component_driver = { + int avs_register_probe_component(struct avs_dev *adev, const char *name) + { + struct snd_soc_component *component; +- int ret; ++ const char *comp_name; + +- component = devm_kzalloc(adev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(adev->dev); + if (!component) + return -ENOMEM; + +- component->name = devm_kstrdup(adev->dev, name, GFP_KERNEL); +- if (!component->name) ++ comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); ++ if (!comp_name) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &avs_probe_component_driver, adev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, comp_name); + +- return snd_soc_add_component(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); ++ return snd_soc_register_component(component, ++ &avs_probe_component_driver, ++ probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + } +diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c +--- a/sound/soc/intel/avs/topology.c ++++ b/sound/soc/intel/avs/topology.c +@@ -2229,7 +2229,7 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) + + int avs_load_topology(struct snd_soc_component *comp, const char *filename) + { +- const struct firmware *fw; ++ const struct firmware *fw __free(firmware) = NULL; + int ret; + + ret = request_firmware(&fw, filename, comp->dev); +@@ -2242,7 +2242,6 @@ int avs_load_topology(struct snd_soc_component *comp, const char *filename) + if (ret < 0) + dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); + +- release_firmware(fw); + return ret; + } + +diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c +--- a/sound/soc/intel/avs/utils.c ++++ b/sound/soc/intel/avs/utils.c +@@ -6,6 +6,7 @@ + // Amadeusz Slawinski + // + ++#include + #include + #include + #include +@@ -48,13 +49,12 @@ int avs_get_module_entry(struct avs_dev *adev, const guid_t *uuid, struct avs_mo + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_entry_index(adev, uuid); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -62,13 +62,12 @@ int avs_get_module_id_entry(struct avs_dev *adev, u32 module_id, struct avs_modu + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + memcpy(entry, &adev->mods_info->entries[idx], sizeof(*entry)); + +- mutex_unlock(&adev->modres_mutex); + return (idx < 0) ? idx : 0; + } + +@@ -86,13 +85,12 @@ bool avs_is_module_ida_empty(struct avs_dev *adev, u32 module_id) + bool ret = false; + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx >= 0) + ret = ida_is_empty(adev->mod_idas[idx]); + +- mutex_unlock(&adev->modres_mutex); + return ret; + } + +@@ -163,68 +161,57 @@ int avs_module_info_init(struct avs_dev *adev, bool purge) + if (ret) + return AVS_IPC_RET(ret); + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + ret = avs_module_ida_alloc(adev, info, purge); + if (ret < 0) { + dev_err(adev->dev, "initialize module idas failed: %d\n", ret); +- goto exit; ++ return ret; + } + + /* Refresh current information with newly received table. */ + kfree(adev->mods_info); + adev->mods_info = info; + +-exit: +- mutex_unlock(&adev->modres_mutex); + return ret; + } + + void avs_module_info_free(struct avs_dev *adev) + { +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + avs_module_ida_destroy(adev); + kfree(adev->mods_info); + adev->mods_info = NULL; +- +- mutex_unlock(&adev->modres_mutex); + } + + int avs_module_id_alloc(struct avs_dev *adev, u16 module_id) + { +- int ret, idx, max_id; ++ int idx, max_id; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); + if (idx == -ENOENT) { + dev_err(adev->dev, "invalid module id: %d", module_id); +- ret = -EINVAL; +- goto exit; ++ return -EINVAL; + } + max_id = adev->mods_info->entries[idx].instance_max_count - 1; +- ret = ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); +-exit: +- mutex_unlock(&adev->modres_mutex); +- return ret; ++ ++ return ida_alloc_max(adev->mod_idas[idx], max_id, GFP_KERNEL); + } + + void avs_module_id_free(struct avs_dev *adev, u16 module_id, u8 instance_id) + { + int idx; + +- mutex_lock(&adev->modres_mutex); ++ guard(mutex)(&adev->modres_mutex); + + idx = avs_module_id_entry_index(adev, module_id); +- if (idx == -ENOENT) { ++ if (idx == -ENOENT) + dev_err(adev->dev, "invalid module id: %d", module_id); +- goto exit; +- } +- +- ida_free(adev->mod_idas[idx], instance_id); +-exit: +- mutex_unlock(&adev->modres_mutex); ++ else ++ ida_free(adev->mod_idas[idx], instance_id); + } + + /* +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -1483,12 +1483,12 @@ static int mc_probe(struct platform_device *pdev) + dmi_check_system(sof_sdw_quirk_table); + + if (quirk_override != -1) { +- dev_info(card->dev, "Overriding quirk 0x%lx => 0x%x\n", ++ dev_info(&pdev->dev, "Overriding quirk 0x%lx => 0x%x\n", + sof_sdw_quirk, quirk_override); + sof_sdw_quirk = quirk_override; + } + +- log_quirks(card->dev); ++ log_quirks(&pdev->dev); + + ctx->mc_quirk = sof_sdw_quirk; + /* reset amp_num to ensure amp_num++ starts from 0 in each probe */ +@@ -1507,13 +1507,13 @@ static int mc_probe(struct platform_device *pdev) + for (i = 0; i < ctx->codec_info_list_count; i++) + amp_num += codec_info_list[i].amp_num; + +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + " cfg-amp:%d", amp_num); + if (!card->components) + return -ENOMEM; + + if (mach->mach_params.dmic_num) { +- card->components = devm_kasprintf(card->dev, GFP_KERNEL, ++ card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:dmic cfg-mics:%d", + card->components, + mach->mach_params.dmic_num); +@@ -1524,7 +1524,7 @@ static int mc_probe(struct platform_device *pdev) + /* Register the card */ + ret = devm_snd_soc_register_card(card->dev, card); + if (ret) { +- dev_err_probe(card->dev, ret, "snd_soc_register_card failed %d\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed %d\n", ret); + asoc_sdw_mc_dailink_exit_loop(card); + return ret; + } +@@ -1542,8 +1542,8 @@ static void mc_remove(struct platform_device *pdev) + } + + static const struct platform_device_id mc_id_table[] = { +- { "sof_sdw", }, +- {} ++ { .name = "sof_sdw" }, ++ { } + }; + MODULE_DEVICE_TABLE(platform, mc_id_table); + +diff --git a/sound/soc/intel/catpt/core.h b/sound/soc/intel/catpt/core.h +--- a/sound/soc/intel/catpt/core.h ++++ b/sound/soc/intel/catpt/core.h +@@ -139,9 +139,7 @@ int catpt_dsp_send_msg(struct catpt_dev *cdev, struct catpt_ipc_msg request, + + int catpt_first_boot_firmware(struct catpt_dev *cdev); + int catpt_boot_firmware(struct catpt_dev *cdev, bool restore); +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan); +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan); ++int catpt_store_firmware_context(struct catpt_dev *cdev); + int catpt_coredump(struct catpt_dev *cdev); + + #include +diff --git a/sound/soc/intel/catpt/device.c b/sound/soc/intel/catpt/device.c +--- a/sound/soc/intel/catpt/device.c ++++ b/sound/soc/intel/catpt/device.c +@@ -28,44 +28,17 @@ + static int catpt_do_suspend(struct device *dev) + { + struct catpt_dev *cdev = dev_get_drvdata(dev); +- struct dma_chan *chan; + int ret; + +- chan = catpt_dma_request_config_chan(cdev); +- if (IS_ERR(chan)) +- return PTR_ERR(chan); +- + memset(&cdev->dx_ctx, 0, sizeof(cdev->dx_ctx)); + ret = catpt_ipc_enter_dxstate(cdev, CATPT_DX_STATE_D3, &cdev->dx_ctx); +- if (ret) { +- ret = CATPT_IPC_RET(ret); +- goto release_dma_chan; +- } +- +- ret = catpt_dsp_stall(cdev, true); + if (ret) +- goto release_dma_chan; ++ return CATPT_IPC_RET(ret); + +- ret = catpt_store_memdumps(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store memdumps failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_module_states(cdev, chan); +- if (ret) { +- dev_err(cdev->dev, "store module states failed: %d\n", ret); +- goto release_dma_chan; +- } +- +- ret = catpt_store_streams_context(cdev, chan); +- if (ret) +- dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); +- +-release_dma_chan: +- dma_release_channel(chan); ++ ret = catpt_store_firmware_context(cdev); + if (ret) + return ret; ++ + return catpt_dsp_power_down(cdev); + } + +diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c +--- a/sound/soc/intel/catpt/loader.c ++++ b/sound/soc/intel/catpt/loader.c +@@ -26,7 +26,7 @@ struct catpt_fw_hdr { + u32 reserved[4]; + } __packed; + +-struct catpt_fw_mod_hdr { ++struct catpt_fw_module_hdr { + char signature[FW_SIGNATURE_SIZE]; + u32 mod_size; + u32 blocks; +@@ -81,7 +81,7 @@ catpt_request_region(struct resource *root, resource_size_t size) + return __request_region(root, addr, size, NULL, 0); + } + +-int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { + struct catpt_stream_runtime *stream; + +@@ -108,7 +108,7 @@ int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -138,7 +138,7 @@ int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_store_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -171,6 +171,39 @@ int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + ++int catpt_store_firmware_context(struct catpt_dev *cdev) ++{ ++ struct dma_chan *chan; ++ int ret; ++ ++ chan = catpt_dma_request_config_chan(cdev); ++ if (IS_ERR(chan)) ++ return PTR_ERR(chan); ++ ++ ret = catpt_dsp_stall(cdev, true); ++ if (ret) ++ goto exit; ++ ++ ret = catpt_store_dram_data(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store memdumps failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_module_states(cdev, chan); ++ if (ret) { ++ dev_err(cdev->dev, "store module states failed: %d\n", ret); ++ goto exit; ++ } ++ ++ ret = catpt_store_streams_context(cdev, chan); ++ if (ret) ++ dev_err(cdev->dev, "store streams ctx failed: %d\n", ret); ++exit: ++ dma_release_channel(chan); ++ return ret; ++} ++ + static int + catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + { +@@ -199,7 +232,7 @@ catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) ++static int catpt_restore_dram_data(struct catpt_dev *cdev, struct dma_chan *chan) + { + int i; + +@@ -234,9 +267,9 @@ static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) + return 0; + } + +-static int catpt_restore_fwimage(struct catpt_dev *cdev, +- struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_block_hdr *blk) ++static int catpt_restore_dram_rodata(struct catpt_dev *cdev, ++ struct dma_chan *chan, dma_addr_t paddr, ++ struct catpt_fw_block_hdr *blk) + { + struct resource r1 = {}; + int i; +@@ -324,28 +357,26 @@ static int catpt_load_block(struct catpt_dev *cdev, + + static int catpt_restore_basefw(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *basefw) ++ struct catpt_fw_module_hdr *basefw) + { +- u32 offset = sizeof(*basefw); ++ u32 off = sizeof(*basefw); + int ret, i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + basefw, sizeof(*basefw), false); + +- /* restore basefw image */ ++ /* Restore IRAM and .rodata for DRAM based on the firmware image. */ + for (i = 0; i < basefw->blocks; i++) { + struct catpt_fw_block_hdr *blk; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_IRAM: +- ret = catpt_load_block(cdev, chan, paddr + offset, +- blk, false); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, false); + break; + default: +- ret = catpt_restore_fwimage(cdev, chan, paddr + offset, +- blk); ++ ret = catpt_restore_dram_rodata(cdev, chan, paddr + off, blk); + break; + } + +@@ -354,11 +385,11 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + +- /* then proceed with memory dumps */ +- ret = catpt_restore_memdumps(cdev, chan); ++ /* Then proceed with DRAM .data saved before D3. */ ++ ret = catpt_restore_dram_data(cdev, chan); + if (ret) + dev_err(cdev->dev, "restore memdumps failed: %d\n", ret); + +@@ -367,9 +398,9 @@ static int catpt_restore_basefw(struct catpt_dev *cdev, + + static int catpt_restore_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -379,7 +410,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + + switch (blk->ram_type) { + case CATPT_RAM_TYPE_INSTANCE: +@@ -390,7 +421,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + ALIGN(blk->size, 4)); + break; + default: +- ret = catpt_load_block(cdev, chan, paddr + offset, ++ ret = catpt_load_block(cdev, chan, paddr + off, + blk, false); + break; + } +@@ -400,7 +431,7 @@ static int catpt_restore_module(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + return 0; +@@ -408,10 +439,10 @@ static int catpt_restore_module(struct catpt_dev *cdev, + + static int catpt_load_module(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, +- struct catpt_fw_mod_hdr *mod) ++ struct catpt_fw_module_hdr *mod) + { + struct catpt_module_type *type; +- u32 offset = sizeof(*mod); ++ u32 off = sizeof(*mod); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, +@@ -423,9 +454,9 @@ static int catpt_load_module(struct catpt_dev *cdev, + struct catpt_fw_block_hdr *blk; + int ret; + +- blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); ++ blk = (struct catpt_fw_block_hdr *)((u8 *)mod + off); + +- ret = catpt_load_block(cdev, chan, paddr + offset, blk, true); ++ ret = catpt_load_block(cdev, chan, paddr + off, blk, true); + if (ret) { + dev_err(cdev->dev, "load block failed: %d\n", ret); + return ret; +@@ -440,7 +471,7 @@ static int catpt_load_module(struct catpt_dev *cdev, + type->state_size = blk->size; + } + +- offset += sizeof(*blk) + blk->size; ++ off += sizeof(*blk) + blk->size; + } + + /* init module type static info */ +@@ -457,17 +488,17 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -479,12 +510,10 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + + switch (mod->module_id) { + case CATPT_MODID_BASE_FW: +- ret = catpt_restore_basefw(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_basefw(cdev, chan, paddr + off, mod); + break; + default: +- ret = catpt_restore_module(cdev, chan, paddr + offset, +- mod); ++ ret = catpt_restore_module(cdev, chan, paddr + off, mod); + break; + } + +@@ -493,7 +522,7 @@ static int catpt_restore_firmware(struct catpt_dev *cdev, + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; +@@ -503,17 +532,17 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + struct dma_chan *chan, dma_addr_t paddr, + struct catpt_fw_hdr *fw) + { +- u32 offset = sizeof(*fw); ++ u32 off = sizeof(*fw); + int i; + + print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, + fw, sizeof(*fw), false); + + for (i = 0; i < fw->modules; i++) { +- struct catpt_fw_mod_hdr *mod; ++ struct catpt_fw_module_hdr *mod; + int ret; + +- mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); ++ mod = (struct catpt_fw_module_hdr *)((u8 *)fw + off); + if (strncmp(fw->signature, mod->signature, + FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "module signature mismatch\n"); +@@ -523,44 +552,40 @@ static int catpt_load_firmware(struct catpt_dev *cdev, + if (mod->module_id > CATPT_MODID_LAST) + return -EINVAL; + +- ret = catpt_load_module(cdev, chan, paddr + offset, mod); ++ ret = catpt_load_module(cdev, chan, paddr + off, mod); + if (ret) { + dev_err(cdev->dev, "load module failed: %d\n", ret); + return ret; + } + +- offset += sizeof(*mod) + mod->mod_size; ++ off += sizeof(*mod) + mod->mod_size; + } + + return 0; + } + +-static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, +- const char *name, const char *signature, +- bool restore) ++static int catpt_request_load_firmware(struct catpt_dev *cdev, struct dma_chan *chan, ++ const char *name, bool restore) + { + struct catpt_fw_hdr *fw; +- struct firmware *img; + dma_addr_t paddr; + void *vaddr; + int ret; + +- ret = request_firmware((const struct firmware **)&img, name, cdev->dev); ++ const struct firmware *img __free(firmware) = NULL; ++ ret = request_firmware(&img, name, cdev->dev); + if (ret) + return ret; + + fw = (struct catpt_fw_hdr *)img->data; +- if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) { ++ if (strncmp(fw->signature, FW_SIGNATURE, FW_SIGNATURE_SIZE)) { + dev_err(cdev->dev, "firmware signature mismatch\n"); +- ret = -EINVAL; +- goto release_fw; ++ return -EINVAL; + } + + vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); +- if (!vaddr) { +- ret = -ENOMEM; +- goto release_fw; +- } ++ if (!vaddr) ++ return -ENOMEM; + + memcpy(vaddr, img->data, img->size); + fw = (struct catpt_fw_hdr *)vaddr; +@@ -570,12 +595,10 @@ static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, + ret = catpt_load_firmware(cdev, chan, paddr, fw); + + dma_free_coherent(cdev->dev, img->size, vaddr, paddr); +-release_fw: +- release_firmware(img); + return ret; + } + +-static int catpt_load_images(struct catpt_dev *cdev, bool restore) ++static int catpt_request_dma_load_firmware(struct catpt_dev *cdev, bool restore) + { + struct dma_chan *chan; + int ret; +@@ -584,8 +607,7 @@ static int catpt_load_images(struct catpt_dev *cdev, bool restore) + if (IS_ERR(chan)) + return PTR_ERR(chan); + +- ret = catpt_load_image(cdev, chan, cdev->spec->fw_name, +- FW_SIGNATURE, restore); ++ ret = catpt_request_load_firmware(cdev, chan, cdev->spec->fw_name, restore); + if (ret) + goto release_dma_chan; + +@@ -605,7 +627,7 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + + catpt_dsp_stall(cdev, true); + +- ret = catpt_load_images(cdev, restore); ++ ret = catpt_request_dma_load_firmware(cdev, restore); + if (ret) { + dev_err(cdev->dev, "load binaries failed: %d\n", ret); + return ret; +@@ -619,10 +641,10 @@ int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) + if (!ret) { + dev_err(cdev->dev, "firmware ready timeout\n"); + return -ETIMEDOUT; +- /* Wake up does not mean FW is ready, an exception could occur. */ +- } else if (!cdev->ipc.ready) { +- return -EREMOTEIO; + } ++ /* Wake up does not mean FW is ready, an exception could occur. */ ++ if (!cdev->ipc.ready) ++ return -EREMOTEIO; + + /* update sram pg & clock once done booting */ + catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); +diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c +--- a/sound/soc/intel/catpt/pcm.c ++++ b/sound/soc/intel/catpt/pcm.c +@@ -75,8 +75,8 @@ static struct catpt_stream_template *catpt_topology[] = { + static struct catpt_stream_template * + catpt_get_stream_template(struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + enum catpt_stream_type type; + + type = cpu_dai->driver->id; +@@ -159,11 +159,11 @@ static void catpt_stream_read_position(struct catpt_dev *cdev, + static void catpt_arrange_page_table(struct snd_pcm_substream *substream, + struct snd_dma_buffer *pgtbl) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *databuf = snd_pcm_get_dma_buf(substream); + int i, pages; + +- pages = snd_sgbuf_aligned_pages(rtm->dma_bytes); ++ pages = snd_sgbuf_aligned_pages(runtime->dma_bytes); + + for (i = 0; i < pages; i++) { + u32 pfn, offset; +@@ -386,7 +386,7 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { +- struct snd_pcm_runtime *rtm = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dmab; + struct catpt_stream_runtime *stream; + struct catpt_audio_format afmt; +@@ -412,8 +412,8 @@ static int catpt_dai_hw_params(struct snd_pcm_substream *substream, + + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.page_table_addr = stream->pgtbl.addr; +- rinfo.num_pages = DIV_ROUND_UP(rtm->dma_bytes, PAGE_SIZE); +- rinfo.size = rtm->dma_bytes; ++ rinfo.num_pages = DIV_ROUND_UP(runtime->dma_bytes, PAGE_SIZE); ++ rinfo.size = runtime->dma_bytes; + rinfo.offset = 0; + rinfo.ring_first_page_pfn = PFN_DOWN(snd_sgbuf_get_addr(dmab, 0)); + +@@ -544,11 +544,11 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + struct catpt_notify_position *pos) + { + struct snd_pcm_substream *substream = stream->substream; +- struct snd_pcm_runtime *r = substream->runtime; ++ struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t dsppos, newpos; + int ret; + +- dsppos = bytes_to_frames(r, pos->stream_position); ++ dsppos = bytes_to_frames(runtime, pos->stream_position); + + if (!stream->prepared) + goto exit; +@@ -556,8 +556,8 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + if (stream->template->type != CATPT_STRM_TYPE_RENDER) + goto exit; + +- if (dsppos >= r->buffer_size / 2) +- newpos = r->buffer_size / 2; ++ if (dsppos >= runtime->buffer_size / 2) ++ newpos = runtime->buffer_size / 2; + else + newpos = 0; + /* +@@ -565,7 +565,7 @@ void catpt_stream_update_position(struct catpt_dev *cdev, + * (buffer half consumed) update wp to allow stream progression. + */ + ret = catpt_ipc_set_write_pos(cdev, stream->info.stream_hw_id, +- frames_to_bytes(r, newpos), ++ frames_to_bytes(runtime, newpos), + false, false); + if (ret) { + dev_err(cdev->dev, "update position for stream %d failed: %d\n", +@@ -600,11 +600,11 @@ static const struct snd_pcm_hardware catpt_pcm_hardware = { + }; + + static int catpt_component_pcm_new(struct snd_soc_component *component, +- struct snd_soc_pcm_runtime *rtm) ++ struct snd_soc_pcm_runtime *rtd) + { + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + +- snd_pcm_set_managed_buffer_all(rtm->pcm, SNDRV_DMA_TYPE_DEV_SG, ++ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV_SG, + cdev->dev, + catpt_pcm_hardware.buffer_bytes_max, + catpt_pcm_hardware.buffer_bytes_max); +@@ -615,9 +615,9 @@ static int catpt_component_pcm_new(struct snd_soc_component *component, + static int catpt_component_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + +- if (!rtm->dai_link->no_pcm) ++ if (!rtd->dai_link->no_pcm) + snd_soc_set_runtime_hwparams(substream, &catpt_pcm_hardware); + return 0; + } +@@ -626,13 +626,13 @@ static snd_pcm_uframes_t + catpt_component_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct snd_soc_pcm_runtime *rtm = snd_soc_substream_to_rtd(substream); +- struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtm, 0); ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct catpt_stream_runtime *stream; + struct catpt_dev *cdev = dev_get_drvdata(component->dev); + u32 pos; + +- if (rtm->dai_link->no_pcm) ++ if (rtd->dai_link->no_pcm) + return 0; + + stream = snd_soc_dai_get_dma_data(cpu_dai, substream); +@@ -650,10 +650,10 @@ static const struct snd_soc_dai_ops catpt_fe_dai_ops = { + .trigger = catpt_dai_trigger, + }; + +-static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtm, ++static int catpt_dai_pcm_new(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai) + { +- struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtm, 0); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct catpt_ssp_device_format devfmt; + struct catpt_dev *cdev = dev_get_drvdata(dai->dev); + int ret; +@@ -871,8 +871,7 @@ static int catpt_set_dspvol(struct catpt_dev *cdev, u8 stream_id, long *ctlvol) + return CATPT_IPC_RET(ret); + } + +-static int catpt_volume_info(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_info *uinfo) ++static int catpt_volume_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) + { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = CATPT_CHANNELS_MAX; +@@ -1073,18 +1072,14 @@ int catpt_arm_stream_templates(struct catpt_dev *cdev) + int catpt_register_plat_component(struct catpt_dev *cdev) + { + struct snd_soc_component *component; +- int ret; + +- component = devm_kzalloc(cdev->dev, sizeof(*component), GFP_KERNEL); ++ component = snd_soc_component_alloc(cdev->dev); + if (!component) + return -ENOMEM; + +- ret = snd_soc_component_initialize(component, &catpt_comp_driver, +- cdev->dev); +- if (ret) +- return ret; ++ snd_soc_component_set_name(component, catpt_comp_driver.name); + +- component->name = catpt_comp_driver.name; +- return snd_soc_add_component(component, dai_drivers, +- ARRAY_SIZE(dai_drivers)); ++ return snd_soc_register_component(component, ++ &catpt_comp_driver, ++ dai_drivers, ARRAY_SIZE(dai_drivers)); + } +diff --git a/sound/soc/intel/common/soc-acpi-intel-arl-match.c b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-arl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-arl-match.c +@@ -572,6 +572,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), +@@ -600,12 +601,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { + .links = arl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x1, /* link0 required */ + .links = arl_sdca_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-rt711-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -717,24 +717,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l23.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2) | BIT(3), + .links = lnl_cs42l43_l2_cs35l56x6_l13, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l2-cs35l56x6-l13.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(3), + .links = lnl_cs42l43_l0_cs35l56_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0-cs35l56-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -748,12 +752,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(2) | BIT(3), + .links = lnl_712_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt712-l2-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -766,19 +772,22 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = lnl_sdw_rt1320_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-lnl-rt1320-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1), + .links = lnl_sdw_rt713_l0_rt1318_l1, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg" ++ .sof_tplg_filename = "sof-lnl-rt713-l0-rt1318-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(1) | BIT(2), +diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +@@ -1321,60 +1321,70 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = tac5572_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5572.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5672_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5672.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tac5682_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tac5682.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2783_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2783.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = tas2883_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-tas2883.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1316_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_rt713_l0_rt1318_l12_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l12-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = mtl_rt713_l0_rt1318_l1_rt1713_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1318-l1-rt1713-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = GENMASK(2, 0), + .links = mtl_rt713_l0_rt1316_l12, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt713-l0-rt1316-l12.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3) | BIT(0), + .links = mtl_712_l0_1712_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt712-l0-rt1712-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1395,7 +1405,8 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .link_mask = GENMASK(2, 0), + .links = mtl_sdw_rt1318_l12_rt714_l0, + .drv_name = "sof_sdw", +- .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg" ++ .sof_tplg_filename = "sof-mtl-rt1318-l12-rt714-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2) | BIT(3), +@@ -1455,12 +1466,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l23-rt714-l1.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = 0x9, /* 2 active links required */ + .links = mtl_rt711_l0_rt1316_l3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l3.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), +@@ -1474,18 +1487,21 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { + .links = mtl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = rt5682_link2_max98373_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-rt5682-l2-max98373-l0.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0) | BIT(2), + .links = cs42l42_link0_max98363_link2, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +@@ -492,7 +492,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rt722_l0_rt1320_l23, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt722-l0-rt1320-l23.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, ++ .machine_check = snd_soc_acpi_intel_no_function_topology, + }, + { + .link_mask = BIT(1) | BIT(2), +@@ -522,14 +522,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { + .links = ptl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-rt711.tplg", ++ .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_sdw_rt712_vb_l3_rt1320_l3, + .drv_name = "sof_sdw", +- .machine_check = snd_soc_acpi_intel_sdca_is_device_rt712_vb, ++ .machine_check = snd_soc_acpi_intel_rt712_vb_no_function_topology, + .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", +- .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + {}, + }; +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,14 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) ++{ ++ return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && ++ snd_soc_acpi_intel_no_function_topology(arg); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_rt712_vb_no_function_topology, ++ "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + MODULE_DESCRIPTION("ASoC ACPI Intel SDCA quirks"); + MODULE_LICENSE("GPL"); + MODULE_IMPORT_NS("SND_SOC_SDCA"); +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,5 +11,6 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/intel/keembay/kmb_platform.c b/sound/soc/intel/keembay/kmb_platform.c +--- a/sound/soc/intel/keembay/kmb_platform.c ++++ b/sound/soc/intel/keembay/kmb_platform.c +@@ -873,6 +873,8 @@ static int kmb_plat_dai_probe(struct platform_device *pdev) + + if (kmb_i2s->use_pio) { + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0, + pdev->name, kmb_i2s); +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -2,24 +2,131 @@ + // + // Loongson ASoC Audio Machine driver + // +-// Copyright (C) 2023 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // Author: Yingkun Meng ++// Binbin Zhou + // + ++#include ++#include + #include ++#include ++#include ++#include + #include + #include +-#include +-#include +-#include + + static char codec_name[SND_ACPI_I2C_ID_LEN]; + + struct loongson_card_data { + struct snd_soc_card snd_card; + unsigned int mclk_fs; ++ struct gpio_desc *gpiod_hp_det; ++ struct gpio_desc *gpiod_hp_ctl; ++ struct gpio_desc *gpiod_spkr_en; ++ const struct loongson_card_config *cfg; + }; + ++struct loongson_card_config { ++ unsigned int fmt; ++ bool add_hp_jack; ++ bool add_dapm_widgets; ++ bool add_dapm_routes; ++}; ++ ++static const struct loongson_card_config ls2k1000_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_forever_pi_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = false, ++ .add_dapm_widgets = false, ++ .add_dapm_routes = false, ++}; ++ ++static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { ++ .fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, ++ .add_hp_jack = true, ++ .add_dapm_widgets = true, ++ .add_dapm_routes = true, ++}; ++ ++static int tegra_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) ++{ ++ struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); ++ struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Speaker")) ++ gpiod_set_value_cansleep(priv->gpiod_spkr_en, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ if (!snd_soc_dapm_widget_name_cmp(w, "Headphone")) ++ gpiod_set_value_cansleep(priv->gpiod_hp_ctl, ++ SND_SOC_DAPM_EVENT_ON(event)); ++ ++ return 0; ++} ++ ++static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", tegra_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++}; ++ ++/* Headphones Jack */ ++ ++static struct snd_soc_jack loongson_asoc_hp_jack; ++ ++static struct snd_soc_jack_pin loongson_asoc_hp_jack_pins[] = { ++ { ++ .pin = "Headphone", ++ .mask = SND_JACK_HEADPHONE ++ }, ++ { ++ .pin = "Speaker", ++ .mask = SND_JACK_HEADPHONE, ++ .invert = 1 ++ }, ++}; ++ ++static struct snd_soc_jack_gpio loongson_asoc_hp_jack_gpio = { ++ .name = "Headphones detection", ++ .report = SND_JACK_HEADPHONE, ++ .debounce_time = 150, ++}; ++ ++static int loongson_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_card *card = rtd->card; ++ struct loongson_card_data *ls_priv = snd_soc_card_get_drvdata(card); ++ int ret = 0; ++ ++ if (!ls_priv->cfg->add_hp_jack || !ls_priv->gpiod_hp_det) ++ return 0; ++ ++ ret = snd_soc_card_jack_new_pins(card, "Headphones Jack", ++ SND_JACK_HEADPHONE, ++ &loongson_asoc_hp_jack, ++ loongson_asoc_hp_jack_pins, ++ ARRAY_SIZE(loongson_asoc_hp_jack_pins)); ++ if (ret) { ++ dev_err(rtd->dev, "Headphones Jack creation failed: %d\n", ret); ++ return ret; ++ } ++ ++ loongson_asoc_hp_jack_gpio.desc = ls_priv->gpiod_hp_det; ++ ++ ret = snd_soc_jack_add_gpios(&loongson_asoc_hp_jack, 1, &loongson_asoc_hp_jack_gpio); ++ if (ret) ++ dev_err(rtd->dev, "Headphone GPIO not added: %d\n", ret); ++ ++ return ret; ++} ++ + static int loongson_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +@@ -45,7 +152,7 @@ static int loongson_card_hw_params(struct snd_pcm_substream *substream, + return ret; + } + +- return 0; ++ return snd_soc_runtime_set_dai_fmt(rtd, ls_card->cfg->fmt); + } + + static const struct snd_soc_ops loongson_ops = { +@@ -61,8 +168,7 @@ static struct snd_soc_dai_link loongson_dai_links[] = { + { + .name = "Loongson Audio Port", + .stream_name = "Loongson Audio", +- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF +- | SND_SOC_DAIFMT_CBC_CFC, ++ .init = loongson_asoc_machine_init, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, +@@ -123,16 +229,35 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) + + static int loongson_card_parse_of(struct loongson_card_data *data) + { +- struct device_node *cpu, *codec; + struct snd_soc_card *card = &data->snd_card; ++ struct device_node *cpu, *codec; + struct device *dev = card->dev; + int ret, i; + ++ data->gpiod_hp_det = devm_gpiod_get_optional(dev, "hp-det", GPIOD_IN); ++ if (IS_ERR(data->gpiod_hp_det)) ++ return PTR_ERR(data->gpiod_hp_det); ++ ++ data->gpiod_hp_ctl = devm_gpiod_get_optional(dev, "hp-ctl", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_hp_ctl)) ++ return PTR_ERR(data->gpiod_hp_ctl); ++ ++ data->gpiod_spkr_en = devm_gpiod_get_optional(dev, "spkr-en", GPIOD_OUT_LOW); ++ if (IS_ERR(data->gpiod_spkr_en)) ++ return PTR_ERR(data->gpiod_spkr_en); ++ ++ if (data->cfg->add_dapm_routes) { ++ ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); ++ if (ret) ++ return ret; ++ } ++ + cpu = of_get_child_by_name(dev->of_node, "cpu"); + if (!cpu) { + dev_err(dev, "platform property missing or invalid\n"); + return -EINVAL; + } ++ + codec = of_get_child_by_name(dev->of_node, "codec"); + if (!codec) { + dev_err(dev, "audio-codec property missing or invalid\n"); +@@ -177,12 +302,22 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + if (!ls_priv) + return -ENOMEM; + ++ ls_priv->cfg = (const struct loongson_card_config *)device_get_match_data(dev); ++ if (!ls_priv->cfg) ++ return -EINVAL; ++ + card = &ls_priv->snd_card; + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = loongson_dai_links; + card->num_links = ARRAY_SIZE(loongson_dai_links); ++ ++ if (ls_priv->cfg->add_dapm_widgets) { ++ card->dapm_widgets = loongson_aosc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ } ++ + snd_soc_card_set_drvdata(card, ls_priv); + + ret = device_property_read_string(dev, "model", &card->name); +@@ -202,7 +337,19 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_asoc_dt_ids[] = { +- { .compatible = "loongson,ls-audio-card" }, ++ /* Loongson-2K1000/Loongson-2K2000/LS7A */ ++ { ++ .compatible = "loongson,ls-audio-card", ++ .data = &ls2k1000_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-forever-pi-audio-card", ++ .data = &ls2k0300_forever_pi_card_config ++ }, ++ { ++ .compatible = "loongson,ls2k0300-dl2k0300b-audio-card", ++ .data = &ls2k0300_dl2k0300b_card_config ++ }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids); +diff --git a/sound/soc/loongson/loongson_i2s_plat.c b/sound/soc/loongson/loongson_i2s_plat.c +--- a/sound/soc/loongson/loongson_i2s_plat.c ++++ b/sound/soc/loongson/loongson_i2s_plat.c +@@ -2,7 +2,7 @@ + // + // Loongson I2S controller master mode dirver(platform device) + // +-// Copyright (C) 2023-2024 Loongson Technology Corporation Limited ++// Copyright (C) 2023-2026 Loongson Technology Corporation Limited + // + // Author: Yingkun Meng + // Binbin Zhou +@@ -21,6 +21,7 @@ + #include "loongson_i2s.h" + #include "loongson_dma.h" + ++/* Loongson-2K1000 APBDMA routing */ + #define LOONGSON_I2S_RX_DMA_OFFSET 21 + #define LOONGSON_I2S_TX_DMA_OFFSET 18 + +@@ -30,6 +31,11 @@ + #define LOONGSON_DMA3_CONF 0x3 + #define LOONGSON_DMA4_CONF 0x4 + ++struct loongson_i2s_plat_config { ++ int rev_id; ++ int (*i2s_dma_config)(struct platform_device *pdev); ++}; ++ + static int loongson_i2s_apbdma_config(struct platform_device *pdev) + { + int val; +@@ -47,8 +53,18 @@ static int loongson_i2s_apbdma_config(struct platform_device *pdev) + return 0; + } + ++static const struct loongson_i2s_plat_config ls2k0300_i2s_plat_config = { ++ .rev_id = 1, ++}; ++ ++static const struct loongson_i2s_plat_config ls2k1000_i2s_plat_config = { ++ .rev_id = 0, ++ .i2s_dma_config = loongson_i2s_apbdma_config, ++}; ++ + static int loongson_i2s_plat_probe(struct platform_device *pdev) + { ++ const struct loongson_i2s_plat_config *plat_config; + struct device *dev = &pdev->dev; + struct loongson_i2s *i2s; + struct resource *res; +@@ -59,12 +75,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (!i2s) + return -ENOMEM; + +- ret = loongson_i2s_apbdma_config(pdev); +- if (ret) +- return ret; ++ plat_config = device_get_match_data(dev); ++ if (!plat_config) ++ return -EINVAL; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- i2s->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (plat_config->i2s_dma_config) { ++ ret = plat_config->i2s_dma_config(pdev); ++ if (ret) ++ return ret; ++ } ++ ++ i2s->reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->reg_base)) + return dev_err_probe(dev, PTR_ERR(i2s->reg_base), + "devm_ioremap_resource failed\n"); +@@ -87,11 +108,17 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + if (IS_ERR(i2s_clk)) + return dev_err_probe(dev, PTR_ERR(i2s_clk), "clock property invalid\n"); + i2s->clk_rate = clk_get_rate(i2s_clk); ++ i2s->rev_id = plat_config->rev_id; + + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + dev_set_name(dev, LS_I2S_DRVNAME); + dev_set_drvdata(dev, i2s); + ++ if (i2s->rev_id == 1) { ++ regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET, I2S_CTRL_RESET); ++ fsleep(200); ++ } ++ + ret = devm_snd_soc_register_component(dev, &loongson_i2s_edma_component, + &loongson_i2s_dai, 1); + if (ret) +@@ -102,7 +129,8 @@ static int loongson_i2s_plat_probe(struct platform_device *pdev) + } + + static const struct of_device_id loongson_i2s_ids[] = { +- { .compatible = "loongson,ls2k1000-i2s" }, ++ { .compatible = "loongson,ls2k0300-i2s", .data = &ls2k0300_i2s_plat_config }, ++ { .compatible = "loongson,ls2k1000-i2s", .data = &ls2k1000_i2s_plat_config }, + { /* sentinel */ }, + }; + MODULE_DEVICE_TABLE(of, loongson_i2s_ids); +diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig +--- a/sound/soc/mediatek/Kconfig ++++ b/sound/soc/mediatek/Kconfig +@@ -7,7 +7,7 @@ config SND_SOC_MEDIATEK + + config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" +- depends on ARCH_MEDIATEK ++ depends on ARCH_MEDIATEK || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards +diff --git a/sound/soc/mediatek/common/mtk-dsp-sof-common.c b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +--- a/sound/soc/mediatek/common/mtk-dsp-sof-common.c ++++ b/sound/soc/mediatek/common/mtk-dsp-sof-common.c +@@ -232,6 +232,7 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + const char *propname) + { + struct device_node *np = dev->of_node; ++ struct snd_soc_dai_link *dai_link; + struct snd_soc_dai_link *parsed_dai_link; + const char *dai_name = NULL; + int i, j, ret, num_links, parsed_num_links = 0; +@@ -254,9 +255,9 @@ int mtk_sof_dailink_parse_of(struct device *dev, struct snd_soc_card *card, + return ret; + } + dev_dbg(dev, "ASoC: Property get dai_name:%s\n", dai_name); +- for (j = 0; j < card->num_links; j++) { +- if (!strcmp(dai_name, card->dai_link[j].name)) { +- memcpy(&parsed_dai_link[parsed_num_links++], &card->dai_link[j], ++ for_each_card_prelinks(card, j, dai_link) { ++ if (!strcmp(dai_name, dai_link->name)) { ++ memcpy(&parsed_dai_link[parsed_num_links++], dai_link, + sizeof(struct snd_soc_dai_link)); + break; + } +diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +--- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c ++++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c +@@ -315,7 +315,7 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, +- "%s snd_soc_register_card fail %d\n", ++ "%s device_property_read_u32() fail %d\n", + __func__, ret); + } + } +diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +--- a/sound/soc/mediatek/mt8189/mt8189-dai-adda.c ++++ b/sound/soc/mediatek/mt8189/mt8189-dai-adda.c +@@ -385,8 +385,6 @@ static int mtk_adda_pad_top_event(struct snd_soc_dapm_widget *w, + if (event == SND_SOC_DAPM_PRE_PMU) { + if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2_CLK_P2) + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB8); +- else if (afe_priv->mtkaif_protocol == MTKAIF_PROTOCOL_2) +- regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + else + regmap_write(afe->regmap, AFE_AUD_PAD_TOP_CFG0, 0xB0); + } +diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +--- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c ++++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +@@ -1019,7 +1019,7 @@ static struct snd_soc_card mt8192_mt6359_rt1015p_rt5682x_card = { + .num_dapm_routes = ARRAY_SIZE(mt8192_mt6359_rt1015p_rt5682x_routes), + }; + +-static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, ++static int mt8192_mt6359_card_set_be_link(struct device *dev, + struct snd_soc_dai_link *link, + struct device_node *node, + char *link_name) +@@ -1027,9 +1027,9 @@ static int mt8192_mt6359_card_set_be_link(struct snd_soc_card *card, + int ret; + + if (node && strcmp(link->name, link_name) == 0) { +- ret = snd_soc_of_get_dai_link_codecs(card->dev, node, link); ++ ret = snd_soc_of_get_dai_link_codecs(dev, node, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, "get dai link codecs fail\n"); ++ dev_err_probe(dev, ret, "get dai link codecs fail\n"); + return ret; + } + } +@@ -1065,21 +1065,21 @@ static int mt8192_mt6359_legacy_probe(struct mtk_soc_card_data *soc_card_data) + } + + for_each_card_prelinks(card, i, dai_link) { +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, speaker_codec, "I2S3"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, speaker_codec, "I2S3"); + if (ret) { + dev_err_probe(dev, ret, "%s set speaker_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S8"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S8"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); + break; + } + +- ret = mt8192_mt6359_card_set_be_link(card, dai_link, headset_codec, "I2S9"); ++ ret = mt8192_mt6359_card_set_be_link(dev, dai_link, headset_codec, "I2S9"); + if (ret) { + dev_err_probe(dev, ret, "%s set headset_codec fail\n", + dai_link->name); +diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +--- a/sound/soc/mediatek/mt8196/mt8196-nau8825.c ++++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c +@@ -104,8 +104,6 @@ static const struct snd_kcontrol_new mt8196_nau8825_controls[] = { + + #define EXT_SPK_AMP_W_NAME "Ext_Speaker_Amp" + +-static struct snd_soc_card mt8196_nau8825_soc_card; +- + static const struct snd_soc_dapm_widget mt8196_nau8825_card_widgets[] = { + /* SOF Uplink */ + SND_SOC_DAPM_MIXER("SOF_DMA_UL0", SND_SOC_NOPM, 0, 0, NULL, 0), +diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +--- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c ++++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +@@ -2120,19 +2120,15 @@ static int mt8365_afe_pcm_dev_probe(struct platform_device *pdev) + spin_lock_init(&afe_priv->afe_ctrl_lock); + mutex_init(&afe_priv->afe_clk_mutex); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- afe->base_addr = devm_ioremap_resource(&pdev->dev, res); ++ afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 1); +- if (res) { +- afe_priv->afe_sram_vir_addr = +- devm_ioremap_resource(&pdev->dev, res); +- if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { +- afe_priv->afe_sram_phy_addr = res->start; +- afe_priv->afe_sram_size = resource_size(res); +- } ++ afe_priv->afe_sram_vir_addr = ++ devm_platform_get_and_ioremap_resource(pdev, 1, &res); ++ if (!IS_ERR(afe_priv->afe_sram_vir_addr)) { ++ afe_priv->afe_sram_phy_addr = res->start; ++ afe_priv->afe_sram_size = resource_size(res); + } + + /* initial audio related clock */ +diff --git a/sound/soc/mediatek/mt8365/mt8365-mt6357.c b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +--- a/sound/soc/mediatek/mt8365/mt8365-mt6357.c ++++ b/sound/soc/mediatek/mt8365/mt8365-mt6357.c +@@ -70,7 +70,8 @@ static const struct snd_soc_dapm_route mt8365_mt6357_routes[] = { + static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -101,7 +102,8 @@ static int mt8365_mt6357_int_adda_startup(struct snd_pcm_substream *substream) + static void mt8365_mt6357_int_adda_shutdown(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(rtd->card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +@@ -245,7 +247,8 @@ static struct snd_soc_dai_link mt8365_mt6357_dais[] = { + + static int mt8365_mt6357_gpio_probe(struct snd_soc_card *card) + { +- struct mt8365_mt6357_priv *priv = snd_soc_card_get_drvdata(card); ++ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card); ++ struct mt8365_mt6357_priv *priv = soc_card_data->mach_priv; + struct device *dev = card->dev; + int ret, i; + +@@ -294,7 +297,6 @@ static int mt8365_mt6357_dev_probe(struct mtk_soc_card_data *soc_card_data, bool + struct mt8365_mt6357_priv *mach_priv; + int ret; + +- card->dev = dev; + ret = parse_dai_link_info(card); + if (ret) + goto err; +diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile +--- a/sound/soc/meson/Makefile ++++ b/sound/soc/meson/Makefile +@@ -4,6 +4,8 @@ snd-soc-meson-aiu-y := aiu.o + snd-soc-meson-aiu-y += aiu-acodec-ctrl.o + snd-soc-meson-aiu-y += aiu-codec-ctrl.o + snd-soc-meson-aiu-y += aiu-encoder-i2s.o ++snd-soc-meson-aiu-y += gx-formatter.o ++snd-soc-meson-aiu-y += aiu-formatter-i2s.o + snd-soc-meson-aiu-y += aiu-encoder-spdif.o + snd-soc-meson-aiu-y += aiu-fifo.o + snd-soc-meson-aiu-y += aiu-fifo-i2s.o +diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c +--- a/sound/soc/meson/aiu-encoder-i2s.c ++++ b/sound/soc/meson/aiu-encoder-i2s.c +@@ -10,14 +10,11 @@ + #include + + #include "aiu.h" ++#include "gx-formatter.h" ++#include "gx-interface.h" + +-#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) +-#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) +-#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) + #define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) +-#define AIU_RST_SOFT_I2S_FAST BIT(0) + +-#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) + #define AIU_CLK_CTRL_I2S_DIV_EN BIT(0) + #define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2) + #define AIU_CLK_CTRL_AOCLK_INVERT BIT(6) +@@ -35,49 +32,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component, + enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0); + } + +-static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) +-{ +- /* Always operate in split (classic interleaved) mode */ +- unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT; +- +- /* Reset required to update the pipeline */ +- snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST); +- snd_soc_component_read(component, AIU_I2S_SYNC); +- +- switch (params_physical_width(params)) { +- case 16: /* Nothing to do */ +- break; +- +- case 32: +- desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT); +- break; +- +- default: +- return -EINVAL; +- } +- +- switch (params_channels(params)) { +- case 2: /* Nothing to do */ +- break; +- case 8: +- desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; +- break; +- default: +- return -EINVAL; +- } +- +- snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC, +- AIU_I2S_SOURCE_DESC_MODE_8CH | +- AIU_I2S_SOURCE_DESC_MODE_24BIT | +- AIU_I2S_SOURCE_DESC_MODE_32BIT | +- AIU_I2S_SOURCE_DESC_MODE_SPLIT, +- desc); +- +- return 0; +-} +- + static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -108,6 +62,32 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, + return 0; + } + ++/* ++ * Return true if the given combination of channels and sample width requires ++ * the bs quirk. Return false otherwise. ++ */ ++static bool aiu_encoder_is_bs_quirk(unsigned int channels, int width) ++{ ++ return (channels == 8) && (width == 16); ++} ++ ++static int aiu_encoder_check_bs_quirk(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); ++ ++ /* Nothing to do if the other stream doesn't exist or it's not configured yet. */ ++ if (!other_stream || !other_stream->channels) ++ return 0; ++ ++ if (aiu_encoder_is_bs_quirk(other_stream->channels, other_stream->width) != ++ aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + struct snd_pcm_hw_params *params, + unsigned int bs) +@@ -119,7 +99,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + * increased by 50% to get the correct output rate. + * No idea why ! + */ +- if (params_width(params) == 16 && params_channels(params) == 8) { ++ if (aiu_encoder_is_bs_quirk(params_channels(params), params_width(params))) { + if (bs % 2) { + dev_err(component->dev, + "Cannot increase i2s divider by 50%%\n"); +@@ -141,25 +121,23 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, + return 0; + } + +-static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, +- struct snd_pcm_hw_params *params) ++static int aiu_encoder_i2s_set_clocks(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params, ++ struct snd_soc_dai *dai) + { ++ struct snd_soc_component *component = dai->component; + struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int srate = params_rate(params); + unsigned int fs, bs; + int ret; + + /* Get the oversampling factor */ +- fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate); ++ fs = DIV_ROUND_CLOSEST(iface->mclk_rate, srate); + +- if (fs % 64) ++ if ((fs % 64) || (fs == 0)) + return -EINVAL; + +- /* Send data MSB first */ +- snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG, +- AIU_I2S_DAC_CFG_MSB_FIRST, +- AIU_I2S_DAC_CFG_MSB_FIRST); +- + /* Set bclk to lrlck ratio */ + snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL, + AIU_CODEC_DAC_LRCLK_CTRL_DIV, +@@ -168,10 +146,22 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, + + bs = fs / 64; + +- if (aiu->platform->has_clk_ctrl_more_i2s_div) ++ if (aiu->platform->has_clk_ctrl_more_i2s_div) { ++ /* ++ * The hw rules added in startup() make this unreachable in the ++ * sequential case, but both streams may be refined concurrently ++ * before either commits its config, since only ops->hw_params ++ * runs under the card's pcm_mutex. Re-check against the committed ++ * state of the other stream, which is stable under that mutex. ++ */ ++ if (aiu_encoder_check_bs_quirk(substream, params, dai)) { ++ dev_err(dai->dev, "bclk requirements incompatible with other stream\n"); ++ return -EINVAL; ++ } + ret = aiu_encoder_i2s_set_more_div(component, params, bs); +- else ++ } else { + ret = aiu_encoder_i2s_set_legacy_div(component, params, bs); ++ } + + if (ret) + return ret; +@@ -188,23 +178,37 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ ret = aiu_encoder_i2s_set_clocks(substream, params, dai); ++ if (ret) { ++ dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret); ++ return ret; ++ } ++ ++ ts->physical_width = params_physical_width(params); ++ ts->width = params_width(params); ++ ts->channels = params_channels(params); ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + int ret; + +- /* Disable the clock while changing the settings */ +- aiu_encoder_i2s_divider_enable(component, false); ++ if (ts->clk_enabled) ++ return 0; + +- ret = aiu_encoder_i2s_setup_desc(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s desc failed\n"); ++ ret = clk_prepare_enable(ts->iface->mclk); ++ if (ret) + return ret; +- } + +- ret = aiu_encoder_i2s_set_clocks(component, params); +- if (ret) { +- dev_err(dai->dev, "setting i2s clocks failed\n"); +- return ret; +- } ++ ts->clk_enabled = true; + + aiu_encoder_i2s_divider_enable(component, true); + +@@ -214,9 +218,24 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + +- aiu_encoder_i2s_divider_enable(component, false); ++ /* ++ * If this is the last substream being closed then disable the i2s ++ * clock divider. ++ */ ++ if (snd_soc_dai_active(dai) <= 1) ++ aiu_encoder_i2s_divider_enable(component, 0); ++ ++ if (ts->clk_enabled) { ++ clk_disable_unprepare(ts->iface->mclk); ++ ts->clk_enabled = false; ++ } ++ ++ ts->channels = 0; ++ ts->width = 0; ++ ts->physical_width = 0; + + return 0; + } +@@ -224,6 +243,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, + static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct snd_soc_component *component = dai->component; ++ struct aiu *aiu = snd_soc_component_get_drvdata(component); ++ struct gx_iface *iface = &aiu->i2s.iface; + unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; + unsigned int val = 0; + unsigned int skew; +@@ -255,9 +276,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + skew = 0; + break; + default: ++ dev_err(dai->dev, "unsupported dai format\n"); + return -EINVAL; + } + ++ iface->fmt = fmt; ++ + val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew); + snd_soc_component_update_bits(component, AIU_CLK_CTRL, + AIU_CLK_CTRL_LRCLK_INVERT | +@@ -272,6 +296,7 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_iface *iface = &aiu->i2s.iface; + int ret; + + if (WARN_ON(clk_id != 0)) +@@ -280,11 +305,15 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + if (dir == SND_SOC_CLOCK_IN) + return 0; + +- ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq); +- if (ret) +- dev_err(dai->dev, "Failed to set sysclk to %uHz", freq); ++ ret = clk_set_rate(iface->mclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret); ++ return ret; ++ } + +- return ret; ++ iface->mclk_rate = freq; ++ ++ return 0; + } + + static const unsigned int hw_channels[] = {2, 8}; +@@ -294,10 +323,45 @@ static const struct snd_pcm_hw_constraint_list hw_channel_constraints = { + .mask = 0, + }; + ++static int aiu_encoder_i2s_pcm_hw_rule(struct snd_pcm_hw_params *params, ++ struct snd_pcm_hw_rule *rule) ++{ ++ struct gx_stream *other = rule->private; ++ struct snd_interval *ch = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); ++ /* ++ * The quirk is technically based on the significant bits whereas here ++ * we're using the physical width for simplicity. This works because ++ * S16_LE is the only format supported by this encoder that has: ++ * significant bits = physical width = 16-bits ++ */ ++ struct snd_interval *phys_width = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); ++ struct snd_interval new_i; ++ ++ if (other->channels == 0) ++ return 0; ++ ++ snd_interval_any(&new_i); ++ ++ if (rule->var == SNDRV_PCM_HW_PARAM_CHANNELS) { ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 8; ++ else if (snd_interval_single(phys_width) && phys_width->min == 16) ++ new_i.max = 2; /* Force 2ch */ ++ } else { /* SNDRV_PCM_HW_PARAM_SAMPLE_BITS */ ++ if (aiu_encoder_is_bs_quirk(other->channels, other->width)) ++ new_i.min = new_i.max = 16; ++ else if (snd_interval_single(ch) && ch->min == 8) ++ new_i.min = 17; /* Request physical width > 16 bits */ ++ } ++ ++ return snd_interval_refine(hw_param_interval(params, rule->var), &new_i); ++} ++ + static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); ++ struct gx_stream *other_stream = snd_soc_dai_dma_data_get(dai, !substream->stream); + int ret; + + /* Make sure the encoder gets either 2 or 8 channels */ +@@ -305,15 +369,79 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, + SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_channel_constraints); + if (ret) { +- dev_err(dai->dev, "adding channels constraints failed\n"); ++ dev_err(dai->dev, "adding channels constraints failed: %d\n", ret); + return ret; + } + +- ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks); +- if (ret) +- dev_err(dai->dev, "failed to enable i2s clocks\n"); ++ /* ++ * If DAI supports both playback and capture streams ensure the bs-quirk is ++ * handled correctly. ++ * This is only valid for GX platforms (has_clk_ctrl_more_i2s_div=true). ++ */ ++ if (aiu->platform->has_clk_ctrl_more_i2s_div && other_stream) { ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; + +- return ret; ++ ret = snd_pcm_hw_rule_add(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, ++ aiu_encoder_i2s_pcm_hw_rule, ++ other_stream, ++ SNDRV_PCM_HW_PARAM_CHANNELS, ++ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Enable only clocks which are required for the interface internal ++ * logic. MCLK is enabled/disabled from the formatter and the I2S ++ * divider is enabled/disabled in "hw_params"/"hw_free", respectively. ++ */ ++ ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable PCLK: %d\n", ret); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable MIXER: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk); ++ if (ret) { ++ dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++ return ret; ++ } ++ ++ /* ++ * We're always operating in split mode for the playback stream. ++ * ++ * This setting arguably belong to the 'aiu-formatter', but it's kept ++ * here for backward compatibility reason. At reset the I2S encoder ++ * operates in normal mode which would only support 8ch, but by default ++ * only 2ch are enabled. If a playback stream is started without ++ * changing to split mode, then the I2S encoder doesn't consume audio ++ * samples and the playback fails. ++ * Moving this to 'aiu-formatter' would cause the split mode to be set ++ * only when the formatter is enabled, which doesn't happen at boot as ++ * the default value for "HDMI CTRL SRC" is "DISABLED". ++ */ ++ ret = snd_soc_component_update_bits(dai->component, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT, ++ AIU_I2S_SOURCE_DESC_MODE_SPLIT); ++ if (ret < 0) ++ dev_err(dai->dev, "failed to update AIU_I2S_SOURCE_DESC: %d", ret); ++ ++ return 0; + } + + static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, +@@ -321,14 +449,89 @@ static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, + { + struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); + +- clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks); ++ clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk); ++ clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); ++ clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); ++} ++ ++static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, ++ int cmd, ++ struct snd_soc_dai *dai) ++{ ++ struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); ++ int ret; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ case SNDRV_PCM_TRIGGER_RESUME: ++ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ++ ret = gx_stream_start(ts); ++ break; ++ case SNDRV_PCM_TRIGGER_SUSPEND: ++ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ++ case SNDRV_PCM_TRIGGER_STOP: ++ gx_stream_stop(ts); ++ ret = 0; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai) ++{ ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ ts = snd_soc_dai_dma_data_get(dai, stream); ++ if (ts) ++ gx_stream_free(ts); ++ ++ snd_soc_dai_dma_data_set(dai, stream, NULL); ++ } ++ ++ return 0; ++} ++ ++static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai) ++{ ++ struct aiu *aiu = snd_soc_dai_get_drvdata(dai); ++ struct gx_iface *iface = &aiu->i2s.iface; ++ int stream; ++ ++ for_each_pcm_streams(stream) { ++ struct gx_stream *ts; ++ ++ if (!snd_soc_dai_get_widget(dai, stream)) ++ continue; ++ ++ ts = gx_stream_alloc(iface); ++ if (!ts) { ++ aiu_encoder_i2s_remove_dai(dai); ++ return -ENOMEM; ++ } ++ snd_soc_dai_dma_data_set(dai, stream, ts); ++ } ++ ++ iface->mclk = aiu->i2s.clks[MCLK].clk; ++ iface->mclk_rate = clk_get_rate(iface->mclk); ++ ++ return 0; + } + + const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = { ++ .probe = aiu_encoder_i2s_probe_dai, ++ .remove = aiu_encoder_i2s_remove_dai, + .hw_params = aiu_encoder_i2s_hw_params, ++ .prepare = aiu_encoder_i2s_prepare, + .hw_free = aiu_encoder_i2s_hw_free, + .set_fmt = aiu_encoder_i2s_set_fmt, + .set_sysclk = aiu_encoder_i2s_set_sysclk, + .startup = aiu_encoder_i2s_startup, + .shutdown = aiu_encoder_i2s_shutdown, ++ .trigger = aiu_encoder_i2s_trigger, + }; +diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/aiu-formatter-i2s.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++ ++#include "aiu.h" ++#include "gx-formatter.h" ++ ++#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) ++#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) ++#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) ++ ++#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) ++ ++static struct snd_soc_dai * ++aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dapm_path *p; ++ struct snd_soc_dai *be; ++ ++ snd_soc_dapm_widget_for_each_sink_path(w, p) { ++ if (!p->connect) ++ continue; ++ ++ if (p->sink->id == snd_soc_dapm_dai_in) ++ return (struct snd_soc_dai *)p->sink->priv; ++ ++ be = aiu_formatter_i2s_get_be(p->sink); ++ if (be) ++ return be; ++ } ++ ++ return NULL; ++} ++ ++static struct gx_stream * ++aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w) ++{ ++ struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w); ++ ++ if (!be) ++ return NULL; ++ ++ return snd_soc_dai_dma_data_get_playback(be); ++} ++ ++static int aiu_formatter_i2s_prepare(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts) ++{ ++ /* Always operate in split (classic interleaved) mode */ ++ unsigned int desc = 0; ++ ++ /* ++ * Pipeline reset is already implemented in aiu_fifo_i2s_trigger() at ++ * trigger time. ++ */ ++ ++ switch (ts->physical_width) { ++ case 16: /* Nothing to do */ ++ break; ++ ++ case 32: ++ desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ switch (ts->channels) { ++ case 2: /* Nothing to do */ ++ break; ++ case 8: ++ desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ regmap_update_bits(map, AIU_I2S_SOURCE_DESC, ++ AIU_I2S_SOURCE_DESC_MODE_8CH | ++ AIU_I2S_SOURCE_DESC_MODE_24BIT | ++ AIU_I2S_SOURCE_DESC_MODE_32BIT, ++ desc); ++ ++ /* Send data MSB first */ ++ regmap_update_bits(map, AIU_I2S_DAC_CFG, ++ AIU_I2S_DAC_CFG_MSB_FIRST, ++ AIU_I2S_DAC_CFG_MSB_FIRST); ++ ++ return 0; ++} ++ ++const struct gx_formatter_ops aiu_formatter_i2s_ops = { ++ .get_stream = aiu_formatter_i2s_get_stream, ++ .prepare = aiu_formatter_i2s_prepare, ++}; +diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c +--- a/sound/soc/meson/aiu.c ++++ b/sound/soc/meson/aiu.c +@@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, + static const struct snd_kcontrol_new aiu_spdif_encode_mux = + SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); + +-static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { +- SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, +- &aiu_spdif_encode_mux), ++#define AIU_WIDGET_SPDIF_SRC_SEL 0 ++#define AIU_WIDGET_I2S_FORMATTER 1 ++ ++static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { ++ [AIU_WIDGET_SPDIF_SRC_SEL] = ++ SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, ++ &aiu_spdif_encode_mux), ++ [AIU_WIDGET_I2S_FORMATTER] = ++ SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0, ++ gx_formatter_event, ++ (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), + }; + + static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = { +- { "I2S Encoder Playback", NULL, "I2S FIFO Playback" }, ++ { "I2S Formatter", NULL, "I2S FIFO Playback" }, ++ { "I2S Encoder Playback", NULL, "I2S Formatter" }, + { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" }, + { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" }, + { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" }, +@@ -145,6 +154,7 @@ static struct snd_soc_dai_driver aiu_cpu_dai_drv[] = { + .formats = AIU_FORMATS, + }, + .ops = &aiu_encoder_i2s_dai_ops, ++ .symmetric_rate = 1, + }, + [CPU_SPDIF_ENCODER] = { + .name = "SPDIF Encoder", +@@ -172,6 +182,11 @@ static const struct regmap_config aiu_regmap_cfg = { + .max_register = 0x2ac, + }; + ++static const struct gx_formatter_driver aiu_formatter_i2s_drv = { ++ .regmap_cfg = &aiu_regmap_cfg, ++ .ops = &aiu_formatter_i2s_ops, ++}; ++ + static int aiu_clk_bulk_get(struct device *dev, + const char * const *ids, + unsigned int num, +@@ -282,6 +297,14 @@ static int aiu_probe(struct platform_device *pdev) + if (ret) + return ret; + ++ /* Allocate the aiu-formatter into its widget */ ++ ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER], ++ &aiu_formatter_i2s_drv, map); ++ if (ret) { ++ dev_err(dev, "Failed to allocate aiu formatter\n"); ++ goto err; ++ } ++ + /* Register the cpu component of the aiu */ + ret = snd_soc_register_component(dev, &aiu_cpu_component, + aiu_cpu_dai_drv, +@@ -310,12 +333,14 @@ static int aiu_probe(struct platform_device *pdev) + + return 0; + err: ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(dev); + return ret; + } + + static void aiu_remove(struct platform_device *pdev) + { ++ gx_formatter_free(&aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER]); + snd_soc_unregister_component(&pdev->dev); + } + +diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h +--- a/sound/soc/meson/aiu.h ++++ b/sound/soc/meson/aiu.h +@@ -7,6 +7,8 @@ + #ifndef _MESON_AIU_H + #define _MESON_AIU_H + ++#include "gx-formatter.h" ++ + struct clk; + struct clk_bulk_data; + struct device; +@@ -25,6 +27,7 @@ struct aiu_interface { + struct clk_bulk_data *clks; + unsigned int clk_num; + int irq; ++ struct gx_iface iface; + }; + + struct aiu_platform_data { +@@ -58,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops; + extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops; ++extern const struct gx_formatter_ops aiu_formatter_i2s_ops; + + #define AIU_IEC958_BPF 0x000 + #define AIU_958_MISC 0x010 +diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c +--- a/sound/soc/meson/axg-card.c ++++ b/sound/soc/meson/axg-card.c +@@ -107,6 +107,7 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + struct snd_soc_dai_link *pad; + struct snd_soc_dai_link *lb; + struct snd_soc_dai_link_component *dlc; ++ struct device *dev = card->dev; + int ret; + + /* extend links */ +@@ -117,11 +118,11 @@ static int axg_card_add_tdm_loopback(struct snd_soc_card *card, + pad = &card->dai_link[*index]; + lb = &card->dai_link[*index + 1]; + +- lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name); ++ lb->name = devm_kasprintf(dev, GFP_KERNEL, "%s-lb", pad->name); + if (!lb->name) + return -ENOMEM; + +- dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL); ++ dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + +@@ -158,13 +159,14 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + struct device_node *node, + struct axg_dai_link_tdm_data *be) + { ++ struct device *dev = card->dev; + char propname[32]; + u32 tx, rx; + int i; + +- be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->tx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->tx_mask), GFP_KERNEL); +- be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, ++ be->rx_mask = devm_kcalloc(dev, AXG_TDM_NUM_LANES, + sizeof(*be->rx_mask), GFP_KERNEL); + if (!be->tx_mask || !be->rx_mask) + return -ENOMEM; +@@ -191,7 +193,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + + /* ... but the interface should at least have one direction */ + if (!tx && !rx) { +- dev_err(card->dev, "tdm link has no cpu slots\n"); ++ dev_err(dev, "tdm link has no cpu slots\n"); + return -EINVAL; + } + +@@ -207,7 +209,7 @@ static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, + * Error if the slots can't accommodate the largest mask or + * if it is just too big + */ +- dev_err(card->dev, "bad slot number\n"); ++ dev_err(dev, "bad slot number\n"); + return -EINVAL; + } + +@@ -222,8 +224,9 @@ static int axg_card_parse_codecs_masks(struct snd_soc_card *card, + struct axg_dai_link_tdm_data *be) + { + struct axg_dai_link_tdm_mask *codec_mask; ++ struct device *dev = card->dev; + +- codec_mask = devm_kcalloc(card->dev, link->num_codecs, ++ codec_mask = devm_kcalloc(dev, link->num_codecs, + sizeof(*codec_mask), GFP_KERNEL); + if (!codec_mask) + return -ENOMEM; +@@ -249,10 +252,11 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct axg_dai_link_tdm_data *be; ++ struct device *dev = card->dev; + int ret; + + /* Allocate tdm link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -266,7 +270,7 @@ static int axg_card_parse_tdm(struct snd_soc_card *card, + + ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); + if (ret) { +- dev_err(card->dev, "error parsing tdm link slots\n"); ++ dev_err(dev, "error parsing tdm link slots\n"); + return ret; + } + +@@ -310,9 +314,10 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/g12a-toacodec.c b/sound/soc/meson/g12a-toacodec.c +--- a/sound/soc/meson/g12a-toacodec.c ++++ b/sound/soc/meson/g12a-toacodec.c +@@ -312,7 +312,7 @@ static int g12a_toacodec_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/g12a-tohdmitx.c b/sound/soc/meson/g12a-tohdmitx.c +--- a/sound/soc/meson/g12a-tohdmitx.c ++++ b/sound/soc/meson/g12a-tohdmitx.c +@@ -251,7 +251,7 @@ static int g12a_tohdmitx_probe(struct platform_device *pdev) + + ret = device_reset(dev); + if (ret) +- return ret; ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/meson/gx-card.c b/sound/soc/meson/gx-card.c +--- a/sound/soc/meson/gx-card.c ++++ b/sound/soc/meson/gx-card.c +@@ -48,9 +48,10 @@ static int gx_card_parse_i2s(struct snd_soc_card *card, + struct meson_card *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link *link = &card->dai_link[*index]; + struct gx_dai_link_i2s_data *be; ++ struct device *dev = card->dev; + + /* Allocate i2s link parameters */ +- be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); ++ be = devm_kzalloc(dev, sizeof(*be), GFP_KERNEL); + if (!be) + return -ENOMEM; + priv->link_data[*index] = be; +@@ -81,9 +82,10 @@ static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np, + { + struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; + struct snd_soc_dai_link_component *cpu; ++ struct device *dev = card->dev; + int ret; + +- cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); ++ cpu = devm_kzalloc(dev, sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return -ENOMEM; + +diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.c +@@ -0,0 +1,282 @@ ++// SPDX-License-Identifier: (GPL-2.0 OR MIT) ++// ++// Copyright (c) 2026 BayLibre, SAS. ++// Author: Valerio Setti ++ ++#include ++#include ++#include ++#include ++ ++#include "gx-formatter.h" ++ ++struct gx_formatter { ++ struct list_head list; ++ struct gx_stream *stream; ++ const struct gx_formatter_driver *drv; ++ bool enabled; ++ struct regmap *map; ++}; ++ ++static int gx_formatter_enable(struct gx_formatter *formatter) ++{ ++ int ret; ++ ++ /* Do nothing if the formatter is already enabled */ ++ if (formatter->enabled) ++ return 0; ++ ++ /* Setup the stream parameter in the formatter */ ++ if (formatter->drv->ops->prepare) { ++ ret = formatter->drv->ops->prepare(formatter->map, ++ formatter->drv->quirks, ++ formatter->stream); ++ if (ret) ++ return ret; ++ } ++ ++ /* Finally, actually enable the formatter */ ++ if (formatter->drv->ops->enable) ++ formatter->drv->ops->enable(formatter->map); ++ ++ formatter->enabled = true; ++ ++ return 0; ++} ++ ++static void gx_formatter_disable(struct gx_formatter *formatter) ++{ ++ /* Do nothing if the formatter is already disabled */ ++ if (!formatter->enabled) ++ return; ++ ++ if (formatter->drv->ops->disable) ++ formatter->drv->ops->disable(formatter->map); ++ ++ formatter->enabled = false; ++} ++ ++static int gx_formatter_attach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Catch up if the stream is already running when we attach */ ++ if (ts->ready) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ list_add_tail(&formatter->list, &ts->formatter_list); ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++ ++static void gx_formatter_detach(struct gx_formatter *formatter) ++{ ++ struct gx_stream *ts = formatter->stream; ++ ++ if (!ts) ++ return; ++ ++ mutex_lock(&ts->lock); ++ list_del(&formatter->list); ++ mutex_unlock(&ts->lock); ++ ++ gx_formatter_disable(formatter); ++} ++ ++static int gx_formatter_power_up(struct gx_formatter *formatter, ++ struct snd_soc_dapm_widget *w) ++{ ++ struct gx_stream *ts = formatter->drv->ops->get_stream(w); ++ int ret; ++ ++ /* ++ * If we don't get a stream at this stage, it would mean that the ++ * widget is powering up but is not attached to any backend DAI. ++ * It should not happen, ever ! ++ */ ++ if (WARN_ON(!ts)) ++ return -ENODEV; ++ ++ formatter->stream = ts; ++ INIT_LIST_HEAD(&formatter->list); ++ ret = gx_formatter_attach(formatter); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void gx_formatter_power_down(struct gx_formatter *formatter) ++{ ++ gx_formatter_detach(formatter); ++ formatter->stream = NULL; ++} ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event) ++{ ++ struct snd_soc_component *c; ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ c = snd_soc_dapm_to_component(w->dapm); ++ ++ if (w->priv) ++ formatter = w->priv; ++ else ++ formatter = snd_soc_component_get_drvdata(c); ++ ++ switch (event) { ++ case SND_SOC_DAPM_PRE_PMU: ++ ret = gx_formatter_power_up(formatter, w); ++ break; ++ ++ case SND_SOC_DAPM_PRE_PMD: ++ gx_formatter_power_down(formatter); ++ break; ++ ++ default: ++ dev_err(c->dev, "Unexpected event %d\n", event); ++ return -EINVAL; ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_event); ++ ++int gx_formatter_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct gx_formatter_driver *drv; ++ struct gx_formatter *formatter; ++ void __iomem *regs; ++ ++ drv = of_device_get_match_data(dev); ++ if (!drv) { ++ dev_err(dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ platform_set_drvdata(pdev, formatter); ++ formatter->drv = drv; ++ ++ regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(regs)) ++ return PTR_ERR(regs); ++ ++ formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); ++ if (IS_ERR(formatter->map)) { ++ dev_err(dev, "failed to init regmap: %ld\n", ++ PTR_ERR(formatter->map)); ++ return PTR_ERR(formatter->map); ++ } ++ ++ return devm_snd_soc_register_component(dev, drv->component_drv, ++ NULL, 0); ++} ++EXPORT_SYMBOL_GPL(gx_formatter_probe); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap) ++{ ++ struct gx_formatter *formatter; ++ ++ formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); ++ if (!formatter) ++ return -ENOMEM; ++ ++ formatter->drv = drv; ++ formatter->map = regmap; ++ ++ w->priv = formatter; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(gx_formatter_create); ++ ++int gx_stream_start(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ int ret = 0; ++ ++ mutex_lock(&ts->lock); ++ ++ /* Start all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ ret = gx_formatter_enable(formatter); ++ if (ret) { ++ pr_err("failed to enable formatter\n"); ++ goto out; ++ } ++ } ++ ++ ts->ready = true; ++ ++out: ++ mutex_unlock(&ts->lock); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(gx_stream_start); ++ ++void gx_stream_stop(struct gx_stream *ts) ++{ ++ struct gx_formatter *formatter; ++ ++ mutex_lock(&ts->lock); ++ ts->ready = false; ++ ++ /* Stop all the formatters attached to the stream */ ++ list_for_each_entry(formatter, &ts->formatter_list, list) { ++ gx_formatter_disable(formatter); ++ } ++ ++ mutex_unlock(&ts->lock); ++} ++EXPORT_SYMBOL_GPL(gx_stream_stop); ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface) ++{ ++ struct gx_stream *ts; ++ ++ ts = kzalloc(sizeof(*ts), GFP_KERNEL); ++ if (ts) { ++ INIT_LIST_HEAD(&ts->formatter_list); ++ mutex_init(&ts->lock); ++ ts->iface = iface; ++ } ++ ++ return ts; ++} ++EXPORT_SYMBOL_GPL(gx_stream_alloc); ++ ++void gx_stream_free(struct gx_stream *ts) ++{ ++ /* ++ * If the list is not empty, it would mean that one of the formatter ++ * widget is still powered and attached to the interface while we ++ * are removing the TDM DAI. It should not be possible ++ */ ++ WARN_ON(!list_empty(&ts->formatter_list)); ++ mutex_destroy(&ts->lock); ++ kfree(ts); ++} ++EXPORT_SYMBOL_GPL(gx_stream_free); ++ ++MODULE_DESCRIPTION("Amlogic GX formatter driver"); ++MODULE_AUTHOR("Valerio Setti "); ++MODULE_LICENSE("GPL"); +diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-formatter.h +@@ -0,0 +1,56 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_FORMATTER_H ++#define _MESON_GX_FORMATTER_H ++ ++#include "gx-interface.h" ++ ++struct platform_device; ++struct regmap; ++struct snd_soc_dapm_widget; ++struct snd_kcontrol; ++ ++struct gx_formatter_hw { ++ unsigned int skew_offset; ++}; ++ ++struct gx_formatter_ops { ++ struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w); ++ void (*enable)(struct regmap *map); ++ void (*disable)(struct regmap *map); ++ int (*prepare)(struct regmap *map, ++ const struct gx_formatter_hw *quirks, ++ struct gx_stream *ts); ++}; ++ ++struct gx_formatter_driver { ++ const struct snd_soc_component_driver *component_drv; ++ const struct regmap_config *regmap_cfg; ++ const struct gx_formatter_ops *ops; ++ const struct gx_formatter_hw *quirks; ++}; ++ ++int gx_formatter_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *control, ++ int event); ++int gx_formatter_probe(struct platform_device *pdev); ++ ++int gx_formatter_create(struct device *dev, ++ struct snd_soc_dapm_widget *w, ++ const struct gx_formatter_driver *drv, ++ struct regmap *regmap); ++ ++/* ++ * Formatter data is already freed when the associated device is removed, ++ * so we just need to remove the pointer from the widget. ++ */ ++static inline void gx_formatter_free(struct snd_soc_dapm_widget *w) ++{ ++ w->priv = NULL; ++} ++ ++#endif /* _MESON_GX_FORMATTER_H */ +diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h +new file mode 100644 +--- /dev/null ++++ b/sound/soc/meson/gx-interface.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ ++/* ++ * Copyright (c) 2026 Baylibre SAS. ++ * Author: Valerio Setti ++ */ ++ ++#ifndef _MESON_GX_INTERFACE_H ++#define _MESON_GX_INTERFACE_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++struct gx_iface { ++ struct clk *mclk; ++ unsigned long mclk_rate; ++ ++ /* format is common to all the DAIs of the iface */ ++ unsigned int fmt; ++}; ++ ++struct gx_stream { ++ struct gx_iface *iface; ++ struct list_head formatter_list; ++ struct mutex lock; ++ unsigned int channels; ++ unsigned int width; ++ unsigned int physical_width; ++ bool ready; ++ ++ /* For continuous clock tracking */ ++ bool clk_enabled; ++}; ++ ++struct gx_stream *gx_stream_alloc(struct gx_iface *iface); ++void gx_stream_free(struct gx_stream *ts); ++int gx_stream_start(struct gx_stream *ts); ++void gx_stream_stop(struct gx_stream *ts); ++ ++#endif /* _MESON_GX_INTERFACE_H */ +diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c +--- a/sound/soc/meson/meson-card-utils.c ++++ b/sound/soc/meson/meson-card-utils.c +@@ -71,6 +71,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + struct device_node *node, + struct snd_soc_dai_link_component *dlc) + { ++ struct device *dev = card->dev; + int ret; + + if (!dlc || !node) +@@ -78,7 +79,7 @@ int meson_card_parse_dai(struct snd_soc_card *card, + + ret = snd_soc_of_get_dlc(node, NULL, dlc, 0); + if (ret) +- return dev_err_probe(card->dev, ret, "can't parse dai\n"); ++ return dev_err_probe(dev, ret, "can't parse dai\n"); + + return ret; + } +@@ -89,7 +90,8 @@ static int meson_card_set_link_name(struct snd_soc_card *card, + struct device_node *node, + const char *prefix) + { +- char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", ++ struct device *dev = card->dev; ++ char *name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", + prefix, node->full_name); + if (!name) + return -ENOMEM; +@@ -132,16 +134,17 @@ int meson_card_set_be_link(struct snd_soc_card *card, + struct device_node *node) + { + struct snd_soc_dai_link_component *codec; ++ struct device *dev = card->dev; + int ret, num_codecs; + + num_codecs = of_get_child_count(node); + if (!num_codecs) { +- dev_err(card->dev, "be link %s has no codec\n", ++ dev_err(dev, "be link %s has no codec\n", + node->full_name); + return -EINVAL; + } + +- codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); ++ codec = devm_kcalloc(dev, num_codecs, sizeof(*codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + +@@ -158,7 +161,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, + + ret = meson_card_set_link_name(card, link, node, "be"); + if (ret) +- dev_err(card->dev, "error setting %pOFn link name\n", node); ++ dev_err(dev, "error setting %pOFn link name\n", node); + + return ret; + } +@@ -189,12 +192,13 @@ EXPORT_SYMBOL_GPL(meson_card_set_fe_link); + static int meson_card_add_links(struct snd_soc_card *card) + { + struct meson_card *priv = snd_soc_card_get_drvdata(card); +- struct device_node *node = card->dev->of_node; ++ struct device *dev = card->dev; ++ struct device_node *node = dev->of_node; + int num, i, ret; + + num = of_get_child_count(node); + if (!num) { +- dev_err(card->dev, "card has no links\n"); ++ dev_err(dev, "card has no links\n"); + return -EINVAL; + } + +@@ -219,8 +223,10 @@ static int meson_card_parse_of_optional(struct snd_soc_card *card, + int (*func)(struct snd_soc_card *c, + const char *p)) + { ++ struct device *dev = card->dev; ++ + /* If property is not provided, don't fail ... */ +- if (!of_property_present(card->dev->of_node, propname)) ++ if (!of_property_present(dev->of_node, propname)) + return 0; + + /* ... but do fail if it is provided and the parsing fails */ +diff --git a/sound/soc/meson/t9015.c b/sound/soc/meson/t9015.c +--- a/sound/soc/meson/t9015.c ++++ b/sound/soc/meson/t9015.c +@@ -78,8 +78,14 @@ static int t9015_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static const u64 t9015_dai_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_LEFT_J; ++ + static const struct snd_soc_dai_ops t9015_dai_ops = { + .set_fmt = t9015_dai_set_fmt, ++ .auto_selectable_formats = &t9015_dai_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver t9015_dai = { +@@ -265,10 +271,8 @@ static int t9015_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(priv->avdd), "failed to AVDD\n"); + + ret = device_reset(dev); +- if (ret) { +- dev_err(dev, "reset failed\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to reset device\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { +diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c +--- a/sound/soc/mxs/mxs-saif.c ++++ b/sound/soc/mxs/mxs-saif.c +@@ -826,12 +826,9 @@ static int mxs_saif_probe(struct platform_device *pdev) + mxs_saif[saif->id] = saif; + + saif->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(saif->clk)) { +- ret = PTR_ERR(saif->clk); +- dev_err(&pdev->dev, "Cannot get the clock: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(saif->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(saif->clk), ++ "Cannot get the clock\n"); + + saif->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(saif->base)) +@@ -844,10 +841,8 @@ static int mxs_saif_probe(struct platform_device *pdev) + saif->dev = &pdev->dev; + ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0, + dev_name(&pdev->dev), saif); +- if (ret) { +- dev_err(&pdev->dev, "failed to request irq\n"); ++ if (ret) + return ret; +- } + + platform_set_drvdata(pdev, saif); + +@@ -860,16 +855,12 @@ static int mxs_saif_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &mxs_saif_component, + &mxs_saif_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "register DAI failed\n"); ++ if (ret) + return ret; +- } + + ret = mxs_pcm_platform_register(&pdev->dev); +- if (ret) { +- dev_err(&pdev->dev, "register PCM failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c +--- a/sound/soc/mxs/mxs-sgtl5000.c ++++ b/sound/soc/mxs/mxs-sgtl5000.c +@@ -155,8 +155,6 @@ static int mxs_sgtl5000_probe(struct platform_device *pdev) + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { +- dev_err(&pdev->dev, "failed to parse audio-routing (%d)\n", +- ret); + mxs_saif_put_mclk(0); + return ret; + } +diff --git a/sound/soc/pxa/pxa2xx-ac97-lib.c b/sound/soc/pxa/pxa2xx-ac97-lib.c +--- a/sound/soc/pxa/pxa2xx-ac97-lib.c ++++ b/sound/soc/pxa/pxa2xx-ac97-lib.c +@@ -316,10 +316,8 @@ int pxa2xx_ac97_hw_probe(struct platform_device *dev) + int irq; + + ac97_reg_base = devm_platform_ioremap_resource(dev, 0); +- if (IS_ERR(ac97_reg_base)) { +- dev_err(&dev->dev, "Missing MMIO resource\n"); ++ if (IS_ERR(ac97_reg_base)) + return PTR_ERR(ac97_reg_base); +- } + + if (cpu_is_pxa27x()) { + /* Assert reset using GPIOD_OUT_HIGH, because reset is GPIO_ACTIVE_LOW */ +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -23,6 +23,161 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { + SND_SOC_DAPM_SPK("DP7 Jack", NULL), + }; + ++static struct device_node *qcom_snd_get_link_node(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_card *card = rtd->card; ++ struct of_phandle_args args; ++ int ret; ++ ++ if (!card->dev || !card->dev->of_node) ++ return NULL; ++ ++ for_each_available_child_of_node_scoped(card->dev->of_node, np) { ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(np, "cpu"); ++ ++ if (!cpu_np) ++ continue; ++ ++ ret = of_parse_phandle_with_args(cpu_np, "sound-dai", "#sound-dai-cells", 0, ++ &args); ++ if (ret) ++ continue; ++ ++ if (args.np == rtd->dai_link->cpus[0].of_node && ++ args.args_count == 1 && args.args[0] == cpu_dai->id) { ++ of_node_put(args.np); ++ return of_node_get(np); ++ } ++ ++ of_node_put(args.np); ++ } ++ ++ return NULL; ++} ++ ++static int qcom_snd_parse_tdm_slot(struct device_node *np, ++ struct qcom_snd_tdm_slot_cfg *cfg) ++{ ++ memset(cfg, 0, sizeof(*cfg)); ++ ++ return snd_soc_of_parse_tdm_slot(np, &cfg->tx_mask, &cfg->rx_mask, ++ &cfg->slots, &cfg->slot_width); ++} ++ ++static int qcom_snd_normalize_tdm_slots(struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ unsigned int slots; ++ unsigned int slot_width; ++ ++ if (cpu_cfg->slots && codec_cfg->slots && cpu_cfg->slots != codec_cfg->slots) ++ return -EINVAL; ++ ++ if (cpu_cfg->slot_width && codec_cfg->slot_width && ++ cpu_cfg->slot_width != codec_cfg->slot_width) ++ return -EINVAL; ++ ++ slots = cpu_cfg->slots ?: codec_cfg->slots; ++ if (!slots) ++ return 0; ++ ++ slot_width = cpu_cfg->slot_width ?: codec_cfg->slot_width; ++ if (!slot_width) ++ return -EINVAL; ++ ++ cpu_cfg->slots = slots; ++ codec_cfg->slots = slots; ++ cpu_cfg->slot_width = slot_width; ++ codec_cfg->slot_width = slot_width; ++ ++ return 0; ++} ++ ++static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct device_node *link_np __free(device_node) = qcom_snd_get_link_node(rtd); ++ int ret; ++ ++ if (!link_np) ++ return -EINVAL; ++ ++ struct device_node *cpu_np __free(device_node) = ++ of_get_child_by_name(link_np, "cpu"); ++ struct device_node *codec_np __free(device_node) = ++ of_get_child_by_name(link_np, "codec"); ++ if (!cpu_np || !codec_np) ++ return -EINVAL; ++ ++ ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_parse_tdm_slot(codec_np, codec_cfg); ++} ++ ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ int ret; ++ ++ ret = qcom_snd_parse_dai_tdm_slots(rtd, cpu_cfg, codec_cfg); ++ if (ret) ++ return ret; ++ ++ return qcom_snd_normalize_tdm_slots(cpu_cfg, codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_get_dai_tdm_slots); ++ ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg) ++{ ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ int i; ++ int ret; ++ ++ if (!cpu_cfg->slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_tdm_slot(cpu_dai, cpu_cfg->tx_mask, cpu_cfg->rx_mask, ++ cpu_cfg->slots, cpu_cfg->slot_width); ++ if (ret) ++ return ret; ++ ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_tdm_slot(codec_dai, ++ codec_cfg->tx_mask, ++ codec_cfg->rx_mask, ++ codec_cfg->slots, ++ codec_cfg->slot_width); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots_cfg); ++ ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) ++{ ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ int ret; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++} ++EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots); ++ + int qcom_snd_parse_of(struct snd_soc_card *card) + { + struct device *dev = card->dev; +@@ -91,7 +246,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { +- dev_err(card->dev, "error getting codec dai_link name\n"); ++ dev_err(dev, "error getting codec dai_link name\n"); + return ret; + } + +@@ -109,7 +264,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); + if (ret) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: error getting cpu dai name\n", link->name); + return ret; + } +@@ -126,7 +281,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + "sound-dai", + 0); + if (!link->platforms->of_node) { +- dev_err(card->dev, "%s: platform dai not found\n", link->name); ++ dev_err(dev, "%s: platform dai not found\n", link->name); + return -EINVAL; + } + } else { +@@ -136,7 +291,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { +- dev_err_probe(card->dev, ret, ++ dev_err_probe(dev, ret, + "%s: codec dai not found\n", link->name); + return ret; + } +@@ -215,6 +370,7 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + } + + switch (cpu_dai->id) { ++ case LPI_MI2S_RX_0: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: +diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h +--- a/sound/soc/qcom/common.h ++++ b/sound/soc/qcom/common.h +@@ -7,9 +7,23 @@ + #include + #include + +-#define LPASS_MAX_PORT (SENARY_MI2S_TX + 1) ++#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) ++ ++struct qcom_snd_tdm_slot_cfg { ++ unsigned int tx_mask; ++ unsigned int rx_mask; ++ unsigned int slots; ++ unsigned int slot_width; ++}; + + int qcom_snd_parse_of(struct snd_soc_card *card); ++int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, ++ struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, ++ const struct qcom_snd_tdm_slot_cfg *cpu_cfg, ++ const struct qcom_snd_tdm_slot_cfg *codec_cfg); ++int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); + int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); + int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, +diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c +--- a/sound/soc/qcom/qdsp6/audioreach.c ++++ b/sound/soc/qcom/qdsp6/audioreach.c +@@ -152,6 +152,13 @@ struct apm_i2s_module_intf_cfg { + + #define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8) + ++struct apm_audio_if_module_intf_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_audio_if_intf_cfg cfg; ++} __packed; ++ ++#define APM_AUDIO_IF_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_audio_if_module_intf_cfg), 8) ++ + struct apm_module_hw_ep_mf_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_mf mf; +@@ -168,6 +175,13 @@ struct apm_module_frame_size_factor_cfg { + + #define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8) + ++struct apm_module_hw_ep_frame_duration_cfg { ++ struct apm_module_param_data param_data; ++ struct param_id_hw_ep_frame_duration frame_duration; ++} __packed; ++ ++#define APM_HW_EP_FRAME_DURATION_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_frame_duration_cfg), 8) ++ + struct apm_module_hw_ep_power_mode_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_power_mode_cfg power_mode; +@@ -703,6 +717,7 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + int pm_sz = APM_HW_EP_PMODE_CFG_PSIZE; + int size = ic_sz + ep_sz + fs_sz + pm_sz; + void *p; ++ int i; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) +@@ -741,7 +756,12 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, + + intf_cfg->cfg.lpaif_type = module->hw_interface_type; + intf_cfg->cfg.intf_index = module->hw_interface_idx; +- intf_cfg->cfg.active_channels_mask = (1 << cfg->num_channels) - 1; ++ intf_cfg->cfg.active_channels_mask = 0; ++ /* Convert the physical channel mapping into a bit field */ ++ for (i = 0; i < AR_PCM_MAX_NUM_CHANNEL; i++) ++ if (cfg->channel_map[i]) ++ intf_cfg->cfg.active_channels_mask |= BIT(i); ++ + p += ic_sz; + + pm_cfg = p; +@@ -840,7 +860,7 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + uint32_t num_channels = cfg->num_channels; + int payload_size = APM_MFC_CFG_PSIZE(media_format, num_channels) + + APM_MODULE_PARAM_DATA_SIZE; +- int i; ++ int i, j; + void *p; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); +@@ -860,8 +880,12 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, + media_format->sample_rate = cfg->sample_rate; + media_format->bit_width = cfg->bit_width; + media_format->num_channels = cfg->num_channels; +- for (i = 0; i < num_channels; i++) +- media_format->channel_mapping[i] = cfg->channel_map[i]; ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!cfg->channel_map[i]) ++ continue; ++ media_format->channel_mapping[j++] = cfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1042,6 +1066,81 @@ static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } + ++static int audioreach_audio_if_set_media_format(struct q6apm_graph *graph, ++ const struct audioreach_module *module, ++ const struct audioreach_module_config *cfg) ++{ ++ struct apm_module_hw_ep_frame_duration_cfg *fd_cfg; ++ struct apm_module_param_data *param_data; ++ struct apm_audio_if_module_intf_cfg *intf_cfg; ++ struct apm_module_hw_ep_mf_cfg *hw_cfg; ++ int ic_sz = APM_AUDIO_IF_INTF_CFG_PSIZE; ++ int ep_sz = APM_HW_EP_CFG_PSIZE; ++ int fd_sz = APM_HW_EP_FRAME_DURATION_PSIZE; ++ int size = ic_sz + ep_sz + fd_sz; ++ u32 slot_mask = cfg->slot_mask ? cfg->slot_mask : module->slot_mask; ++ u16 nslots_per_frame = cfg->nslots_per_frame ? ++ (u16)cfg->nslots_per_frame : module->nslots_per_frame; ++ u16 slot_width = cfg->slot_width ? (u16)cfg->slot_width : module->slot_width; ++ void *p; ++ ++ struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); ++ if (IS_ERR(pkt)) ++ return PTR_ERR(pkt); ++ ++ p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; ++ intf_cfg = p; ++ ++ param_data = &intf_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_AUDIO_IF_INTF_CFG; ++ param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; ++ intf_cfg->cfg.qaif_type = module->qaif_type; ++ intf_cfg->cfg.intf_idx = (u16)module->hw_interface_idx; ++ intf_cfg->cfg.intf_mode = module->intf_mode; ++ intf_cfg->cfg.ctrl_data_out_enable = module->ctrl_data_out_enable; ++ intf_cfg->cfg.active_slot_mask = slot_mask; ++ intf_cfg->cfg.nslots_per_frame = nslots_per_frame; ++ intf_cfg->cfg.slot_width = slot_width; ++ intf_cfg->cfg.active_lane_mask = module->active_lane_mask; ++ intf_cfg->cfg.frame_sync_rate = module->frame_sync_rate; ++ intf_cfg->cfg.frame_sync_src = module->sync_src; ++ intf_cfg->cfg.frame_sync_mode = module->sync_mode; ++ intf_cfg->cfg.invert_frame_sync_pulse = module->ctrl_invert_sync_pulse; ++ intf_cfg->cfg.frame_sync_data_delay = module->ctrl_sync_data_delay; ++ intf_cfg->cfg.bit_clk_type = module->bit_clk_type; ++ intf_cfg->cfg.inv_int_bit_clk = module->inv_int_bit_clk; ++ intf_cfg->cfg.inv_ext_bit_clk = module->inv_ext_bit_clk; ++ ++ p += ic_sz; ++ hw_cfg = p; ++ param_data = &hw_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_MF_CFG; ++ param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; ++ ++ hw_cfg->mf.sample_rate = cfg->sample_rate; ++ hw_cfg->mf.bit_width = cfg->bit_width; ++ hw_cfg->mf.num_channels = cfg->num_channels; ++ hw_cfg->mf.data_format = module->data_format; ++ ++ p += ep_sz; ++ fd_cfg = p; ++ param_data = &fd_cfg->param_data; ++ param_data->module_instance_id = module->instance_id; ++ param_data->error_code = 0; ++ param_data->param_id = PARAM_ID_HW_EP_FRAME_DURATION; ++ param_data->param_size = fd_sz - APM_MODULE_PARAM_DATA_SIZE; ++ fd_cfg->frame_duration.frame_duration_in_us = AUDIO_IF_FRAME_DURATION_US; ++ fd_cfg->frame_duration.allow_frame_duration_normalization = AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE; ++ fd_cfg->frame_duration.min_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MIN_US; ++ fd_cfg->frame_duration.max_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MAX_US; ++ ++ return q6apm_send_cmd_sync(graph->apm, pkt, 0); ++} ++ + static int audioreach_logging_set_media_format(struct q6apm_graph *graph, + const struct audioreach_module *module) + { +@@ -1080,6 +1179,7 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + struct apm_pcm_module_media_fmt_cmd *cfg; + struct apm_module_param_data *param_data; + int payload_size; ++ int i, j; + + if (num_channels > 4) { + dev_err(graph->dev, "Error: Invalid channels (%d)!\n", num_channels); +@@ -1113,7 +1213,12 @@ static int audioreach_pcm_set_media_format(struct q6apm_graph *graph, + media_cfg->num_channels = mcfg->num_channels; + media_cfg->q_factor = mcfg->bit_width - 1; + media_cfg->bits_per_sample = mcfg->bit_width; +- memcpy(media_cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < mcfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ media_cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); + } +@@ -1163,6 +1268,7 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + struct payload_media_fmt_pcm *cfg; + struct media_format *header; + int rc, payload_size; ++ int i, j; + void *p; + + if (num_channels > 4) { +@@ -1202,7 +1308,12 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; +- memcpy(cfg->channel_mapping, mcfg->channel_map, mcfg->num_channels); ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < cfg->num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; ++ cfg->channel_mapping[j++] = mcfg->channel_map[i]; ++ } + } else { + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) +@@ -1279,7 +1390,7 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + struct apm_module_sp_vi_ex_mode_cfg *ex_cfg; + int op_sz, cm_sz, ex_sz; + struct apm_module_param_data *param_data; +- int rc, i, payload_size; ++ int rc, i, payload_size, j; + struct gpr_pkt *pkt; + void *p; + +@@ -1320,14 +1431,19 @@ static int audioreach_speaker_protection_vi(struct q6apm_graph *graph, + param_data->param_size = cm_sz - APM_MODULE_PARAM_DATA_SIZE; + + cm_cfg->cfg.num_channels = num_channels * 2; +- for (i = 0; i < num_channels; i++) { ++ /* Convert the physical mapping to a logical mapping of the channels */ ++ for (i = 0, j = 0; i < AR_PCM_MAX_NUM_CHANNEL && j < num_channels; i++) { ++ if (!mcfg->channel_map[i]) ++ continue; + /* + * Map speakers into Vsense and then Isense of each channel. + * E.g. for PCM_CHANNEL_FL and PCM_CHANNEL_FR to: + * [1, 2, 3, 4] + */ +- cm_cfg->cfg.channel_mapping[2 * i] = (mcfg->channel_map[i] - 1) * 2 + 1; +- cm_cfg->cfg.channel_mapping[2 * i + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ cm_cfg->cfg.channel_mapping[2 * j] = (mcfg->channel_map[i] - 1) * 2 + 1; ++ cm_cfg->cfg.channel_mapping[2 * j + 1] = (mcfg->channel_map[i] - 1) * 2 + 2; ++ ++ ++j; + } + + p += cm_sz; +@@ -1411,6 +1527,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, + if (!rc) + rc = audioreach_module_enable(graph, module, true); + break; ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ case MODULE_ID_AUDIO_IF_SINK: ++ rc = audioreach_audio_if_set_media_format(graph, module, cfg); ++ break; + + default: + rc = 0; +diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h +--- a/sound/soc/qcom/qdsp6/audioreach.h ++++ b/sound/soc/qcom/qdsp6/audioreach.h +@@ -36,6 +36,8 @@ struct q6apm_graph; + #define MODULE_ID_SPEAKER_PROTECTION 0x070010E2 + #define MODULE_ID_SPEAKER_PROTECTION_VI 0x070010E3 + #define MODULE_ID_OPUS_DEC 0x07001174 ++#define MODULE_ID_AUDIO_IF_SINK 0x0700117C ++#define MODULE_ID_AUDIO_IF_SOURCE 0x0700117D + + #define APM_CMD_GET_SPF_STATE 0x01001021 + #define APM_CMD_RSP_GET_SPF_STATE 0x02001007 +@@ -544,6 +546,74 @@ struct param_id_i2s_intf_cfg { + #define PORT_ID_I2S_OUPUT 1 + #define I2S_STACK_SIZE 2048 + ++#define PARAM_ID_AUDIO_IF_INTF_CFG 0x08001B11 ++ ++/* ++ * struct param_id_audio_if_intf_cfg - Audio interface configuration ++ * @qaif_type: Audio interface type (e.g. QAIF, QAIF_VA) ++ * @intf_idx: Interface instance index ++ * @intf_mode: Interface operating mode (TDM/PCM/I2S) ++ * @ctrl_data_out_enable: Enable sharing of data-out signal with other masters ++ * @active_slot_mask: Bitmask indicating active slots ++ * @nslots_per_frame: Number of slots per audio frame ++ * @slot_width: Width of each slot in bits ++ * @active_lane_mask: Bitmask of active data lanes ++ * @frame_sync_rate: Frame sync rate in Hz ++ * @frame_sync_src: Frame sync source selection ++ * @frame_sync_mode: Frame sync mode configuration ++ * @invert_frame_sync_pulse: Invert frame sync polarity when set ++ * @frame_sync_data_delay: Data delay from frame sync in bit clocks ++ * @bit_clk_type: Bit clock type (internal / external) ++ * @inv_int_bit_clk: Invert internal bit clock when set ++ * @inv_ext_bit_clk: Invert external bit clock when set ++ * ++ * This structure defines configuration parameters for the Qualcomm ++ * Audio Interface (QAIF) block. It is used to program interface ++ * characteristics such as slot configuration, clocking and frame ++ * synchronization behaviour. ++ */ ++struct param_id_audio_if_intf_cfg { ++ uint16_t qaif_type; ++ uint16_t intf_idx; ++ uint16_t intf_mode; ++ uint16_t ctrl_data_out_enable; ++ uint32_t active_slot_mask; ++ uint16_t nslots_per_frame; ++ uint16_t slot_width; ++ uint32_t active_lane_mask; ++ uint32_t frame_sync_rate; ++ uint16_t frame_sync_src; ++ uint16_t frame_sync_mode; ++ uint16_t invert_frame_sync_pulse; ++ uint16_t frame_sync_data_delay; ++ uint16_t bit_clk_type; ++ uint8_t inv_int_bit_clk; ++ uint8_t inv_ext_bit_clk; ++} __packed; ++ ++#define PARAM_ID_HW_EP_FRAME_DURATION 0x08001B2F ++#define AUDIO_IF_FRAME_DURATION_US 1000 ++#define AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE 1 ++#define AUDIO_IF_FRAME_DURATION_MIN_US 1 ++#define AUDIO_IF_FRAME_DURATION_MAX_US 100000 ++ ++/** ++ * struct param_id_hw_ep_frame_duration - Hardware endpoint frame duration ++ * @frame_duration_in_us: Frame duration in microseconds. ++ * @allow_frame_duration_normalization: Permit SPF to normalize frame duration. ++ * @min_normalized_frame_dur_us: Minimum normalized frame duration in microseconds. ++ * @max_normalized_frame_dur_us: Maximum normalized frame duration in microseconds. ++ * ++ * This structure configures the frame duration for the Audio IF hardware ++ * endpoint and, when enabled, the allowed normalization range. ++ */ ++struct param_id_hw_ep_frame_duration { ++ uint32_t frame_duration_in_us; ++ uint32_t allow_frame_duration_normalization; ++ uint32_t min_normalized_frame_dur_us; ++ uint32_t max_normalized_frame_dur_us; ++} __packed; ++ + #define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154 + + struct param_id_display_port_intf_cfg { +@@ -877,6 +947,23 @@ struct audioreach_module { + uint32_t data_format; + uint32_t hw_interface_type; + ++ /* Audio IF module (TDM/PCM/I2S) */ ++ u32 slot_mask; ++ u32 active_lane_mask; ++ u32 frame_sync_rate; ++ u16 qaif_type; ++ u16 sync_src; ++ u16 ctrl_data_out_enable; ++ u16 nslots_per_frame; ++ u16 slot_width; ++ u16 intf_mode; ++ u16 sync_mode; ++ u16 ctrl_invert_sync_pulse; ++ u16 ctrl_sync_data_delay; ++ u16 bit_clk_type; ++ u8 inv_int_bit_clk; ++ u8 inv_ext_bit_clk; ++ + /* PCM module specific */ + uint32_t interleave_type; + +@@ -907,6 +994,9 @@ struct audioreach_module_config { + u32 channel_allocation; + u32 sd_line_mask; + int fmt; ++ u32 slot_mask; ++ u16 nslots_per_frame; ++ u16 slot_width; + struct snd_codec codec; + u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; + }; +diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c +--- a/sound/soc/qcom/qdsp6/q6afe-dai.c ++++ b/sound/soc/qcom/qdsp6/q6afe-dai.c +@@ -412,6 +412,8 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + if (rc < 0) { +@@ -665,6 +667,21 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + + /* USB playback AFE port receives data for playback, hence use the RX port */ + {"USB Playback", NULL, "USB_RX"}, ++ ++ {"LPI RX0 MI2S Playback", NULL, "LPI_MI2S_RX_0"}, ++ {"LPI_MI2S_TX_0", NULL, "LPI TX0 MI2S Capture"}, ++ {"LPI RX1 MI2S Playback", NULL, "LPI_MI2S_RX_1"}, ++ {"LPI_MI2S_TX_1", NULL, "LPI TX1 MI2S Capture"}, ++ {"LPI RX2 MI2S Playback", NULL, "LPI_MI2S_RX_2"}, ++ {"LPI_MI2S_TX_2", NULL, "LPI TX2 MI2S Capture"}, ++ {"LPI RX3 MI2S Playback", NULL, "LPI_MI2S_RX_3"}, ++ {"LPI_MI2S_TX_3", NULL, "LPI TX3 MI2S Capture"}, ++ {"LPI RX4 MI2S Playback", NULL, "LPI_MI2S_RX_4"}, ++ {"LPI_MI2S_TX_4", NULL, "LPI TX4 MI2S Capture"}, ++ {"LPI RX5 MI2S Playback", NULL, "LPI_MI2S_RX_5"}, ++ {"LPI_MI2S_TX_5", NULL, "LPI TX5 MI2S Capture"}, ++ {"LPI RX6 MI2S Playback", NULL, "LPI_MI2S_RX_6"}, ++ {"LPI_MI2S_TX_6", NULL, "LPI TX6 MI2S Capture"}, + }; + + static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +@@ -1011,6 +1028,35 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("USB_RX", NULL, 0, SND_SOC_NOPM, 0, 0), ++ ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_0", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_1", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_2", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_3", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_4", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_5", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_IN("LPI_MI2S_RX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), ++ SND_SOC_DAPM_AIF_OUT("LPI_MI2S_TX_6", NULL, ++ 0, SND_SOC_NOPM, 0, 0), + }; + + static const struct snd_soc_component_driver q6afe_dai_component = { +@@ -1045,6 +1091,8 @@ static void of_q6afe_parse_dai_data(struct device *dev, + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + priv = &data->priv[id]; + ret = of_property_read_variable_u32_array(node, + "qcom,sd-lines", +diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c +--- a/sound/soc/qcom/qdsp6/q6afe.c ++++ b/sound/soc/qcom/qdsp6/q6afe.c +@@ -132,6 +132,20 @@ + #define AFE_PORT_ID_QUINARY_MI2S_TX 0x1017 + #define AFE_PORT_ID_SENARY_MI2S_RX 0x1018 + #define AFE_PORT_ID_SENARY_MI2S_TX 0x1019 ++#define AFE_PORT_ID_INT0_MI2S_RX 0x102e ++#define AFE_PORT_ID_INT0_MI2S_TX 0x102f ++#define AFE_PORT_ID_INT1_MI2S_RX 0x1030 ++#define AFE_PORT_ID_INT1_MI2S_TX 0x1031 ++#define AFE_PORT_ID_INT2_MI2S_RX 0x1032 ++#define AFE_PORT_ID_INT2_MI2S_TX 0x1033 ++#define AFE_PORT_ID_INT3_MI2S_RX 0x1034 ++#define AFE_PORT_ID_INT3_MI2S_TX 0x1035 ++#define AFE_PORT_ID_INT4_MI2S_RX 0x1036 ++#define AFE_PORT_ID_INT4_MI2S_TX 0x1037 ++#define AFE_PORT_ID_INT5_MI2S_RX 0x1038 ++#define AFE_PORT_ID_INT5_MI2S_TX 0x1039 ++#define AFE_PORT_ID_INT6_MI2S_RX 0x103a ++#define AFE_PORT_ID_INT6_MI2S_TX 0x103b + + /* Start of the range of port IDs for TDM devices. */ + #define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 +@@ -931,6 +945,34 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, + RX_CODEC_DMA_RX_7, 1, 1}, + [USB_RX] = { AFE_PORT_ID_USB_RX, USB_RX, 1, 1}, ++ [LPI_MI2S_RX_0] = { AFE_PORT_ID_INT0_MI2S_RX, ++ LPI_MI2S_RX_0, 1, 1}, ++ [LPI_MI2S_TX_0] = { AFE_PORT_ID_INT0_MI2S_TX, ++ LPI_MI2S_TX_0, 0, 1}, ++ [LPI_MI2S_RX_1] = { AFE_PORT_ID_INT1_MI2S_RX, ++ LPI_MI2S_RX_1, 1, 1}, ++ [LPI_MI2S_TX_1] = { AFE_PORT_ID_INT1_MI2S_TX, ++ LPI_MI2S_TX_1, 0, 1}, ++ [LPI_MI2S_RX_2] = { AFE_PORT_ID_INT2_MI2S_RX, ++ LPI_MI2S_RX_2, 1, 1}, ++ [LPI_MI2S_TX_2] = { AFE_PORT_ID_INT2_MI2S_TX, ++ LPI_MI2S_TX_2, 0, 1}, ++ [LPI_MI2S_RX_3] = { AFE_PORT_ID_INT3_MI2S_RX, ++ LPI_MI2S_RX_3, 1, 1}, ++ [LPI_MI2S_TX_3] = { AFE_PORT_ID_INT3_MI2S_TX, ++ LPI_MI2S_TX_3, 0, 1}, ++ [LPI_MI2S_RX_4] = { AFE_PORT_ID_INT4_MI2S_RX, ++ LPI_MI2S_RX_4, 1, 1}, ++ [LPI_MI2S_TX_4] = { AFE_PORT_ID_INT4_MI2S_TX, ++ LPI_MI2S_TX_4, 0, 1}, ++ [LPI_MI2S_RX_5] = { AFE_PORT_ID_INT5_MI2S_RX, ++ LPI_MI2S_RX_5, 1, 1}, ++ [LPI_MI2S_TX_5] = { AFE_PORT_ID_INT5_MI2S_TX, ++ LPI_MI2S_TX_5, 0, 1}, ++ [LPI_MI2S_RX_6] = { AFE_PORT_ID_INT6_MI2S_RX, ++ LPI_MI2S_RX_6, 1, 1}, ++ [LPI_MI2S_TX_6] = { AFE_PORT_ID_INT6_MI2S_TX, ++ LPI_MI2S_TX_6, 0, 1}, + }; + + static void q6afe_port_free(struct kref *ref) +@@ -1785,6 +1827,20 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) + case AFE_PORT_ID_QUINARY_MI2S_TX: + case AFE_PORT_ID_SENARY_MI2S_RX: + case AFE_PORT_ID_SENARY_MI2S_TX: ++ case AFE_PORT_ID_INT0_MI2S_RX: ++ case AFE_PORT_ID_INT0_MI2S_TX: ++ case AFE_PORT_ID_INT1_MI2S_RX: ++ case AFE_PORT_ID_INT1_MI2S_TX: ++ case AFE_PORT_ID_INT2_MI2S_RX: ++ case AFE_PORT_ID_INT2_MI2S_TX: ++ case AFE_PORT_ID_INT3_MI2S_RX: ++ case AFE_PORT_ID_INT3_MI2S_TX: ++ case AFE_PORT_ID_INT4_MI2S_RX: ++ case AFE_PORT_ID_INT4_MI2S_TX: ++ case AFE_PORT_ID_INT5_MI2S_RX: ++ case AFE_PORT_ID_INT5_MI2S_TX: ++ case AFE_PORT_ID_INT6_MI2S_RX: ++ case AFE_PORT_ID_INT6_MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +--- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c ++++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +@@ -2,10 +2,12 @@ + // Copyright (c) 2021, Linaro Limited + + #include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -15,15 +17,54 @@ + #include "q6dsp-common.h" + #include "audioreach.h" + #include "q6apm.h" ++#include "q6prm.h" + + #define AUDIOREACH_BE_PCM_BASE 16 + ++struct q6apm_dai_priv_data { ++ struct clk *mclk; ++ struct clk *bclk; ++ bool mclk_enabled, bclk_enabled; ++}; ++ + struct q6apm_lpass_dai_data { + struct q6apm_graph *graph[APM_PORT_MAX]; + bool is_port_started[APM_PORT_MAX]; + struct audioreach_module_config module_config[APM_PORT_MAX]; ++ struct q6apm_dai_priv_data priv[APM_PORT_MAX]; + }; + ++static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id) ++{ ++ if (dai_data->priv[id].mclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].mclk); ++ dai_data->priv[id].mclk_enabled = false; ++ } ++ ++ if (dai_data->priv[id].bclk_enabled) { ++ clk_disable_unprepare(dai_data->priv[id].bclk); ++ dai_data->priv[id].bclk_enabled = false; ++ } ++} ++ ++static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data) ++{ ++ int i; ++ ++ for (i = 0; i < APM_PORT_MAX; i++) { ++ q6apm_lpass_dai_disable_clocks(dai_data, i); ++ ++ if (dai_data->priv[i].mclk) { ++ clk_put(dai_data->priv[i].mclk); ++ dai_data->priv[i].mclk = NULL; ++ } ++ if (dai_data->priv[i].bclk) { ++ clk_put(dai_data->priv[i].bclk); ++ dai_data->priv[i].bclk = NULL; ++ } ++ } ++} ++ + static int q6dma_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, + const unsigned int *tx_ch_mask, +@@ -251,6 +292,62 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s + return 0; + } + ++static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ return q6apm_lpass_dai_startup(substream, dai); ++} ++ ++static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ ++ q6apm_lpass_dai_shutdown(substream, dai); ++ q6apm_lpass_dai_disable_clocks(dai_data, dai->id); ++} ++ ++static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct clk *sysclk = NULL; ++ bool *enabled = NULL; ++ int ret = 0; ++ ++ switch (clk_id) { ++ case LPAIF_MI2S_MCLK: ++ sysclk = dai_data->priv[dai->id].mclk; ++ enabled = &dai_data->priv[dai->id].mclk_enabled; ++ break; ++ case LPAIF_MI2S_BCLK: ++ sysclk = dai_data->priv[dai->id].bclk; ++ enabled = &dai_data->priv[dai->id].bclk_enabled; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ if (sysclk) { ++ ret = clk_set_rate(sysclk, freq); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n", ++ freq, clk_id); ++ return ret; ++ } ++ ++ if (*enabled) ++ return 0; ++ ++ ret = clk_prepare_enable(sysclk); ++ if (ret) { ++ dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); ++ return ret; ++ } ++ ++ *enabled = true; ++ } ++ ++ return ret; ++} ++ + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + { + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); +@@ -261,6 +358,53 @@ static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) + return 0; + } + ++static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, ++ unsigned int tx_mask, ++ unsigned int rx_mask, ++ int slots, int slot_width) ++{ ++ struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); ++ struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; ++ unsigned int cap_mask, slot_mask; ++ ++ if (slot_width != 16 && slot_width != 32) { ++ dev_err(dai->dev, "%s: invalid slot_width %d\n", __func__, slot_width); ++ return -EINVAL; ++ } ++ ++ switch (slots) { ++ case 2: ++ case 4: ++ case 8: ++ case 16: ++ cap_mask = GENMASK(slots - 1, 0); ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid slots %d\n", __func__, slots); ++ return -EINVAL; ++ } ++ ++ switch (dai->id) { ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; ++ if (slot_mask & ~cap_mask) { ++ dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", ++ __func__, slot_mask, slots); ++ return -EINVAL; ++ } ++ ++ cfg->nslots_per_frame = slots; ++ cfg->slot_width = slot_width; ++ cfg->slot_mask = slot_mask; ++ break; ++ default: ++ dev_err(dai->dev, "%s: invalid dai id 0x%x\n", __func__, dai->id); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + static const struct snd_soc_dai_ops q6dma_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, +@@ -272,11 +416,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { + + static const struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6apm_lpass_dai_prepare, +- .startup = q6apm_lpass_dai_startup, +- .shutdown = q6apm_lpass_dai_shutdown, ++ .startup = q6i2s_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, + .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, + .trigger = q6apm_lpass_dai_trigger, + }; + +@@ -289,6 +434,17 @@ static const struct snd_soc_dai_ops q6hdmi_ops = { + .trigger = q6apm_lpass_dai_trigger, + }; + ++static const struct snd_soc_dai_ops q6tdm_ops = { ++ .prepare = q6apm_lpass_dai_prepare, ++ .startup = q6apm_lpass_dai_startup, ++ .shutdown = q6i2s_lpass_dai_shutdown, ++ .set_tdm_slot = q6tdm_set_tdm_slot, ++ .hw_params = q6dma_hw_params, ++ .set_fmt = q6i2s_set_fmt, ++ .set_sysclk = q6i2s_set_sysclk, ++ .trigger = q6apm_lpass_dai_trigger, ++}; ++ + static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .name = "q6apm-be-dai-component", + .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, +@@ -297,6 +453,65 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { + .remove_order = SND_SOC_COMP_ORDER_FIRST, + }; + ++static int of_q6apm_parse_dai_data(struct device *dev, ++ struct q6apm_lpass_dai_data *data) ++{ ++ int ret; ++ ++ for_each_child_of_node_scoped(dev->of_node, node) { ++ struct q6apm_dai_priv_data *priv; ++ int id; ++ ++ ret = of_property_read_u32(node, "reg", &id); ++ if (ret || id < 0 || id >= APM_PORT_MAX) { ++ dev_err(dev, "valid dai id not found:%d\n", ret); ++ continue; ++ } ++ ++ switch (id) { ++ /* MI2S specific properties */ ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ priv = &data->priv[id]; ++ priv->mclk = of_clk_get_by_name(node, "mclk"); ++ if (IS_ERR(priv->mclk)) { ++ int err = PTR_ERR(priv->mclk); ++ ++ priv->mclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s mclk\n"); ++ } ++ } ++ ++ priv->bclk = of_clk_get_by_name(node, "bclk"); ++ if (IS_ERR(priv->bclk)) { ++ int err = PTR_ERR(priv->bclk); ++ ++ priv->bclk = NULL; ++ if (err == -EPROBE_DEFER) { ++ q6apm_lpass_dai_put_clocks(data); ++ return dev_err_probe(dev, err, ++ "unable to get mi2s bclk\n"); ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return 0; ++} ++ ++static void q6apm_lpass_dai_clocks_action(void *data) ++{ ++ q6apm_lpass_dai_put_clocks(data); ++} ++ + static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + { + struct q6dsp_audio_port_dai_driver_config cfg; +@@ -304,17 +519,26 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) + struct snd_soc_dai_driver *dais; + struct device *dev = &pdev->dev; + int num_dais; ++ int ret; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); ++ ret = of_q6apm_parse_dai_data(dev, dai_data); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data); ++ if (ret) ++ return ret; + + memset(&cfg, 0, sizeof(cfg)); + cfg.q6i2s_ops = &q6i2s_ops; + cfg.q6dma_ops = &q6dma_ops; + cfg.q6hdmi_ops = &q6hdmi_ops; ++ cfg.q6tdm_ops = &q6tdm_ops; + dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); + + return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-clocks.c +@@ -12,7 +12,7 @@ + #include + #include "q6dsp-lpass-clocks.h" + +-#define Q6DSP_MAX_CLK_ID 104 ++#define Q6DSP_MAX_CLK_ID 105 + #define Q6DSP_LPASS_CLK_ROOT_DEFAULT 0 + + +diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +--- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c ++++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +@@ -553,11 +553,15 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { + Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("LPI RX3", LPI_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("LPI RX4", LPI_MI2S_RX_4), ++ Q6AFE_MI2S_RX_DAI("LPI RX5", LPI_MI2S_RX_5), ++ Q6AFE_MI2S_RX_DAI("LPI RX6", LPI_MI2S_RX_6), + Q6AFE_MI2S_TX_DAI("LPI TX0", LPI_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("LPI TX1", LPI_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("LPI TX2", LPI_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("LPI TX3", LPI_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), ++ Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), ++ Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), +@@ -712,6 +716,7 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -42,6 +42,11 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_1), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_2), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_3), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_4), ++ Q6PRM_CLK(LPASS_CLK_ID_MCLK_5), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK), +@@ -59,10 +64,40 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, + "LPASS_HW_DCODEC"), ++ Q6DSP_VOTE_CLK(LPASS_HW_LPR_VOTE, Q6PRM_HW_LPR_VOTE, ++ "LPASS_HW_LPR_VOTE"), + }; + + static const struct q6dsp_clk_desc q6dsp_clk_q6prm __maybe_unused = { +diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c +--- a/sound/soc/qcom/qdsp6/q6prm.c ++++ b/sound/soc/qcom/qdsp6/q6prm.c +@@ -31,10 +31,16 @@ struct q6prm { + #define PARAM_ID_RSC_HW_CORE 0x08001032 + #define PARAM_ID_RSC_LPASS_CORE 0x0800102B + #define PARAM_ID_RSC_AUDIO_HW_CLK 0x0800102C ++#define PARAM_ID_RSC_CPU_LPR 0x08001A6E ++ ++#define LPR_CPU_SS_SLEEP_DISABLE 0x1 + + struct prm_cmd_request_hw_core { + struct apm_module_param_data param_data; +- uint32_t hw_clk_id; ++ union { ++ u32 hw_clk_id; ++ u32 lpr_state; ++ }; + } __packed; + + struct prm_cmd_request_rsc { +@@ -62,6 +68,7 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + struct prm_cmd_request_hw_core *req; + gpr_device_t *gdev = prm->gdev; + uint32_t opcode, rsp_opcode; ++ bool lpr_req = (hw_block_id == Q6PRM_HW_LPR_VOTE); + + if (enable) { + opcode = PRM_CMD_REQUEST_HW_RSC; +@@ -82,10 +89,13 @@ static int q6prm_set_hw_core_req(struct device *dev, uint32_t hw_block_id, bool + + param_data->module_instance_id = GPR_PRM_MODULE_IID; + param_data->error_code = 0; +- param_data->param_id = PARAM_ID_RSC_HW_CORE; ++ param_data->param_id = lpr_req ? PARAM_ID_RSC_CPU_LPR : PARAM_ID_RSC_HW_CORE; + param_data->param_size = sizeof(*req) - APM_MODULE_PARAM_DATA_SIZE; + +- req->hw_clk_id = hw_block_id; ++ if (lpr_req) ++ req->lpr_state = LPR_CPU_SS_SLEEP_DISABLE; ++ else ++ req->hw_clk_id = hw_block_id; + + return q6prm_send_cmd_sync(prm, pkt, rsp_opcode); + } +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -3,6 +3,9 @@ + #ifndef __Q6PRM_H__ + #define __Q6PRM_H__ + ++#define LPAIF_MI2S_MCLK 1 ++#define LPAIF_MI2S_BCLK 2 ++ + /* Clock ID for Primary I2S IBIT */ + #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 + /* Clock ID for Primary I2S EBIT */ +@@ -52,6 +55,17 @@ + /* Clock ID for QUINARY MI2S OSR CLK */ + #define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + ++/* Clock ID for MCLK1 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_1 0x300 ++/* Clock ID for MCLK2 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_2 0x301 ++/* Clock ID for MCLK3 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_3 0x302 ++/* Clock ID for MCLK4 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_4 0x303 ++/* Clock ID for MCLK5 */ ++#define Q6PRM_LPASS_CLK_ID_MCLK_5 0x304 ++ + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305 + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306 + +@@ -83,10 +97,40 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++ + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 + #define Q6PRM_HW_CORE_ID_LPASS 1 + #define Q6PRM_HW_CORE_ID_DCODEC 2 ++#define Q6PRM_HW_LPR_VOTE 3 + + int q6prm_set_lpass_clock(struct device *dev, int clk_id, int clk_attr, + int clk_root, unsigned int freq); +diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c +--- a/sound/soc/qcom/qdsp6/q6routing.c ++++ b/sound/soc/qcom/qdsp6/q6routing.c +@@ -127,7 +127,14 @@ + { mix_name, "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2"}, \ + { mix_name, "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3"}, \ + { mix_name, "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4"}, \ +- { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"} ++ { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"}, \ ++ { mix_name, "LPI_MI2S_TX_0", "LPI_MI2S_TX_0" }, \ ++ { mix_name, "LPI_MI2S_TX_1", "LPI_MI2S_TX_1" }, \ ++ { mix_name, "LPI_MI2S_TX_2", "LPI_MI2S_TX_2" }, \ ++ { mix_name, "LPI_MI2S_TX_3", "LPI_MI2S_TX_3" }, \ ++ { mix_name, "LPI_MI2S_TX_4", "LPI_MI2S_TX_4" }, \ ++ { mix_name, "LPI_MI2S_TX_5", "LPI_MI2S_TX_5" }, \ ++ { mix_name, "LPI_MI2S_TX_6", "LPI_MI2S_TX_6" } + + #define Q6ROUTING_TX_MIXERS(id) \ + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \ +@@ -320,6 +327,27 @@ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_5", TX_CODEC_DMA_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_0", LPI_MI2S_TX_0, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_1", LPI_MI2S_TX_1, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_2", LPI_MI2S_TX_2, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_3", LPI_MI2S_TX_3, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_4", LPI_MI2S_TX_4, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_5", LPI_MI2S_TX_5, \ ++ id, 1, 0, msm_routing_get_audio_mixer, \ ++ msm_routing_put_audio_mixer), \ ++ SOC_SINGLE_EXT("LPI_MI2S_TX_6", LPI_MI2S_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), + +@@ -709,6 +737,26 @@ static const struct snd_kcontrol_new rxcodec_dma_rx_6_mixer_controls[] = { + static const struct snd_kcontrol_new rx_codec_dma_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_7) }; + ++static const struct snd_kcontrol_new lpi_mi2s_rx_0_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_0) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_1_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_1) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_2_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_2) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_3_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_3) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_4_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_4) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_5_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_5) }; ++ ++static const struct snd_kcontrol_new lpi_mi2s_rx_6_mixer_controls[] = { ++ Q6ROUTING_RX_MIXERS(LPI_MI2S_RX_6) }; + + static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) }; +@@ -938,6 +986,27 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + SND_SOC_DAPM_MIXER("USB_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + usb_rx_mixer_controls, + ARRAY_SIZE(usb_rx_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_0_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_0_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_1_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_1_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_2_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_2_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_3_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_3_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_4_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_4_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_5_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_5_mixer_controls)), ++ SND_SOC_DAPM_MIXER("LPI_MI2S_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, ++ lpi_mi2s_rx_6_mixer_controls, ++ ARRAY_SIZE(lpi_mi2s_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, +@@ -1031,6 +1100,13 @@ static const struct snd_soc_dapm_route intercon[] = { + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("USB_RX Audio Mixer", "USB_RX"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_0 Audio Mixer", "LPI_MI2S_RX_0"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_1 Audio Mixer", "LPI_MI2S_RX_1"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_2 Audio Mixer", "LPI_MI2S_RX_2"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_3 Audio Mixer", "LPI_MI2S_RX_3"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_4 Audio Mixer", "LPI_MI2S_RX_4"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_5 Audio Mixer", "LPI_MI2S_RX_5"), ++ Q6ROUTING_RX_DAPM_ROUTE("LPI_MI2S_RX_6 Audio Mixer", "LPI_MI2S_RX_6"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), +diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c +--- a/sound/soc/qcom/qdsp6/topology.c ++++ b/sound/soc/qcom/qdsp6/topology.c +@@ -753,6 +753,103 @@ static int audioreach_widget_i2s_module_load(struct audioreach_module *mod, + return 0; + } + ++static int audioreach_widget_audio_if_module_load(struct audioreach_module *mod, ++ const struct snd_soc_tplg_vendor_array *mod_array) ++{ ++ const struct snd_soc_tplg_vendor_value_elem *mod_elem; ++ int tkn_count = 0; ++ u32 val; ++ ++ mod_elem = mod_array->value; ++ ++ while (tkn_count < le32_to_cpu(mod_array->num_elems)) { ++ val = le32_to_cpu(mod_elem->value); ++ switch (le32_to_cpu(mod_elem->token)) { ++ case AR_TKN_U32_MODULE_HW_IF_IDX: ++ mod->hw_interface_idx = val; ++ break; ++ case AR_TKN_U32_MODULE_FMT_DATA: ++ mod->data_format = val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_SRC: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_src = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_data_out_enable = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_SLOT_MASK: ++ mod->slot_mask = val; ++ break; ++ case AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->nslots_per_frame = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SLOT_WIDTH: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->slot_width = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_INTF_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->intf_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_SYNC_MODE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->sync_mode = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_invert_sync_pulse = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->ctrl_sync_data_delay = (u16)val; ++ break; ++ case AR_TKN_U16_MODULE_QAIF_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->qaif_type = (u16)val; ++ break; ++ case AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: ++ mod->active_lane_mask = val; ++ break; ++ case AR_TKN_U32_MODULE_FRAME_SYNC_RATE: ++ mod->frame_sync_rate = val; ++ break; ++ case AR_TKN_U16_MODULE_BIT_CLK_TYPE: ++ if (val > U16_MAX) ++ return -EINVAL; ++ mod->bit_clk_type = (u16)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_INT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_int_bit_clk = (u8)val; ++ break; ++ case AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: ++ if (val > U8_MAX) ++ return -EINVAL; ++ mod->inv_ext_bit_clk = (u8)val; ++ break; ++ default: ++ break; ++ } ++ tkn_count++; ++ mod_elem++; ++ } ++ ++ return 0; ++} ++ + static int audioreach_widget_dp_module_load(struct audioreach_module *mod, + const struct snd_soc_tplg_vendor_array *mod_array) + { +@@ -806,6 +903,12 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, + case MODULE_ID_I2S_SOURCE: + audioreach_widget_i2s_module_load(mod, mod_array); + break; ++ case MODULE_ID_AUDIO_IF_SINK: ++ case MODULE_ID_AUDIO_IF_SOURCE: ++ ret = audioreach_widget_audio_if_module_load(mod, mod_array); ++ if (ret) ++ return ret; ++ break; + case MODULE_ID_DISPLAY_PORT_SINK: + audioreach_widget_dp_module_load(mod, mod_array); + break; +@@ -1313,7 +1416,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + { + struct snd_soc_card *card = component->card; + struct device *dev = component->dev; +- const struct firmware *fw; + int ret; + + /* Inline with Qualcomm UCM configs and linux-firmware path */ +@@ -1323,6 +1425,7 @@ int audioreach_tplg_init(struct snd_soc_component *component) + if (!tplg_fw_name) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_fw_name, dev); + if (ret < 0) { + dev_err(dev, "tplg firmware loading %s failed %d\n", tplg_fw_name, ret); +@@ -1335,8 +1438,6 @@ int audioreach_tplg_init(struct snd_soc_component *component) + dev_err(dev, "tplg component load failed: %d\n", ret); + } + +- release_firmware(fw); +- + return ret; + } + EXPORT_SYMBOL_GPL(audioreach_tplg_init); +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -12,17 +12,157 @@ + #include + #include + #include "qdsp6/q6afe.h" ++#include "qdsp6/q6apm.h" ++#include "qdsp6/q6prm.h" ++#include "qdsp6/q6dsp-common.h" + #include "common.h" + #include "sdw.h" + ++#define I2S_MCLKFS 256 ++ ++#define I2S_MCLK_RATE(rate) \ ++ ((rate) * (I2S_MCLKFS)) ++#define I2S_BIT_RATE(rate, channels, format) \ ++ ((rate) * (channels) * (format)) ++ ++static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_SPK("DP0 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP1 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP2 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP3 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP4 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP5 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP6 Jack", NULL), ++ SND_SOC_DAPM_SPK("DP7 Jack", NULL), ++}; ++ ++static const struct snd_kcontrol_new max98090_controls[] = { ++ SOC_DAPM_PIN_SWITCH("Headset Mic12"), ++ SOC_DAPM_PIN_SWITCH("Headphone"), ++ SOC_DAPM_PIN_SWITCH("Headset Mic56"), ++ SOC_DAPM_PIN_SWITCH("Speaker"), ++ SOC_DAPM_PIN_SWITCH("Receiver"), ++ SOC_DAPM_PIN_SWITCH("Int Mic"), ++}; ++ ++static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone Jack", NULL), ++ SND_SOC_DAPM_MIC("Mic Jack", NULL), ++ SND_SOC_DAPM_HP("Headphone", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic12", NULL), ++ SND_SOC_DAPM_MIC("Headset Mic56", NULL), ++ SND_SOC_DAPM_MIC("Int Mic", NULL), ++ SND_SOC_DAPM_SPK("Receiver", NULL), ++ SND_SOC_DAPM_SPK("Speaker", NULL), ++}; ++ ++struct qcom_snd_soc_common { ++ const char *driver_name; ++ const struct snd_soc_dapm_widget *dapm_widgets; ++ int num_dapm_widgets; ++ const struct snd_soc_dapm_route *dapm_routes; ++ int num_dapm_routes; ++ const struct snd_kcontrol_new *controls; ++ int num_controls; ++ unsigned int codec_dai_fmt; ++ bool codec_sysclk_set; ++ bool mi2s_mclk_enable; ++ bool mi2s_bclk_enable; ++ bool wcd_jack; ++ int (*snd_prepare)(struct snd_pcm_substream *substream); ++}; ++ + struct sc8280xp_snd_data { + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + struct snd_soc_jack jack; + struct snd_soc_jack dp_jack[8]; ++ const struct qcom_snd_soc_common *priv; + bool jack_setup; + }; + ++static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) ++{ ++ int rate = params_rate(params); ++ ++ switch (rate) { ++ case 11025: ++ case 44100: ++ case 88200: ++ return I2S_MCLK_RATE(44100); ++ default: ++ break; ++ } ++ ++ return I2S_MCLK_RATE(rate); ++} ++ ++static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params) ++{ ++ return I2S_BIT_RATE(params_rate(params), ++ params_channels(params), ++ snd_pcm_format_width(params_format(params))); ++} ++ ++static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai; ++ struct qcom_snd_tdm_slot_cfg cpu_cfg; ++ struct qcom_snd_tdm_slot_cfg codec_cfg; ++ unsigned int bclk_freq; ++ int ret; ++ int i; ++ ++ ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret == -EINVAL ? 0 : ret; ++ ++ if (!cpu_cfg.slots) ++ return 0; ++ ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret) ++ return ret; ++ ++ ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); ++ if (ret) ++ return ret; ++ ++ bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); ++ if (!bclk_freq) ++ return -EINVAL; ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", ++ __func__, ret); ++ return ret; ++ } ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret) { ++ dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", ++ __func__, codec_dai->name, ret); ++ return ret; ++ } ++ } ++ } ++ ++ return 0; ++} ++ + static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + { + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); +@@ -32,10 +172,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + int dp_pcm_id = 0; + + switch (cpu_dai->id) { +- case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX: +- case QUINARY_MI2S_RX...QUINARY_MI2S_TX: +- snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- break; + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + /* +@@ -64,7 +200,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) + if (dp_jack) + return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); + +- return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ if (data->priv->wcd_jack) ++ return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); ++ ++ return 0; + } + + static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, +@@ -96,12 +235,118 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + return 0; + } + ++static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *params) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); ++ int mclk_freq = sc8280xp_get_mclk_freq(params); ++ int bclk_freq = sc8280xp_get_bclk_freq(params); ++ int ret; ++ ++ switch (cpu_dai->id) { ++ case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: ++ case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: ++ case SENARY_MI2S_RX ... SENARY_MI2S_TX: ++ case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: ++ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ ++ if (data->priv->codec_dai_fmt) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_mclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_MCLK, mclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->mi2s_bclk_enable) { ++ ret = snd_soc_dai_set_sysclk(cpu_dai, ++ LPAIF_MI2S_BCLK, bclk_freq, ++ SND_SOC_CLOCK_OUT); ++ if (ret) ++ return ret; ++ } ++ ++ if (data->priv->codec_sysclk_set) { ++ ret = snd_soc_dai_set_sysclk(codec_dai, ++ 0, mclk_freq, ++ SND_SOC_CLOCK_IN); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ break; ++ case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: ++ return sc8280xp_tdm_hw_params(substream, params); ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++/* ++ * WSA and WSA2 are handled as a single interface with the ++ * following channels mask: ++ * __________________________________________________ ++ * | Bits | 3 | 2 | 1 | 0 | ++ * --------------------------------------------------- ++ * | Line | WSA2 Ch2 | WSA2 Ch1 | WSA Ch2 | WSA Ch1 | ++ * --------------------------------------------------- ++ * ++ * The Ayaneo Pocket S2 speakers are connected only to ++ * the WSA2 interface and the WSA interface is not enabled. ++ * ++ * Set the channel mapping on the WSA2 channels only. ++ */ ++static const unsigned int ayaneo_ps2_channels_mapping[] = { ++ 0, /* WSA Ch1 */ ++ 0, /* WSA Ch2 */ ++ PCM_CHANNEL_FL, /* WSA2 Ch1 */ ++ PCM_CHANNEL_FR /* WSA2 Ch2 */ ++}; ++ ++static int ayaneo_ps2_snd_prepare(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); ++ unsigned int channels = substream->runtime->channels; ++ ++ if (cpu_dai->id != WSA_CODEC_DMA_RX_0) ++ return 0; ++ ++ if (channels != 2) ++ return -EINVAL; ++ ++ return snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, ++ ARRAY_SIZE(ayaneo_ps2_channels_mapping), ++ ayaneo_ps2_channels_mapping); ++} ++ + static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + ++ if (data->priv->snd_prepare) { ++ int ret; ++ ++ ret = data->priv->snd_prepare(substream); ++ if (ret) ++ return ret; ++ } ++ + return qcom_snd_sdw_prepare(substream, &data->stream_prepared[cpu_dai->id]); + } + +@@ -117,6 +362,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) + static const struct snd_soc_ops sc8280xp_be_ops = { + .startup = qcom_snd_sdw_startup, + .shutdown = qcom_snd_sdw_shutdown, ++ .hw_params = sc8280xp_snd_hw_params, + .hw_free = sc8280xp_snd_hw_free, + .prepare = sc8280xp_snd_prepare, + }; +@@ -145,38 +391,182 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; +- card->owner = THIS_MODULE; ++ + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + ++ data->priv = of_device_get_match_data(dev); ++ if (!data->priv) ++ return -ENODEV; ++ ++ card->owner = THIS_MODULE; + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); ++ card->dapm_widgets = data->priv->dapm_widgets; ++ card->num_dapm_widgets = data->priv->num_dapm_widgets; ++ card->dapm_routes = data->priv->dapm_routes; ++ card->num_dapm_routes = data->priv->num_dapm_routes; ++ card->controls = data->priv->controls; ++ card->num_controls = data->priv->num_controls; ++ + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + +- card->driver_name = of_device_get_match_data(dev); ++ card->driver_name = data->priv->driver_name; + sc8280xp_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); + } + ++static struct qcom_snd_soc_common ayaneo_ps2_priv_data = { ++ .driver_name = "ayaneo-ps2", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .snd_prepare = ayaneo_ps2_snd_prepare, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common eliza_priv_data = { ++ .driver_name = "eliza", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common hawi_priv_data = { ++ .driver_name = "hawi", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++ .mi2s_bclk_enable = true, ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common kaanapali_priv_data = { ++ .driver_name = "kaanapali", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs9100_priv_data = { ++ .driver_name = "sa8775p", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++}; ++ ++static const struct qcom_snd_soc_common qcs615_priv_data = { ++ .driver_name = "qcs615", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .codec_sysclk_set = true, ++}; ++ ++static const struct qcom_snd_soc_common qcm6490_priv_data = { ++ .driver_name = "qcm6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs6490_priv_data = { ++ .driver_name = "qcs6490", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common qcs8275_priv_data = { ++ .driver_name = "qcs8300", ++ .dapm_widgets = max98090_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(max98090_dapm_widgets), ++ .controls = max98090_controls, ++ .num_controls = ARRAY_SIZE(max98090_controls), ++ .codec_sysclk_set = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_BC_FC, ++}; ++ ++static const struct qcom_snd_soc_common sc8280xp_priv_data = { ++ .driver_name = "sc8280xp", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8450_priv_data = { ++ .driver_name = "sm8450", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8475_priv_data = { ++ .driver_name = "sm8475", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ ++static const struct qcom_snd_soc_common sm8550_priv_data = { ++ .driver_name = "sm8550", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8650_priv_data = { ++ .driver_name = "sm8650", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++ /* I2S Connected to HDMI */ ++ .mi2s_mclk_enable = true, ++ .mi2s_bclk_enable = true, ++ .codec_dai_fmt = SND_SOC_DAIFMT_BC_FC | ++ SND_SOC_DAIFMT_NB_NF | ++ SND_SOC_DAIFMT_I2S, ++}; ++ ++static const struct qcom_snd_soc_common sm8750_priv_data = { ++ .driver_name = "sm8750", ++ .dapm_widgets = sc8280xp_dapm_widgets, ++ .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), ++ .wcd_jack = true, ++}; ++ + static const struct of_device_id snd_sc8280xp_dt_match[] = { +- {.compatible = "qcom,eliza-sndcard", "eliza"}, +- {.compatible = "qcom,kaanapali-sndcard", "kaanapali"}, +- {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, +- {.compatible = "qcom,qcs615-sndcard", "qcs615"}, +- {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, +- {.compatible = "qcom,qcs8275-sndcard", "qcs8300"}, +- {.compatible = "qcom,qcs9075-sndcard", "sa8775p"}, +- {.compatible = "qcom,qcs9100-sndcard", "sa8775p"}, +- {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"}, +- {.compatible = "qcom,sm8450-sndcard", "sm8450"}, +- {.compatible = "qcom,sm8550-sndcard", "sm8550"}, +- {.compatible = "qcom,sm8650-sndcard", "sm8650"}, +- {.compatible = "qcom,sm8750-sndcard", "sm8750"}, ++ { .compatible = "ayaneo,pocket-s2-sndcard", .data = &ayaneo_ps2_priv_data }, ++ { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, ++ { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, ++ { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, ++ { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, ++ { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, ++ { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, ++ { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, ++ { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, ++ { .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data }, ++ { .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data }, ++ { .compatible = "qcom,sm8475-sndcard", .data = &sm8475_priv_data }, ++ { .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data }, ++ { .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data }, ++ { .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data }, + {} + }; + +diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c +--- a/sound/soc/qcom/sdm845.c ++++ b/sound/soc/qcom/sdm845.c +@@ -36,7 +36,6 @@ struct sdm845_snd_data { + bool jack_setup; + bool slim_port_setup; + bool stream_prepared[AFE_PORT_MAX]; +- struct snd_soc_card *card; + uint32_t pri_mi2s_clk_count; + uint32_t sec_mi2s_clk_count; + uint32_t quat_tdm_clk_count; +@@ -564,7 +563,6 @@ static int sdm845_snd_platform_probe(struct platform_device *pdev) + if (ret) + return ret; + +- data->card = card; + snd_soc_card_set_drvdata(card, data); + + sdm845_add_ops(card); +diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c +--- a/sound/soc/qcom/sm8250.c ++++ b/sound/soc/qcom/sm8250.c +@@ -112,6 +112,22 @@ static int sm8250_snd_startup(struct snd_pcm_substream *substream) + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; ++ case LPI_MI2S_RX_0: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; ++ case LPI_MI2S_TX_3: ++ codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; ++ snd_soc_dai_set_sysclk(cpu_dai, ++ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT, ++ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE); ++ snd_soc_dai_set_fmt(cpu_dai, fmt); ++ snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); ++ break; + default: + break; + } +@@ -194,6 +210,7 @@ static const struct of_device_id snd_sm8250_dt_match[] = { + { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" }, + { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" }, + { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" }, ++ { .compatible = "qcom,sdm660-sndcard", .data = "sdm660" }, + { .compatible = "qcom,sm8250-sndcard", .data = "sm8250" }, + {} + }; +diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c +--- a/sound/soc/qcom/storm.c ++++ b/sound/soc/qcom/storm.c +@@ -64,21 +64,21 @@ static struct snd_soc_dai_link storm_dai_link = { + SND_SOC_DAILINK_REG(hifi), + }; + +-static int storm_parse_of(struct snd_soc_card *card) ++static int storm_parse_of(struct device *dev) + { +- struct snd_soc_dai_link *dai_link = card->dai_link; +- struct device_node *np = card->dev->of_node; ++ struct snd_soc_dai_link *dai_link = &storm_dai_link; ++ struct device_node *np = dev->of_node; + + dai_link->cpus->of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpus->of_node) { +- dev_err(card->dev, "error getting cpu phandle\n"); ++ dev_err(dev, "error getting cpu phandle\n"); + return -EINVAL; + } + dai_link->platforms->of_node = dai_link->cpus->of_node; + + dai_link->codecs->of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codecs->of_node) { +- dev_err(card->dev, "error getting codec phandle\n"); ++ dev_err(dev, "error getting codec phandle\n"); + return -EINVAL; + } + +@@ -106,7 +106,7 @@ static int storm_platform_probe(struct platform_device *pdev) + card->dai_link = &storm_dai_link; + card->num_links = 1; + +- ret = storm_parse_of(card); ++ ret = storm_parse_of(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "error resolving dai links: %d\n", ret); + return ret; +diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c +--- a/sound/soc/renesas/fsi.c ++++ b/sound/soc/renesas/fsi.c +@@ -1979,8 +1979,8 @@ static const struct of_device_id fsi_of_match[] = { + MODULE_DEVICE_TABLE(of, fsi_of_match); + + static const struct platform_device_id fsi_id_table[] = { +- { "sh_fsi", (kernel_ulong_t)&fsi1_core }, +- {}, ++ /* an array with no valid entry prevents matching on driver name */ ++ { } + }; + MODULE_DEVICE_TABLE(platform, fsi_id_table); + +@@ -1992,7 +1992,7 @@ static int fsi_probe(struct platform_device *pdev) + const struct fsi_core *core; + struct fsi_priv *fsi; + struct resource *res; +- unsigned int irq; ++ int irq; + int ret; + + memset(&info, 0, sizeof(info)); +@@ -2007,12 +2007,15 @@ static int fsi_probe(struct platform_device *pdev) + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- irq = platform_get_irq(pdev, 0); +- if (!res || (int)irq <= 0) { ++ if (!res) { + dev_err(&pdev->dev, "Not enough FSI platform resources.\n"); + return -ENODEV; + } + ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) ++ return irq; ++ + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; +diff --git a/sound/soc/renesas/rcar/adg.c b/sound/soc/renesas/rcar/adg.c +--- a/sound/soc/renesas/rcar/adg.c ++++ b/sound/soc/renesas/rcar/adg.c +@@ -565,7 +565,7 @@ static struct clk *rsnd_adg_create_null_clk(struct rsnd_priv *priv, + struct clk *clk; + + clk = clk_register_fixed_rate(dev, name, parent, 0, 0); +- if (IS_ERR_OR_NULL(clk)) { ++ if (IS_ERR(clk)) { + dev_err(dev, "create null clk error\n"); + return ERR_CAST(clk); + } +@@ -618,7 +618,7 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + * No "adg" is not error + */ + clk = devm_clk_get(dev, "adg"); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); + adg->adg = clk; + +@@ -626,9 +626,9 @@ static int rsnd_adg_get_clkin(struct rsnd_priv *priv) + for (i = 0; i < clkin_size; i++) { + clk = devm_clk_get(dev, clkin_name[i]); + +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + clk = rsnd_adg_null_clk_get(priv); +- if (IS_ERR_OR_NULL(clk)) ++ if (IS_ERR(clk)) + goto err; + + adg->clkin[i] = clk; +diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c +--- a/sound/soc/renesas/siu_dai.c ++++ b/sound/soc/renesas/siu_dai.c +@@ -715,7 +715,6 @@ static struct snd_soc_dai_driver siu_i2s_dai = { + + static int siu_probe(struct platform_device *pdev) + { +- const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; +@@ -726,6 +725,7 @@ static int siu_probe(struct platform_device *pdev) + siu_i2s_data = info; + info->dev = &pdev->dev; + ++ const struct firmware *fw_entry __free(firmware) = NULL; + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + return ret; +@@ -736,8 +736,6 @@ static int siu_probe(struct platform_device *pdev) + */ + memcpy(&info->fw, fw_entry->data, fw_entry->size); + +- release_firmware(fw_entry); +- + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; +diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c +--- a/sound/soc/rockchip/rk3288_hdmi_analog.c ++++ b/sound/soc/rockchip/rk3288_hdmi_analog.c +@@ -185,10 +185,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + gpiod_set_consumer_name(machine->gpio_hp_en, "hp_en"); + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(card->dev, "SoC parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + rk_dailink.codecs[0].of_node = of_parse_phandle(np, + "rockchip,audio-codec", +@@ -207,10 +205,9 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + } + + ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); +- if (ret) { +- dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Unable to get codec_dai_name\n"); + + rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", + 0); +@@ -223,11 +220,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); +- if (ret) { +- dev_err(&pdev->dev, +- "Unable to parse 'rockchip,routing' property\n"); ++ if (ret) + return ret; +- } + + snd_soc_card_set_drvdata(card, machine); + +diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c +--- a/sound/soc/rockchip/rockchip_i2s.c ++++ b/sound/soc/rockchip/rockchip_i2s.c +@@ -767,16 +767,14 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + /* try to prepare related clocks */ + i2s->hclk = devm_clk_get_enabled(&pdev->dev, "i2s_hclk"); +- if (IS_ERR(i2s->hclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n"); +- return PTR_ERR(i2s->hclk); +- } ++ if (IS_ERR(i2s->hclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->hclk), ++ "Can't retrieve i2s bus clock\n"); + + i2s->mclk = devm_clk_get(&pdev->dev, "i2s_clk"); +- if (IS_ERR(i2s->mclk)) { +- dev_err(&pdev->dev, "Can't retrieve i2s master clock\n"); +- return PTR_ERR(i2s->mclk); +- } ++ if (IS_ERR(i2s->mclk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mclk), ++ "Can't retrieve i2s master clock\n"); + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) +@@ -792,7 +790,11 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + + i2s->bclk_ratio = 64; + i2s->pinctrl = devm_pinctrl_get(&pdev->dev); +- if (!IS_ERR(i2s->pinctrl)) { ++ if (IS_ERR(i2s->pinctrl)) { ++ if (PTR_ERR(i2s->pinctrl) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); ++ } else { + i2s->bclk_on = pinctrl_lookup_state(i2s->pinctrl, "bclk_on"); + if (!IS_ERR_OR_NULL(i2s->bclk_on)) { + i2s->bclk_off = pinctrl_lookup_state(i2s->pinctrl, "bclk_off"); +@@ -801,8 +803,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + } +- } else { +- dev_dbg(&pdev->dev, "failed to find i2s pinctrl\n"); + } + + i2s_pinctrl_select_bclk_off(i2s); +@@ -831,16 +831,12 @@ static int rockchip_i2s_probe(struct platform_device *pdev) + &rockchip_i2s_component, + dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c +--- a/sound/soc/rockchip/rockchip_i2s_tdm.c ++++ b/sound/soc/rockchip/rockchip_i2s_tdm.c +@@ -1263,16 +1263,14 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "tx-m"); + if (IS_ERR(i2s_tdm->tx_reset)) { +- ret = PTR_ERR(i2s_tdm->tx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->tx_reset), + "Error in tx-m reset control\n"); + } + + i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "rx-m"); + if (IS_ERR(i2s_tdm->rx_reset)) { +- ret = PTR_ERR(i2s_tdm->rx_reset); +- return dev_err_probe(i2s_tdm->dev, ret, ++ return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->rx_reset), + "Error in rx-m reset control\n"); + } + +@@ -1363,17 +1361,12 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_tdm_component, + i2s_tdm->dai, 1); +- +- if (ret) { +- dev_err(&pdev->dev, "Could not register DAI\n"); ++ if (ret) + goto err_suspend; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Could not register PCM\n"); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c +--- a/sound/soc/rockchip/rockchip_max98090.c ++++ b/sound/soc/rockchip/rockchip_max98090.c +@@ -428,11 +428,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + + /* Parse card name. */ + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + return ret; +- } + + /* register the soc card */ + ret = devm_snd_soc_register_card(&pdev->dev, card); +diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c +--- a/sound/soc/rockchip/rockchip_pdm.c ++++ b/sound/soc/rockchip/rockchip_pdm.c +@@ -630,10 +630,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + &rockchip_pdm_component, + &rockchip_pdm_dai, 1); + +- if (ret) { +- dev_err(&pdev->dev, "could not register dai: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + rockchip_pdm_rxctrl(pdm, 0); + +@@ -642,10 +640,8 @@ static int rockchip_pdm_probe(struct platform_device *pdev) + goto err_suspend; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "could not register pcm: %d\n", ret); ++ if (ret) + goto err_suspend; +- } + + return 0; + +diff --git a/sound/soc/rockchip/rockchip_rt5645.c b/sound/soc/rockchip/rockchip_rt5645.c +--- a/sound/soc/rockchip/rockchip_rt5645.c ++++ b/sound/soc/rockchip/rockchip_rt5645.c +@@ -191,11 +191,8 @@ static int snd_rk_mc_probe(struct platform_device *pdev) + rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; + + ret = snd_soc_of_parse_card_name(card, "rockchip,model"); +- if (ret) { +- dev_err(&pdev->dev, +- "Soc parse card name failed %d\n", ret); ++ if (ret) + goto put_cpu_of_node; +- } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { +diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c +--- a/sound/soc/rockchip/rockchip_sai.c ++++ b/sound/soc/rockchip/rockchip_sai.c +@@ -1428,12 +1428,13 @@ static int rockchip_sai_probe(struct platform_device *pdev) + "Failed to initialize regmap\n"); + + irq = platform_get_irq_optional(pdev, 0); ++ if (irq == -EPROBE_DEFER) ++ return irq; + if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, rockchip_sai_isr, + IRQF_SHARED, node->name, sai); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "Failed to request irq %d\n", irq); ++ return ret; + } else { + dev_dbg(&pdev->dev, "Asked for an IRQ but got %d\n", irq); + } +@@ -1456,7 +1457,7 @@ static int rockchip_sai_probe(struct platform_device *pdev) + + ret = rockchip_sai_parse_paths(sai, node); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Failed to parse paths\n"); ++ return ret; + + /* + * From here on, all register accesses need to be wrapped in +@@ -1471,18 +1472,14 @@ static int rockchip_sai_probe(struct platform_device *pdev) + return dev_err_probe(&pdev->dev, ret, "Failed to resume device\n"); + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register PCM: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_sai_component, + dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register component: %d\n", ret); ++ if (ret) + goto err_runtime_suspend; +- } + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_put(&pdev->dev); +diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c +--- a/sound/soc/rockchip/rockchip_spdif.c ++++ b/sound/soc/rockchip/rockchip_spdif.c +@@ -396,13 +396,13 @@ static int rk_spdif_probe(struct platform_device *pdev) + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register PCM\n"); ++ return ret; + + ret = devm_snd_soc_register_component(&pdev->dev, + &rk_spdif_component, + &rk_spdif_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "Could not register DAI\n"); ++ return ret; + + return 0; + } +diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c +--- a/sound/soc/samsung/i2s.c ++++ b/sound/soc/samsung/i2s.c +@@ -1116,15 +1116,31 @@ static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = { + + static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = { + { "Playback Mixer", NULL, "Primary Playback" }, +- { "Playback Mixer", NULL, "Secondary Playback" }, +- + { "Mixer DAI TX", NULL, "Playback Mixer" }, + { "Primary Capture", NULL, "Mixer DAI RX" }, + }; + ++static const struct snd_soc_dapm_route samsung_i2s_dapm_routes_sec_play[] = { ++ { "Playback Mixer", NULL, "Secondary Playback" }, ++}; ++ ++static int samsung_i2s_component_probe(struct snd_soc_component *component) ++{ ++ struct samsung_i2s_priv *priv = snd_soc_component_get_drvdata(component); ++ ++ if (priv->quirks & QUIRK_SEC_DAI) ++ snd_soc_dapm_add_routes(snd_soc_component_to_dapm(component), ++ samsung_i2s_dapm_routes_sec_play, ++ ARRAY_SIZE(samsung_i2s_dapm_routes_sec_play)); ++ ++ return 0; ++} ++ + static const struct snd_soc_component_driver samsung_i2s_component = { + .name = "samsung-i2s", + ++ .probe = samsung_i2s_component_probe, ++ + .dapm_widgets = samsung_i2s_widgets, + .num_dapm_widgets = ARRAY_SIZE(samsung_i2s_widgets), + +@@ -1441,10 +1457,10 @@ static int samsung_i2s_probe(struct platform_device *pdev) + regs_base = res->start; + + priv->clk = devm_clk_get(&pdev->dev, "iis"); +- if (IS_ERR(priv->clk)) { +- dev_err(&pdev->dev, "Failed to get iis clock\n"); +- return PTR_ERR(priv->clk); +- } ++ if (IS_ERR(priv->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk), ++ "Failed to get iis clock\n"); ++ + + ret = clk_prepare_enable(priv->clk); + if (ret != 0) { +@@ -1650,8 +1666,7 @@ static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = { + }; + + static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = { +- .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | +- QUIRK_SUPPORTS_TDM, ++ .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM, + .pcm_rates = SNDRV_PCM_RATE_8000_192000, + .i2s_variant_regs = &i2sv7_regs, + }; +@@ -1675,7 +1690,7 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { + .name = "samsung-i2s", + .driver_data = (kernel_ulong_t)&i2sv3_dai_type, + }, +- {}, ++ { } + }; + MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids); + +diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c +--- a/sound/soc/samsung/smdk_spdif.c ++++ b/sound/soc/samsung/smdk_spdif.c +@@ -130,12 +130,8 @@ static int smdk_hw_params(struct snd_pcm_substream *substream, + return ret; + + /* Set S/PDIF uses internal source clock */ +- ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, +- rclk_rate, SND_SOC_CLOCK_IN); +- if (ret < 0) +- return ret; +- +- return ret; ++ return snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, ++ rclk_rate, SND_SOC_CLOCK_IN); + } + + static const struct snd_soc_ops smdk_spdif_ops = { +diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c +--- a/sound/soc/samsung/spdif.c ++++ b/sound/soc/samsung/spdif.c +@@ -380,8 +380,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->pclk = devm_clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif->pclk)) { +- dev_err(&pdev->dev, "failed to get peri-clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->pclk), ++ "failed to get peri-clock\n"); + goto err0; + } + ret = clk_prepare_enable(spdif->pclk); +@@ -390,8 +390,8 @@ static int spdif_probe(struct platform_device *pdev) + + spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif"); + if (IS_ERR(spdif->sclk)) { +- dev_err(&pdev->dev, "failed to get internal source clock\n"); +- ret = -ENOENT; ++ ret = dev_err_probe(&pdev->dev, PTR_ERR(spdif->sclk), ++ "failed to get internal source clock\n"); + goto err1; + } + ret = clk_prepare_enable(spdif->sclk); +diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c +--- a/sound/soc/sdca/sdca_asoc.c ++++ b/sound/soc/sdca/sdca_asoc.c +@@ -364,7 +364,6 @@ static int entity_parse_ot(struct device *dev, + + /** + * sdca_asoc_pde_poll_actual_ps - Verify PDE power state reached target state +- * @dev: Pointer to the device for error logging. + * @regmap: Register map for reading ACTUAL_PS register. + * @function_id: SDCA function identifier. + * @entity_id: SDCA entity identifier for the power domain. +@@ -389,11 +388,11 @@ static int entity_parse_ot(struct device *dev, + * polling times out before reaching the target state, or a negative error code if + * a register read fails. + */ +-int sdca_asoc_pde_poll_actual_ps(struct device *dev, struct regmap *regmap, ++int sdca_asoc_pde_poll_actual_ps(struct regmap *regmap, + int function_id, int entity_id, +- int from_ps, int to_ps, +- const struct sdca_pde_delay *pde_delays, +- int num_delays) ++ int from_ps, int to_ps, ++ const struct sdca_pde_delay *pde_delays, ++ int num_delays) + { + static const int polls = 100; + static const int default_poll_us = 1000; +@@ -451,14 +450,14 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget, + return 0; + } + +- ret = sdca_asoc_pde_poll_actual_ps(component->dev, component->regmap, ++ ret = sdca_asoc_pde_poll_actual_ps(component->regmap, + SDW_SDCA_CTL_FUNC(widget->reg), + SDW_SDCA_CTL_ENT(widget->reg), + from, to, + entity->pde.max_delay, + entity->pde.num_max_delay); + if (ret) +- dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n", ++ dev_err(component->dev, "%s: pde transition %x -> %x failed: %d\n", + entity->label, from, to, ret); + + return ret; +@@ -1231,7 +1230,7 @@ static u64 width_find_mask(unsigned int bits) + } + } + +-static int populate_rate_format(struct device *dev, ++int sdca_asoc_populate_rate_format(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_pcm_stream *stream) +@@ -1292,6 +1291,7 @@ static int populate_rate_format(struct device *dev, + + return 0; + } ++EXPORT_SYMBOL_NS(sdca_asoc_populate_rate_format, "SND_SOC_SDCA"); + + /** + * sdca_asoc_populate_dais - fill in an array of DAI drivers for a Function +@@ -1344,7 +1344,7 @@ int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *funct + stream->channels_min = 1; + stream->channels_max = SDCA_MAX_CHANNEL_COUNT; + +- ret = populate_rate_format(dev, function, entity, stream); ++ ret = sdca_asoc_populate_rate_format(dev, function, entity, stream); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -329,7 +329,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, + drv->core = core; + drv->function = &sdev->function; + +- ret = sdca_parse_function(dev, core->sdw, drv->function); ++ ret = sdca_parse_function(dev, drv->function); + if (ret) + return ret; + +diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c +--- a/sound/soc/sdca/sdca_fdl.c ++++ b/sound/soc/sdca/sdca_fdl.c +@@ -195,7 +195,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + { + struct device *dev = interrupt->dev; + struct sdca_fdl_data *fdl_data = &interrupt->function->fdl_data; +- const struct firmware *firmware = NULL; + struct acpi_sw_file *swf = NULL, *tmp; + struct sdca_fdl_file *fdl_file; + char *disk_filename; +@@ -230,6 +229,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + dev_dbg(dev, "FDL disk filename: %s\n", disk_filename); + ++ const struct firmware *firmware __free(firmware) = NULL; + ret = firmware_request_nowarn(&firmware, disk_filename, dev); + kfree(disk_filename); + if (ret) { +@@ -258,8 +258,7 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + + if (!swf) { + dev_err(dev, "failed to locate SWF\n"); +- ret = -ENOENT; +- goto error; ++ return -ENOENT; + } + + dev_info(dev, "loading SWF: %x-%x-%x\n", +@@ -271,9 +270,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, + SDCA_CTL_XU_FDL_MESSAGEOFFSET, fdl_file->fdl_offset, + SDCA_CTL_XU_FDL_MESSAGELENGTH, swf->data, + swf->file_length - offsetof(struct acpi_sw_file, data)); +- +-error: +- release_firmware(firmware); + return ret; + } + +diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c +--- a/sound/soc/sdca/sdca_function_device.c ++++ b/sound/soc/sdca/sdca_function_device.c +@@ -32,7 +32,8 @@ static void sdca_dev_release(struct device *dev) + + /* alloc, init and add link devices */ + static struct sdca_dev *sdca_dev_register(struct device *parent, +- struct sdca_function_desc *function_desc) ++ struct sdca_function_desc *function_desc, ++ struct acpi_table_swft *swft) + { + struct sdca_dev *sdev; + struct auxiliary_device *auxdev; +@@ -50,6 +51,7 @@ static struct sdca_dev *sdca_dev_register(struct device *parent, + auxdev->dev.release = sdca_dev_release; + + sdev->function.desc = function_desc; ++ sdev->function.fdl_data.swft = swft; + + rc = ida_alloc(&sdca_function_ida, GFP_KERNEL); + if (rc < 0) { +@@ -99,7 +101,8 @@ int sdca_dev_register_functions(struct sdw_slave *slave) + struct sdca_dev *func_dev; + + func_dev = sdca_dev_register(&slave->dev, +- &sdca_data->function[i]); ++ &sdca_data->function[i], ++ sdca_data->swft); + if (IS_ERR(func_dev)) { + ret = PTR_ERR(func_dev); + /* +diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c +--- a/sound/soc/sdca/sdca_functions.c ++++ b/sound/soc/sdca/sdca_functions.c +@@ -7,6 +7,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -18,7 +19,6 @@ + #include + #include + #include +-#include + + /* + * Should be long enough to encompass all the MIPI DisCo properties. +@@ -1365,90 +1365,76 @@ static int find_sdca_entity_ge(struct device *dev, + return -EINVAL; + } + +-static int +-find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, +- struct fwnode_handle *entity_node, struct sdca_entity *entity) ++static int find_sdca_entity_hide(struct device *dev, ++ struct fwnode_handle *entity_node, ++ struct sdca_entity *entity) + { + struct sdca_entity_hide *hide = &entity->hide; +- unsigned int delay, *af_list = hide->af_number_list; +- int nval, ret; +- unsigned char *report_desc = NULL; ++ int num_reports, ret; ++ unsigned int delay; + + ret = fwnode_property_read_u32(entity_node, +- "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay); ++ "mipi-sdca-RxUMP-ownership-transition-max-delay", ++ &delay); + if (!ret) + hide->max_delay = delay; + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDTx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidtx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid tx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidtx_ids = num_reports; + hide->hidtx_ids = devm_kcalloc(dev, hide->num_hidtx_ids, + sizeof(*hide->hidtx_ids), GFP_KERNEL); + if (!hide->hidtx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDTx-supported-report-ids", +- hide->hidtx_ids, +- hide->num_hidtx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDTx-supported-report-ids", ++ hide->hidtx_ids, hide->num_hidtx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-HIDRx-supported-report-ids"); +- if (nval > 0) { +- hide->num_hidrx_ids = nval; ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids"); ++ if (num_reports < 0 && num_reports != -EINVAL) { ++ dev_err(dev, "%pfwP: failed to read hid rx ids: %d\n", ++ entity_node, num_reports); ++ return num_reports; ++ } else if (num_reports > 0) { ++ hide->num_hidrx_ids = num_reports; + hide->hidrx_ids = devm_kcalloc(dev, hide->num_hidrx_ids, + sizeof(*hide->hidrx_ids), GFP_KERNEL); + if (!hide->hidrx_ids) + return -ENOMEM; + +- ret = fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-HIDRx-supported-report-ids", +- hide->hidrx_ids, +- hide->num_hidrx_ids); +- if (ret < 0) +- return ret; ++ fwnode_property_read_u32_array(entity_node, ++ "mipi-sdca-HIDRx-supported-report-ids", ++ hide->hidrx_ids, hide->num_hidrx_ids); + } + +- nval = fwnode_property_count_u32(entity_node, "mipi-sdca-hide-related-audio-function-list"); +- if (nval <= 0) { ++ /* ++ * FIXME: This should probably link to the actual sdca_function_data pointer, ++ * but updating to do so should probably wait until we have a user. ++ */ ++ num_reports = fwnode_property_count_u32(entity_node, ++ "mipi-sdca-hide-related-audio-function-list"); ++ if (num_reports <= 0) { + dev_err(dev, "%pfwP: audio function numbers list missing: %d\n", +- entity_node, nval); ++ entity_node, num_reports); + return -EINVAL; +- } else if (nval > SDCA_MAX_FUNCTION_COUNT) { +- dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", entity_node); ++ } else if (num_reports > ARRAY_SIZE(hide->af_number_list)) { ++ dev_err(dev, "%pfwP: maximum number of audio function exceeded\n", ++ entity_node); + return -EINVAL; + } + +- hide->hide_reside_function_num = nval; ++ hide->hide_reside_function_num = num_reports; + fwnode_property_read_u32_array(entity_node, +- "mipi-sdca-hide-related-audio-function-list", af_list, nval); +- +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); +- if (nval) +- fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", +- (u8 *)&hide->hid_desc, nval); +- +- if (hide->hid_desc.bNumDescriptors) { +- nval = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); +- if (nval) { +- report_desc = devm_kzalloc(dev, nval, GFP_KERNEL); +- if (!report_desc) +- return -ENOMEM; +- hide->hid_report_desc = report_desc; +- fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", +- report_desc, nval); +- +- /* add HID device */ +- ret = sdca_add_hid_device(dev, sdw, entity); +- if (ret) { +- dev_err(dev, "%pfwP: failed to add HID device: %d\n", entity_node, ret); +- return ret; +- } +- } +- } ++ "mipi-sdca-hide-related-audio-function-list", ++ hide->af_number_list, num_reports); + + return 0; + } +@@ -1475,8 +1461,7 @@ static int find_sdca_entity_xu(struct device *dev, + return 0; + } + +-static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function, ++static int find_sdca_entity(struct device *dev, struct sdca_function_data *function, + struct fwnode_handle *function_node, + struct fwnode_handle *entity_node, + struct sdca_entity *entity) +@@ -1528,8 +1513,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + ret = find_sdca_entity_ge(dev, entity_node, entity); + break; + case SDCA_ENTITY_TYPE_HIDE: +- ret = find_sdca_entity_hide(dev, sdw, function_node, +- entity_node, entity); ++ ret = find_sdca_entity_hide(dev, entity_node, entity); + break; + default: + break; +@@ -1544,8 +1528,7 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_entities(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + struct sdca_entity *entities; +@@ -1596,7 +1579,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return -EINVAL; + } + +- ret = find_sdca_entity(dev, sdw, function, function_node, ++ ret = find_sdca_entity(dev, function, function_node, + entity_node, &entities[i]); + fwnode_handle_put(entity_node); + if (ret) +@@ -1619,7 +1602,7 @@ static int find_sdca_entities(struct device *dev, struct sdw_slave *sdw, + return 0; + } + +-static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data *function, ++struct sdca_entity *sdca_find_entity_by_label(struct sdca_function_data *function, + const char *entity_label) + { + struct sdca_entity *entity = NULL; +@@ -1648,6 +1631,7 @@ static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data * + + return NULL; + } ++EXPORT_SYMBOL_NS(sdca_find_entity_by_label, "SND_SOC_SDCA"); + + static struct sdca_entity *find_sdca_entity_by_id(struct sdca_function_data *function, + const int id) +@@ -1688,7 +1672,7 @@ static int find_sdca_entity_connection_iot(struct device *dev, + return ret; + } + +- clock_entity = find_sdca_entity_by_label(function, clock_label); ++ clock_entity = sdca_find_entity_by_label(function, clock_label); + if (!clock_entity) { + dev_err(dev, "%s: failed to find clock with label %s\n", + entity->label, clock_label); +@@ -1873,7 +1857,7 @@ static int find_sdca_entity_connection(struct device *dev, + return ret; + } + +- connected_entity = find_sdca_entity_by_label(function, connected_label); ++ connected_entity = sdca_find_entity_by_label(function, connected_label); + if (!connected_entity) { + dev_err(dev, "%s: failed to find entity with label %s\n", + entity->label, connected_label); +@@ -2088,8 +2072,7 @@ static int find_sdca_clusters(struct device *dev, + return 0; + } + +-static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, +- struct fwnode_handle *function_node, ++static int find_sdca_filesets(struct device *dev, struct fwnode_handle *function_node, + struct sdca_function_data *function) + { + static const int mult_fileset = 3; +@@ -2171,23 +2154,65 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw, + set->files = files; + } + +- function->fdl_data.swft = sdw->sdca_data.swft; + function->fdl_data.num_sets = num_sets; + function->fdl_data.sets = sets; + + return 0; + } + ++static int find_sdca_hid(struct device *dev, struct fwnode_handle *function_node, ++ struct sdca_function_data *function) ++{ ++ int num_desc; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-hid-descriptor"); ++ if (!num_desc) { ++ return 0; ++ } else if (num_desc < 0) { ++ dev_err(dev, "%pfwP: failed to read hid descriptor: %d\n", ++ function_node, num_desc); ++ return num_desc; ++ } else if (num_desc > sizeof(function->hid.desc)) { ++ dev_err(dev, "%pfwP: hid descriptor too large: %d\n", ++ function_node, num_desc); ++ return -EINVAL; ++ } ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-hid-descriptor", ++ (u8 *)&function->hid.desc, num_desc); ++ ++ if (!function->hid.desc.bNumDescriptors) ++ return 0; ++ ++ num_desc = fwnode_property_count_u8(function_node, "mipi-sdca-report-descriptor"); ++ if (num_desc <= 0) { ++ dev_err(dev, "%pfwP: failed to read report descriptor: %d\n", ++ function_node, num_desc); ++ ++ if (!num_desc) ++ return -EINVAL; ++ ++ return num_desc; ++ } ++ ++ function->hid.report_desc = devm_kzalloc(dev, num_desc, GFP_KERNEL); ++ if (!function->hid.report_desc) ++ return -ENOMEM; ++ ++ fwnode_property_read_u8_array(function_node, "mipi-sdca-report-descriptor", ++ function->hid.report_desc, num_desc); ++ ++ return 0; ++} ++ + /** + * sdca_parse_function - parse ACPI DisCo for a Function + * @dev: Pointer to device against which function data will be allocated. +- * @sdw: SoundWire slave device to be processed. + * @function: Pointer to the Function information, to be populated. + * + * Return: Returns 0 for success. + */ +-int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, +- struct sdca_function_data *function) ++int sdca_parse_function(struct device *dev, struct sdca_function_data *function) + { + struct fwnode_handle *node = function->desc->node; + u32 tmp; +@@ -2213,7 +2238,7 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret) + return ret; + +- ret = find_sdca_entities(dev, sdw, node, function); ++ ret = find_sdca_entities(dev, node, function); + if (ret) + return ret; + +@@ -2225,10 +2250,20 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw, + if (ret < 0) + return ret; + +- ret = find_sdca_filesets(dev, sdw, node, function); ++ ret = find_sdca_filesets(dev, node, function); + if (ret) + return ret; + ++ switch (function->desc->type) { ++ case SDCA_FUNCTION_TYPE_HID: ++ ret = find_sdca_hid(dev, node, function); ++ if (ret) ++ return ret; ++ break; ++ default: ++ break; ++ } ++ + return 0; + } + EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA"); +diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c +--- a/sound/soc/sdca/sdca_hid.c ++++ b/sound/soc/sdca/sdca_hid.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -23,18 +24,18 @@ + + static int sdwhid_parse(struct hid_device *hid) + { +- struct sdca_entity *entity = hid->driver_data; ++ struct sdca_function_data *function = hid->driver_data; + unsigned int rsize; + int ret; + +- rsize = le16_to_cpu(entity->hide.hid_desc.rpt_desc.wDescriptorLength); ++ rsize = le16_to_cpu(function->hid.desc.rpt_desc.wDescriptorLength); + + if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) { + dev_err(&hid->dev, "invalid size of report descriptor (%u)\n", rsize); + return -EINVAL; + } + +- ret = hid_parse_report(hid, entity->hide.hid_report_desc, rsize); ++ ret = hid_parse_report(hid, function->hid.report_desc, rsize); + + if (!ret) + return 0; +@@ -85,10 +86,16 @@ static const struct hid_ll_driver sdw_hid_driver = { + .raw_request = sdwhid_raw_request, + }; + +-int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, +- struct sdca_entity *entity) ++/** ++ * sdca_add_hid_device - create a new SDCA HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ * ++ * Return: Zero on success, and a negative error code on failure. ++ */ ++int sdca_add_hid_device(struct sdca_interrupt *interrupt) + { +- struct sdw_bus *bus = sdw->bus; ++ struct device *dev = interrupt->dev; ++ struct sdca_function_data *function = interrupt->function; + struct hid_device *hid; + int ret; + +@@ -100,16 +107,13 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + + hid->dev.parent = dev; + hid->bus = BUS_SDW; +- hid->version = le16_to_cpu(entity->hide.hid_desc.bcdHID); ++ hid->version = le16_to_cpu(function->hid.desc.bcdHID); + +- snprintf(hid->name, sizeof(hid->name), +- "HID sdw:%01x:%01x:%04x:%04x:%02x", +- bus->controller_id, bus->link_id, sdw->id.mfg_id, +- sdw->id.part_id, sdw->id.class_id); ++ strscpy(hid->phys, dev_name(dev)); ++ snprintf(hid->name, sizeof(hid->name), "SDCA %s:%02x", ++ function->desc->name, function->desc->adr); + +- snprintf(hid->phys, sizeof(hid->phys), "%s", dev->bus->name); +- +- hid->driver_data = entity; ++ hid->driver_data = function; + + ret = hid_add_device(hid); + if (ret && ret != -ENODEV) { +@@ -118,12 +122,24 @@ int sdca_add_hid_device(struct device *dev, struct sdw_slave *sdw, + return ret; + } + +- entity->hide.hid = hid; ++ interrupt->priv = hid; + + return 0; + } + EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + ++/** ++ * sdca_destroy_hid_device - destroy the HID device ++ * @interrupt: Pointer to the SDCA interrupt information structure. ++ */ ++void sdca_destroy_hid_device(struct sdca_interrupt *interrupt) ++{ ++ struct hid_device *hid = interrupt->priv; ++ ++ hid_destroy_device(hid); ++} ++EXPORT_SYMBOL_NS(sdca_destroy_hid_device, "SND_SOC_SDCA"); ++ + /** + * sdca_hid_process_report - read a HID event from the device and report + * @interrupt: Pointer to the SDCA interrupt information structure. +@@ -133,7 +149,7 @@ EXPORT_SYMBOL_NS(sdca_add_hid_device, "SND_SOC_SDCA"); + int sdca_hid_process_report(struct sdca_interrupt *interrupt) + { + struct device *dev = interrupt->dev; +- struct hid_device *hid = interrupt->entity->hide.hid; ++ struct hid_device *hid = interrupt->priv; + void *val __free(kfree) = NULL; + int len, ret; + +diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c +--- a/sound/soc/sdca/sdca_interrupts.c ++++ b/sound/soc/sdca/sdca_interrupts.c +@@ -487,6 +487,12 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap, + } + break; + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER): ++ interrupt->free_priv = sdca_destroy_hid_device; ++ ++ ret = sdca_add_hid_device(interrupt); ++ if (ret) ++ return ret; ++ + interrupt->handler = hid_handler; + break; + default: +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -834,6 +834,35 @@ struct asoc_sdw_codec_info codec_info_list[] = { + }, + .dai_num = 2, + }, ++ { ++ .vendor_id = 0x01fa, ++ .part_id = 0x3562, ++ .name_prefix = "AMP", ++ .is_amp = true, ++ .dais = { ++ { ++ .direction = {true, false}, ++ .dai_name = "cs35l56-sdw1", ++ .component_name = "cs35l56", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, ++ .init = asoc_sdw_cs_amp_init, ++ .rtd_init = asoc_sdw_cs_spk_rtd_init, ++ .controls = generic_spk_controls, ++ .num_controls = ARRAY_SIZE(generic_spk_controls), ++ .widgets = generic_spk_widgets, ++ .num_widgets = ARRAY_SIZE(generic_spk_widgets), ++ }, ++ { ++ .direction = {false, true}, ++ .dai_name = "cs35l56-sdw1c", ++ .dai_type = SOC_SDW_DAI_TYPE_AMP, ++ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, ++ .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, ++ }, ++ }, ++ .dai_num = 2, ++ }, + { + .vendor_id = 0x01fa, + .part_id = 0x3563, +@@ -1789,9 +1818,7 @@ static int is_sdca_aux_dev_present(struct device *dev, + int adr_index) + { + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret = 0; + int i; + + if (!aux_codec_name) +@@ -1801,7 +1828,8 @@ static int is_sdca_aux_dev_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1811,24 +1839,19 @@ static int is_sdca_aux_dev_present(struct device *dev, + + if (!slave->sdca_data.interface_revision) { + dev_warn(dev, "No SDCA properties, assuming aux '%s' present\n", aux_codec_name); +- ret = 1; +- goto put_dev; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { + const char *fname = slave->sdca_data.function[i].name; + +- if (fname && strstr(aux_codec_name, fname)) { +- ret = 1; +- goto put_dev; +- } ++ if (fname && strstr(aux_codec_name, fname)) ++ return 1; + } + + dev_dbg(dev, "SDCA function for aux '%s' NOT FOUND on slave, skipping\n", aux_codec_name); + +-put_dev: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_count_sdw_endpoints(struct snd_soc_card *card, +@@ -1928,9 +1951,8 @@ static int is_sdca_endpoint_present(struct device *dev, + const struct snd_soc_acpi_endpoint *adr_end; + const struct asoc_sdw_dai_info *dai_info; + struct sdw_slave *slave; +- struct device *sdw_dev; + const char *sdw_codec_name; +- int ret, i; ++ int i; + + adr_end = &adr_dev->endpoints[end_index]; + dai_info = &codec_info->dais[adr_end->num]; +@@ -1939,7 +1961,8 @@ static int is_sdca_endpoint_present(struct device *dev, + if (!sdw_codec_name) + return -ENOMEM; + +- sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); ++ struct device *sdw_dev __free(put_device) = ++ bus_find_device_by_name(&sdw_bus_type, NULL, sdw_codec_name); + if (!sdw_dev) { + dev_err(dev, "codec %s not found\n", sdw_codec_name); + return -EINVAL; +@@ -1950,8 +1973,7 @@ static int is_sdca_endpoint_present(struct device *dev, + /* Make sure BIOS provides SDCA properties */ + if (!slave->sdca_data.interface_revision) { + dev_warn(&slave->dev, "SDCA properties not found in the BIOS\n"); +- ret = 1; +- goto put_device; ++ return 1; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { +@@ -1960,8 +1982,7 @@ static int is_sdca_endpoint_present(struct device *dev, + if (dai_type == dai_info->dai_type) { + dev_dbg(&slave->dev, "DAI type %d sdca function %s found\n", + dai_type, slave->sdca_data.function[i].name); +- ret = 1; +- goto put_device; ++ return 1; + } + } + +@@ -1969,11 +1990,7 @@ static int is_sdca_endpoint_present(struct device *dev, + "SDCA device function for DAI type %d not supported, skip endpoint\n", + dai_info->dai_type); + +- ret = 0; +- +-put_device: +- put_device(sdw_dev); +- return ret; ++ return 0; + } + + int asoc_sdw_parse_sdw_endpoints(struct device *dev, +diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c +--- a/sound/soc/soc-component.c ++++ b/sound/soc/soc-component.c +@@ -29,17 +29,42 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, + func, component->name, reg); + } + +-static inline int soc_component_field_shift(struct snd_soc_component *component, +- unsigned int mask) ++struct snd_soc_component *snd_soc_component_alloc(struct device *dev) + { +- if (!mask) { +- dev_err(component->dev, "ASoC: error field mask is zero for %s\n", +- component->name); +- return 0; +- } ++ struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + +- return (ffs(mask) - 1); ++ if (!component) ++ return NULL; ++ ++ component->dev = dev; ++ ++ return component; + } ++EXPORT_SYMBOL_GPL(snd_soc_component_alloc); ++ ++void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) ++{ ++ component->name = name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_name); ++ ++const char *snd_soc_component_name(struct snd_soc_component *component) ++{ ++ return component->name; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_name); ++ ++void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv) ++{ ++ component->priv = priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_set_priv); ++ ++void *snd_soc_component_to_priv(struct snd_soc_component *component) ++{ ++ return component->priv; ++} ++EXPORT_SYMBOL_GPL(snd_soc_component_to_priv); + + /* + * We might want to check substream by using list. +@@ -820,6 +845,18 @@ int snd_soc_component_update_bits_async(struct snd_soc_component *component, + } + EXPORT_SYMBOL_GPL(snd_soc_component_update_bits_async); + ++static inline int soc_component_field_shift(struct snd_soc_component *component, ++ unsigned int mask) ++{ ++ if (!mask) { ++ dev_err(component->dev, "ASoC: error field mask is zero for %s\n", ++ component->name); ++ return 0; ++ } ++ ++ return (ffs(mask) - 1); ++} ++ + /** + * snd_soc_component_read_field() - Read register field value + * @component: Component to read from +diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c +--- a/sound/soc/soc-core.c ++++ b/sound/soc/soc-core.c +@@ -2707,10 +2707,11 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) + list_del(&component->list); + } + +-int snd_soc_component_initialize(struct snd_soc_component *component, +- const struct snd_soc_component_driver *driver, +- struct device *dev) ++static int soc_component_initialize(struct snd_soc_component *component, ++ const struct snd_soc_component_driver *driver) + { ++ struct device *dev = component->dev; ++ + component->dapm = snd_soc_dapm_alloc(dev); + if (!component->dapm) + return -ENOMEM; +@@ -2730,16 +2731,14 @@ int snd_soc_component_initialize(struct snd_soc_component *component, + } + } + +- component->dev = dev; + component->driver = driver; + + return 0; + } +-EXPORT_SYMBOL_GPL(snd_soc_component_initialize); + +-int snd_soc_add_component(struct snd_soc_component *component, +- struct snd_soc_dai_driver *dai_drv, +- int num_dai) ++static int soc_component_add(struct snd_soc_component *component, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) + { + struct snd_soc_card *card, *c; + int ret; +@@ -2778,27 +2777,36 @@ int snd_soc_add_component(struct snd_soc_component *component, + + return ret; + } +-EXPORT_SYMBOL_GPL(snd_soc_add_component); + +-int snd_soc_register_component(struct device *dev, ++int snd_soc_register_component_c(struct snd_soc_component *component, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) + { +- struct snd_soc_component *component; + int ret; + +- component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); +- if (!component) +- return -ENOMEM; +- +- ret = snd_soc_component_initialize(component, component_driver, dev); ++ ret = soc_component_initialize(component, component_driver); + if (ret < 0) + return ret; + +- return snd_soc_add_component(component, dai_drv, num_dai); ++ return soc_component_add(component, dai_drv, num_dai); + } +-EXPORT_SYMBOL_GPL(snd_soc_register_component); ++EXPORT_SYMBOL_GPL(snd_soc_register_component_c); ++ ++int snd_soc_register_component_d(struct device *dev, ++ const struct snd_soc_component_driver *component_driver, ++ struct snd_soc_dai_driver *dai_drv, ++ int num_dai) ++{ ++ struct snd_soc_component *component; ++ ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ ++ return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); ++} ++EXPORT_SYMBOL_GPL(snd_soc_register_component_d); + + /** + * snd_soc_unregister_component_by_driver - Unregister component using a given driver +diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c +--- a/sound/soc/soc-dapm.c ++++ b/sound/soc/soc-dapm.c +@@ -1203,6 +1203,8 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + wname_in_long_name = true; + kcname_in_long_name = true; + break; +@@ -2785,6 +2787,8 @@ static ssize_t dapm_widget_show_component(struct snd_soc_component *component, + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + case snd_soc_dapm_mixer: + case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: +@@ -3367,6 +3371,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) + case snd_soc_dapm_pga: + case snd_soc_dapm_effect: + case snd_soc_dapm_out_drv: ++ case snd_soc_dapm_encoder: ++ case snd_soc_dapm_decoder: + dapm_new_pga(w); + break; + case snd_soc_dapm_dai_link: +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -3,6 +3,7 @@ + // Copyright (C) 2013, Analog Devices Inc. + // Author: Lars-Peter Clausen + ++#include + #include + #include + #include +@@ -77,7 +78,7 @@ static int dmaengine_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; +@@ -99,7 +100,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct device *dma_dev = dmaengine_dma_dev(pcm, substream); + struct dma_chan *chan = pcm->chan[substream->stream]; + struct snd_dmaengine_dai_dma_data *dma_data; +@@ -148,7 +149,7 @@ dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + static int dmaengine_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct dma_chan *chan = pcm->chan[substream->stream]; + int ret; + +@@ -176,7 +177,7 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel( + struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + struct snd_dmaengine_dai_dma_data *dma_data; + + if (rtd->dai_link->num_cpus > 1) { +@@ -219,7 +220,7 @@ static bool dmaengine_pcm_can_report_residue(struct device *dev, + static int dmaengine_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + const struct snd_dmaengine_pcm_config *config = pcm->config; + struct device *dev = component->dev; + size_t prealloc_buffer_size; +@@ -279,7 +280,7 @@ static snd_pcm_uframes_t dmaengine_pcm_pointer( + struct snd_soc_component *component, + struct snd_pcm_substream *substream) + { +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) + return snd_dmaengine_pcm_pointer_no_residue(substream); +@@ -293,7 +294,7 @@ static int dmaengine_copy(struct snd_soc_component *component, + struct iov_iter *iter, unsigned long bytes) + { + struct snd_pcm_runtime *runtime = substream->runtime; +- struct dmaengine_pcm *pcm = soc_component_to_pcm(component); ++ struct dmaengine_pcm *pcm = snd_soc_component_to_priv(component); + int (*process)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + unsigned long bytes) = pcm->config->process; +@@ -395,6 +396,27 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; ++ ++ bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); ++ bool name_exists_in_fw = false; ++ ++ if (has_fw_node) ++ name_exists_in_fw = device_property_match_string(dev, ++ "dma-names", ++ name) >= 0; ++ ++ if (has_fw_node && name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (has_fw_node && !name_exists_in_fw) ++ dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ ++ if (!has_fw_node) ++ dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ name, PTR_ERR(chan)); ++ + pcm->chan[i] = NULL; + } else { + pcm->chan[i] = chan; +@@ -406,6 +428,12 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) + pcm->chan[1] = pcm->chan[0]; + ++ if (!pcm->chan[0] && ++ !pcm->chan[1]) { ++ dev_err(dev, "no DMA channel found for either playback or capture\n"); ++ return -ENODEV; ++ } ++ + return 0; + } + +@@ -435,10 +463,15 @@ static const struct snd_dmaengine_pcm_config snd_dmaengine_pcm_default_config = + int snd_dmaengine_pcm_register(struct device *dev, + const struct snd_dmaengine_pcm_config *config, unsigned int flags) + { ++ struct snd_soc_component *component; + const struct snd_soc_component_driver *driver; + struct dmaengine_pcm *pcm; + int ret; + ++ component = snd_soc_component_alloc(dev); ++ if (!component) ++ return -ENOMEM; ++ + pcm = kzalloc_obj(*pcm); + if (!pcm) + return -ENOMEM; +@@ -449,7 +482,8 @@ int snd_dmaengine_pcm_register(struct device *dev, + pcm->flags = flags; + + if (config->name) +- pcm->component.name = config->name; ++ snd_soc_component_set_name(component, config->name); ++ snd_soc_component_set_priv(component, pcm); + + ret = dmaengine_pcm_request_chan_of(pcm, dev, config); + if (ret) +@@ -460,11 +494,7 @@ int snd_dmaengine_pcm_register(struct device *dev, + else + driver = &dmaengine_pcm_component; + +- ret = snd_soc_component_initialize(&pcm->component, driver, dev); +- if (ret) +- goto err_free_dma; +- +- ret = snd_soc_add_component(&pcm->component, NULL, 0); ++ ret = snd_soc_register_component(component, driver, NULL, 0); + if (ret) + goto err_free_dma; + +@@ -493,7 +523,7 @@ void snd_dmaengine_pcm_unregister(struct device *dev) + if (!component) + return; + +- pcm = soc_component_to_pcm(component); ++ pcm = snd_soc_component_to_priv(component); + + snd_soc_unregister_component_by_driver(dev, component->driver); + dmaengine_pcm_release_chan(pcm); +diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c +--- a/sound/soc/soc-topology-test.c ++++ b/sound/soc/soc-topology-test.c +@@ -45,15 +45,13 @@ static void snd_soc_tplg_test_exit(struct kunit *test) + struct kunit_soc_component { + struct kunit *kunit; + int expect; /* what result we expect when loading topology */ +- struct snd_soc_component comp; + struct snd_soc_card card; + struct firmware fw; + }; + + static int d_probe(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_load(component, NULL, &kunit_comp->fw); +@@ -65,8 +63,7 @@ static int d_probe(struct snd_soc_component *component) + + static void d_remove(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + ret = snd_soc_tplg_component_remove(component); +@@ -214,8 +211,7 @@ static struct tplg_tmpl_002 tplg_tmpl_with_pcm = { + */ + static int d_probe_null_comp(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing component pointer as first argument, pass NULL here */ +@@ -234,6 +230,7 @@ static const struct snd_soc_component_driver test_component_null_comp = { + static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -249,15 +246,17 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_comp, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -276,6 +275,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) + static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -291,15 +291,17 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -318,8 +320,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) + */ + static int d_probe_null_fw(struct snd_soc_component *component) + { +- struct kunit_soc_component *kunit_comp = +- container_of(component, struct kunit_soc_component, comp); ++ struct kunit_soc_component *kunit_comp = snd_soc_component_to_priv(component); + int ret; + + /* instead of passing fw pointer as third argument, pass NULL here */ +@@ -338,6 +339,7 @@ static const struct snd_soc_component_driver test_component_null_fw = { + static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + int ret; + + /* prepare */ +@@ -353,15 +355,17 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component_null_fw, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -375,6 +379,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -401,15 +406,17 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -425,6 +432,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -456,15 +464,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -480,6 +490,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -511,15 +522,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -535,6 +548,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -566,15 +580,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -590,6 +606,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) + static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + struct tplg_tmpl_001 *data; + int size; + int ret; +@@ -622,15 +639,17 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + /* cleanup */ +@@ -644,6 +663,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes + static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -670,15 +690,17 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); +- +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -693,6 +715,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -720,16 +743,19 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); ++ ++ snd_soc_component_set_priv(component, kunit_comp); ++ + /* run test */ + ret = snd_soc_register_card(&kunit_comp->card); + if (ret != 0 && ret != -EPROBE_DEFER) + KUNIT_FAIL(test, "Failed to register card"); + + for (i = 0; i < 100; i++) { +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + snd_soc_unregister_component(test_dev); +@@ -745,6 +771,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) + static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + { + struct kunit_soc_component *kunit_comp; ++ struct snd_soc_component *component; + u8 *data; + int size; + int ret; +@@ -772,11 +799,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) + kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); + kunit_comp->card.fully_routed = true; + +- /* run test */ +- ret = snd_soc_component_initialize(&kunit_comp->comp, &test_component, test_dev); +- KUNIT_EXPECT_EQ(test, 0, ret); ++ component = snd_soc_component_alloc(test_dev); ++ KUNIT_ASSERT_NOT_NULL(test, component); + +- ret = snd_soc_add_component(&kunit_comp->comp, NULL, 0); ++ snd_soc_component_set_priv(component, kunit_comp); ++ ++ /* run test */ ++ ret = snd_soc_register_component(component, &test_component, NULL, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + + for (i = 0; i < 100; i++) { +diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig +--- a/sound/soc/sof/amd/Kconfig ++++ b/sound/soc/sof/amd/Kconfig +@@ -104,4 +104,15 @@ config SND_SOC_SOF_AMD_ACP70 + AMD ACP7.0/ACP7.1 version based platforms. + Say Y if you want to enable SOF on ACP7.0/ACP7.1 based platforms. + ++config SND_SOC_SOF_AMD_ACP7X ++ tristate "SOF support for ACP7.B/7.F platforms" ++ depends on SND_SOC_SOF_PCI ++ depends on AMD_NODE ++ select SND_SOC_SOF_AMD_COMMON ++ help ++ Select this option for SOF support on AMD ACP7.B and ACP7.F PCI ++ revision based platforms. ++ Say Y if you want to enable SOF on ACP7.B/7.F based platforms. ++ If unsure select "N". ++ + endif +diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile +--- a/sound/soc/sof/amd/Makefile ++++ b/sound/soc/sof/amd/Makefile +@@ -11,6 +11,7 @@ snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o + snd-sof-amd-vangogh-y := pci-vangogh.o vangogh.o + snd-sof-amd-acp63-y := pci-acp63.o acp63.o + snd-sof-amd-acp70-y := pci-acp70.o acp70.o ++snd-sof-amd-acp7x-y := pci-acp7x.o acp7x.o + + obj-$(CONFIG_SND_SOC_SOF_AMD_COMMON) += snd-sof-amd-acp.o + obj-$(CONFIG_SND_SOC_SOF_AMD_RENOIR) += snd-sof-amd-renoir.o +@@ -18,3 +19,4 @@ obj-$(CONFIG_SND_SOC_SOF_AMD_REMBRANDT) += snd-sof-amd-rembrandt.o + obj-$(CONFIG_SND_SOC_SOF_AMD_VANGOGH) += snd-sof-amd-vangogh.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP63) += snd-sof-amd-acp63.o + obj-$(CONFIG_SND_SOC_SOF_AMD_ACP70) += snd-sof-amd-acp70.o ++obj-$(CONFIG_SND_SOC_SOF_AMD_ACP7X) += snd-sof-amd-acp7x.o +diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h +--- a/sound/soc/sof/amd/acp-dsp-offset.h ++++ b/sound/soc/sof/amd/acp-dsp-offset.h +@@ -68,12 +68,14 @@ + #define ACP5X_PGFSM_BASE 0x1424 + #define ACP6X_PGFSM_BASE 0x1024 + #define ACP70_PGFSM_BASE ACP6X_PGFSM_BASE ++#define ACP7X_PGFSM_BASE ACP6X_PGFSM_BASE + #define PGFSM_CONTROL_OFFSET 0x0 + #define PGFSM_STATUS_OFFSET 0x4 + #define ACP3X_CLKMUX_SEL 0x1424 + #define ACP5X_CLKMUX_SEL 0x142C + #define ACP6X_CLKMUX_SEL 0x102C + #define ACP70_CLKMUX_SEL ACP6X_CLKMUX_SEL ++#define ACP7X_CLKMUX_SEL ACP6X_CLKMUX_SEL + + /* Registers from ACP_INTR block */ + #define ACP3X_EXT_INTR_STAT 0x1808 +@@ -86,21 +88,31 @@ + #define ACP70_EXTERNAL_INTR_CNTL ACP6X_EXTERNAL_INTR_CNTL + #define ACP70_EXT_INTR_STAT ACP6X_EXT_INTR_STAT + #define ACP70_EXT_INTR_STAT1 ACP6X_EXT_INTR_STAT1 ++#define ACP7X_EXTERNAL_INTR_ENB ACP6X_EXTERNAL_INTR_ENB ++#define ACP7X_EXTERNAL_INTR_CNTL 0x1A04 ++#define ACP7X_EXT_INTR_STAT 0x1A1C ++#define ACP7X_EXTERNAL_INTR_CNTL1 0x1A08 ++#define ACP7X_EXT_INTR_STAT1 0x1A20 + + #define ACP3X_DSP_SW_INTR_BASE 0x1814 + #define ACP5X_DSP_SW_INTR_BASE 0x1814 + #define ACP6X_DSP_SW_INTR_BASE 0x1808 + #define ACP70_DSP_SW_INTR_BASE ACP6X_DSP_SW_INTR_BASE ++#define ACP7X_DSP_SW_INTR_BASE 0x1860 + #define DSP_SW_INTR_CNTL_OFFSET 0x0 + #define DSP_SW_INTR_STAT_OFFSET 0x4 ++#define ACP7X_DSP_SW_INTR_STAT (ACP7X_DSP_SW_INTR_BASE + DSP_SW_INTR_STAT_OFFSET) + #define DSP_SW_INTR_TRIG_OFFSET 0x8 ++#define ACP7X_DSP_SW_INTR_TRIG_OFFSET 0x30 + #define ACP3X_ERROR_STATUS 0x18C4 + #define ACP6X_ERROR_STATUS 0x1A4C + #define ACP70_ERROR_STATUS ACP6X_ERROR_STATUS ++#define ACP7X_ERROR_STATUS 0x1A88 + #define ACP3X_AXI2DAGB_SEM_0 0x1880 + #define ACP5X_AXI2DAGB_SEM_0 0x1884 + #define ACP6X_AXI2DAGB_SEM_0 0x1874 + #define ACP70_AXI2DAGB_SEM_0 ACP6X_AXI2DAGB_SEM_0 ++#define ACP7X_AXI2DAGB_SEM_0 0x18F4 + + /* ACP common registers to report errors related to I2S & SoundWire interfaces */ + #define ACP3X_SW_I2S_ERROR_REASON 0x18C8 +@@ -123,6 +135,7 @@ + #define ACP_SCRATCH_REG_0 0x10000 + #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 + #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL ++#define ACP7X_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL + + /* Cache window registers */ + #define ACP_DSP0_CACHE_OFFSET0 0x0420 +@@ -139,5 +152,9 @@ + #define ACP70_SDW1_HOST_WAKE_STAT BIT(25) + #define ACP70_SDW0_PME_STAT BIT(26) + #define ACP70_SDW1_PME_STAT BIT(27) ++#define ACP7X_DSP0_IDMA_ERROR_MASK 0x4B0 ++#define ACP7X_IDMA_ERROR_MASK 0x1FF9FF ++#define ACP7X_ZSC_DSP_CTRL 0x001014 ++#define ACP7X_PME_EN ACP70_PME_EN + + #endif +diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c +--- a/sound/soc/sof/amd/acp-ipc.c ++++ b/sound/soc/sof/amd/acp-ipc.c +@@ -32,11 +32,20 @@ static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata) + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 swintr_trigger; ++ unsigned int swintr_trigger_reg_offset; + ++ switch (adata->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ swintr_trigger_reg_offset = ACP7X_DSP_SW_INTR_TRIG_OFFSET; ++ break; ++ default: ++ swintr_trigger_reg_offset = DSP_SW_INTR_TRIG_OFFSET; ++ } + swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->dsp_intr_base + +- DSP_SW_INTR_TRIG_OFFSET); ++ swintr_trigger_reg_offset); + swintr_trigger |= 0x01; +- snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_TRIG_OFFSET, ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + swintr_trigger_reg_offset, + swintr_trigger); + } + +diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c +--- a/sound/soc/sof/amd/acp-loader.c ++++ b/sound/soc/sof/amd/acp-loader.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "../ops.h" + #include "acp-dsp-offset.h" +@@ -173,10 +174,31 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) + + adata = sdev->pdata->hw_pdata; + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if (adata->pci_rev >= ACP7B_PCI_ID) { ++ if (adata->acp_sof_signed_firmware_image) { ++ if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, "Invalid signed firmware size %u\n", ++ adata->fw_bin_size); ++ return -EINVAL; ++ } ++ size_fw = get_unaligned_le32(adata->bin_buf + ++ ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); ++ if (!size_fw || ++ size_fw > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { ++ dev_err(sdev->dev, ++ "Invalid signed firmware payload size %u (max %u)\n", ++ size_fw, adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE); ++ return -EINVAL; ++ } ++ size_fw += ACP_IMAGE_HEADER_SIZE; ++ } else { ++ size_fw = adata->fw_bin_size; ++ } ++ } else if (adata->quirks && adata->quirks->signed_fw_image) { + size_fw = adata->fw_bin_size - ACP_FIRMWARE_SIGNATURE; +- else ++ } else { + size_fw = adata->fw_bin_size; ++ } + + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; +@@ -312,9 +334,14 @@ int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev) + } + kfree(fw_filename); + +- ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, +- (void *)adata->fw_dbin->data, +- adata->fw_dbin->size); ++ if (adata->pci_rev >= ACP7B_PCI_ID) ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); ++ else ++ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0, ++ (void *)adata->fw_dbin->data, ++ adata->fw_dbin->size); + return ret; + } + EXPORT_SYMBOL_NS(acp_sof_load_signed_firmware, "SND_SOC_SOF_AMD_COMMON"); +diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c +--- a/sound/soc/sof/amd/acp.c ++++ b/sound/soc/sof/amd/acp.c +@@ -12,6 +12,7 @@ + * Hardware interface for generic AMD ACP processor + */ + ++#include + #include + #include + #include +@@ -60,6 +61,8 @@ static void init_dma_descriptor(struct acp_dev_data *adata) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_desc_base_addr = ACP70_DMA_DESC_BASE_ADDR; + acp_dma_desc_max_num_dscr = ACP70_DMA_DESC_MAX_NUM_DSCR; + break; +@@ -101,6 +104,8 @@ static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch, + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_cntl_0 = ACP70_DMA_CNTL_0; + acp_dma_ch_rst_sts = ACP70_DMA_CH_RST_STS; + acp_dma_dscr_err_sts_0 = ACP70_DMA_ERR_STS_0; +@@ -283,7 +288,8 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, + } + } + +- if (adata->quirks && adata->quirks->signed_fw_image) ++ if ((adata->quirks && adata->quirks->signed_fw_image) || ++ adata->acp_sof_signed_firmware_image) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); +@@ -342,6 +348,8 @@ int acp_dma_status(struct acp_dev_data *adata, unsigned char ch) + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: + acp_dma_ch_sts = ACP70_DMA_CH_STS; + break; + default: +@@ -534,8 +542,17 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_sw0_i2s_err_reason, 0); + /* ACP_SW1_I2S_ERROR_REASON is newly added register from rmb platform onwards */ +- if (adata->pci_rev >= ACP_RMB_PCI_ID) ++ switch (adata->pci_rev) { ++ case ACP_RMB_PCI_ID: ++ case ACP63_PCI_ID: ++ case ACP70_PCI_ID: ++ case ACP71_PCI_ID: ++ case ACP72_PCI_ID: + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SW1_I2S_ERROR_REASON, 0); ++ break; ++ default: ++ break; ++ } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + irq_flag = 1; + } +@@ -564,6 +581,35 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) + return IRQ_NONE; + } + ++static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) ++{ ++ struct snd_sof_dev *sdev = dev_id; ++ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); ++ unsigned int base = desc->dsp_intr_base; ++ unsigned int val; ++ unsigned int ext_intr_stat; ++ int irq_flag = 0; ++ ++ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); ++ if (val & ACP_DSP_TO_HOST_IRQ) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ++ ACP_DSP_TO_HOST_IRQ); ++ return IRQ_WAKE_THREAD; ++ } ++ ++ ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); ++ if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); ++ irq_flag = 1; ++ } ++ ++ if (irq_flag) ++ return IRQ_HANDLED; ++ ++ return IRQ_NONE; ++} ++ + static int acp_power_on(struct snd_sof_dev *sdev) + { + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); +@@ -571,6 +617,7 @@ static int acp_power_on(struct snd_sof_dev *sdev) + unsigned int base = desc->pgfsm_base; + unsigned int val; + unsigned int acp_pgfsm_status_mask, acp_pgfsm_cntl_mask; ++ bool use_masked_status = false; + int ret; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET); +@@ -595,6 +642,12 @@ static int acp_power_on(struct snd_sof_dev *sdev) + acp_pgfsm_status_mask = ACP70_PGFSM_STATUS_MASK; + acp_pgfsm_cntl_mask = ACP70_PGFSM_CNTL_POWER_ON_MASK; + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ acp_pgfsm_status_mask = ACP7X_PGFSM_STATUS_MASK; ++ acp_pgfsm_cntl_mask = ACP7X_PGFSM_CNTL_POWER_ON_MASK; ++ use_masked_status = true; ++ break; + default: + return -EINVAL; + } +@@ -603,8 +656,17 @@ static int acp_power_on(struct snd_sof_dev *sdev) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + PGFSM_CONTROL_OFFSET, + acp_pgfsm_cntl_mask); + +- ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET, val, +- !val, ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); ++ if (use_masked_status) ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !(val & acp_pgfsm_status_mask), ++ ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); ++ else ++ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ++ base + PGFSM_STATUS_OFFSET, val, ++ !val, ACP_REG_POLL_INTERVAL, ++ ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n"); + +@@ -703,6 +765,13 @@ static int acp_init(struct snd_sof_dev *sdev) + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP70_PME_EN, 1); + break; ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ++ ACP7X_IDMA_ERROR_MASK); ++ break; + } + return 0; + } +@@ -749,6 +818,8 @@ int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state) + case ACP72_PCI_ID: + enable = true; + break; ++ default: ++ break; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + +@@ -990,6 +1061,194 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) + } + EXPORT_SYMBOL_NS(amd_sof_acp_remove, "SND_SOC_SOF_AMD_COMMON"); + ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) ++{ ++ struct pci_dev *pci = to_pci_dev(sdev->dev); ++ struct acp_dev_data *adata; ++ const struct sof_amd_acp_desc *chip; ++ const union acpi_object *obj; ++ struct acpi_device *adev; ++ unsigned int addr; ++ unsigned int irqflags; ++ int ret; ++ ++ chip = get_chip_info(sdev->pdata); ++ if (!chip) { ++ dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); ++ return -EIO; ++ } ++ adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data), GFP_KERNEL); ++ if (!adata) ++ return -ENOMEM; ++ ++ adata->dev = sdev; ++ adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", ++ PLATFORM_DEVID_NONE, NULL, 0); ++ if (IS_ERR(adata->dmic_dev)) { ++ dev_err(sdev->dev, "failed to register platform for dmic codec\n"); ++ return PTR_ERR(adata->dmic_dev); ++ } ++ ++ addr = pci_resource_start(pci, ACP_DSP_BAR); ++ sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR)); ++ if (!sdev->bar[ACP_DSP_BAR]) { ++ dev_err(sdev->dev, "ioremap error\n"); ++ ret = -ENXIO; ++ goto unregister_dev; ++ } ++ ++ pci_set_master(pci); ++ adata->addr = addr; ++ adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; ++ adata->pci_rev = pci->revision; ++ mutex_init(&adata->acp_lock); ++ sdev->pdata->hw_pdata = adata; ++ ++ ret = acp_init(sdev); ++ if (ret < 0) ++ goto unregister_dev; ++ ++ adev = ACPI_COMPANION(&pci->dev); ++ ++ sdev->ipc_irq = pci->irq; ++ irqflags = IRQF_SHARED; ++ ++ ret = request_threaded_irq(pci->irq, acp7x_irq_handler, acp_irq_thread, ++ irqflags, "AudioDSP", sdev); ++ if (ret < 0) { ++ dev_err(sdev->dev, "failed to register IRQ %d\n", sdev->ipc_irq); ++ goto unregister_dev; ++ } ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ adata->acp_sof_signed_firmware_image = obj->integer.value; ++ } ++ ++ sdev->dsp_box.offset = 0; ++ sdev->dsp_box.size = BOX_SIZE_512; ++ ++ sdev->host_box.offset = sdev->dsp_box.offset + sdev->dsp_box.size; ++ sdev->host_box.size = BOX_SIZE_512; ++ ++ sdev->debug_box.offset = sdev->host_box.offset + sdev->host_box.size; ++ sdev->debug_box.size = BOX_SIZE_1024; ++ ++ if (adata->acp_sof_signed_firmware_image) { ++ adata->fw_code_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-code.bin", chip->name); ++ if (!adata->fw_code_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ adata->fw_data_bin = devm_kasprintf(sdev->dev, GFP_KERNEL, ++ "sof-%s-data.bin", chip->name); ++ if (!adata->fw_data_bin) { ++ ret = -ENOMEM; ++ goto free_ipc_irq; ++ } ++ } ++ ++ adata->enable_fw_debug = enable_fw_debug; ++ acp_memory_init(sdev); ++ acp_dsp_stream_init(sdev); ++ ++ return 0; ++ ++free_ipc_irq: ++ free_irq(sdev->ipc_irq, sdev); ++unregister_dev: ++ platform_device_unregister(adata->dmic_dev); ++ return ret; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_probe, "SND_SOC_SOF_AMD_COMMON"); ++ ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *adata = sdev->pdata->hw_pdata; ++ ++ if (sdev->ipc_irq) ++ free_irq(sdev->ipc_irq, sdev); ++ ++ if (adata->dmic_dev) ++ platform_device_unregister(adata->dmic_dev); ++ ++ acp_reset(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ bool enable = false; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_reset(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Reset failed\n"); ++ return ret; ++ } ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ enable = true; ++ break; ++ default: ++ break; ++ } ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) ++{ ++ struct acp_dev_data *acp_data; ++ int ret; ++ ++ acp_data = sdev->pdata->hw_pdata; ++ ++ ret = acp_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Init failed\n"); ++ return ret; ++ } ++ ret = acp_memory_init(sdev); ++ if (ret) { ++ dev_err(sdev->dev, "ACP Memory init failed\n"); ++ return ret; ++ } ++ ++ switch (acp_data->pci_rev) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_suspend(sdev, 0); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) ++{ ++ return amd_sof_acp7x_resume(sdev); ++} ++EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); ++ + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("AMD ACP sof driver"); + MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT"); +diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h +--- a/sound/soc/sof/amd/acp.h ++++ b/sound/soc/sof/amd/acp.h +@@ -76,6 +76,12 @@ + #define ACP70_PCI_ID 0x70 + #define ACP71_PCI_ID 0x71 + #define ACP72_PCI_ID 0x72 ++#define ACP7B_PCI_ID 0x7B ++#define ACP7F_PCI_ID 0x7F ++ ++#define ACP7X_PGFSM_CNTL_POWER_ON_MASK 0x7F ++#define ACP7X_PGFSM_STATUS_MASK 0xFFF ++#define ACP7X_SRAM_PTE_OFFSET ACP6X_SRAM_PTE_OFFSET + + #define HOST_BRIDGE_CZN 0x1630 + #define HOST_BRIDGE_VGH 0x1645 +@@ -107,6 +113,9 @@ + #define PROBE_STATUS_BIT BIT(31) + + #define ACP_FIRMWARE_SIGNATURE 0x100 ++#define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE ++#define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 ++ + #define ACP_ERROR_IRQ_MASK BIT(29) + #define ACP_SDW0_IRQ_MASK BIT(21) + #define ACP_SDW1_IRQ_MASK BIT(2) +@@ -271,6 +280,7 @@ struct acp_dev_data { + /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ + bool acp70_sdw1_wake_event; + unsigned int pci_rev; ++ int acp_sof_signed_firmware_image; + }; + + void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); +@@ -344,6 +354,16 @@ int sof_acp63_ops_init(struct snd_sof_dev *sdev); + extern struct snd_sof_dsp_ops sof_acp70_ops; + int sof_acp70_ops_init(struct snd_sof_dev *sdev); + ++extern struct snd_sof_dsp_ops sof_acp7x_ops; ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev); ++ ++int amd_sof_acp7x_probe(struct snd_sof_dev *sdev); ++void amd_sof_acp7x_remove(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state); ++int amd_sof_acp7x_resume(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev); ++int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev); ++ + struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev); + /* Machine configuration */ + int snd_amd_acp_find_config(struct pci_dev *pci); +diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/acp7x.c +@@ -0,0 +1,185 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * Hardware interface for Audio DSP on ACP7.B/7.F platforms ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-audio.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define I2S_TDM0_INSTANCE 0 ++#define I2S_TDM1_INSTANCE 1 ++#define I2S_TDM2_INSTANCE 2 ++#define PDM0_DMIC_INSTANCE 3 ++#define PDM1_DMIC_INSTANCE 4 ++ ++static struct snd_soc_dai_driver acp7x_sof_dai[] = { ++ [I2S_TDM0_INSTANCE] = { ++ .id = I2S_TDM0_INSTANCE, ++ .name = "acp-sof-i2s0", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S HS controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM1_INSTANCE] = { ++ .id = I2S_TDM1_INSTANCE, ++ .name = "acp-sof-i2s1", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S BT controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [I2S_TDM2_INSTANCE] = { ++ .id = I2S_TDM2_INSTANCE, ++ .name = "acp-sof-i2s2", ++ .playback = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 8, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | ++ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, ++ /* Supporting only stereo for I2S SP controller capture */ ++ .channels_min = 2, ++ .channels_max = 2, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM0_DMIC_INSTANCE] = { ++ .id = PDM0_DMIC_INSTANCE, ++ .name = "acp-sof-dmic0", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_48000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 48000, ++ }, ++ }, ++ ++ [PDM1_DMIC_INSTANCE] = { ++ .id = PDM1_DMIC_INSTANCE, ++ .name = "acp-sof-dmic1", ++ .capture = { ++ .rates = SNDRV_PCM_RATE_8000_96000, ++ .formats = SNDRV_PCM_FMTBIT_S32_LE, ++ .channels_min = 2, ++ .channels_max = 4, ++ .rate_min = 8000, ++ .rate_max = 96000, ++ }, ++ }, ++}; ++ ++static int sof_acp7x_post_fw_run_delay(struct snd_sof_dev *sdev) ++{ ++ /* ++ * Resuming from suspend in some cases may cause the DSP firmware ++ * to enter an unrecoverable faulty state. Delaying a bit any host ++ * to DSP transmission right after firmware boot completion seems ++ * to resolve the issue. ++ */ ++ if (!sdev->first_boot) ++ usleep_range(100, 150); ++ ++ return 0; ++} ++ ++struct snd_sof_dsp_ops sof_acp7x_ops; ++EXPORT_SYMBOL_NS(sof_acp7x_ops, "SND_SOC_SOF_AMD_COMMON"); ++ ++int sof_acp7x_ops_init(struct snd_sof_dev *sdev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&to_pci_dev(sdev->dev)->dev); ++ const union acpi_object *obj; ++ int acp_sof_signed_firmware_image = 0; ++ int acp_sof_post_fw_run_delay = 0; ++ ++ /* common defaults */ ++ memcpy(&sof_acp7x_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops)); ++ ++ sof_acp7x_ops.drv = acp7x_sof_dai; ++ sof_acp7x_ops.num_drv = ARRAY_SIZE(acp7x_sof_dai); ++ sof_acp7x_ops.probe = amd_sof_acp7x_probe; ++ sof_acp7x_ops.remove = amd_sof_acp7x_remove; ++ ++ if (adev) { ++ if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_signed_firmware_image = obj->integer.value; ++ ++ if (!acpi_dev_get_property(adev, "acp-sof-post_fw_run_delay", ++ ACPI_TYPE_INTEGER, &obj)) ++ acp_sof_post_fw_run_delay = obj->integer.value; ++ } ++ ++ if (acp_sof_signed_firmware_image) ++ sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; ++ ++ if (acp_sof_post_fw_run_delay) ++ sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; ++ ++ sof_acp7x_ops.suspend = amd_sof_acp7x_suspend; ++ sof_acp7x_ops.resume = amd_sof_acp7x_resume; ++ sof_acp7x_ops.runtime_suspend = amd_sof_acp7x_suspend_runtime; ++ sof_acp7x_ops.runtime_resume = amd_sof_acp7x_resume_runtime; ++ ++ return 0; ++} +diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c +new file mode 100644 +--- /dev/null ++++ b/sound/soc/sof/amd/pci-acp7x.c +@@ -0,0 +1,116 @@ ++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) ++// ++// This file is provided under a dual BSD/GPLv2 license. When using or ++// redistributing this file, you may do so under either license. ++// ++// Copyright(c) 2025 Advanced Micro Devices, Inc. All rights reserved. ++// ++// Authors: Vijendar Mukunda ++ ++/* ++ * PCI interface for ACP7.B/7.F devices ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "../ops.h" ++#include "../sof-pci-dev.h" ++#include "../../amd/mach-config.h" ++#include "acp.h" ++#include "acp-dsp-offset.h" ++ ++#define ACP7X_FUTURE_REG_ACLK_0 0x18e0 ++#define ACP7X_REG_START 0x1240000 ++#define ACP7X_REG_END 0x125C000 ++ ++static const struct sof_amd_acp_desc acp7x_chip_info = { ++ .name = "acp7x", ++ .pgfsm_base = ACP7X_PGFSM_BASE, ++ .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, ++ .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, ++ .ext_intr_stat = ACP7X_EXT_INTR_STAT, ++ .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, ++ .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, ++ .acp_error_stat = ACP7X_ERROR_STATUS, ++ .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, ++ .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, ++ .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, ++ .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, ++ .reg_start_addr = ACP7X_REG_START, ++ .reg_end_addr = ACP7X_REG_END, ++}; ++ ++static const struct sof_dev_desc acp7x_desc = { ++ .machines = snd_soc_acpi_amd_acp7x_sof_machines, ++ .resindex_lpe_base = 0, ++ .resindex_pcicfg_base = -1, ++ .resindex_imr_base = -1, ++ .irqindex_host_ipc = -1, ++ .chip_info = &acp7x_chip_info, ++ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), ++ .ipc_default = SOF_IPC_TYPE_3, ++ .default_fw_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof", ++ }, ++ .default_tplg_path = { ++ [SOF_IPC_TYPE_3] = "amd/sof-tplg", ++ }, ++ .default_fw_filename = { ++ [SOF_IPC_TYPE_3] = "sof-acp7x.ri", ++ }, ++ .nocodec_tplg_filename = "sof-acp.tplg", ++ .ops = &sof_acp7x_ops, ++ .ops_init = sof_acp7x_ops_init, ++}; ++ ++static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) ++{ ++ unsigned int flag; ++ ++ switch (pci->revision) { ++ case ACP7B_PCI_ID: ++ case ACP7F_PCI_ID: ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ flag = snd_amd_acp_find_config(pci); ++ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) ++ return -ENODEV; ++ ++ return sof_pci_probe(pci, pci_id); ++} ++ ++static void acp7x_pci_remove(struct pci_dev *pci) ++{ ++ sof_pci_remove(pci); ++} ++ ++/* PCI IDs */ ++static const struct pci_device_id acp7x_pci_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), ++ .driver_data = (unsigned long)&acp7x_desc}, ++ { 0, } ++}; ++MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); ++ ++/* pci_driver definition */ ++static struct pci_driver snd_sof_pci_amd_acp7x_driver = { ++ .name = KBUILD_MODNAME, ++ .id_table = acp7x_pci_ids, ++ .probe = acp7x_pci_probe, ++ .remove = acp7x_pci_remove, ++ .driver = { ++ .pm = pm_ptr(&sof_pci_pm), ++ }, ++}; ++module_pci_driver(snd_sof_pci_amd_acp7x_driver); ++ ++MODULE_LICENSE("Dual BSD/GPL"); ++MODULE_DESCRIPTION("ACP7X SOF Driver"); ++MODULE_IMPORT_NS("SND_SOC_SOF_AMD_COMMON"); ++MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV"); +diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c +--- a/sound/soc/sof/fw-file-profile.c ++++ b/sound/soc/sof/fw-file-profile.c +@@ -16,20 +16,19 @@ static int sof_test_firmware_file(struct device *dev, + enum sof_ipc_type *ipc_type_to_adjust) + { + enum sof_ipc_type fw_ipc_type; +- const struct firmware *fw; +- const char *fw_filename; + const u32 *magic; + int ret; + +- fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, +- profile->fw_name); ++ const char *fw_filename __free(kfree) = ++ kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path, ++ profile->fw_name); + if (!fw_filename) + return -ENOMEM; + ++ const struct firmware *fw __free(firmware) = NULL; + ret = firmware_request_nowarn(&fw, fw_filename, dev); + if (ret < 0) { + dev_dbg(dev, "Failed to open firmware file: %s\n", fw_filename); +- kfree(fw_filename); + return ret; + } + +@@ -44,8 +43,7 @@ static int sof_test_firmware_file(struct device *dev, + break; + default: + dev_err(dev, "Invalid firmware magic: %#x\n", *magic); +- ret = -EINVAL; +- goto out; ++ return -EINVAL; + } + + if (ipc_type_to_adjust) { +@@ -54,13 +52,10 @@ static int sof_test_firmware_file(struct device *dev, + dev_err(dev, + "ipc type mismatch between %s and expected: %d vs %d\n", + fw_filename, fw_ipc_type, profile->ipc_type); +- ret = -EINVAL; ++ return -EINVAL; + } +-out: +- release_firmware(fw); +- kfree(fw_filename); + +- return ret; ++ return 0; + } + + static int sof_test_topology_file(struct device *dev, +diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c +--- a/sound/soc/sof/intel/hda-ctrl.c ++++ b/sound/soc/sof/intel/hda-ctrl.c +@@ -186,6 +186,7 @@ EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON" + int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + { + struct hdac_bus *bus = sof_to_bus(sdev); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct hdac_stream *stream; + int sd_offset, ret = 0; + u32 gctl; +@@ -193,6 +194,16 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + if (bus->chip_init) + return 0; + ++ /* ++ * The controller reset clears the ACE2+ link DMA stream allocation ++ * constraints; reset the masks to reflect this. ++ */ ++ memset(sof_hda->link_dma_active_sdw_mask, 0, ++ sizeof(sof_hda->link_dma_active_sdw_mask)); ++ memset(sof_hda->link_dma_active_multi_mask, 0, ++ sizeof(sof_hda->link_dma_active_multi_mask)); ++ sof_hda->link_dma_out_hda_used_mask = 0; ++ + hda_codec_set_codec_wakeup(sdev, true); + + hda_dsp_ctrl_misc_clock_gating(sdev, false); +@@ -223,6 +234,14 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec) + /* Accept unsolicited responses */ + snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); + ++ /* Perform a one-time enumeration of the Multi-Link capability */ ++ ret = hda_bus_ml_init(bus); ++ if (ret < 0) { ++ dev_err(sdev->dev, "%s: failed to enumerate multi-links\n", ++ __func__); ++ goto err; ++ } ++ + if (detect_codec) + hda_codec_detect_mask(sdev); + +diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c +--- a/sound/soc/sof/intel/hda-dai-ops.c ++++ b/sound/soc/sof/intel/hda-dai-ops.c +@@ -20,7 +20,7 @@ + /* These ops are only applicable for the HDA DAI's in their current form */ + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) + /* +- * This function checks if the host dma channel corresponding ++ * This function checks if the host DMA stream corresponding + * to the link DMA stream_tag argument is assigned to one + * of the FEs connected to the BE DAI. + */ +@@ -42,23 +42,53 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, + } + + static struct hdac_ext_stream * +-hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream) ++hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream, ++ enum hda_bus_ml_link_type link_type) + { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); + struct sof_intel_hda_stream *hda_stream; + const struct sof_intel_dsp_desc *chip; + struct snd_sof_dev *sdev; + struct hdac_ext_stream *res = NULL; + struct hdac_stream *hstream = NULL; +- + int stream_dir = substream->stream; ++ bool is_multi = link_type == HDA_BUS_ML_LINK_HDA || link_type == HDA_BUS_ML_LINK_UAOL; ++ bool is_play = stream_dir == SNDRV_PCM_STREAM_PLAYBACK; ++ bool is_sdw = link_type == HDA_BUS_ML_LINK_SDW; ++ bool is_hda = link_type == HDA_BUS_ML_LINK_HDA; ++ u32 concur_block_mask = 0; ++ u32 seq_block_mask = 0; ++ unsigned int stream_idx; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + ++ /* ++ * On ACE2+ the link DMA stream allocator must avoid two HW errata, ++ * see the comment on struct sof_intel_hda_dev. ++ * ++ * - Concurrent cross-direction: SoundWire conflicts with HDA, iDisp ++ * and UAOL on the same physical stream index; SSP and DMIC are safe. ++ * - Sequential playback: a stream index previously used by an HDA/iDisp ++ * link cannot drive any non-HDA/iDisp link in the same direction ++ * until the next controller reset. ++ * ++ * The masks are protected by bus->reg_lock; sample them inside the ++ * lock together with the stream walk to keep the decision atomic ++ * with concurrent allocations and releases. ++ */ + guard(spinlock_irq)(&bus->reg_lock); ++ ++ if (is_sdw) ++ concur_block_mask = sof_hda->link_dma_active_multi_mask[!stream_dir]; ++ else if (is_multi) ++ concur_block_mask = sof_hda->link_dma_active_sdw_mask[!stream_dir]; ++ if (is_play && !is_hda) ++ seq_block_mask = sof_hda->link_dma_out_hda_used_mask; ++ + list_for_each_entry(hstream, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = + stream_to_hdac_ext_stream(hstream); +@@ -69,6 +99,12 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + sdev = hda_stream->sdev; + chip = get_chip_info(sdev->pdata); + ++ stream_idx = hstream->stream_tag - 1; ++ ++ /* skip streams blocked by the ACE2+ allocator constraints */ ++ if ((concur_block_mask | seq_block_mask) & BIT(stream_idx)) ++ continue; ++ + /* check if link is available */ + if (!hext_stream->link_locked) { + /* +@@ -95,7 +131,7 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + /* + * This must be a hostless stream. +- * So reserve the host DMA channel. ++ * So reserve the host DMA stream. + */ + hda_stream->host_reserved = 1; + break; +@@ -109,6 +145,16 @@ hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream + + res->link_locked = 1; + res->link_substream = substream; ++ ++ stream_idx = res->hstream.stream_tag - 1; ++ if (is_sdw) ++ sof_hda->link_dma_active_sdw_mask[stream_dir] |= BIT(stream_idx); ++ else if (is_multi) ++ sof_hda->link_dma_active_multi_mask[stream_dir] |= BIT(stream_idx); ++ ++ /* persistent OUT HDA/iDisp shadow, cleared only on CRST# */ ++ if (is_hda && is_play) ++ sof_hda->link_dma_out_hda_used_mask |= BIT(stream_idx); + } + + return res; +@@ -143,11 +189,13 @@ static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev + + static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream) ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink) + { + struct hdac_ext_stream *hext_stream; ++ enum hda_bus_ml_link_type link_type = hda_bus_ml_link_get_type(hlink); + +- hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream); ++ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream, link_type); + if (!hext_stream) + return NULL; + +@@ -160,6 +208,22 @@ static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai + struct snd_pcm_substream *substream) + { + struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream); ++ struct sof_intel_hda_dev *sof_hda = sdev->pdata->hw_pdata; ++ struct hdac_bus *bus = sof_to_bus(sdev); ++ int dir = substream->stream; ++ unsigned int stream_idx = hext_stream->hstream.stream_tag - 1; ++ ++ /* ++ * Drop the stream index from the per-direction active concurrency masks. ++ * The two masks are mutually exclusive for a given stream/direction ++ * (and a stream of the SSP/DMIC kind appears in neither), so a blind ++ * clear of both is safe and lets us avoid having to remember the ++ * link type at allocation time. ++ */ ++ scoped_guard(spinlock_irq, &bus->reg_lock) { ++ sof_hda->link_dma_active_sdw_mask[dir] &= ~BIT(stream_idx); ++ sof_hda->link_dma_active_multi_mask[dir] &= ~BIT(stream_idx); ++ } + + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); +diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c +--- a/sound/soc/sof/intel/hda-dai.c ++++ b/sound/soc/sof/intel/hda-dai.c +@@ -188,7 +188,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, + + if (!hext_stream) { + if (ops->assign_hext_stream) +- hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream); ++ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream, hlink); + } + + if (!hext_stream) +diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c +--- a/sound/soc/sof/intel/hda-dsp.c ++++ b/sound/soc/sof/intel/hda-dsp.c +@@ -1114,11 +1114,7 @@ static int hda_dsp_s5_quirk(struct snd_sof_dev *sdev) + usleep_range(500, 1000); + + /* Restore state for shutdown, back to reset */ +- ret = hda_dsp_ctrl_link_reset(sdev, true); +- if (ret < 0) +- return ret; +- +- return ret; ++ return hda_dsp_ctrl_link_reset(sdev, true); + } + + int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev) +diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c +--- a/sound/soc/sof/intel/hda-loader.c ++++ b/sound/soc/sof/intel/hda-loader.c +@@ -216,9 +216,10 @@ int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int + EXPORT_SYMBOL_NS(hda_cl_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax) + { +- return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, false); ++ return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, ++ is_iccmax, false); + } + EXPORT_SYMBOL_NS(hda_cl_cleanup, "SND_SOC_SOF_INTEL_HDA_COMMON"); + +@@ -302,7 +303,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->iccmax_dmab, +- persistent_cl_buffer, iccmax_stream); ++ persistent_cl_buffer, iccmax_stream, true); + if (ret1 < 0) { + dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); + +@@ -458,7 +459,7 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) + * If the cleanup also fails, we return the initial error + */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, +- persistent_cl_buffer, hext_stream); ++ persistent_cl_buffer, hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); + +@@ -587,7 +588,7 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, + cleanup: + /* clean up even in case of error and return the first error */ + ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, persistent_cl_buffer, +- hext_stream); ++ hext_stream, false); + if (ret1 < 0) { + dev_err(sdev->dev, "%s: Code loader DSP cleanup failed\n", __func__); + +diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c +--- a/sound/soc/sof/intel/hda-mlink.c ++++ b/sound/soc/sof/intel/hda-mlink.c +@@ -432,6 +432,10 @@ int hda_bus_ml_init(struct hdac_bus *bus) + if (!bus->mlcap) + return 0; + ++ /* Enumeration is a one time operation, skip if already done */ ++ if (!list_empty(&bus->hlink_list)) ++ return 0; ++ + link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count); +@@ -880,19 +884,6 @@ int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y, + return 0; + } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK"); + +-void hda_bus_ml_put_all(struct hdac_bus *bus) +-{ +- struct hdac_ext_link *hlink; +- +- list_for_each_entry(hlink, &bus->hlink_list, list) { +- struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); +- +- if (!h2link->alt) +- snd_hdac_ext_bus_link_put(bus, hlink); +- } +-} +-EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK"); +- + void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +@@ -903,6 +894,24 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus) + } + EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK"); + ++enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink) ++{ ++ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); ++ ++ if (!h2link->alt) ++ return HDA_BUS_ML_LINK_HDA; ++ ++ switch (h2link->elid) { ++ case AZX_REG_ML_LEPTR_ID_SDW: ++ return HDA_BUS_ML_LINK_SDW; ++ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL: ++ return HDA_BUS_ML_LINK_UAOL; ++ default: ++ return HDA_BUS_ML_LINK_OTHER; ++ } ++} ++EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK"); ++ + int hda_bus_ml_resume(struct hdac_bus *bus) + { + struct hdac_ext_link *hlink; +diff --git a/sound/soc/sof/intel/hda-sdw-bpt.c b/sound/soc/sof/intel/hda-sdw-bpt.c +--- a/sound/soc/sof/intel/hda-sdw-bpt.c ++++ b/sound/soc/sof/intel/hda-sdw-bpt.c +@@ -163,7 +163,7 @@ static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream + u32 mask; + int ret; + +- ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, true); ++ ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, false, true); + if (ret < 0) { + dev_err(sdev->dev, "%s: SDW BPT DMA cleanup failed\n", + __func__); +diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c +--- a/sound/soc/sof/intel/hda-stream.c ++++ b/sound/soc/sof/intel/hda-stream.c +@@ -198,13 +198,18 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, + + mask = (1 << hstream->index); + ++ /* Reset the spib_addr before disabling SPIB */ ++ if (!enable) ++ sof_io_write(sdev, hstream->spib_addr, 0); ++ + /* enable/disable SPIB for the stream */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_SPIB_BAR, + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, mask, + enable << hstream->index); + + /* set the SPIB value */ +- sof_io_write(sdev, hstream->spib_addr, size); ++ if (enable) ++ sof_io_write(sdev, hstream->spib_addr, size); + + return 0; + } +@@ -608,6 +613,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, + return ret; + } + ++ /* Host DMA is not running */ ++ hstream->running = false; ++ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, + sd_offset + SOF_HDA_ADSP_REG_SD_STS, + SOF_HDA_CL_DMA_SD_INT_MASK, +@@ -1323,16 +1331,18 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + EXPORT_SYMBOL_NS(hda_data_stream_prepare, "SND_SOC_SOF_INTEL_HDA_COMMON"); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair) ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair) + { + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct hdac_stream *hstream = hdac_stream(hext_stream); + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int ret = 0; + +- if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) ++ if (!is_iccmax) + ret = hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); +- else ++ ++ if (hstream->direction == SNDRV_PCM_STREAM_CAPTURE) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, + SOF_HDA_SD_CTL_DMA_START, 0); + +diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c +--- a/sound/soc/sof/intel/hda.c ++++ b/sound/soc/sof/intel/hda.c +@@ -421,6 +421,20 @@ static inline void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev) { } + /* pre fw run operations */ + int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { ++ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; ++ const struct sof_intel_dsp_desc *chip = hda->desc; ++ int ret; ++ ++ /* Power down DSP if left enabled to ensure a clean boot state. */ ++ if (hda_dsp_core_is_enabled(sdev, chip->host_managed_cores_mask)) { ++ dev_dbg(sdev->dev, "DSP core enabled, power down DSP first\n"); ++ ++ ret = chip->power_down_dsp(sdev); ++ if (ret < 0) ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ } ++ + /* disable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, false); + } +@@ -625,8 +639,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + return ret; + } + +- hda_bus_ml_init(bus); +- + /* Skip SoundWire if it is not supported */ + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + goto skip_soundwire; +@@ -670,8 +682,6 @@ static int hda_init_caps(struct snd_sof_dev *sdev) + if (!HDA_IDISP_CODEC(bus->codec_mask)) + hda_codec_i915_display_power(sdev, false); + +- hda_bus_ml_put_all(bus); +- + return 0; + } + +diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h +--- a/sound/soc/sof/intel/hda.h ++++ b/sound/soc/sof/intel/hda.h +@@ -523,6 +523,29 @@ struct sof_intel_hda_dev { + /* the maximum number of streams (playback + capture) supported */ + u32 stream_max; + ++ /* ++ * ACE2+ link DMA stream allocation constraints (stream index = ++ * stream_tag - 1, shared between input and output directions). All ++ * masks are cleared by hda_dsp_ctrl_init_chip() on controller reset ++ * (CRST#). ++ * ++ * - Concurrent (cross-direction) constraint: a SoundWire stream and ++ * a HDA/iDisp/UAOL stream cannot share a physical stream index ++ * across directions, the resulting LLP/timestamp values are wrong. ++ * link_dma_active_sdw_mask and link_dma_active_multi_mask ++ * (indexed by SNDRV_PCM_STREAM_*) track currently allocated ++ * streams per direction in each of the conflicting groups; SSP ++ * and DMIC do not participate. Bits are cleared on stream release. ++ * ++ * - Sequential (playback only) constraint: once a HDA/iDisp link ++ * has used a playback stream index, that index cannot drive a ++ * non-HDA/iDisp link in the same direction until the next CRST#. ++ * link_dma_out_hda_used_mask records this. ++ */ ++ u32 link_dma_active_sdw_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_active_multi_mask[SNDRV_PCM_STREAM_LAST + 1]; ++ u32 link_dma_out_hda_used_mask; ++ + /* PM related */ + bool l1_disabled;/* is DMI link L1 disabled? */ + +@@ -739,7 +762,7 @@ struct hdac_ext_stream *hda_cl_prepare(struct device *dev, unsigned int format, + int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int cmd); + + int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool is_iccmax); + int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); + #define HDA_CL_STREAM_FORMAT 0x40 + +@@ -911,7 +934,8 @@ hda_data_stream_prepare(struct device *dev, unsigned int format, unsigned int si + bool is_iccmax, bool pair); + + int hda_data_stream_cleanup(struct device *dev, struct snd_dma_buffer *dmab, +- bool persistent_buffer, struct hdac_ext_stream *hext_stream, bool pair); ++ bool persistent_buffer, struct hdac_ext_stream *hext_stream, ++ bool is_iccmax, bool pair); + + /* common dai driver */ + extern struct snd_soc_dai_driver skl_dai[]; +@@ -1030,7 +1054,8 @@ struct hda_dai_widget_dma_ops { + struct snd_pcm_substream *substream); + struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, +- struct snd_pcm_substream *substream); ++ struct snd_pcm_substream *substream, ++ struct hdac_ext_link *hlink); + void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, +diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c +--- a/sound/soc/sof/intel/icl.c ++++ b/sound/soc/sof/intel/icl.c +@@ -9,10 +9,14 @@ + * Hardware interface for audio DSP on IceLake. + */ + +-#include +-#include +-#include ++#include + #include ++#include ++#include ++#include ++#include ++#include ++ + #include "../ipc4-priv.h" + #include "../ops.h" + #include "hda.h" +diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c +--- a/sound/soc/sof/intel/mtl.c ++++ b/sound/soc/sof/intel/mtl.c +@@ -236,6 +236,17 @@ int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) + } + EXPORT_SYMBOL_NS(mtl_enable_interrupts, "SND_SOC_SOF_INTEL_MTL"); + ++static bool mtl_dsp_is_enabled(struct snd_sof_dev *sdev) ++{ ++ int val; ++ ++ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS); ++ if (val & MTL_HFDSSCS_CPA_MASK) ++ return true; ++ ++ return false; ++} ++ + /* pre fw run operations */ + static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + { +@@ -249,6 +260,18 @@ static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) + u32 dsppwrsts; + const struct sof_intel_dsp_desc *chip; + ++ /* Power down the DSP if it is left enabled to ensure clean boot state */ ++ if (mtl_dsp_is_enabled(sdev)) { ++ dev_dbg(sdev->dev, "powering down DSP first\n"); ++ ++ ret = mtl_power_down_dsp(sdev); ++ if (ret < 0) { ++ dev_warn(sdev->dev, ++ "%s: failed to power down already-enabled DSP\n", __func__); ++ /* Continue anyway to attempt recovery */ ++ } ++ } ++ + chip = get_chip_info(sdev->pdata); + if (chip->hw_ip_version > SOF_INTEL_ACE_2_0) { + dsppwrctl = PTL_HFPWRCTL2; +diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c +--- a/sound/soc/sof/ipc3-pcm.c ++++ b/sound/soc/sof/ipc3-pcm.c +@@ -421,6 +421,12 @@ static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + dev_dbg(component->dev, "AMD_SDW channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; ++ case SOF_DAI_AMD_I2S: ++ rate->min = private->dai_config->acp_i2s.fsync_rate; ++ rate->max = private->dai_config->acp_i2s.fsync_rate; ++ channels->min = private->dai_config->acp_i2s.tdm_slots; ++ channels->max = private->dai_config->acp_i2s.tdm_slots; ++ break; + default: + dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type); + break; +diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c +--- a/sound/soc/sof/ipc3-topology.c ++++ b/sound/soc/sof/ipc3-topology.c +@@ -281,7 +281,10 @@ static const struct sof_topology_token acpdmic_tokens[] = { + offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)}, + }; + +-/* ACPI2S */ ++/* ++ * ACPI2S tokens fill struct sof_ipc_dai_acp_params; SOF_DAI_AMD_I2S (ACPTDM ++ * on ACP7.B/7.F) reuses this tuple group rather than defining a parallel set. ++ */ + static const struct sof_topology_token acpi2s_tokens[] = { + {SOF_TKN_AMD_ACPI2S_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, fsync_rate)}, +@@ -289,6 +292,8 @@ static const struct sof_topology_token acpi2s_tokens[] = { + offsetof(struct sof_ipc_dai_acp_params, tdm_slots)}, + {SOF_TKN_AMD_ACPI2S_TDM_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acp_params, tdm_mode)}, ++ {SOF_TKN_AMD_ACPI2S_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, ++ offsetof(struct sof_ipc_dai_acp_params, format)}, + }; + + /* MICFIL PDM */ +@@ -1363,6 +1368,35 @@ static int sof_link_acp_sdw_load(struct snd_soc_component *scomp, struct snd_sof + return 0; + } + ++static int sof_link_acp_i2s_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, ++ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) ++{ ++ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; ++ struct sof_dai_private_data *private = dai->private; ++ u32 size = sizeof(*config); ++ int ret; ++ ++ /* handle master/slave and inverted clocks */ ++ sof_dai_set_format(hw_config, config); ++ ++ /* init IPC */ ++ memset(&config->acp_i2s, 0, sizeof(config->acp_i2s)); ++ config->hdr.size = size; ++ ++ ret = sof_update_ipc_object(scomp, &config->acp_i2s, SOF_ACPI2S_TOKENS, slink->tuples, ++ slink->num_tuples, size, slink->num_hw_configs); ++ if (ret < 0) ++ return ret; ++ ++ dai->number_configs = 1; ++ dai->current_config = 0; ++ private->dai_config = kmemdup(config, size, GFP_KERNEL); ++ if (!private->dai_config) ++ return -ENOMEM; ++ ++ return 0; ++} ++ + static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) + { +@@ -1692,6 +1726,9 @@ static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget) + case SOF_DAI_AMD_SDW: + ret = sof_link_acp_sdw_load(scomp, slink, config, dai); + break; ++ case SOF_DAI_AMD_I2S: ++ ret = sof_link_acp_i2s_load(scomp, slink, config, dai); ++ break; + default: + break; + } +diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c +--- a/sound/soc/sof/ipc4-topology.c ++++ b/sound/soc/sof/ipc4-topology.c +@@ -163,11 +163,11 @@ static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_SCHED_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_domain, + offsetof(struct snd_sof_widget, comp_domain)}, + {SOF_TKN_COMP_DOMAIN_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_domain_id)}, ++ offsetof(struct snd_sof_widget, domain_id)}, + {SOF_TKN_COMP_HEAP_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_heap_bytes)}, ++ offsetof(struct snd_sof_widget, heap_bytes)}, + {SOF_TKN_COMP_STACK_BYTES_REQUIREMENT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, +- offsetof(struct snd_sof_widget, dp_stack_bytes)}, ++ offsetof(struct snd_sof_widget, stack_bytes)}, + }; + + static const struct sof_topology_token gain_tokens[] = { +@@ -406,6 +406,39 @@ sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) + return NULL; + } + ++static void ++sof_ipc4_evaluate_params_change(struct sof_ipc4_available_audio_format *available_fmt) ++{ ++ struct sof_ipc4_audio_format *fmt; ++ u32 in_rate, in_channels, in_valid_bits; ++ u32 out_rate, out_channels, out_valid_bits; ++ u32 changed_params = 0; ++ int i, j; ++ ++ for (i = 0; i < available_fmt->num_input_formats; i++) { ++ fmt = &available_fmt->input_pin_fmts[i].audio_fmt; ++ in_rate = fmt->sampling_frequency; ++ in_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ in_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ for (j = 0; j < available_fmt->num_output_formats; j++) { ++ fmt = &available_fmt->output_pin_fmts[j].audio_fmt; ++ out_rate = fmt->sampling_frequency; ++ out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); ++ out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); ++ ++ if (in_rate != out_rate) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_RATE); ++ if (in_channels != out_channels) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_CHANNELS); ++ if (in_valid_bits != out_valid_bits) ++ changed_params |= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ } ++ } ++ ++ available_fmt->changed_params = changed_params; ++} ++ + /** + * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples + * @scomp: pointer to pointer to SOC component +@@ -497,6 +530,8 @@ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, + available_fmt->num_output_formats); + } + ++ sof_ipc4_evaluate_params_change(available_fmt); ++ + return 0; + + err_out: +@@ -661,6 +696,9 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + /* + * This callback is used by host copier and module-to-module copier, + * and only host copier needs to set gtw_cfg. +@@ -789,6 +827,9 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) + if (ret) + goto free_copier; + ++ /* Copier can only change format */ ++ available_fmt->changed_params &= BIT(SNDRV_PCM_HW_PARAM_FORMAT); ++ + ret = sof_update_ipc_object(scomp, &node_type, + SOF_COPIER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(node_type), 1); +@@ -1371,6 +1412,22 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * + pipeline = pipe_widget->private; + pipeline->mem_usage += total; + ++ /* ++ * If this is not a Data Processing module instance, add the ++ * required heap sizes to the sum of all module instances belonging ++ * to the same pipeline, and find the maximum stack requirement ++ * among all module instances belonging to the same pipeline. ++ */ ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { ++ pipe_widget->heap_bytes += swidget->heap_bytes; ++ if (pipe_widget->stack_bytes < swidget->stack_bytes) ++ pipe_widget->stack_bytes = swidget->stack_bytes; ++ ++ dev_dbg(sdev->dev, "%s mem reqs to %s heap %u stack %u", ++ swidget->widget->name, pipe_widget->widget->name, ++ pipe_widget->heap_bytes, pipe_widget->stack_bytes); ++ } ++ + /* Update base_config->cpc from the module manifest */ + sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config); + +@@ -1688,6 +1745,8 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + pipeline->mem_usage = 0; ++ pipe_widget->heap_bytes = 0; ++ pipe_widget->stack_bytes = 0; + + if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) { + if (pipeline->use_chain_dma) { +@@ -2065,10 +2124,57 @@ static void sof_ipc4_host_config(struct snd_sof_dev *sdev, struct snd_sof_widget + } + + static int +-sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, +- struct snd_pcm_hw_params *fe_params, +- struct snd_sof_platform_stream_params *platform_params, +- struct snd_pcm_hw_params *pipeline_params, int dir) ++sof_ipc4_copier_module_update_params(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *pipeline_params) ++{ ++ struct snd_soc_component *scomp = swidget->scomp; ++ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); ++ struct sof_ipc4_copier_data *copier_data; ++ struct sof_ipc4_copier *ipc4_copier; ++ ++ switch (swidget->id) { ++ case snd_soc_dapm_aif_in: ++ case snd_soc_dapm_aif_out: ++ case snd_soc_dapm_buffer: ++ ipc4_copier = swidget->private; ++ copier_data = &ipc4_copier->data; ++ break; ++ case snd_soc_dapm_dai_in: ++ case snd_soc_dapm_dai_out: ++ { ++ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; ++ struct sof_ipc4_pipeline *pipeline = pipe_widget->private; ++ struct snd_sof_dai *dai; ++ ++ if (pipeline->use_chain_dma) ++ return 0; ++ ++ dai = swidget->private; ++ ++ ipc4_copier = (struct sof_ipc4_copier *)dai->private; ++ copier_data = &ipc4_copier->data; ++ ++ break; ++ } ++ default: ++ dev_err(sdev->dev, "unsupported type %d for copier %s", ++ swidget->id, swidget->widget->name); ++ return -EINVAL; ++ } ++ ++ /* modify the input params for the next widget */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &copier_data->out_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++} ++ ++static int ++_sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) + { + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; +@@ -2540,6 +2646,21 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, + return 0; + } + ++static int ++sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ++ struct snd_pcm_hw_params *fe_params, ++ struct snd_sof_platform_stream_params *platform_params, ++ struct snd_pcm_hw_params *pipeline_params, int dir) ++{ ++ if (swidget->prepared) ++ return sof_ipc4_copier_module_update_params(swidget, ++ pipeline_params); ++ ++ return _sof_ipc4_prepare_copier_module(swidget, fe_params, ++ platform_params, pipeline_params, ++ dir); ++} ++ + static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, +@@ -2553,6 +2674,10 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &gain->data.base_config, + pipeline_params, +@@ -2598,6 +2723,10 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int input_fmt_index, output_fmt_index; + ++ /* Already prepared, nothing to do */ ++ if (swidget->prepared) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &mixer->base_config, + pipeline_params, +@@ -2644,6 +2773,10 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits, out_ref_type; + int output_fmt_index, input_fmt_index; + ++ /* This cannot happen */ ++ if (unlikely(swidget->prepared)) ++ return 0; ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &src->data.base_config, + pipeline_params, +@@ -2810,6 +2943,18 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + int ret; + + if (available_fmt->num_input_formats) { ++ if (swidget->prepared) { ++ if (!available_fmt->num_output_formats) ++ return 0; ++ ++ /* modify the pipeline params with the output format */ ++ return sof_ipc4_update_hw_params(sdev, pipeline_params, ++ &process->output_format, ++ BIT(SNDRV_PCM_HW_PARAM_FORMAT) | ++ BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | ++ BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ } ++ + input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, + &process->base_config, + pipeline_params, +@@ -2822,39 +2967,69 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + if (available_fmt->num_output_formats) { + struct sof_ipc4_audio_format *in_fmt; + struct sof_ipc4_pin_format *pin_fmt; +- u32 out_ref_rate, out_ref_channels; +- int out_ref_valid_bits, out_ref_type; ++ u32 ref_rate, ref_channels; ++ int ref_valid_bits, ref_type; + + if (available_fmt->num_input_formats) { ++ /* ++ * The process module can change parameters and their operation ++ * depends on the direction: ++ * Playback: typically they have single output format. This is ++ * to 'force' the conversion from input to output. ++ * Use the input format as reference since the single ++ * format is going to be picked. ++ * Capture: typically they have multiple output formats to ++ * convert from dai (input) to FE (output) parameters. ++ * Use the input format as base and replace the param ++ * which is changed by the module with the FE parameter ++ * Reason: we can have module which changes the ++ * parameters in path, we cannot use the full ++ * FE param set for the module output lookup. ++ */ + in_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt; + +- out_ref_rate = in_fmt->sampling_frequency; +- out_ref_channels = ++ ref_rate = in_fmt->sampling_frequency; ++ ref_channels = + SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); +- out_ref_valid_bits = ++ ref_valid_bits = + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); +- out_ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); ++ ref_type = sof_ipc4_fmt_cfg_to_type(in_fmt->fmt_cfg); + } else { + /* for modules without input formats, use FE params as reference */ +- out_ref_rate = params_rate(fe_params); +- out_ref_channels = params_channels(fe_params); ++ ref_rate = params_rate(fe_params); ++ ref_channels = params_channels(fe_params); + ret = sof_ipc4_get_sample_type(sdev, fe_params); + if (ret < 0) + return ret; +- out_ref_type = (u32)ret; ++ ref_type = (u32)ret; + +- out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); +- if (out_ref_valid_bits < 0) +- return out_ref_valid_bits; ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; + } + ++ if (dir == SNDRV_PCM_STREAM_CAPTURE) { ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_RATE)) ++ ref_rate = params_rate(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_CHANNELS)) ++ ref_channels = params_channels(fe_params); ++ if (available_fmt->changed_params & BIT(SNDRV_PCM_HW_PARAM_FORMAT)) { ++ ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); ++ if (ref_valid_bits < 0) ++ return ref_valid_bits; ++ ++ ref_type = sof_ipc4_get_sample_type(sdev, fe_params); ++ if (ref_type < 0) ++ return ref_type; ++ } ++ } + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, swidget, + &process->base_config, + available_fmt, +- out_ref_rate, +- out_ref_channels, +- out_ref_valid_bits, +- out_ref_type); ++ ref_rate, ++ ref_channels, ++ ref_valid_bits, ++ ref_type); + if (output_fmt_index < 0) + return output_fmt_index; + +@@ -2868,9 +3043,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + /* modify the pipeline params with the output format */ + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, + &process->output_format, +- BIT(SNDRV_PCM_HW_PARAM_FORMAT) | +- BIT(SNDRV_PCM_HW_PARAM_CHANNELS) | +- BIT(SNDRV_PCM_HW_PARAM_RATE)); ++ available_fmt->changed_params); + if (ret) + return ret; + } +@@ -3085,27 +3258,47 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr + return 0; + } + +-static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, +- struct snd_sof_widget *swidget, +- struct sof_ipc4_msg *msg, +- void *ipc_data, u32 ipc_size, +- void **new_data) ++static void sof_ipc4_add_init_ext_dp_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_module_init_ext_object **hdr) ++{ ++ /* Add memory_data if comp_domain indicates DP */ ++ if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { ++ struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; ++ ++ *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | ++ SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; ++ dp_mem_data->domain_id = swidget->domain_id; ++ dp_mem_data->stack_bytes = swidget->stack_bytes; ++ dp_mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ } ++} ++ ++static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void *ipc_data, u32 ipc_size, ++ void **new_data) + { +- struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; + struct sof_ipc4_module_init_ext_init *ext_init; +- struct sof_ipc4_module_init_ext_object *hdr; ++ struct sof_ipc4_module_init_ext_object *hdr = NULL; + int new_size; + u32 *payload; + u32 ext_pos; + +- /* For the moment the only reason for adding init_ext_init payload is DP +- * memory data. If both stack and heap size are 0 (= use default), then +- * there is no need for init_ext_init payload. ++ /* ++ * Only DP widgets currently add init-ext objects here. Avoid allocating ++ * a max-sized payload buffer for widgets that will immediately return 0. + */ +- if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) { +- msg->extension &= ~SOF_IPC4_MOD_EXT_EXTENDED_INIT_MASK; ++ if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) + return 0; +- } + + payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); + if (!payload) +@@ -3113,27 +3306,26 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + + /* Add ext_init first and set objects array flag to 1 */ + ext_init = (struct sof_ipc4_module_init_ext_init *)payload; +- ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; + ext_pos = DIV_ROUND_UP(sizeof(*ext_init), sizeof(u32)); + + /* Add object array objects after ext_init */ + +- /* Add dp_memory_data if comp_domain indicates DP */ +- if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { +- hdr = (struct sof_ipc4_module_init_ext_object *)&payload[ext_pos]; +- hdr->header = SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK | +- SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | +- SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), +- sizeof(u32))); +- ext_pos += DIV_ROUND_UP(sizeof(*hdr), sizeof(u32)); +- dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[ext_pos]; +- dp_mem_data->domain_id = swidget->dp_domain_id; +- dp_mem_data->stack_bytes = swidget->dp_stack_bytes; +- dp_mem_data->heap_bytes = swidget->dp_heap_bytes; +- ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); ++ sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ ++ /* Add following object array items here */ ++ ++ if (!hdr) { ++ /* ++ * NOTE: Remove this early bail out, when struct ++ * sof_ipc4_module_init_ext_init alone has some ++ * function. ++ */ ++ kfree(payload); ++ return 0; + } + +- /* If another array object is added, remember clear previous OBJ_LAST bit */ ++ ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; ++ hdr->header |= SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK; + + /* Calculate final size and check that it fits to max payload size */ + new_size = ext_pos * sizeof(u32) + ipc_size; +@@ -3156,6 +3348,69 @@ static int sof_ipc4_widget_setup_msg_payload(struct snd_sof_dev *sdev, + return new_size; + } + ++static void sof_ipc4_widget_pipe_ext_obj_memory_data(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ u32 *payload, u32 *ext_pos, ++ struct sof_ipc4_glb_pipe_ext_object **hdr) ++{ ++ struct sof_ipc4_glb_pipe_ext_obj_memory_data *mem_data; ++ ++ *hdr = (struct sof_ipc4_glb_pipe_ext_object *)&payload[*ext_pos]; ++ (*hdr)->header = ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_ID(SOF_IPC4_GLB_PIPE_DATA_ID_MEM_DATA) | ++ SOF_IPC4_GLB_PIPE_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*mem_data), ++ sizeof(u32))); ++ *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); ++ mem_data = (struct sof_ipc4_glb_pipe_ext_obj_memory_data *)&payload[*ext_pos]; ++ mem_data->domain_id = swidget->domain_id; ++ mem_data->stack_bytes = swidget->stack_bytes; ++ mem_data->heap_bytes = swidget->heap_bytes; ++ *ext_pos += DIV_ROUND_UP(sizeof(*mem_data), sizeof(u32)); ++ ++ dev_dbg(sdev->dev, ++ "%s; domain_id %u stack %u heap %u bytes", ++ swidget->widget->name, mem_data->domain_id, mem_data->stack_bytes, ++ mem_data->heap_bytes); ++} ++ ++static int sof_ipc4_widget_pipe_create_msg_payload(struct snd_sof_dev *sdev, ++ struct snd_sof_widget *swidget, ++ struct sof_ipc4_msg *msg, ++ void **new_data) ++{ ++ struct sof_ipc4_glb_pipe_payload *payload_hdr; ++ struct sof_ipc4_glb_pipe_ext_object *hdr = NULL; ++ u32 *payload; ++ u32 ext_pos; ++ ++ payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); ++ if (!payload) ++ return -ENOMEM; ++ ++ /* Add sof_ipc4_glb_pipe_payload and set array bit to 1 */ ++ payload_hdr = (struct sof_ipc4_glb_pipe_payload *)payload; ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_EXT_OBJ_ARRAY_MASK; ++ ext_pos = DIV_ROUND_UP(sizeof(*payload_hdr), sizeof(u32)); ++ ++ sof_ipc4_widget_pipe_ext_obj_memory_data(sdev, swidget, payload, &ext_pos, &hdr); ++ /* Add following array objects here */ ++ ++ /* Mark end of object array */ ++ hdr->header |= SOF_IPC4_GLB_PIPE_EXT_OBJ_LAST_MASK; ++ ++ /* Put total payload size in words to the payload header */ ++ payload_hdr->word0 |= SOF_IPC4_GLB_PIPE_PAYLOAD_WORDS(ext_pos); ++ *new_data = payload; ++ ++ /* Update msg extension bits according to the payload changes */ ++ msg->extension |= SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ++ dev_dbg(sdev->dev, "%s: payload word0 %#x", swidget->widget->name, ++ payload_hdr->word0); ++ ++ return ext_pos * sizeof(int32_t); ++} ++ + static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) + { + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; +@@ -3342,8 +3597,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name, swidget->pipeline_id, module_id, + swidget->instance_id, swidget->core); + +- ret = sof_ipc4_widget_setup_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, +- &ext_data); ++ ret = sof_ipc4_widget_mod_init_msg_payload(sdev, swidget, msg, ipc_data, ipc_size, ++ &ext_data); + if (ret < 0) + goto fail; + +@@ -3355,6 +3610,17 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget + dev_dbg(sdev->dev, "Create pipeline %s (pipe %d) - instance %d, core %d\n", + swidget->widget->name, swidget->pipeline_id, + swidget->instance_id, swidget->core); ++ ++ msg->extension &= ~SOF_IPC4_GLB_PIPE_PAYLOAD_MASK; ++ ret = sof_ipc4_widget_pipe_create_msg_payload(sdev, swidget, msg, ++ &ext_data); ++ if (ret < 0) ++ goto fail; ++ ++ if (ret > 0) { ++ ipc_size = ret; ++ ipc_data = ext_data; ++ } + } + + msg->data_size = ipc_size; +@@ -3412,6 +3678,8 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget + swidget->widget->name); + + pipeline->mem_usage = 0; ++ swidget->heap_bytes = 0; ++ swidget->stack_bytes = 0; + pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + ida_free(&pipeline_ida, swidget->instance_id); + swidget->instance_id = -EINVAL; +diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h +--- a/sound/soc/sof/ipc4-topology.h ++++ b/sound/soc/sof/ipc4-topology.h +@@ -167,7 +167,7 @@ struct sof_ipc4_pipeline { + }; + + /** +- * struct sof_ipc4_multi_pipeline_data - multi pipeline trigger IPC data ++ * struct ipc4_pipeline_set_state_data - multi pipeline trigger IPC data + * @count: Number of pipelines to be triggered + * @pipeline_instance_ids: Flexible array of IDs of the pipelines to be triggered + */ +@@ -197,12 +197,15 @@ struct sof_ipc4_pin_format { + * @input_pin_fmts: Available input pin formats + * @num_input_formats: Number of input pin formats + * @num_output_formats: Number of output pin formats ++ * @changed_params: Mask of changed params by the module instance between it's ++ * input and output formts (rate, channels, depth) + */ + struct sof_ipc4_available_audio_format { + struct sof_ipc4_pin_format *output_pin_fmts; + struct sof_ipc4_pin_format *input_pin_fmts; + u32 num_input_formats; + u32 num_output_formats; ++ u32 changed_params; + }; + + /** +@@ -210,7 +213,7 @@ struct sof_ipc4_available_audio_format { + * @node_id: ID of Gateway Node + * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes) + * @config_length: Length of gateway node configuration blob specified in #config_data +- * config_data: Gateway node configuration blob ++ * @config_data: Gateway node configuration blob + */ + struct sof_copier_gateway_cfg { + uint32_t node_id; +@@ -285,7 +288,9 @@ struct sof_ipc4_dma_stream_ch_map { + struct sof_ipc4_dma_config { + uint8_t dma_method; + uint8_t pre_allocated_by_host; ++ /* private: */ + uint16_t rsvd; ++ /* public: */ + uint32_t dma_channel_id; + uint32_t stream_id; + struct sof_ipc4_dma_stream_ch_map dma_stream_channel_map; +@@ -296,7 +301,7 @@ struct sof_ipc4_dma_config { + #define SOF_IPC4_GTW_DMA_CONFIG_ID 0x1000 + + /** +- * struct sof_ipc4_dma_config: DMA configuration ++ * struct sof_ipc4_dma_config_tlv - DMA configuration + * @type: set to SOF_IPC4_GTW_DMA_CONFIG_ID + * @length: sizeof(struct sof_ipc4_dma_config) + dma_config.dma_priv_config_size + * @dma_config: actual DMA configuration +@@ -321,6 +326,7 @@ struct sof_ipc4_alh_configuration_blob { + * @data: IPC copier data + * @copier_config: Copier + blob + * @ipc_config_size: Size of copier_config ++ * @ipc_config_data: Copier module config data + * @available_fmt: Available audio format + * @frame_fmt: frame format + * @msg: message structure for copier +diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c +--- a/sound/soc/sof/ipc4.c ++++ b/sound/soc/sof/ipc4.c +@@ -289,6 +289,89 @@ static void sof_ipc4_dump_payload(struct snd_sof_dev *sdev, + 16, 4, ipc_data, size, false); + } + ++static const char *sof_ipc4_resource_type_str(u32 type) ++{ ++ switch (type) { ++ case SOF_IPC4_MODULE_INSTANCE: ++ return "resource: MODULE_INSTANCE"; ++ case SOF_IPC4_PIPELINE: ++ return "resource: PIPELINE"; ++ case SOF_IPC4_GATEWAY: ++ return "resource: GATEWAY"; ++ case SOF_IPC4_EDF_TASK: ++ return "resource: EDF_TASK"; ++ case SOF_IPC4_INVALID_RESOURCE_TYPE: ++ return "Resource is invalid"; ++ default: ++ return "Unknown resource type"; ++ } ++} ++ ++static const char *sof_ipc4_resource_event_type_str(u32 event_type) ++{ ++ switch (event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ return "event: MIXER_UNDERRUN_DETECTED"; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ return "event: PROCESS_DATA_ERROR"; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ return "event: GATEWAY_UNDERRUN_DETECTED"; ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ return "event: GATEWAY_OVERRUN_DETECTED"; ++ default: ++ return "Unknown event type"; ++ } ++} ++ ++static void sof_ipc4_resource_event_handler(struct snd_sof_dev *sdev, ++ struct sof_ipc4_msg *ipc4_msg) ++{ ++ struct sof_ipc4_notify_resource_data *data = ipc4_msg->data_ptr; ++ ++ /* Print event details */ ++ switch (data->event_type) { ++ case SOF_IPC4_MIXER_UNDERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u): eos %u, mixed %u, expected %u\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.mixer_underrun.eos_flag, ++ data->data.mixer_underrun.data_mixed, ++ data->data.mixer_underrun.expected_data_mixed); ++ break; ++ case SOF_IPC4_PROCESS_DATA_ERROR: ++ dev_dbg(sdev->dev, "%s (%u): error_code %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, data->data.process_data_error.error_code); ++ break; ++ case SOF_IPC4_GATEWAY_UNDERRUN_DETECTED: ++ case SOF_IPC4_GATEWAY_OVERRUN_DETECTED: ++ dev_dbg(sdev->dev, "%s (%u)\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type); ++ break; ++ default: ++ dev_dbg(sdev->dev, "%s (%u): raw dws %#x %#x %#x %#x %#x %#x\n", ++ sof_ipc4_resource_event_type_str(data->event_type), ++ data->event_type, ++ data->data.dws[0], data->data.dws[1], data->data.dws[2], ++ data->data.dws[3], data->data.dws[4], data->data.dws[5]); ++ break; ++ } ++ ++ /* Print resource details */ ++ if (data->resource_type == SOF_IPC4_MODULE_INSTANCE) { ++ u32 module_id = SOF_IPC4_MOD_ID_GET(data->resource_id); ++ u32 instance_id = SOF_IPC4_MOD_INSTANCE_GET(data->resource_id); ++ ++ dev_dbg(sdev->dev, "%s (%u), module_id %u, instance_id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, module_id, instance_id); ++ } else if (data->resource_type != SOF_IPC4_INVALID_RESOURCE_TYPE) { ++ dev_dbg(sdev->dev, "%s (%u), id %u\n", ++ sof_ipc4_resource_type_str(data->resource_type), ++ data->resource_type, data->resource_id); ++ } ++} ++ + static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) + { + struct snd_sof_ipc_msg *msg = sdev->msg; +@@ -734,6 +817,7 @@ static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev) + break; + case SOF_IPC4_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct sof_ipc4_notify_resource_data); ++ handler_func = sof_ipc4_resource_event_handler; + break; + case SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS: + sof_ipc4_mtrace_update_pos(sdev, SOF_IPC4_LOG_CORE_GET(ipc4_msg->primary)); +diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c +--- a/sound/soc/sof/nocodec.c ++++ b/sound/soc/sof/nocodec.c +@@ -21,12 +21,13 @@ static struct snd_soc_card sof_nocodec_card = { + static int sof_nocodec_bes_setup(struct device *dev, + struct snd_soc_dai_driver *drv, + struct snd_soc_dai_link *links, +- int link_num, struct snd_soc_card *card) ++ int link_num) + { ++ struct snd_soc_card *card = &sof_nocodec_card; + struct snd_soc_dai_link_component *dlc; + int i; + +- if (!drv || !links || !card) ++ if (!drv || !links) + return -EINVAL; + + /* set up BE dai_links */ +@@ -67,10 +68,10 @@ static int sof_nocodec_bes_setup(struct device *dev, + return 0; + } + +-static int sof_nocodec_setup(struct device *dev, +- u32 num_dai_drivers, +- struct snd_soc_dai_driver *dai_drivers) ++static int sof_nocodec_setup(struct device *dev, struct snd_soc_acpi_mach *mach) + { ++ u32 num_dai_drivers = mach->mach_params.num_dai_drivers; ++ struct snd_soc_dai_driver *dai_drivers = mach->mach_params.dai_drivers; + struct snd_soc_dai_link *links; + + /* create dummy BE dai_links */ +@@ -78,7 +79,7 @@ static int sof_nocodec_setup(struct device *dev, + if (!links) + return -ENOMEM; + +- return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers, &sof_nocodec_card); ++ return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers); + } + + static int sof_nocodec_probe(struct platform_device *pdev) +@@ -92,8 +93,7 @@ static int sof_nocodec_probe(struct platform_device *pdev) + + snd_soc_card_set_topology_name(card, "sof"); + +- ret = sof_nocodec_setup(card->dev, mach->mach_params.num_dai_drivers, +- mach->mach_params.dai_drivers); ++ ret = sof_nocodec_setup(card->dev, mach); + if (ret < 0) + return ret; + +diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c +--- a/sound/soc/sof/pcm.c ++++ b/sound/soc/sof/pcm.c +@@ -62,7 +62,7 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) + * To avoid sending IPC before the previous IPC is handled, we + * schedule delayed work here to call the snd_pcm_period_elapsed(). + */ +- schedule_work(&spcm->stream[substream->stream].period_elapsed_work); ++ queue_work(system_highpri_wq, &spcm->stream[substream->stream].period_elapsed_work); + } + EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); + +diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c +--- a/sound/soc/sof/sof-audio.c ++++ b/sound/soc/sof/sof-audio.c +@@ -501,9 +501,8 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget + !sof_widget_in_same_direction(swidget, dir)) + return 0; + +- /* skip widgets already prepared or aggregated DAI widgets*/ +- if (!widget_ops[widget->id].ipc_prepare || swidget->prepared || +- is_aggregated_dai(swidget)) ++ /* skip widgets aggregated DAI widgets */ ++ if (!widget_ops[widget->id].ipc_prepare || is_aggregated_dai(swidget)) + goto sink_prepare; + + /* prepare the source widget */ +diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h +--- a/sound/soc/sof/sof-audio.h ++++ b/sound/soc/sof/sof-audio.h +@@ -459,10 +459,10 @@ struct snd_sof_widget { + /* Scheduling domain (enum sof_comp_domain), unset, Low Latency, or Data Processing */ + u32 comp_domain; + +- /* The values below are added to mod_init pay load if comp_domain indicates DP component */ +- u32 dp_domain_id; /* DP process userspace domain ID */ +- u32 dp_stack_bytes; /* DP process stack size requirement in bytes */ +- u32 dp_heap_bytes; /* DP process heap size requirement in bytes */ ++ /* Module instance's memory configuration. */ ++ u32 domain_id; /* Module instance's userspace domain ID */ ++ u32 stack_bytes; /* Module instance's stack size requirement */ ++ u32 heap_bytes; /* Module instance's heap size requirement */ + + struct snd_soc_dapm_widget *widget; + struct list_head list; /* list in sdev widget list */ +diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c +--- a/sound/soc/sof/topology.c ++++ b/sound/soc/sof/topology.c +@@ -309,7 +309,7 @@ static const struct sof_dai_types sof_dais[] = { + {"ACPHS_VIRTUAL", SOF_DAI_AMD_HS_VIRTUAL}, + {"MICFIL", SOF_DAI_IMX_MICFIL}, + {"ACP_SDW", SOF_DAI_AMD_SDW}, +- ++ {"ACPTDM", SOF_DAI_AMD_I2S}, + }; + + static enum sof_ipc_dai_type find_dai(const char *name) +@@ -1108,7 +1108,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, + + full = NULL; + partial = NULL; +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (rtd->dai_link->stream_name) { + if (!strcmp(rtd->dai_link->stream_name, w->sname)) { +@@ -1173,7 +1173,7 @@ static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, + else + return; + +- list_for_each_entry(rtd, &card->rtd_list, list) { ++ for_each_card_rtds(card, rtd) { + /* does stream match DAI link ? */ + if (!rtd->dai_link->stream_name || + !strstr(rtd->dai_link->stream_name, sname)) +@@ -1949,8 +1949,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + private->array, le32_to_cpu(private->size)); + if (ret < 0) { + dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); +- kfree(slink); +- return ret; ++ goto free_slink; + } + + token_list = tplg_ops ? tplg_ops->token_list : NULL; +@@ -2001,6 +2000,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + case SOF_DAI_AMD_HS: + case SOF_DAI_AMD_SP_VIRTUAL: + case SOF_DAI_AMD_HS_VIRTUAL: ++ case SOF_DAI_AMD_I2S: + token_id = SOF_ACPI2S_TOKENS; + num_tuples += token_list[SOF_ACPI2S_TOKENS].count; + break; +@@ -2019,8 +2019,8 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + /* allocate memory for tuples array */ + slink->tuples = kzalloc_objs(*slink->tuples, num_tuples); + if (!slink->tuples) { +- kfree(slink); +- return -ENOMEM; ++ ret = -ENOMEM; ++ goto free_slink; + } + + if (token_list[SOF_DAI_LINK_TOKENS].tokens) { +@@ -2076,6 +2076,7 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_ + + err: + kfree(slink->tuples); ++free_slink: + kfree(slink); + + return ret; +@@ -2511,13 +2512,12 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const char *tplg_filename_prefix = sof_pdata->tplg_filename_prefix; +- const struct firmware *fw; +- const char **tplg_files; + int tplg_cnt = 0; + int ret; + int i; + +- tplg_files = kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); ++ const char **tplg_files __free(kfree) = ++ kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + if (!tplg_files) + return -ENOMEM; + +@@ -2543,10 +2543,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + tplg_filename_prefix, + &tplg_files, + no_fallback); +- if (tplg_cnt < 0) { +- kfree(tplg_files); ++ if (tplg_cnt < 0) + return tplg_cnt; +- } + } + + /* +@@ -2557,8 +2555,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (strstr(file, "dummy")) { + dev_err(scomp->dev, + "Function topology is required, please upgrade sof-firmware\n"); +- +- kfree(tplg_files); + return -EINVAL; + } + tplg_files[0] = file; +@@ -2573,6 +2569,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (tplg_files[0] != file) + dev_info(scomp->dev, "loading topology %d: %s\n", i, tplg_files[i]); + ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, tplg_files[i], scomp->dev); + if (ret < 0) { + /* +@@ -2591,8 +2588,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "tplg %s component load failed %d\n", + tplg_files[i], ret); +@@ -2611,6 +2606,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + goto out; + } + dev_info(scomp->dev, "loading feature topology %d: %s\n", i, feature_topology); ++ ++ const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, feature_topology, scomp->dev); + if (ret < 0) { + /* +@@ -2635,8 +2632,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + +- release_firmware(fw); +- + if (ret < 0) { + dev_err(scomp->dev, "feature tplg %s component load failed %d\n", + feature_topologies[i], ret); +@@ -2655,8 +2650,6 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) + if (ret >= 0 && sdev->led_present) + ret = snd_ctl_led_request(); + +- kfree(tplg_files); +- + return ret; + } + EXPORT_SYMBOL(snd_sof_load_topology); +diff --git a/sound/soc/sophgo/cv1800b-tdm.c b/sound/soc/sophgo/cv1800b-tdm.c +--- a/sound/soc/sophgo/cv1800b-tdm.c ++++ b/sound/soc/sophgo/cv1800b-tdm.c +@@ -558,7 +558,7 @@ static const struct snd_soc_dai_ops cv1800b_i2s_dai_ops = { + .set_sysclk = cv1800b_i2s_dai_set_sysclk, + }; + +-static const struct snd_soc_dai_driver cv1800b_i2s_dai_template = { ++static struct snd_soc_dai_driver cv1800b_i2s_dai_template = { + .name = "cv1800b-i2s", + .playback = { + .stream_name = "Playback", +@@ -636,7 +636,6 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + struct cv1800b_i2s *i2s; + struct resource *res; + void __iomem *regs; +- struct snd_soc_dai_driver *dai; + int ret; + + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); +@@ -666,21 +665,14 @@ static int cv1800b_i2s_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, i2s); + cv1800b_i2s_setup_tdm(i2s); + +- dai = devm_kmemdup(dev, &cv1800b_i2s_dai_template, sizeof(*dai), +- GFP_KERNEL); +- if (!dai) +- return -ENOMEM; +- +- ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, dai, +- 1); ++ ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, ++ &cv1800b_i2s_dai_template, 1); + if (ret) + return ret; + + ret = devm_snd_dmaengine_pcm_register(dev, &cv1800b_i2s_pcm_config, 0); +- if (ret) { +- dev_err(dev, "dmaengine_pcm_register failed: %d\n", ret); ++ if (ret) + return ret; +- } + + return 0; + } +diff --git a/sound/soc/spacemit/k1_i2s.c b/sound/soc/spacemit/k1_i2s.c +--- a/sound/soc/spacemit/k1_i2s.c ++++ b/sound/soc/spacemit/k1_i2s.c +@@ -335,6 +335,11 @@ static int spacemit_i2s_dai_remove(struct snd_soc_dai *dai) + return 0; + } + ++static const u64 spacemit_i2s_selectable_formats = ++ SND_SOC_POSSIBLE_DAIFMT_I2S | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_A | ++ SND_SOC_POSSIBLE_DAIFMT_DSP_B; ++ + static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .probe = spacemit_i2s_dai_probe, + .remove = spacemit_i2s_dai_remove, +@@ -343,6 +348,8 @@ static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { + .set_sysclk = spacemit_i2s_set_sysclk, + .set_fmt = spacemit_i2s_set_fmt, + .trigger = spacemit_i2s_trigger, ++ .auto_selectable_formats = &spacemit_i2s_selectable_formats, ++ .num_auto_selectable_formats = 1, + }; + + static struct snd_soc_dai_driver spacemit_i2s_dai = { +@@ -459,7 +466,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + + i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(i2s->base)) +- return dev_err_probe(i2s->dev, PTR_ERR(i2s->base), "failed to map registers\n"); ++ return PTR_ERR(i2s->base); + + i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s->reset)) +@@ -476,7 +483,7 @@ static int spacemit_i2s_probe(struct platform_device *pdev) + &spacemit_i2s_component, + dai, 1); + if (ret) +- return dev_err_probe(i2s->dev, ret, "failed to register component"); ++ return ret; + + return devm_snd_dmaengine_pcm_register(&pdev->dev, &spacemit_dmaengine_pcm_config, 0); + } +diff --git a/sound/soc/spear/spdif_in.c b/sound/soc/spear/spdif_in.c +--- a/sound/soc/spear/spdif_in.c ++++ b/sound/soc/spear/spdif_in.c +@@ -55,7 +55,7 @@ static int spdif_in_dai_probe(struct snd_soc_dai *dai) + struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); + + host->dma_params_rx.filter_data = &host->dma_params; +- dai->capture_dma_data = &host->dma_params_rx; ++ snd_soc_dai_dma_data_set_capture(dai, &host->dma_params_rx); + + return 0; + } +@@ -150,12 +150,12 @@ static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, + + static const struct snd_soc_dai_ops spdif_in_dai_ops = { + .shutdown = spdif_in_shutdown, ++ .probe = spdif_in_dai_probe, + .trigger = spdif_in_trigger, + .hw_params = spdif_in_hw_params, + }; + + static struct snd_soc_dai_driver spdif_in_dai = { +- .probe = spdif_in_dai_probe, + .capture = { + .channels_min = 2, + .channels_max = 2, +@@ -218,10 +218,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + host->io_base = io_base; + host->irq = platform_get_irq(pdev, 0); +- if (host->irq < 0) { +- dev_warn(&pdev->dev, "failed to get IRQ: %d\n", host->irq); ++ if (host->irq < 0) + return host->irq; +- } + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) +@@ -243,10 +241,8 @@ static int spdif_in_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, + "spdif-in", host); +- if (ret) { +- dev_warn(&pdev->dev, "request_irq failed\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component, + &spdif_in_dai, 1); +diff --git a/sound/soc/spear/spdif_out.c b/sound/soc/spear/spdif_out.c +--- a/sound/soc/spear/spdif_out.c ++++ b/sound/soc/spear/spdif_out.c +@@ -249,6 +249,7 @@ static int spdif_soc_dai_probe(struct snd_soc_dai *dai) + } + + static const struct snd_soc_dai_ops spdif_out_dai_ops = { ++ .probe = spdif_soc_dai_probe, + .mute_stream = spdif_mute, + .startup = spdif_out_startup, + .shutdown = spdif_out_shutdown, +@@ -266,7 +267,6 @@ static struct snd_soc_dai_driver spdif_out_dai = { + SNDRV_PCM_RATE_192000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +- .probe = spdif_soc_dai_probe, + .ops = &spdif_out_dai_ops, + }; + +diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c +--- a/sound/soc/sprd/sprd-mcdt.c ++++ b/sound/soc/sprd/sprd-mcdt.c +@@ -931,10 +931,8 @@ static int sprd_mcdt_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, + 0, "sprd-mcdt", mcdt); +- if (ret) { +- dev_err(&pdev->dev, "Failed to request MCDT IRQ\n"); ++ if (ret) + return ret; +- } + + sprd_mcdt_init_chans(mcdt, res); + +diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c +--- a/sound/soc/sprd/sprd-pcm-dma.c ++++ b/sound/soc/sprd/sprd-pcm-dma.c +@@ -469,8 +469,6 @@ static int sprd_soc_platform_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &sprd_soc_component, + NULL, 0); +- if (ret) +- dev_err(&pdev->dev, "could not register platform:%d\n", ret); + + return ret; + } +diff --git a/sound/soc/starfive/jh7110_pwmdac.c b/sound/soc/starfive/jh7110_pwmdac.c +--- a/sound/soc/starfive/jh7110_pwmdac.c ++++ b/sound/soc/starfive/jh7110_pwmdac.c +@@ -477,25 +477,22 @@ static int jh7110_pwmdac_probe(struct platform_device *pdev) + &jh7110_pwmdac_component, + &jh7110_pwmdac_dai, 1); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register dai\n"); ++ return ret; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n"); ++ return ret; + + pm_runtime_enable(dev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_pwmdac_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_pwmdac_remove(struct platform_device *pdev) +diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c +--- a/sound/soc/starfive/jh7110_tdm.c ++++ b/sound/soc/starfive/jh7110_tdm.c +@@ -559,10 +559,8 @@ static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, + tdm->clks[5].id = "tdm"; + + ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(tdm->clks), tdm->clks); +- if (ret) { +- dev_err(&pdev->dev, "Failed to get tdm clocks\n"); ++ if (ret) + return ret; +- } + + tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev); + if (IS_ERR(tdm->resets)) { +@@ -589,42 +587,33 @@ static int jh7110_tdm_probe(struct platform_device *pdev) + tdm->dev = &pdev->dev; + + ret = jh7110_tdm_clk_reset_get(pdev, tdm); +- if (ret) { +- dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n"); ++ if (ret) + return ret; +- } + + jh7110_tdm_init_params(tdm); + + dev_set_drvdata(&pdev->dev, tdm); + ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component, + &jh7110_tdm_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register dai\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &jh7110_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +- if (ret) { +- dev_err(&pdev->dev, "Could not register pcm: %d\n", ret); ++ if (ret) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_tdm_runtime_resume(&pdev->dev); +- if (ret) +- goto err_pm_disable; ++ if (ret) { ++ pm_runtime_disable(&pdev->dev); ++ return ret; ++ } + } + + return 0; +- +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +- +- return ret; + } + + static void jh7110_tdm_dev_remove(struct platform_device *pdev) +diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c +--- a/sound/soc/stm/stm32_adfsdm.c ++++ b/sound/soc/stm/stm32_adfsdm.c +@@ -360,11 +360,8 @@ static int stm32_adfsdm_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_adfsdm_soc_platform, + NULL, 0); +- if (ret < 0) { +- dev_err(&pdev->dev, "%s: Failed to register PCM platform\n", +- __func__); ++ if (ret < 0) + return ret; +- } + + pm_runtime_enable(&pdev->dev); + +diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c +--- a/sound/soc/stm/stm32_i2s.c ++++ b/sound/soc/stm/stm32_i2s.c +@@ -1238,10 +1238,8 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); +- if (ret) { +- dev_err(&pdev->dev, "irq request returned %d\n", ret); ++ if (ret) + return ret; +- } + + /* Reset */ + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); +@@ -1295,7 +1293,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) + + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, &stm32_i2s_component, + i2s->dai_drv, 1); +diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c +--- a/sound/soc/stm/stm32_sai_sub.c ++++ b/sound/soc/stm/stm32_sai_sub.c +@@ -1696,19 +1696,15 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, + IRQF_SHARED, dev_name(&pdev->dev), sai); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + goto err_unprepare_pclk; +- } + + if (STM_SAI_PROTOCOL_IS_SPDIF(sai)) + conf = &stm32_sai_pcm_config_spdif; + + ret = snd_dmaengine_pcm_register(&pdev->dev, conf, 0); +- if (ret) { +- ret = dev_err_probe(&pdev->dev, ret, "Could not register pcm dma\n"); ++ if (ret) + goto err_unprepare_pclk; +- } + + ret = snd_soc_register_component(&pdev->dev, &stm32_component, + &sai->cpu_dai_drv, 1); +diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c +--- a/sound/soc/stm/stm32_spdifrx.c ++++ b/sound/soc/stm/stm32_spdifrx.c +@@ -970,10 +970,8 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + + ret = devm_request_irq(&pdev->dev, spdifrx->irq, stm32_spdifrx_isr, 0, + dev_name(&pdev->dev), spdifrx); +- if (ret) { +- dev_err(&pdev->dev, "IRQ request returned %d\n", ret); ++ if (ret) + return ret; +- } + + rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); + if (IS_ERR(rst)) +@@ -987,7 +985,7 @@ static int stm32_spdifrx_probe(struct platform_device *pdev) + pcm_config = &stm32_spdifrx_pcm_config; + ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0); + if (ret) +- return dev_err_probe(&pdev->dev, ret, "PCM DMA register error\n"); ++ return ret; + + ret = snd_soc_register_component(&pdev->dev, + &stm32_spdifrx_component, +diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c +--- a/sound/soc/sunxi/sun4i-codec.c ++++ b/sound/soc/sunxi/sun4i-codec.c +@@ -22,13 +22,13 @@ + #include + + #include ++#include ++#include + #include + #include + #include + #include + #include +-#include +-#include + + /* Codec DAC digital controls and FIFO registers */ + #define SUN4I_CODEC_DAC_DPC (0x00) +@@ -2317,67 +2317,54 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base, + quirks->regmap_config); +- if (IS_ERR(scodec->regmap)) { +- dev_err(&pdev->dev, "Failed to create our regmap\n"); +- return PTR_ERR(scodec->regmap); +- } ++ if (IS_ERR(scodec->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->regmap), ++ "Failed to create our regmap\n"); + + /* Get the clocks from the DT */ + scodec->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb"); +- if (IS_ERR(scodec->clk_apb)) { +- dev_err(&pdev->dev, "Failed to get the APB clock\n"); +- return PTR_ERR(scodec->clk_apb); +- } ++ if (IS_ERR(scodec->clk_apb)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_apb), ++ "Failed to get the APB clock\n"); + + scodec->clk_module = devm_clk_get(&pdev->dev, "codec"); +- if (IS_ERR(scodec->clk_module)) { +- dev_err(&pdev->dev, "Failed to get the module clock\n"); +- return PTR_ERR(scodec->clk_module); +- } ++ if (IS_ERR(scodec->clk_module)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->clk_module), ++ "Failed to get the module clock\n"); + + if (quirks->has_reset) { + scodec->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); +- if (IS_ERR(scodec->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(scodec->rst); +- } ++ if (IS_ERR(scodec->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->rst), ++ "Failed to get reset control\n"); + } + + scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", + GPIOD_OUT_LOW); +- if (IS_ERR(scodec->gpio_pa)) { +- ret = PTR_ERR(scodec->gpio_pa); +- dev_err_probe(&pdev->dev, ret, "Failed to get pa gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_pa)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_pa), ++ "Failed to get pa gpio\n"); ++ + + scodec->gpio_hp = devm_gpiod_get_optional(&pdev->dev, "hp-det", GPIOD_IN); +- if (IS_ERR(scodec->gpio_hp)) { +- ret = PTR_ERR(scodec->gpio_hp); +- dev_err_probe(&pdev->dev, ret, "Failed to get hp-det gpio\n"); +- return ret; +- } ++ if (IS_ERR(scodec->gpio_hp)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->gpio_hp), ++ "Failed to get hp-det gpio\n"); + + /* reg_field setup */ + scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_adc_fifoc); +- if (IS_ERR(scodec->reg_adc_fifoc)) { +- ret = PTR_ERR(scodec->reg_adc_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_adc_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_adc_fifoc), ++ "Failed to create regmap fields\n"); + + scodec->reg_dac_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_dac_fifoc); +- if (IS_ERR(scodec->reg_dac_fifoc)) { +- ret = PTR_ERR(scodec->reg_dac_fifoc); +- dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", +- ret); +- return ret; +- } ++ if (IS_ERR(scodec->reg_dac_fifoc)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(scodec->reg_dac_fifoc), ++ "Failed to create regmap fields\n"); + + /* DMA configuration for TX FIFO */ + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; +@@ -2394,39 +2381,29 @@ static int sun4i_codec_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, + &sun4i_codec_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our codec\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_codec_component, + &dummy_cpu_dai, 1); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register our DAI\n"); ++ if (ret) + return ret; +- } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); +- if (ret) { +- dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); ++ if (ret) + return ret; +- } + + card = quirks->create_card(&pdev->dev); +- if (IS_ERR(card)) { +- ret = PTR_ERR(card); +- dev_err(&pdev->dev, "Failed to create our card\n"); +- return ret; +- } ++ if (IS_ERR(card)) ++ return PTR_ERR(card); + + snd_soc_card_set_drvdata(card, scodec); + + ret = snd_soc_register_card(card); +- if (ret) { +- dev_err_probe(&pdev->dev, ret, "Failed to register our card\n"); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Failed to register our card\n"); + + return 0; + } +diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c +--- a/sound/soc/sunxi/sun4i-i2s.c ++++ b/sound/soc/sunxi/sun4i-i2s.c +@@ -1550,30 +1550,26 @@ static int sun4i_i2s_probe(struct platform_device *pdev) + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(i2s->bus_clk)) { +- dev_err(&pdev->dev, "Can't get our bus clock\n"); +- return PTR_ERR(i2s->bus_clk); +- } ++ if (IS_ERR(i2s->bus_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->bus_clk), ++ "Can't get our bus clock\n"); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + i2s->variant->sun4i_i2s_regmap); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "Regmap initialisation failed\n"); +- return PTR_ERR(i2s->regmap); +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), ++ "Regmap initialisation failed\n"); + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); +- if (IS_ERR(i2s->mod_clk)) { +- dev_err(&pdev->dev, "Can't get our mod clock\n"); +- return PTR_ERR(i2s->mod_clk); +- } ++ if (IS_ERR(i2s->mod_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->mod_clk), ++ "Can't get our mod clock\n"); + + if (i2s->variant->has_reset) { + i2s->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); +- if (IS_ERR(i2s->rst)) { +- dev_err(&pdev->dev, "Failed to get reset control\n"); +- return PTR_ERR(i2s->rst); +- } ++ if (IS_ERR(i2s->rst)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->rst), ++ "Failed to get reset control\n"); + } + + if (!IS_ERR(i2s->rst)) { +diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c +--- a/sound/soc/sunxi/sun4i-spdif.c ++++ b/sound/soc/sunxi/sun4i-spdif.c +@@ -684,26 +684,23 @@ static int sun4i_spdif_probe(struct platform_device *pdev) + + host->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sun4i_spdif_regmap_config); +- if (IS_ERR(host->regmap)) { +- dev_err(&pdev->dev, "failed to initialise regmap.\n"); +- return PTR_ERR(host->regmap); +- } ++ if (IS_ERR(host->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->regmap), ++ "failed to initialise regmap.\n"); + + /* Clocks */ + host->apb_clk = devm_clk_get(&pdev->dev, "apb"); +- if (IS_ERR(host->apb_clk)) { +- dev_err(&pdev->dev, "failed to get a apb clock.\n"); +- return PTR_ERR(host->apb_clk); +- } ++ if (IS_ERR(host->apb_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->apb_clk), ++ "failed to get a apb clock.\n"); + + if (quirks->tx_clk_name) + tx_clk_name = quirks->tx_clk_name; + host->spdif_clk = devm_clk_get(&pdev->dev, tx_clk_name); +- if (IS_ERR(host->spdif_clk)) { +- dev_err(&pdev->dev, "failed to get the \"%s\" clock.\n", +- tx_clk_name); +- return PTR_ERR(host->spdif_clk); +- } ++ if (IS_ERR(host->spdif_clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(host->spdif_clk), ++ "failed to get the \"%s\" clock.\n", ++ tx_clk_name); + + host->dma_params_tx.addr = res->start + quirks->reg_dac_txdata; + host->dma_params_tx.maxburst = 8; +diff --git a/sound/soc/sunxi/sun50i-codec-analog.c b/sound/soc/sunxi/sun50i-codec-analog.c +--- a/sound/soc/sunxi/sun50i-codec-analog.c ++++ b/sound/soc/sunxi/sun50i-codec-analog.c +@@ -551,16 +551,13 @@ static int sun50i_codec_analog_probe(struct platform_device *pdev) + bool enable; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + enable = device_property_read_bool(&pdev->dev, + "allwinner,internal-bias-resistor"); +diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c +--- a/sound/soc/sunxi/sun8i-codec-analog.c ++++ b/sound/soc/sunxi/sun8i-codec-analog.c +@@ -822,16 +822,13 @@ static int sun8i_codec_analog_probe(struct platform_device *pdev) + void __iomem *base; + + base = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(base)) { +- dev_err(&pdev->dev, "Failed to map the registers\n"); ++ if (IS_ERR(base)) + return PTR_ERR(base); +- } + + regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); +- if (IS_ERR(regmap)) { +- dev_err(&pdev->dev, "Failed to create regmap\n"); +- return PTR_ERR(regmap); +- } ++ if (IS_ERR(regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(regmap), ++ "Failed to create regmap\n"); + + return devm_snd_soc_register_component(&pdev->dev, + &sun8i_codec_analog_cmpnt_drv, +diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c +--- a/sound/soc/tegra/tegra20_das.c ++++ b/sound/soc/tegra/tegra20_das.c +@@ -167,10 +167,9 @@ static int tegra20_das_probe(struct platform_device *pdev) + + das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &tegra20_das_regmap_config); +- if (IS_ERR(das->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- return PTR_ERR(das->regmap); +- } ++ if (IS_ERR(das->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(das->regmap), ++ "regmap init failed\n"); + + tegra20_das_connect_dap_to_dac(das, TEGRA20_DAS_DAP_ID_1, + TEGRA20_DAS_DAP_SEL_DAC1); +diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c +--- a/sound/soc/tegra/tegra210_adx.c ++++ b/sound/soc/tegra/tegra210_adx.c +@@ -496,16 +496,12 @@ static struct snd_kcontrol_new tegra264_adx_controls[] = { + static int tegra210_adx_component_probe(struct snd_soc_component *component) + { + struct tegra210_adx *adx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (adx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, adx->soc_data->controls, +- adx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add ADX controls, err: %d\n", err); +- } ++ if (adx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, adx->soc_data->controls, ++ adx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_adx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_amx.c b/sound/soc/tegra/tegra210_amx.c +--- a/sound/soc/tegra/tegra210_amx.c ++++ b/sound/soc/tegra/tegra210_amx.c +@@ -504,16 +504,12 @@ static struct snd_kcontrol_new tegra264_amx_controls[] = { + static int tegra210_amx_component_probe(struct snd_soc_component *component) + { + struct tegra210_amx *amx = snd_soc_component_get_drvdata(component); +- int err = 0; + +- if (amx->soc_data->num_controls) { +- err = snd_soc_add_component_controls(component, amx->soc_data->controls, +- amx->soc_data->num_controls); +- if (err) +- dev_err(component->dev, "can't add AMX controls, err: %d\n", err); +- } ++ if (amx->soc_data->num_controls) ++ return snd_soc_add_component_controls(component, amx->soc_data->controls, ++ amx->soc_data->num_controls); + +- return err; ++ return 0; + } + + static const struct snd_soc_component_driver tegra210_amx_cmpnt = { +diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c +--- a/sound/soc/tegra/tegra210_i2s.c ++++ b/sound/soc/tegra/tegra210_i2s.c +@@ -1090,13 +1090,13 @@ static int tegra210_i2s_probe(struct platform_device *pdev) + "can't retrieve I2S bit clock\n"); + + /* +- * Not an error, as this clock is needed only when some other I/O +- * requires input clock from current I2S instance, which is +- * configurable from DT. ++ * This clock is optional and is only needed when another I/O uses ++ * the current I2S instance as its input clock, as configured in DT. + */ +- i2s->clk_sync_input = devm_clk_get(dev, "sync_input"); ++ i2s->clk_sync_input = devm_clk_get_optional(dev, "sync_input"); + if (IS_ERR(i2s->clk_sync_input)) +- dev_dbg(dev, "can't retrieve I2S sync input clock\n"); ++ return dev_err_probe(dev, PTR_ERR(i2s->clk_sync_input), ++ "can't retrieve I2S sync input clock\n"); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) +diff --git a/sound/soc/ti/ams-delta.c b/sound/soc/ti/ams-delta.c +--- a/sound/soc/ti/ams-delta.c ++++ b/sound/soc/ti/ams-delta.c +@@ -562,21 +562,21 @@ static int ams_delta_probe(struct platform_device *pdev) + + card->dev = &pdev->dev; + +- handset_mute = devm_gpiod_get(card->dev, "handset_mute", ++ handset_mute = devm_gpiod_get(&pdev->dev, "handset_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handset_mute)) + return PTR_ERR(handset_mute); + +- handsfree_mute = devm_gpiod_get(card->dev, "handsfree_mute", ++ handsfree_mute = devm_gpiod_get(&pdev->dev, "handsfree_mute", + GPIOD_OUT_HIGH); + if (IS_ERR(handsfree_mute)) + return PTR_ERR(handsfree_mute); + + ret = snd_soc_register_card(card); + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + card->dev = NULL; +- return ret; ++ return dev_err_probe(&pdev->dev, ret, ++ "snd_soc_register_card() failed\n"); + } + return 0; + } +diff --git a/sound/soc/ti/davinci-evm.c b/sound/soc/ti/davinci-evm.c +--- a/sound/soc/ti/davinci-evm.c ++++ b/sound/soc/ti/davinci-evm.c +@@ -245,9 +245,8 @@ static int davinci_evm_probe(struct platform_device *pdev) + + snd_soc_card_set_drvdata(&evm_soc_card, drvdata); + ret = devm_snd_soc_register_card(&pdev->dev, &evm_soc_card); +- + if (ret) { +- dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); ++ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n"); + goto err_put; + } + +diff --git a/sound/soc/ti/j721e-evm.c b/sound/soc/ti/j721e-evm.c +--- a/sound/soc/ti/j721e-evm.c ++++ b/sound/soc/ti/j721e-evm.c +@@ -870,10 +870,9 @@ static int j721e_soc_probe(struct platform_device *pdev) + card->num_dapm_routes = ARRAY_SIZE(j721e_cpb_dapm_routes); + card->fully_routed = 1; + +- if (snd_soc_of_parse_card_name(card, "model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "model"); ++ if (ret) ++ return ret; + + link_cnt = 0; + conf_cnt = 0; +diff --git a/sound/soc/ti/omap-abe-twl6040.c b/sound/soc/ti/omap-abe-twl6040.c +--- a/sound/soc/ti/omap-abe-twl6040.c ++++ b/sound/soc/ti/omap-abe-twl6040.c +@@ -234,16 +234,13 @@ static int omap_abe_probe(struct platform_device *pdev) + card->dapm_routes = audio_map; + card->num_dapm_routes = ARRAY_SIZE(audio_map); + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + + ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); +- if (ret) { +- dev_err(&pdev->dev, "Error while parsing DAPM routing\n"); ++ if (ret) + return ret; +- } + + dai_node = of_parse_phandle(node, "ti,mcpdm", 0); + if (!dai_node) { +@@ -299,8 +296,8 @@ static int omap_abe_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); ++ dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return ret; + } +diff --git a/sound/soc/ti/omap-dmic.c b/sound/soc/ti/omap-dmic.c +--- a/sound/soc/ti/omap-dmic.c ++++ b/sound/soc/ti/omap-dmic.c +@@ -465,10 +465,9 @@ static int asoc_dmic_probe(struct platform_device *pdev) + mutex_init(&dmic->mutex); + + dmic->fclk = devm_clk_get(dmic->dev, "fck"); +- if (IS_ERR(dmic->fclk)) { +- dev_err(dmic->dev, "can't get fck\n"); +- return -ENODEV; +- } ++ if (IS_ERR(dmic->fclk)) ++ return dev_err_probe(dmic->dev, PTR_ERR(dmic->fclk), ++ "can't get fck\n"); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (!res) { +diff --git a/sound/soc/ti/omap-hdmi.c b/sound/soc/ti/omap-hdmi.c +--- a/sound/soc/ti/omap-hdmi.c ++++ b/sound/soc/ti/omap-hdmi.c +@@ -24,8 +24,6 @@ + #define DRV_NAME "omap-hdmi-audio" + + struct hdmi_audio_data { +- struct snd_soc_card *card; +- + const struct omap_hdmi_audio_ops *ops; + struct device *dssdev; + struct snd_dmaengine_dai_dma_data dma_data; +@@ -375,12 +373,9 @@ static int omap_hdmi_audio_probe(struct platform_device *pdev) + card->dev = dev; + + ret = devm_snd_soc_register_card(dev, card); +- if (ret) { +- dev_err(dev, "snd_soc_register_card failed (%d)\n", ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "snd_soc_register_card() failed\n"); + +- ad->card = card; + snd_soc_card_set_drvdata(card, ad); + + dev_set_drvdata(dev, ad); +diff --git a/sound/soc/ti/omap-twl4030.c b/sound/soc/ti/omap-twl4030.c +--- a/sound/soc/ti/omap-twl4030.c ++++ b/sound/soc/ti/omap-twl4030.c +@@ -17,7 +17,6 @@ + */ + + #include +-#include + #include + #include + +@@ -29,6 +28,7 @@ + #include "omap-mcbsp.h" + + struct omap_twl4030 { ++ struct snd_soc_jack_gpio hs_jack_gpio; + struct snd_soc_jack hs_jack; + }; + +@@ -124,38 +124,18 @@ static struct snd_soc_jack_pin hs_jack_pins[] = { + }, + }; + +-/* Headset jack detection gpios */ +-static struct snd_soc_jack_gpio hs_jack_gpios[] = { +- { +- .name = "ti,jack-det", +- .report = SND_JACK_HEADSET, +- .debounce_time = 200, +- }, +-}; +- +-static inline void twl4030_disconnect_pin(struct snd_soc_dapm_context *dapm, +- int connected, char *pin) +-{ +- if (!connected) +- snd_soc_dapm_disable_pin(dapm, pin); +-} +- + static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + { + struct snd_soc_card *card = rtd->card; +- struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); +- struct omap_tw4030_pdata *pdata = dev_get_platdata(card->dev); ++ + struct omap_twl4030 *priv = snd_soc_card_get_drvdata(card); +- int ret = 0; ++ int ret; + + /* + * This is a bit of a hack, but the GPIO is optional so we + * only want to add the jack detection if the GPIO is there. + */ + if (of_property_present(card->dev->of_node, "ti,jack-det-gpio")) { +- hs_jack_gpios[0].gpiod_dev = card->dev; +- hs_jack_gpios[0].idx = 0; +- + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET, + &priv->hs_jack, hs_jack_pins, +@@ -163,36 +143,19 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd) + if (ret) + return ret; + +- ret = snd_soc_jack_add_gpios(&priv->hs_jack, +- ARRAY_SIZE(hs_jack_gpios), +- hs_jack_gpios); ++ priv->hs_jack_gpio.name = "ti,jack-det"; ++ priv->hs_jack_gpio.report = SND_JACK_HEADSET; ++ priv->hs_jack_gpio.debounce_time = 200; ++ priv->hs_jack_gpio.gpiod_dev = card->dev; ++ priv->hs_jack_gpio.idx = 0; ++ ++ ret = snd_soc_jack_add_gpios(&priv->hs_jack, 1, ++ &priv->hs_jack_gpio); + if (ret) + return ret; + } + +- /* +- * NULL pdata means we booted with DT. In this case the routing is +- * provided and the card is fully routed, no need to mark pins. +- */ +- if (!pdata || !pdata->custom_routing) +- return ret; +- +- /* Disable not connected paths if not used */ +- twl4030_disconnect_pin(dapm, pdata->has_ear, "Earpiece Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hf, "Handsfree Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_hs, "Headset Stereophone"); +- twl4030_disconnect_pin(dapm, pdata->has_predriv, "Ext Spk"); +- twl4030_disconnect_pin(dapm, pdata->has_carkit, "Carkit Spk"); +- +- twl4030_disconnect_pin(dapm, pdata->has_mainmic, "Main Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_submic, "Sub Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_hsmic, "Headset Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_carkitmic, "Carkit Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic0, "Digital0 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic"); +- twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In"); +- +- return ret; ++ return 0; + } + + /* Digital audio interface glue - connects codec <--> CPU */ +@@ -237,11 +200,15 @@ static struct snd_soc_card omap_twl4030_card = { + + static int omap_twl4030_probe(struct platform_device *pdev) + { +- struct omap_tw4030_pdata *pdata = dev_get_platdata(&pdev->dev); +- struct device_node *node = pdev->dev.of_node; + struct snd_soc_card *card = &omap_twl4030_card; ++ struct device_node *node, *dai_node; + struct omap_twl4030 *priv; +- int ret = 0; ++ struct property *prop; ++ int ret; ++ ++ node = pdev->dev.of_node; ++ if (!node) ++ return -ENODEV; + + card->dev = &pdev->dev; + +@@ -249,69 +216,52 @@ static int omap_twl4030_probe(struct platform_device *pdev) + if (priv == NULL) + return -ENOMEM; + +- if (node) { +- struct device_node *dai_node; +- struct property *prop; ++ ret = snd_soc_of_parse_card_name(card, "ti,model"); ++ if (ret) ++ return ret; + +- if (snd_soc_of_parse_card_name(card, "ti,model")) { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- dai_node = of_parse_phandle(node, "ti,mcbsp", 0); +- if (!dai_node) { +- dev_err(&pdev->dev, "McBSP node is not provided\n"); +- return -EINVAL; +- } +- omap_twl4030_dai_links[0].cpus->dai_name = NULL; +- omap_twl4030_dai_links[0].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[0].platforms->name = NULL; +- omap_twl4030_dai_links[0].platforms->of_node = dai_node; +- +- dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); +- if (!dai_node) { +- card->num_links = 1; +- } else { +- omap_twl4030_dai_links[1].cpus->dai_name = NULL; +- omap_twl4030_dai_links[1].cpus->of_node = dai_node; +- +- omap_twl4030_dai_links[1].platforms->name = NULL; +- omap_twl4030_dai_links[1].platforms->of_node = dai_node; +- } +- +- /* Optional: audio routing can be provided */ +- prop = of_find_property(node, "ti,audio-routing", NULL); +- if (prop) { +- ret = snd_soc_of_parse_audio_routing(card, +- "ti,audio-routing"); +- if (ret) +- return ret; +- +- card->fully_routed = 1; +- } +- } else if (pdata) { +- if (pdata->card_name) { +- card->name = pdata->card_name; +- } else { +- dev_err(&pdev->dev, "Card name is not provided\n"); +- return -ENODEV; +- } +- +- if (!pdata->voice_connected) +- card->num_links = 1; +- } else { +- dev_err(&pdev->dev, "Missing pdata\n"); ++ if (!card->name) { ++ dev_err(&pdev->dev, "Card name is not provided\n"); + return -ENODEV; + } + ++ dai_node = of_parse_phandle(node, "ti,mcbsp", 0); ++ if (!dai_node) { ++ dev_err(&pdev->dev, "McBSP node is not provided\n"); ++ return -EINVAL; ++ } ++ omap_twl4030_dai_links[0].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[0].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[0].platforms->name = NULL; ++ omap_twl4030_dai_links[0].platforms->of_node = dai_node; ++ ++ dai_node = of_parse_phandle(node, "ti,mcbsp-voice", 0); ++ if (!dai_node) { ++ card->num_links = 1; ++ } else { ++ omap_twl4030_dai_links[1].cpus->dai_name = NULL; ++ omap_twl4030_dai_links[1].cpus->of_node = dai_node; ++ ++ omap_twl4030_dai_links[1].platforms->name = NULL; ++ omap_twl4030_dai_links[1].platforms->of_node = dai_node; ++ } ++ ++ /* Optional: audio routing can be provided */ ++ prop = of_find_property(node, "ti,audio-routing", NULL); ++ if (prop) { ++ ret = snd_soc_of_parse_audio_routing(card, "ti,audio-routing"); ++ if (ret) ++ return ret; ++ ++ card->fully_routed = 1; ++ } ++ + snd_soc_card_set_drvdata(card, priv); + ret = devm_snd_soc_register_card(&pdev->dev, card); +- if (ret) { +- dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n", +- ret); +- return ret; +- } ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "devm_snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/ti/rx51.c b/sound/soc/ti/rx51.c +--- a/sound/soc/ti/rx51.c ++++ b/sound/soc/ti/rx51.c +@@ -358,21 +358,22 @@ static struct snd_soc_card rx51_sound_card = { + static int rx51_soc_probe(struct platform_device *pdev) + { + struct rx51_audio_pdata *pdata; +- struct device_node *np = pdev->dev.of_node; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; + struct snd_soc_card *card = &rx51_sound_card; + int err; + + if (!of_machine_is_compatible("nokia,omap3-n900")) + return -ENODEV; + +- card->dev = &pdev->dev; ++ card->dev = dev; + + if (np) { + struct device_node *dai_node; + + dai_node = of_parse_phandle(np, "nokia,cpu-dai", 0); + if (!dai_node) { +- dev_err(card->dev, "McBSP node is not provided\n"); ++ dev_err(dev, "McBSP node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].cpus->dai_name = NULL; +@@ -382,7 +383,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 0); + if (!dai_node) { +- dev_err(card->dev, "Codec node is not provided\n"); ++ dev_err(dev, "Codec node is not provided\n"); + return -EINVAL; + } + rx51_dai[0].codecs->name = NULL; +@@ -390,7 +391,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,audio-codec", 1); + if (!dai_node) { +- dev_err(card->dev, "Auxiliary Codec node is not provided\n"); ++ dev_err(dev, "Auxiliary Codec node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[0].dlc.name = NULL; +@@ -400,7 +401,7 @@ static int rx51_soc_probe(struct platform_device *pdev) + + dai_node = of_parse_phandle(np, "nokia,headphone-amplifier", 0); + if (!dai_node) { +- dev_err(card->dev, "Headphone amplifier node is not provided\n"); ++ dev_err(dev, "Headphone amplifier node is not provided\n"); + return -EINVAL; + } + rx51_aux_dev[1].dlc.name = NULL; +@@ -409,40 +410,36 @@ static int rx51_soc_probe(struct platform_device *pdev) + rx51_codec_conf[1].dlc.of_node = dai_node; + } + +- pdata = devm_kzalloc(card->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (pdata == NULL) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, pdata); + +- pdata->tvout_selection_gpio = devm_gpiod_get(card->dev, ++ pdata->tvout_selection_gpio = devm_gpiod_get(dev, + "tvout-selection", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->tvout_selection_gpio)) { +- dev_err(card->dev, "could not get tvout selection gpio\n"); +- return PTR_ERR(pdata->tvout_selection_gpio); +- } ++ if (IS_ERR(pdata->tvout_selection_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->tvout_selection_gpio), ++ "could not get tvout selection gpio\n"); + +- pdata->eci_sw_gpio = devm_gpiod_get(card->dev, "eci-switch", ++ pdata->eci_sw_gpio = devm_gpiod_get(dev, "eci-switch", + GPIOD_OUT_HIGH); +- if (IS_ERR(pdata->eci_sw_gpio)) { +- dev_err(card->dev, "could not get eci switch gpio\n"); +- return PTR_ERR(pdata->eci_sw_gpio); +- } ++ if (IS_ERR(pdata->eci_sw_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->eci_sw_gpio), ++ "could not get eci switch gpio\n"); + +- pdata->speaker_amp_gpio = devm_gpiod_get(card->dev, ++ pdata->speaker_amp_gpio = devm_gpiod_get(dev, + "speaker-amplifier", + GPIOD_OUT_LOW); +- if (IS_ERR(pdata->speaker_amp_gpio)) { +- dev_err(card->dev, "could not get speaker enable gpio\n"); +- return PTR_ERR(pdata->speaker_amp_gpio); +- } ++ if (IS_ERR(pdata->speaker_amp_gpio)) ++ return dev_err_probe(dev, PTR_ERR(pdata->speaker_amp_gpio), ++ "could not get speaker enable gpio\n"); + +- err = devm_snd_soc_register_card(card->dev, card); +- if (err) { +- dev_err(card->dev, "snd_soc_register_card failed (%d)\n", err); +- return err; +- } ++ err = devm_snd_soc_register_card(dev, card); ++ if (err) ++ return dev_err_probe(dev, err, ++ "snd_soc_register_card() failed\n"); + + return 0; + } +diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c +--- a/sound/soc/uniphier/aio-cpu.c ++++ b/sound/soc/uniphier/aio-cpu.c +@@ -795,16 +795,12 @@ int uniphier_aio_probe(struct platform_device *pdev) + ret = devm_snd_soc_register_component(dev, &uniphier_aio_component, + chip->chip_spec->dais, + chip->chip_spec->num_dais); +- if (ret) { +- dev_err(dev, "Register component failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + ret = uniphier_aiodma_soc_register_platform(pdev); +- if (ret) { +- dev_err(dev, "Register platform failed.\n"); ++ if (ret) + goto err_out_reset; +- } + + return 0; + +diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h +--- a/sound/soc/uniphier/aio.h ++++ b/sound/soc/uniphier/aio.h +@@ -156,7 +156,7 @@ struct uniphier_aio_selector { + int hw; + }; + +-/** ++/* + * 'SoftWare MAPping' setting of UniPhier AIO registers. + * + * We have to setup 'virtual' register maps to access 'real' registers of AIO. +diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c +--- a/sound/soc/ux500/mop500.c ++++ b/sound/soc/ux500/mop500.c +@@ -68,10 +68,11 @@ static void mop500_of_node_put(void) + of_node_put(mop500_dai_links[0].codecs->of_node); + } + +-static int mop500_of_probe(struct platform_device *pdev, +- struct device_node *np) ++static int mop500_of_probe(struct snd_soc_card *card) + { ++ struct device *dev = card->dev; + struct device_node *codec_np, *msp_np[2]; ++ struct device_node *np = dev->of_node; + int i; + + msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); +@@ -79,7 +80,7 @@ static int mop500_of_probe(struct platform_device *pdev, + codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); + + if (!(msp_np[0] && msp_np[1] && codec_np)) { +- dev_err(&pdev->dev, "Phandle missing or invalid\n"); ++ dev_err(dev, "Phandle missing or invalid\n"); + for (i = 0; i < 2; i++) + of_node_put(msp_np[i]); + of_node_put(codec_np); +@@ -95,21 +96,20 @@ static int mop500_of_probe(struct platform_device *pdev, + mop500_dai_links[i].codecs->name = NULL; + } + +- snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name"); ++ snd_soc_of_parse_card_name(card, "stericsson,card-name"); + + return 0; + } + + static int mop500_probe(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; + int ret; + + dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); + + mop500_card.dev = &pdev->dev; + +- ret = mop500_of_probe(pdev, np); ++ ret = mop500_of_probe(&mop500_card); + if (ret) + return ret; + +diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c +--- a/sound/soc/ux500/mop500_ab8500.c ++++ b/sound/soc/ux500/mop500_ab8500.c +@@ -433,5 +433,5 @@ void mop500_ab8500_remove(struct snd_soc_card *card) + clk_put(drvdata->clk_ptr_ulpclk); + clk_put(drvdata->clk_ptr_intclk); + +- snd_soc_card_set_drvdata(card, drvdata); ++ snd_soc_card_set_drvdata(card, NULL); + } +diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c +--- a/sound/soc/xilinx/xlnx_formatter_pcm.c ++++ b/sound/soc/xilinx/xlnx_formatter_pcm.c +@@ -9,8 +9,6 @@ + #include + #include + #include +-#include +-#include + #include + + #include +diff --git a/sound/soc/xilinx/xlnx_i2s.c b/sound/soc/xilinx/xlnx_i2s.c +--- a/sound/soc/xilinx/xlnx_i2s.c ++++ b/sound/soc/xilinx/xlnx_i2s.c +@@ -9,9 +9,8 @@ + + #include + #include +-#include +-#include + #include ++#include + #include + #include + +@@ -174,7 +173,6 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + int ret; + u32 format; + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) +@@ -184,18 +182,16 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + if (IS_ERR(drv_data->base)) + return PTR_ERR(drv_data->base); + +- ret = of_property_read_u32(node, "xlnx,num-channels", &drv_data->channels); +- if (ret < 0) { +- dev_err(dev, "cannot get supported channels\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,num-channels", &drv_data->channels); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get supported channels\n"); ++ + drv_data->channels *= 2; + +- ret = of_property_read_u32(node, "xlnx,dwidth", &drv_data->data_width); +- if (ret < 0) { +- dev_err(dev, "cannot get data width\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,dwidth", &drv_data->data_width); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get data width\n"); ++ + switch (drv_data->data_width) { + case 16: + format = SNDRV_PCM_FMTBIT_S16_LE; +@@ -207,7 +203,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + return -EINVAL; + } + +- if (of_device_is_compatible(node, "xlnx,i2s-transmitter-1.0")) { ++ if (device_is_compatible(dev, "xlnx,i2s-transmitter-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_playback"; + drv_data->dai_drv.playback.stream_name = "Playback"; + drv_data->dai_drv.playback.formats = format; +@@ -215,7 +211,7 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + drv_data->dai_drv.playback.channels_max = drv_data->channels; + drv_data->dai_drv.playback.rates = SNDRV_PCM_RATE_8000_192000; + drv_data->dai_drv.ops = &xlnx_i2s_dai_ops; +- } else if (of_device_is_compatible(node, "xlnx,i2s-receiver-1.0")) { ++ } else if (device_is_compatible(dev, "xlnx,i2s-receiver-1.0")) { + drv_data->dai_drv.name = "xlnx_i2s_capture"; + drv_data->dai_drv.capture.stream_name = "Capture"; + drv_data->dai_drv.capture.formats = format; +@@ -233,10 +229,8 @@ static int xlnx_i2s_probe(struct platform_device *pdev) + + ret = devm_snd_soc_register_component(&pdev->dev, &xlnx_i2s_component, + &drv_data->dai_drv, 1); +- if (ret) { +- dev_err(&pdev->dev, "i2s component registration failed\n"); ++ if (ret) + return ret; +- } + + dev_info(&pdev->dev, "%s DAI registered\n", drv_data->dai_drv.name); + +diff --git a/sound/soc/xilinx/xlnx_spdif.c b/sound/soc/xilinx/xlnx_spdif.c +--- a/sound/soc/xilinx/xlnx_spdif.c ++++ b/sound/soc/xilinx/xlnx_spdif.c +@@ -10,8 +10,7 @@ + #include + #include + #include +-#include +-#include ++#include + #include + #include + #include +@@ -242,28 +241,24 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + struct spdif_dev_data *ctx; + + struct device *dev = &pdev->dev; +- struct device_node *node = dev->of_node; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->axi_clk = devm_clk_get_enabled(dev, "s_axi_aclk"); +- if (IS_ERR(ctx->axi_clk)) { +- ret = PTR_ERR(ctx->axi_clk); +- dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret); +- return ret; +- } ++ if (IS_ERR(ctx->axi_clk)) ++ return dev_err_probe(dev, PTR_ERR(ctx->axi_clk), ++ "failed to get s_axi_aclk\n"); + + ctx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctx->base)) + return PTR_ERR(ctx->base); + +- ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode); +- if (ret < 0) { +- dev_err(dev, "cannot get SPDIF mode\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,spdif-mode", &ctx->mode); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get SPDIF mode\n"); ++ + if (ctx->mode) { + dai_drv = &xlnx_spdif_tx_dai; + } else { +@@ -274,29 +269,23 @@ static int xlnx_spdif_probe(struct platform_device *pdev) + ret = devm_request_irq(dev, ret, + xlnx_spdifrx_irq_handler, + 0, "XLNX_SPDIF_RX", ctx); +- if (ret) { +- dev_err(dev, "spdif rx irq request failed\n"); +- return -ENODEV; +- } ++ if (ret) ++ return ret; + + init_waitqueue_head(&ctx->chsts_q); + dai_drv = &xlnx_spdif_rx_dai; + } + +- ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk); +- if (ret < 0) { +- dev_err(dev, "cannot get aud_clk_i value\n"); +- return ret; +- } ++ ret = device_property_read_u32(dev, "xlnx,aud_clk_i", &ctx->aclk); ++ if (ret < 0) ++ return dev_err_probe(dev, ret, "cannot get aud_clk_i value\n"); + + dev_set_drvdata(dev, ctx); + + ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component, + dai_drv, 1); +- if (ret) { +- dev_err(dev, "SPDIF component registration failed\n"); ++ if (ret) + return ret; +- } + + writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG); + dev_info(dev, "%s DAI registered\n", dai_drv->name); +diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c +--- a/sound/soc/xtensa/xtfpga-i2s.c ++++ b/sound/soc/xtensa/xtfpga-i2s.c +@@ -533,34 +533,25 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + int err, irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); +- if (!i2s) { +- err = -ENOMEM; +- goto err; +- } ++ if (!i2s) ++ return -ENOMEM; ++ + platform_set_drvdata(pdev, i2s); + i2s->dev = &pdev->dev; + dev_dbg(&pdev->dev, "dev: %p, i2s: %p\n", &pdev->dev, i2s); + + i2s->regs = devm_platform_ioremap_resource(pdev, 0); +- if (IS_ERR(i2s->regs)) { +- err = PTR_ERR(i2s->regs); +- goto err; +- } ++ if (IS_ERR(i2s->regs)) ++ return PTR_ERR(i2s->regs); + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->regs, + &xtfpga_i2s_regmap_config); +- if (IS_ERR(i2s->regmap)) { +- dev_err(&pdev->dev, "regmap init failed\n"); +- err = PTR_ERR(i2s->regmap); +- goto err; +- } ++ if (IS_ERR(i2s->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "regmap init failed\n"); + + i2s->clk = devm_clk_get(&pdev->dev, NULL); +- if (IS_ERR(i2s->clk)) { +- dev_err(&pdev->dev, "couldn't get clock\n"); +- err = PTR_ERR(i2s->clk); +- goto err; +- } ++ if (IS_ERR(i2s->clk)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(i2s->clk), "couldn't get clock\n"); + + regmap_write(i2s->regmap, XTFPGA_I2S_CONFIG, + (0x1 << XTFPGA_I2S_CONFIG_CHANNEL_BASE)); +@@ -568,41 +559,34 @@ static int xtfpga_i2s_probe(struct platform_device *pdev) + regmap_write(i2s->regmap, XTFPGA_I2S_INT_MASK, XTFPGA_I2S_INT_UNDERRUN); + + irq = platform_get_irq(pdev, 0); +- if (irq < 0) { +- err = irq; +- goto err; +- } ++ if (irq < 0) ++ return irq; ++ + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + xtfpga_i2s_threaded_irq_handler, + IRQF_SHARED | IRQF_ONESHOT, + pdev->name, i2s); +- if (err < 0) { +- dev_err(&pdev->dev, "request_irq failed\n"); +- goto err; +- } ++ if (err < 0) ++ return err; + + err = devm_snd_soc_register_component(&pdev->dev, + &xtfpga_i2s_component, + xtfpga_i2s_dai, + ARRAY_SIZE(xtfpga_i2s_dai)); +- if (err < 0) { +- dev_err(&pdev->dev, "couldn't register component\n"); +- goto err; +- } ++ if (err < 0) ++ return err; ++ + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + err = xtfpga_i2s_runtime_resume(&pdev->dev); +- if (err) +- goto err_pm_disable; ++ if (err) { ++ pm_runtime_disable(&pdev->dev); ++ return err; ++ } + } +- return 0; + +-err_pm_disable: +- pm_runtime_disable(&pdev->dev); +-err: +- dev_err(&pdev->dev, "%s: err = %d\n", __func__, err); +- return err; ++ return 0; + } + + static void xtfpga_i2s_remove(struct platform_device *pdev) diff --git a/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch.sig new file mode 100644 index 0000000..943e088 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0511-asoc-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch new file mode 100644 index 0000000..7de337b --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch @@ -0,0 +1,661 @@ +diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h +--- a/include/sound/rawmidi.h ++++ b/include/sound/rawmidi.h +@@ -179,7 +179,8 @@ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info + int snd_rawmidi_kernel_open_nested(struct snd_rawmidi *rmidi, int subdevice, + int mode, struct snd_rawmidi_file *rfile, + int depth); +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth); + int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params); + int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, +@@ -201,6 +202,11 @@ static inline int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, + return snd_rawmidi_kernel_open_nested(rmidi, subdevice, mode, rfile, 0); + } + ++static inline int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++{ ++ return snd_rawmidi_kernel_release_nested(rfile, 0); ++} ++ + /* set up the tied devices */ + static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1, + struct snd_rawmidi *r2) +diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c +--- a/sound/aoa/soundbus/i2sbus/core.c ++++ b/sound/aoa/soundbus/i2sbus/core.c +@@ -240,6 +240,8 @@ static int i2sbus_add_dev(struct macio_dev *macio, + } + for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) { + int irq = irq_of_parse_and_map(np, i); ++ if (!irq) ++ goto err; + if (request_irq(irq, ints[i], 0, dev->rnames[i], dev)) + goto err; + dev->interrupts[i] = irq; +diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c +--- a/sound/core/rawmidi.c ++++ b/sound/core/rawmidi.c +@@ -571,7 +571,6 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; +- guard(mutex)(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; +@@ -585,7 +584,8 @@ static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) + } + + /* called from sound/core/seq/seq_midi.c */ +-int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) ++int snd_rawmidi_kernel_release_nested(struct snd_rawmidi_file *rfile, ++ int depth) + { + struct snd_rawmidi *rmidi; + +@@ -593,11 +593,13 @@ int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) + return -ENXIO; + + rmidi = rfile->rmidi; ++ mutex_lock_nested(&rmidi->open_mutex, depth); + rawmidi_release_priv(rfile); ++ mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + return 0; + } +-EXPORT_SYMBOL(snd_rawmidi_kernel_release); ++EXPORT_SYMBOL(snd_rawmidi_kernel_release_nested); + + static int snd_rawmidi_release(struct inode *inode, struct file *file) + { +@@ -607,7 +609,8 @@ static int snd_rawmidi_release(struct inode *inode, struct file *file) + + rfile = file->private_data; + rmidi = rfile->rmidi; +- rawmidi_release_priv(rfile); ++ scoped_guard(mutex, &rmidi->open_mutex) ++ rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); +diff --git a/sound/core/ump.c b/sound/core/ump.c +--- a/sound/core/ump.c ++++ b/sound/core/ump.c +@@ -1184,7 +1184,8 @@ static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) + ump->legacy_substreams[dir][group] = NULL; + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) +- snd_rawmidi_kernel_release(&ump->legacy_out_rfile); ++ snd_rawmidi_kernel_release_nested(&ump->legacy_out_rfile, ++ SINGLE_DEPTH_NESTING); + } + return 0; + } +diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c +--- a/sound/hda/codecs/conexant.c ++++ b/sound/hda/codecs/conexant.c +@@ -207,7 +207,7 @@ static void cx_remove(struct hda_codec *codec) + snd_hda_gen_remove(codec); + } + +-static void cx_process_headset_plugin(struct hda_codec *codec) ++static void cx_process_headset_detect_plug_type(struct hda_codec *codec) + { + unsigned int val; + unsigned int count = 0; +@@ -223,11 +223,9 @@ static void cx_process_headset_plugin(struct hda_codec *codec) + count++; + } while (count < 3); + val = snd_hda_codec_read(codec, 0x1c, 0, 0xcb0, 0x0); +- if (val & 0x800) { +- codec_dbg(codec, "headset plugin, type is CTIA\n"); +- snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); +- } else if (val & 0x400) { +- codec_dbg(codec, "headset plugin, type is OMTP\n"); ++ if (val & 0xc00) { ++ codec_dbg(codec, "headset plugin, type is %s\n", ++ val & 0x800 ? "CTIA" : "OMTP"); + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); + } else { + codec_dbg(codec, "headphone plugin\n"); +@@ -243,10 +241,12 @@ static void cx_update_headset_mic_vref(struct hda_codec *codec, struct hda_jack_ + * Check hp&mic tag to process headset plugin & plugout. + */ + mic_present = snd_hda_codec_read(codec, 0x19, 0, AC_VERB_GET_PIN_SENSE, 0x0); +- if (!(mic_present & AC_PINSENSE_PRESENCE)) /* mic plugout */ ++ if (!(mic_present & AC_PINSENSE_PRESENCE)) { /* mic plugout */ + snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20); +- else +- cx_process_headset_plugin(codec); ++ } else { ++ cx_process_headset_detect_plug_type(codec); ++ snd_hda_codec_write(codec, 0x19, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24); ++ } + } + + static int cx_suspend(struct hda_codec *codec) +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -1370,6 +1370,61 @@ static void alc269_fixup_hp_gpio_led(struct hda_codec *codec, + alc_fixup_hp_gpio_led(codec, action, 0x08, 0x10); + } + ++/* ++ * HP Laptop 15-fd0xxx (SSID 103c:8bb6) Speaker Mute LED fix ++ * ++ * The speaker mute LED is controlled via VREF100 on NID 0x1a. ++ * This pin must remain powered (D0) even during suspend, otherwise ++ * the LED state is lost and the pin defaults to Hi-Z on resume, ++ * causing the LED to stop responding to mute toggles. ++ * Windows keeps this pin powered unconditionally, so matching that ++ * behavior ensures consistent LED operation across suspend/resume. ++ * The mic-mute LED is controlled via GPIO 0 with active-low polarity. ++ */ ++static unsigned int hp_8bb6_power_filter(struct hda_codec *codec, ++ hda_nid_t nid, ++ unsigned int power_state) ++{ ++ if (nid == 0x1a) ++ return AC_PWRST_D0; ++ return snd_hda_gen_path_power_filter(codec, nid, power_state); ++} ++ ++static int hp_8bb6_speaker_mute_led_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct hda_codec *codec = dev_to_hda_codec(led_cdev->dev->parent); ++ unsigned int val = (brightness == LED_OFF) ? PIN_IN : PIN_VREF100; ++ ++ snd_hda_set_pin_ctl_cache(codec, 0x1a, val); ++ return 0; ++} ++ ++static void alc236_fixup_hp_15_fd0xxx(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ struct alc_spec *spec = codec->spec; ++ ++ switch (action) { ++ case HDA_FIXUP_ACT_PRE_PROBE: ++ spec->micmute_led_polarity = 1; ++ alc_fixup_hp_gpio_led(codec, action, 0x00, 0x01); ++ ++ spec->mute_led_polarity = 0; ++ snd_hda_gen_add_mute_led_cdev(codec, hp_8bb6_speaker_mute_led_set); ++ ++ codec->power_filter = hp_8bb6_power_filter; ++ spec->no_shutup_pins = 1; ++ break; ++ ++ case HDA_FIXUP_ACT_INIT: ++ if (spec->gen.vmaster_mute.hook) ++ snd_hda_sync_vmaster_hook(&spec->gen.vmaster_mute); ++ break; ++ } ++} ++ + static void alc285_fixup_hp_gpio_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) + { +@@ -4089,6 +4144,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_MIC_NO_PRESENCE, + ALC225_FIXUP_S3_POP_NOISE, + ALC700_FIXUP_INTEL_REFERENCE, ++ ALC700_FIXUP_INTEL_HADES_CANYON, + ALC274_FIXUP_DELL_BIND_DACS, + ALC274_FIXUP_DELL_AIO_LINEOUT_VERB, + ALC298_FIXUP_TPT470_DOCK_FIX, +@@ -4152,6 +4208,7 @@ enum { + ALC236_FIXUP_HP_GPIO_LED, + ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ++ ALC236_FIXUP_HP_15_FD0XXX, + ALC236_FIXUP_LENOVO_INV_DMIC, + ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, +@@ -4297,6 +4354,7 @@ enum { + ALC287_FIXUP_LENOVO_LEGION_AW88399, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, ++ ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4333,6 +4391,19 @@ static void alc287_fixup_lenovo_yoga_book_9i(struct hda_codec *codec, + __snd_hda_apply_fixup(codec, id, action, 0); + } + ++static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, ++ const struct hda_fixup *fix, ++ int action) ++{ ++ /* ++ * Windows may leave coef 0x1b bit 0x0400 cleared, causing broken ++ * analog playback after rebooting into Linux. Restore the bit ++ * during codec initialization. ++ */ ++ if (action == HDA_FIXUP_ACT_INIT) ++ alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); ++} ++ + static const struct hda_fixup alc269_fixups[] = { + [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { + .type = HDA_FIXUP_PINS, +@@ -5411,6 +5482,10 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC700_FIXUP_INTEL_HADES_CANYON] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc700_fixup_intel_hades_canyon, ++ }, + [ALC274_FIXUP_DELL_BIND_DACS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc274_fixup_bind_dacs, +@@ -5913,6 +5988,10 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_gpio, + }, ++ [ALC236_FIXUP_HP_15_FD0XXX] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc236_fixup_hp_15_fd0xxx, ++ }, + [ALC236_FIXUP_LENOVO_INV_DMIC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_inv_dmic, +@@ -7010,6 +7089,12 @@ static const struct hda_fixup alc269_fixups[] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_lenovo_dac_rename, + }, ++ [ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = alc285_fixup_speaker2_to_dac1, ++ .chained = true, ++ .chain_id = ALC287_FIXUP_TXNW2781_I2C, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7448,6 +7533,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), +@@ -8031,6 +8117,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), ++ SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8044,14 +8131,15 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), ++ SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), +- HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ ++ HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8231,6 +8319,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ++ ALC700_FIXUP_INTEL_HADES_CANYON), + SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), + SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), + SND_PCI_QUIRK(0x8086, 0x2081, "Intel NUC 10", ALC256_FIXUP_INTEL_NUC10), +diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c +--- a/sound/usb/mixer.c ++++ b/sound/usb/mixer.c +@@ -1237,21 +1237,27 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) + } + + /* +- * Additional checks for sticky mixers ++ * Additional checks for sticky GET_CUR + * +- * Some devices' volume control mixers are sticky, which accept SET_CUR but +- * do absolutely nothing. ++ * Some devices' volume control mixers have sticky GET_CUR, which implies either ++ * stubbed SET_CUR or broken GET_CUR. For the former case, the mixer accepts ++ * SET_CUR but do absolutely nothing, so falling back to soft mixer is the only ++ * way to control the volume. For the latter case, the mixer has effective ++ * SET_CUR despite GET_CUR being constant, and the mixer is usable as long as we ++ * always provide mixer value from the ceche. + * + * Check the return values of GET_CUR with different SET_CUR values. Consider +- * the mixer as sticky if GET_CUR always returns a constant value. ++ * GET_CUR as sticky if GET_CUR always returns a constant value. + * +- * Some devices have effective SET_CUR despite GET_CUR being constant. Do not +- * consider the mixer as sticky if a quirk flag indicates that. ++ * Unfortunately, we can't distinguish between stubbed SET_CUR and broken ++ * GET_CUR with simple read-back tests. Disabling the mixer regardless and ++ * forcing userspace to use soft mixer instead can lead to audible distortion at ++ * low volume on some wireless headphones, probably due to their poorly- ++ * performed lossy codec. + * +- * Gate the registration of sticky mixers to prevent confusing userspace, so +- * that they won't cause ineffective volume control. However, for mixers with +- * effective SET_CUR but broken GET_CUR, the registration can continue normally +- * but further GET_CUR requests will be gated. ++ * Instead, mark GET_CUR as broken regardless and only provide mixer value from ++ * the cache. Users may opt into soft mixer in userspace audio stack if they ++ * need it. + */ + static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +@@ -1271,24 +1277,13 @@ static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + return 0; + } + +- if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) { +- usb_audio_info(cval->head.mixer->chip, +- "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); +- +- cval->get_cur_broken = 1; +- return -ENXIO; +- } +- +- usb_audio_err(cval->head.mixer->chip, +- "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", +- cval->head.id, mixer_ctrl_intf(cval->head.mixer), +- cval->min, cval->max, cval->res, saved); + usb_audio_info(cval->head.mixer->chip, +- "check MIXER_GET_CUR_BROKEN if you believe the mixer is non-sticky"); ++ "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n", ++ cval->head.id, mixer_ctrl_intf(cval->head.mixer), ++ cval->min, cval->max, cval->res, saved); + +- return -ENODEV; ++ cval->get_cur_broken = 1; ++ return -ENXIO; + } + + /* +@@ -1385,8 +1380,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + goto no_checks; + + ret = check_sticky_volume_control(cval, minchn, saved); +- if (ret == -ENODEV) +- goto sticky; + if (ret) + goto no_checks; + +@@ -1454,34 +1447,15 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, + } + } + +- return 0; +- +-sticky: + /* +- * It makes no sense to restore the saved value for a sticky mixer, +- * since setting any value is a no-op. +- * +- * However, in some rare cases, SET_CUR is effective despite GET_CUR +- * always returns a constant value. These mixers are not sticky, but +- * there's no way to distinguish them. Without any additional +- * information, the best thing we can do is to set the mixer value to +- * the maximum before bailing out, so that a soft mixer can still reach +- * the maximum hardware volume if the mixer turns out to be non-sticky. +- * Meanwhile, all channels must be synchronized to prevent imbalance +- * volume. ++ * When GET_CUR is sticky, the saved value is bogus, so mixer values set ++ * by the sanity checks must be discarded through init_cur_mix_raw(). ++ * After that, we can clear the flag as per QUIRK_FLAG_MIXER_GET_CUR_OK. + */ +- if (!cval->cmask) { +- snd_usb_set_cur_mix_value(cval, 0, 0, cval->max); +- } else { +- idx = 0; +- for (i = 0; i < MAX_CHANNELS; i++) { +- if (cval->cmask & BIT(i)) { +- snd_usb_set_cur_mix_value(cval, i + 1, idx, cval->max); +- idx++; +- } +- } +- } +- return ret; ++ if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_OK) ++ cval->get_cur_broken = 0; ++ ++ return 0; + } + + #define get_min_max(cval, def) get_min_max_with_quirks(cval, def, NULL) +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -505,13 +505,13 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { + {} + }; + +-/* Audient iD14: FU 12 advertises Volume on only 4 of its 6 logical channels +- * and sits on the monitor mixer branch, but it is traced through to the +- * Speaker output terminal and gets named "Speaker Playback Volume". Userspace +- * then adopts it as the stream's hardware volume, and any setting below 0 dB +- * attenuates some channels but not others (20 dB imbalance at 80%). Give it a +- * non-standard name so that it is no longer taken for the stream's master +- * volume, while remaining reachable for anyone who wants the monitor gain. ++/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is ++ * traced through to the Speaker output terminal, so it is named "Speaker ++ * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI ++ * testing found asymmetric attenuation within the main stereo pair. Userspace ++ * adopts this control as the stream's hardware volume, causing imbalance below ++ * 0 dB. Give it a non-standard name so that userspace no longer treats it as ++ * the stream master, while keeping the monitor gain reachable. + */ + static const struct usbmix_name_map audient_id14_map[] = { + { 12, "Monitor Mix Playback" }, /* FU, partial coverage */ +@@ -602,7 +602,12 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .map = maya44_map, + }, + { +- /* Audient iD14 */ ++ /* Audient iD14 MkI */ ++ .id = USB_ID(0x2708, 0x0002), ++ .map = audient_id14_map, ++ }, ++ { ++ /* Audient iD14 MkII */ + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, +diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h +--- a/sound/usb/quirks-table.h ++++ b/sound/usb/quirks-table.h +@@ -1814,6 +1814,28 @@ YAMAHA_DEVICE(0x7010, "UB99"), + } + } + }, ++{ ++ /* ++ * M-Audio Venom ++ * ++ * The AudioControl interface times out on every GET_CUR request, ++ * which adds around 47 seconds to the card registration and ++ * freezes the device, blocking streaming. ++ * Using an explicit composite quirk to skip the mixer entirely. ++ */ ++ USB_DEVICE_VENDOR_SPEC(0x0763, 0x2084), ++ QUIRK_DRIVER_INFO { ++ .vendor_name = "M-Audio", ++ .product_name = "Venom", ++ QUIRK_DATA_COMPOSITE { ++ { QUIRK_DATA_IGNORE(0) }, ++ { QUIRK_DATA_STANDARD_AUDIO(1) }, ++ { QUIRK_DATA_STANDARD_AUDIO(2) }, ++ { QUIRK_DATA_STANDARD_MIDI(3) }, ++ QUIRK_COMPOSITE_END ++ } ++ } ++}, + + /* Casio devices */ + { +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -2215,10 +2215,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x040b, 0x0897, /* Weltrend Semiconductor, sold as Redragon H510-PRO Wireless headset */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x041e, 0x324d, /* Creative Sound Blaster Play! 3 */ ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x041e, 0x4080, /* Creative Live Cam VF0610 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x045e, 0x083c, /* MS USB Link headset */ +@@ -2256,8 +2256,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x046d, 0x0a8f, /* Logitech H390 headset */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x046d, 0x0af7, /* Logitech PRO X 2 LIGHTSPEED */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), ++ DEVICE_FLG(0x046d, 0x0aba, /* Logitech PRO X Wireless */ ++ QUIRK_FLAG_MIXER_GET_CUR_OK | ++ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0499, 0x1506, /* Yamaha THR5 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ +@@ -2329,9 +2330,11 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + DEVICE_FLG(0x0763, 0x2031, /* M-Audio Fast Track C600 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2080, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0763, 0x2081, /* M-Audio Fast Track Ultra */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN | QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ QUIRK_FLAG_GENERIC_IMPLICIT_FB), ++ DEVICE_FLG(0x0763, 0x2084, /* M-Audio Venom */ ++ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x07fd, 0x000b, /* MOTU M Series 2nd hardware revision */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x08bb, 0x2702, /* LineX FM Transmitter */ +@@ -2370,8 +2373,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x1038, 0x1294, /* SteelSeries Arctis Pro Wireless */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x1038, 0x2232, /* SteelSeries Arctis Nova 5 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ +@@ -2379,8 +2380,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), +- DEVICE_FLG(0x1377, 0x6004, /* Sennheiser MOMENTUM 3 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ +@@ -2393,6 +2392,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), ++ DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ ++ QUIRK_FLAG_DSD_RAW | QUIRK_FLAG_DISABLE_AUTOSUSPEND | ++ QUIRK_FLAG_SKIP_IFACE_SETUP), + DEVICE_FLG(0x152a, 0x880a, /* NeuralDSP Quad Cortex */ + 0), /* Doesn't have the vendor quirk which would otherwise apply */ + DEVICE_FLG(0x1532, 0x055e, /* Razer Nommo V2 X */ +@@ -2505,8 +2507,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x2d99, 0x0026, /* HECATE G2 GAMING HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), +- DEVICE_FLG(0x2d99, 0xa024, /* Edifier MF200 */ +- QUIRK_FLAG_MIXER_GET_CUR_BROKEN), + DEVICE_FLG(0x2fc6, 0xf06b, /* MOONDROP Moonriver2 Ti */ + QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x2fc6, 0xf0b5, /* iBasso DC-Elite */ +@@ -2647,7 +2647,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), + QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY), +- QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN), ++ QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), + QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), + QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + NULL +diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h +--- a/sound/usb/usbaudio.h ++++ b/sound/usb/usbaudio.h +@@ -243,24 +243,18 @@ extern bool snd_usb_skip_validation; + * from snd_usb_handle_sync_urb. Instead fall through and enqueue a + * packet_info containing only size-0 packets, so the OUT ring keeps + * moving (emits silence). Needed by Behringer Flow 8 (1397:050c). +- * QUIRK_FLAG_MIXER_GET_CUR_BROKEN +- * Some mixers are sticky, which means that setting their current volume is a +- * no-op, and reading the current volume returns a constant value. The sticky +- * check disables these mixers to prevent confusing userspace. However, some +- * devices do have a tunable volume despite the reported current volume being +- * constant. As the sticky check can't distinguish between the two categories, +- * setting this flag tells that the device should fall into the second +- * category when GET_CUR returns a constant value, resulting in the sticky +- * check being non-fatal and only disabling GET_CUR instead of the whole mixer. +- * The current volume will then be provided by the internal cache that stores +- * the last set volume ++ * QUIRK_FLAG_MIXER_GET_CUR_OK ++ * On some devices, whether their GET_CUR being sticky depends on whether ++ * hotpluggable components are present. When the hotpluggable components are ++ * missing on probe, their GET_CUR behavior is classified as broken. Set the ++ * flag to prevent the heuristics from gating GET_CUR. + * QUIRK_FLAG_PLAYBACK_URB_FIXUP + * Set URB_ISO_ASAP flag for isochronous URBs and force nurbs to MAX_URBS. + * This is needed for devices that exhibit boot-time audio stuttering due + * to insufficient buffer depth combined with xHCI scheduling variability. + * The larger buffer (MAX_URBS = 12, ~64ms) absorbs system scheduling + * jitter during boot, while URB_ISO_ASAP ensures consistent xHCI scheduling. +- * QUIRK_FLAG_ALWAYS_SET_RATE: ++ * QUIRK_FLAG_ALWAYS_SET_RATE + * Issue SET_CUR for the sample rate even when the clock already reports the + * requested rate. A device advertising a single rate is otherwise never sent + * the request at all, and some require it before streaming will start. +@@ -297,10 +291,10 @@ enum { + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, + QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29, +- QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30, ++ QUIRK_TYPE_MIXER_GET_CUR_OK = 30, + QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, + QUIRK_TYPE_ALWAYS_SET_RATE = 32, +-/* Please also edit snd_usb_audio_quirk_flag_names */ ++/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ + }; + + #define QUIRK_FLAG(x) BIT_U64(QUIRK_TYPE_ ## x) +@@ -335,7 +329,7 @@ enum { + #define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) + #define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) + #define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY) +-#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN) ++#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) + #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) + #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) + diff --git a/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch.sig new file mode 100644 index 0000000..cdb2260 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0512-sound-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch new file mode 100644 index 0000000..49c1eb3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch @@ -0,0 +1,871 @@ +diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +--- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h ++++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +@@ -237,34 +237,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 + +-#define LAPSS_CLK_ID_QAIF_IF0_IBIT 71 +-#define LAPSS_CLK_ID_QAIF_IF0_EBIT 72 +-#define LAPSS_CLK_ID_QAIF_IF1_IBIT 73 +-#define LAPSS_CLK_ID_QAIF_IF1_EBIT 74 +-#define LAPSS_CLK_ID_QAIF_IF2_IBIT 75 +-#define LAPSS_CLK_ID_QAIF_IF2_EBIT 76 +-#define LAPSS_CLK_ID_QAIF_IF3_IBIT 77 +-#define LAPSS_CLK_ID_QAIF_IF3_EBIT 78 +-#define LAPSS_CLK_ID_QAIF_IF4_IBIT 79 +-#define LAPSS_CLK_ID_QAIF_IF4_EBIT 80 +-#define LAPSS_CLK_ID_QAIF_IF5_IBIT 81 +-#define LAPSS_CLK_ID_QAIF_IF5_EBIT 82 +-#define LAPSS_CLK_ID_QAIF_IF6_IBIT 83 +-#define LAPSS_CLK_ID_QAIF_IF6_EBIT 84 +-#define LAPSS_CLK_ID_QAIF_IF7_IBIT 85 +-#define LAPSS_CLK_ID_QAIF_IF7_EBIT 86 +-#define LAPSS_CLK_ID_QAIF_IF8_IBIT 87 +-#define LAPSS_CLK_ID_QAIF_IF8_EBIT 88 +-#define LAPSS_CLK_ID_QAIF_IF9_IBIT 89 +-#define LAPSS_CLK_ID_QAIF_IF9_EBIT 90 +-#define LAPSS_CLK_ID_QAIF_IF10_IBIT 91 +-#define LAPSS_CLK_ID_QAIF_IF10_EBIT 92 +-#define LAPSS_CLK_ID_QAIF_IF11_IBIT 93 +-#define LAPSS_CLK_ID_QAIF_IF11_EBIT 94 +-#define LAPSS_CLK_ID_QAIF_IF12_IBIT 95 +-#define LAPSS_CLK_ID_QAIF_IF12_EBIT 96 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 97 +-#define LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 98 ++#define LPASS_CLK_ID_QAIF_IF0_IBIT 71 ++#define LPASS_CLK_ID_QAIF_IF0_EBIT 72 ++#define LPASS_CLK_ID_QAIF_IF1_IBIT 73 ++#define LPASS_CLK_ID_QAIF_IF1_EBIT 74 ++#define LPASS_CLK_ID_QAIF_IF2_IBIT 75 ++#define LPASS_CLK_ID_QAIF_IF2_EBIT 76 ++#define LPASS_CLK_ID_QAIF_IF3_IBIT 77 ++#define LPASS_CLK_ID_QAIF_IF3_EBIT 78 ++#define LPASS_CLK_ID_QAIF_IF4_IBIT 79 ++#define LPASS_CLK_ID_QAIF_IF4_EBIT 80 ++#define LPASS_CLK_ID_QAIF_IF5_IBIT 81 ++#define LPASS_CLK_ID_QAIF_IF5_EBIT 82 ++#define LPASS_CLK_ID_QAIF_IF6_IBIT 83 ++#define LPASS_CLK_ID_QAIF_IF6_EBIT 84 ++#define LPASS_CLK_ID_QAIF_IF7_IBIT 85 ++#define LPASS_CLK_ID_QAIF_IF7_EBIT 86 ++#define LPASS_CLK_ID_QAIF_IF8_IBIT 87 ++#define LPASS_CLK_ID_QAIF_IF8_EBIT 88 ++#define LPASS_CLK_ID_QAIF_IF9_IBIT 89 ++#define LPASS_CLK_ID_QAIF_IF9_EBIT 90 ++#define LPASS_CLK_ID_QAIF_IF10_IBIT 91 ++#define LPASS_CLK_ID_QAIF_IF10_EBIT 92 ++#define LPASS_CLK_ID_QAIF_IF11_IBIT 93 ++#define LPASS_CLK_ID_QAIF_IF11_EBIT 94 ++#define LPASS_CLK_ID_QAIF_IF12_IBIT 95 ++#define LPASS_CLK_ID_QAIF_IF12_EBIT 96 ++#define LPASS_CLK_ID_VA_QAIF_IF0_IBIT 97 ++#define LPASS_CLK_ID_VA_QAIF_IF0_EBIT 98 + + #define LPASS_HW_AVTIMER_VOTE 101 + #define LPASS_HW_MACRO_VOTE 102 +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "8EA1"), + }, + }, ++ { ++ /* HP OmniBook X Flip 16-cc0xxx */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "HP"), ++ DMI_MATCH(DMI_BOARD_NAME, "8EA2"), ++ }, ++ }, + {} + }; + +@@ -209,6 +216,20 @@ static const struct config_entry config_table[] = { + {} + }, + }, ++ { ++ .flags = FLAG_AMD_LEGACY, ++ .device = ACP_PCI_DEV_ID, ++ .dmi_table = (const struct dmi_system_id []) { ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ }, ++ {} ++ }, ++ }, + }; + + static int snd_amd_acp_acpi_find_config(struct pci_dev *pci) +diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +--- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c ++++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +@@ -382,6 +382,14 @@ static const struct dmi_system_id acp3x_es83xx_dmi_table[] = { + }, + .driver_data = (void *)(ES83XX_ENABLE_DMIC), + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "HUAWEI"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "HVY-WXX9"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "M1060"), ++ }, ++ .driver_data = (void *)(ES83XX_ENABLE_DMIC), ++ }, + {} + }; + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -570,6 +570,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m17 R5 AMD"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Alienware"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Alienware m18 R1 AMD"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c +--- a/sound/soc/codecs/max98926.c ++++ b/sound/soc/codecs/max98926.c +@@ -360,7 +360,7 @@ static int max98926_dai_set_fmt(struct snd_soc_dai *codec_dai, + regmap_write(max98926->regmap, + MAX98926_FORMAT, MAX98926_DAI_DLY_MASK); + regmap_update_bits(max98926->regmap, MAX98926_FORMAT, +- MAX98926_DAI_BCI_MASK, invert); ++ MAX98926_DAI_BCI_MASK | MAX98926_DAI_WCI_MASK, invert); + return 0; + } + +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -1779,6 +1779,7 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + dev_dbg(dev, "%s jack/mic/amp func_status=0x%x, 0x%x, 0x%x\n", + __func__, jack_func_status, mic_func_status, amp_func_status); + ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -74,6 +74,7 @@ struct rt712_dmic_kctrl_priv { + #define RT712_JD_PRODUCT_NUM 0x00 + #define RT712_ANALOG_BIAS_CTL3 0x04 + #define RT712_JD_CTL1 0x09 ++#define RT712_JD_CTL3 0x0b + #define RT712_IO_CTL 0x0c + #define RT712_LDO2_3_CTL1 0x0e + #define RT712_PARA_VERB_CTL 0x1a +diff --git a/sound/soc/codecs/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c +--- a/sound/soc/codecs/rt766-sdca-sdw.c ++++ b/sound/soc/codecs/rt766-sdca-sdw.c +@@ -233,6 +233,8 @@ static void rt766_sdca_sdw_remove(struct sdw_slave *slave) + static const struct sdw_device_id rt766_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x4, 0x1, 0), ++ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x4, 0x1, 0), + {}, + }; + MODULE_DEVICE_TABLE(sdw, rt766_sdca_id); +diff --git a/sound/soc/codecs/tac5xx2-sdw.c b/sound/soc/codecs/tac5xx2-sdw.c +--- a/sound/soc/codecs/tac5xx2-sdw.c ++++ b/sound/soc/codecs/tac5xx2-sdw.c +@@ -55,6 +55,12 @@ + #define TAC_XU_BYPASS_REG(func, xu) \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##xu, \ + TAC_SDCA_CTL_XU_BYPASS, 0) ++#define TAC_VOLUME_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_##ch) ++#define TAC_GAIN_REG(func, fu, ch) \ ++ SDW_SDCA_CTL(TAC_FUNCTION_ID_##func, TAC_SDCA_ENT_##fu, \ ++ TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_##ch) + + /* mute registers */ + #define FU21_L_MUTE_REG TAC_MUTE_REG(SA, FU21, LEFT) +@@ -123,9 +129,9 @@ static const DECLARE_TLV_DB_SCALE(tac5xx2_dvc_tlv, -7200, 50, 0); + #define TAC_DOUBLE_Q78_TLV(name, func_id, ent_id) \ + SDCA_DOUBLE_Q78_TLV(name, \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), \ + SDW_SDCA_CTL(TAC_FUNCTION_ID_##func_id, TAC_SDCA_ENT_##ent_id, \ +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), \ ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), \ + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv) + + struct tac5xx2_prv { +@@ -381,37 +387,53 @@ static const struct reg_default tac_reg_default[] = { + {TAC_REG_SDW(0, 1, 0x71), 0x5}, + {TAC_REG_SDW(0, 1, 0x72), 0x6}, + {TAC_REG_SDW(0, 1, 0x73), 0x7}, ++ /* SA */ ++ {TAC_MUTE_REG(SA, FU21, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU21, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(SA, FU21, LEFT), 0x9c00}, ++ {TAC_VOLUME_REG(SA, FU21, RIGHT), 0x9c00}, ++ {TAC_MUTE_REG(SA, FU23, LEFT), 0x1}, ++ {TAC_MUTE_REG(SA, FU23, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SA, FU23, LEFT), 0x0}, ++ {TAC_GAIN_REG(SA, FU23, RIGHT), 0x0}, ++ /* SM */ ++ {TAC_USAGE_REG(SM, IT11), 0x0}, ++ {TAC_USAGE_REG(SM, OT113), 0x0}, ++ {TAC_MUTE_REG(SM, FU113, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU113, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU113, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU113, RIGHT), 0x0}, ++ {TAC_MUTE_REG(SM, FU11, LEFT), 0x1}, ++ {TAC_MUTE_REG(SM, FU11, RIGHT), 0x1}, ++ {TAC_GAIN_REG(SM, FU11, LEFT), 0x0}, ++ {TAC_GAIN_REG(SM, FU11, RIGHT), 0x0}, ++ {TAC_XU_BYPASS_REG(SM, XU12), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_CS113, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ /* UAJ */ ++ {TAC_USAGE_REG(UAJ, IT33), 0x0}, ++ {TAC_USAGE_REG(UAJ, IT41), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT36), 0x0}, ++ {TAC_USAGE_REG(UAJ, OT45), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU41, LEFT), 0x1}, ++ {TAC_MUTE_REG(UAJ, FU41, RIGHT), 0x1}, ++ {TAC_VOLUME_REG(UAJ, FU41, LEFT), 0x0}, ++ {TAC_VOLUME_REG(UAJ, FU41, RIGHT), 0x0}, ++ {TAC_MUTE_REG(UAJ, FU36, RIGHT), 0x1}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), 0x0}, ++ {TAC_XU_BYPASS_REG(UAJ, XU42), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS36, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, ++ {SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_CS41, ++ TAC_SDCA_CTL_CS_SAMP_RATE_IDX, 0), 0x0}, + }; + + static const struct reg_sequence tac_spk_seq[] = { + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT), 0), + REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_sm_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +-}; +- +-static const struct reg_sequence tac_uaj_seq[] = { +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT), 0), +- REG_SEQ0(SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), 0), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT), 0), + }; + + static bool tac_volatile_reg(struct device *dev, unsigned int reg) +@@ -468,25 +490,25 @@ static int tac_sdca_mbq_size(struct device *dev, unsigned int reg) + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU21, + TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SA, TAC_SDCA_ENT_FU23, +- TAC_SDCA_MASTER_GAIN, 0): ++ TAC_SDCA_CHANNEL_GAIN, 0): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU113, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_SM, TAC_SDCA_ENT_FU11, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_LEFT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_LEFT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU41, +- TAC_SDCA_CHANNEL_GAIN, TAC_CHANNEL_RIGHT): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_CHANNEL_RIGHT): + case SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS): ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS): + return 2; + + default: +@@ -531,7 +553,7 @@ static const struct snd_kcontrol_new tac_uaj_controls[] = { + TAC_DOUBLE_Q78_TLV("UAJ Playback Volume", UAJ, FU41), + SDCA_SINGLE_Q78_TLV("UAJ Capture Volume", + SDW_SDCA_CTL(TAC_FUNCTION_ID_UAJ, TAC_SDCA_ENT_FU36, +- TAC_SDCA_CHANNEL_GAIN, TAC_JACK_MONO_CS), ++ TAC_SDCA_CHANNEL_VOLUME, TAC_JACK_MONO_CS), + TAC_DVC_MIN, TAC_DVC_MAX, TAC_DVC_STEP, tac5xx2_dvc_tlv), + }; + +@@ -1761,16 +1783,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + goto io_init_err; + } + dev_dbg(dev, "smartmic init done\n"); +- +- if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_sm_seq, +- ARRAY_SIZE(tac_sm_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- } + } + + if (tac_dev->uaj_func_data) { +@@ -1783,14 +1795,6 @@ static int tac_io_init(struct device *dev, struct sdw_slave *slave, bool first) + dev_dbg(dev, "uaj init done\n"); + + if (first) { +- ret = regmap_multi_reg_write(tac_dev->regmap, tac_uaj_seq, +- ARRAY_SIZE(tac_uaj_seq)); +- if (ret) { +- dev_err(tac_dev->dev, +- "init writes failed, err=%d", ret); +- goto io_init_err; +- } +- + if (tac_dev->hs_jack) { + ret = tac5xx2_jack_init(tac_dev); + if (ret) { +diff --git a/sound/soc/codecs/tac5xx2.h b/sound/soc/codecs/tac5xx2.h +--- a/sound/soc/codecs/tac5xx2.h ++++ b/sound/soc/codecs/tac5xx2.h +@@ -174,14 +174,13 @@ + #define TAC_SDCA_ENT_SAPU29 0x35 + + /* Control selector definitions */ +-#define TAC_SDCA_MASTER_GAIN 0x0B + #define TAC_SDCA_MASTER_MUTE 0x01 + #define TAC_SDCA_CHANNEL_MUTE 0x01 +-#define TAC_SDCA_CHANNEL_GAIN 0x02 ++#define TAC_SDCA_CHANNEL_VOLUME 0x02 + #define TAC_SDCA_POSTURENUMBER 0x10 + #define TAC_SDCA_REQUESTED_PS 0x01 + #define TAC_SDCA_ACTUAL_PS 0x10 +-#define TAC_SDCA_CHANNEL_VOLUME 0x02 ++#define TAC_SDCA_CHANNEL_GAIN 0x0B + + /* 2. smart mic function */ + #define TAC_FUNCTION_ID_SM 0x2 +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1310,6 +1310,7 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + enum sdw_port_prep_ops pre_ops) + { + struct device *dev = &slave->dev; ++ struct tas2783_prv *tas_dev = dev_get_drvdata(dev); + struct sdw_dpn_prop *dpn_prop; + u32 addr; + int ret; +@@ -1321,6 +1322,25 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch + addr = SDW_DPN_PREPARECTRL(prep_ch->num); + switch (pre_ops) { + case SDW_OPS_PORT_PRE_PREP: ++ /* ++ * The Function has to be powered before the port can complete ++ * channel preparation. hw_params() does that when a stream is ++ * set up, but a stream that is only re-prepared - as it is ++ * after the peripheral lost power in S0i3 - does not go ++ * through hw_params() again, and the peripheral is back at its ++ * PS3 reset default. Power it up here, where it is needed. ++ */ ++ scoped_guard(mutex, &tas_dev->pde_lock) ++ ret = regmap_write(tas_dev->regmap, ++ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, ++ TAS2783_SDCA_CTL_REQ_POW_STATE, 0), ++ TAS2783_SDCA_POW_STATE_ON); ++ if (ret) { ++ dev_err(dev, "power up failed for port %d, err=%d\n", ++ prep_ch->num, ret); ++ return ret; ++ } ++ + ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask); + if (ret) + dev_err(dev, "prep failed for port %d, err=%d\n", +diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c +--- a/sound/soc/loongson/loongson_card.c ++++ b/sound/soc/loongson/loongson_card.c +@@ -55,8 +55,8 @@ static const struct loongson_card_config ls2k0300_dl2k0300b_card_config = { + .add_dapm_routes = true, + }; + +-static int tegra_machine_event(struct snd_soc_dapm_widget *w, +- struct snd_kcontrol *k, int event) ++static int loongson_asoc_machine_event(struct snd_soc_dapm_widget *w, ++ struct snd_kcontrol *k, int event) + { + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct loongson_card_data *priv = snd_soc_card_get_drvdata(card); +@@ -72,9 +72,9 @@ static int tegra_machine_event(struct snd_soc_dapm_widget *w, + return 0; + } + +-static const struct snd_soc_dapm_widget loongson_aosc_dapm_widgets[] = { +- SND_SOC_DAPM_HP("Headphone", tegra_machine_event), +- SND_SOC_DAPM_SPK("Speaker", tegra_machine_event), ++static const struct snd_soc_dapm_widget loongson_asoc_dapm_widgets[] = { ++ SND_SOC_DAPM_HP("Headphone", loongson_asoc_machine_event), ++ SND_SOC_DAPM_SPK("Speaker", loongson_asoc_machine_event), + }; + + /* Headphones Jack */ +@@ -314,8 +314,8 @@ static int loongson_asoc_card_probe(struct platform_device *pdev) + card->num_links = ARRAY_SIZE(loongson_dai_links); + + if (ls_priv->cfg->add_dapm_widgets) { +- card->dapm_widgets = loongson_aosc_dapm_widgets; +- card->num_dapm_widgets = ARRAY_SIZE(loongson_aosc_dapm_widgets); ++ card->dapm_widgets = loongson_asoc_dapm_widgets; ++ card->num_dapm_widgets = ARRAY_SIZE(loongson_asoc_dapm_widgets); + } + + snd_soc_card_set_drvdata(card, ls_priv); +diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c +--- a/sound/soc/qcom/common.c ++++ b/sound/soc/qcom/common.c +@@ -103,14 +103,14 @@ static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, + int ret; + + if (!link_np) +- return -EINVAL; ++ return -ENOENT; + + struct device_node *cpu_np __free(device_node) = + of_get_child_by_name(link_np, "cpu"); + struct device_node *codec_np __free(device_node) = + of_get_child_by_name(link_np, "codec"); + if (!cpu_np || !codec_np) +- return -EINVAL; ++ return -ENOENT; + + ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); + if (ret) +@@ -172,7 +172,7 @@ int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + } +diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c +--- a/sound/soc/qcom/qdsp6/q6apm-dai.c ++++ b/sound/soc/qcom/qdsp6/q6apm-dai.c +@@ -216,7 +216,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, + { + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct device *dev = component->dev; + struct q6apm_dai_data *pdata; + int ret; +@@ -814,7 +814,7 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; +- struct audioreach_module_config cfg; ++ struct audioreach_module_config cfg = {}; + struct snd_codec *codec = ¶ms->codec; + int dir = stream->direction; + int ret; +diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c +--- a/sound/soc/qcom/qdsp6/q6prm-clocks.c ++++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c +@@ -64,34 +64,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF0_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF1_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF2_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF3_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF4_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF5_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF6_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF7_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF8_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF9_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF10_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF11_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_QAIF_IF12_EBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_IBIT), +- Q6PRM_CLK(LAPSS_CLK_ID_VA_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT), ++ Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT), + Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, + "LPASS_HW_MACRO"), + Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, +diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h +--- a/sound/soc/qcom/qdsp6/q6prm.h ++++ b/sound/soc/qcom/qdsp6/q6prm.h +@@ -97,34 +97,34 @@ + /* Clock ID for RX CORE MCLK2 2X MCLK */ + #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 + +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_IBIT 0x500 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF0_EBIT 0x501 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_IBIT 0x502 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF1_EBIT 0x503 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_IBIT 0x504 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF2_EBIT 0x505 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_IBIT 0x506 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF3_EBIT 0x507 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_IBIT 0x508 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF4_EBIT 0x509 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_IBIT 0x50A +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF5_EBIT 0x50B +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_IBIT 0x50C +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF6_EBIT 0x50D +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_IBIT 0x50E +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF7_EBIT 0x50F +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_IBIT 0x510 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF8_EBIT 0x511 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_IBIT 0x512 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF9_EBIT 0x513 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_IBIT 0x514 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF10_EBIT 0x515 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_IBIT 0x516 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF11_EBIT 0x517 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_IBIT 0x518 +-#define Q6PRM_LAPSS_CLK_ID_QAIF_IF12_EBIT 0x519 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 +-#define Q6PRM_LAPSS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518 ++#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 ++#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 + + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 + #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 +diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c +--- a/sound/soc/qcom/sc8280xp.c ++++ b/sound/soc/qcom/sc8280xp.c +@@ -115,33 +115,42 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai; + struct qcom_snd_tdm_slot_cfg cpu_cfg; + struct qcom_snd_tdm_slot_cfg codec_cfg; +- unsigned int bclk_freq; ++ int bclk_freq; + int ret; + int i; + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) +- return ret == -EINVAL ? 0 : ret; ++ return ret == -ENOENT ? 0 : ret; + + if (!cpu_cfg.slots) + return 0; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); +- if (ret) ++ if (ret && ret != -ENOTSUPP) + return ret; + ++ if (data->priv->codec_dai_fmt) { ++ for_each_rtd_codec_dais(rtd, i, codec_dai) { ++ ret = snd_soc_dai_set_fmt(codec_dai, ++ data->priv->codec_dai_fmt); ++ if (ret && ret != -ENOTSUPP) ++ return ret; ++ } ++ } ++ + ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + if (ret) + return ret; + + bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); +- if (!bclk_freq) ++ if (bclk_freq <= 0) + return -EINVAL; + + if (data->priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", + __func__, ret); + return ret; +@@ -152,7 +161,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, + SND_SOC_CLOCK_IN); +- if (ret) { ++ if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", + __func__, codec_dai->name, ret); + return ret; +diff --git a/sound/soc/sdw_utils/soc_sdw_rt_amp.c b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_rt_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_rt_amp.c +@@ -252,11 +252,13 @@ int asoc_sdw_rt_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai + if (ctx->amp_dev1) { + device_remove_software_node(ctx->amp_dev1); + put_device(ctx->amp_dev1); ++ ctx->amp_dev1 = NULL; + } + + if (ctx->amp_dev2) { + device_remove_software_node(ctx->amp_dev2); + put_device(ctx->amp_dev2); ++ ctx->amp_dev2 = NULL; + } + + return 0; +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -81,6 +81,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker */ + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -94,6 +95,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -102,6 +104,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5572", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -122,6 +125,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with IV sense feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -135,6 +139,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -143,6 +148,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5672", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -163,6 +169,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* speaker with echo reference feedback */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -176,6 +183,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -184,6 +192,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* UAJ */ + .direction = {true, true}, + .dai_name = "tac5xx2-aif3", ++ .component_name = "tac5682", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .controls = generic_jack_controls, +@@ -203,6 +212,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + { + .direction = {true, false}, + .dai_name = "tac5xx2-aif1", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_ti_amp_init, +@@ -216,6 +226,7 @@ struct asoc_sdw_codec_info codec_info_list[] = { + /* mic */ + .direction = {false, true}, + .dai_name = "tac5xx2-aif2", ++ .component_name = "tas2883", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_ti_dmic_rtd_init, +@@ -1510,9 +1521,24 @@ int asoc_sdw_trigger(struct snd_pcm_substream *substream, int cmd) + } + + switch (cmd) { ++ case SNDRV_PCM_TRIGGER_RESUME: ++ /* ++ * The peripherals lose their port configuration when the ++ * controller is power-gated during system suspend, and an ++ * application that restarts the stream with ++ * SNDRV_PCM_IOCTL_RESUME - which platforms advertising ++ * SNDRV_PCM_INFO_RESUME allow - never goes through ++ * .prepare() again. Prepare the stream here so that the ++ * ports are reprogrammed before they are enabled; ++ * sdw_prepare_stream() reapplies the parameters without ++ * recomputing them when the stream is disabled. ++ */ ++ ret = sdw_prepare_stream(sdw_stream); ++ if (ret) ++ break; ++ fallthrough; + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +- case SNDRV_PCM_TRIGGER_RESUME: + ret = sdw_enable_stream(sdw_stream); + break; + +diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c +--- a/sound/soc/soc-generic-dmaengine-pcm.c ++++ b/sound/soc/soc-generic-dmaengine-pcm.c +@@ -397,24 +397,8 @@ static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm, + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; + +- bool has_fw_node = dev->of_node || is_acpi_device_node(dev->fwnode); +- bool name_exists_in_fw = false; +- +- if (has_fw_node) +- name_exists_in_fw = device_property_match_string(dev, +- "dma-names", +- name) >= 0; +- +- if (has_fw_node && name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI DMA channel '%s' request failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (has_fw_node && !name_exists_in_fw) +- dev_warn(dev, "DTS/ACPI name '%s' not found, legacy failed (%ld)\n", +- name, PTR_ERR(chan)); +- +- if (!has_fw_node) +- dev_warn(dev, "Legacy DMA channel '%s' request failed (%ld)\n", ++ if (device_property_match_string(dev, "dma-names", name) >= 0) ++ dev_warn(dev, "dma-names has '%s' but request failed (%ld)\n", + name, PTR_ERR(chan)); + + pcm->chan[i] = NULL; diff --git a/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch.sig new file mode 100644 index 0000000..c7079ef Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0513-asoc-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch new file mode 100644 index 0000000..043c34e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch @@ -0,0 +1,362 @@ +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/usb/fcp.c b/sound/usb/fcp.c +--- a/sound/usb/fcp.c ++++ b/sound/usb/fcp.c +@@ -191,6 +191,10 @@ static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode, + const int max_retries = 5; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + if (!private->urb) + return -ENODEV; + +@@ -1026,6 +1030,10 @@ static int fcp_init(struct usb_mixer_interface *mixer, + struct usb_device *dev = mixer->chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + FCP_USB_REQ_STEP0, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -518,6 +518,19 @@ static const struct usbmix_name_map audient_id14_map[] = { + {} + }; + ++/* ++ * Audient iD24: feature unit 12 ("Speaker Playback Volume") sits in the ++ * monitor-mixer branch and does not apply volume to all of its channels; ++ * when userspace adopts it as the master playback volume, the left main ++ * output stays at 0 dB while the right one is attenuated, producing a ++ * stereo imbalance. Rename it so that it is not picked up as the ++ * stream's master volume control. ++ */ ++static const struct usbmix_name_map audient_id24_map[] = { ++ { 12, "Monitor Mix Playback" }, /* FU, partial channel coverage */ ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -611,6 +624,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x2708, 0x0008), + .map = audient_id14_map, + }, ++ { ++ /* Audient iD24 */ ++ .id = USB_ID(0x2708, 0x000d), ++ .map = audient_id24_map, ++ }, + { + /* KEF X300A */ + .id = USB_ID(0x27ac, 0x1000), +diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c +--- a/sound/usb/mixer_quirks.c ++++ b/sound/usb/mixer_quirks.c +@@ -3480,6 +3480,10 @@ static int snd_rme_digiface_write_reg(struct snd_kcontrol *kcontrol, int item, u + struct usb_device *dev = chip->dev; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + item, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +@@ -3499,6 +3503,10 @@ static int snd_rme_digiface_read_status(struct snd_kcontrol *kcontrol, u32 statu + __le32 buf[4] = {}; + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + RME_DIGIFACE_READ_STATUS, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, +diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c +--- a/sound/usb/mixer_s1810c.c ++++ b/sound/usb/mixer_s1810c.c +@@ -474,6 +474,10 @@ snd_s1810c_switch_get(struct snd_kcontrol *kctl, + u32 state = 0; + int ret; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &state); + if (ret < 0) +@@ -504,6 +508,10 @@ snd_s1810c_switch_set(struct snd_kcontrol *kctl, + u32 newval = 0; + int ret = 0; + ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ + guard(mutex)(&private->data_mutex); + ret = snd_s1810c_get_switch_state(mixer, kctl, &curval); + if (ret < 0) +diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c +--- a/sound/usb/mixer_scarlett.c ++++ b/sound/usb/mixer_scarlett.c +@@ -707,6 +707,10 @@ static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + int idx = snd_usb_ctrl_intf(elem->head.mixer->hostif) | (elem->head.id << 8); + int err; + ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, +diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c +--- a/sound/usb/mixer_scarlett2.c ++++ b/sound/usb/mixer_scarlett2.c +@@ -2603,9 +2603,9 @@ static int scarlett2_usb_rx(struct usb_device *dev, int interface, + } + + /* Send a proprietary format request to the Scarlett interface */ +-static int scarlett2_usb( +- struct usb_mixer_interface *mixer, u32 cmd, +- void *req_data, u16 req_size, void *resp_data, u16 resp_size) ++static int scarlett2_usb_nopm(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) + { + struct scarlett2_data *private = mixer->private_data; + struct usb_device *dev = mixer->chip->dev; +@@ -2713,6 +2713,18 @@ static int scarlett2_usb( + return err; + } + ++static int scarlett2_usb(struct usb_mixer_interface *mixer, u32 cmd, ++ void *req_data, u16 req_size, ++ void *resp_data, u16 resp_size) ++{ ++ CLASS(snd_usb_lock, pm)(mixer->chip); ++ if (pm.err < 0) ++ return -EIO; ++ ++ return scarlett2_usb_nopm(mixer, cmd, req_data, req_size, ++ resp_data, resp_size); ++} ++ + /* Send a USB message to get data; result placed in *buf */ + static int scarlett2_usb_get( + struct usb_mixer_interface *mixer, +@@ -3020,9 +3032,21 @@ static int scarlett2_usb_set_config_buf( + /* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */ + static void scarlett2_config_save(struct usb_mixer_interface *mixer) + { +- int err; ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); ++ ++ if (err < 0) ++ usb_audio_err(mixer->chip, "config save failed: %d\n", err); ++} ++ ++/* The USB suspend callback must not acquire another PM reference. */ ++static void scarlett2_config_save_nopm(struct usb_mixer_interface *mixer) ++{ ++ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE); ++ int err = scarlett2_usb_nopm(mixer, SCARLETT2_USB_DATA_CMD, ++ &req, sizeof(req), NULL, 0); + +- err = scarlett2_usb_activate_config(mixer, SCARLETT2_USB_CONFIG_SAVE); + if (err < 0) + usb_audio_err(mixer->chip, "config save failed: %d\n", err); + } +@@ -8639,7 +8663,7 @@ static void scarlett2_private_suspend(struct usb_mixer_interface *mixer) + struct scarlett2_data *private = mixer->private_data; + + if (cancel_delayed_work_sync(&private->work)) +- scarlett2_config_save(private->mixer); ++ scarlett2_config_save_nopm(private->mixer); + + scarlett2_cleanup_urb(mixer); + } +diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c +--- a/sound/usb/mixer_us16x08.c ++++ b/sound/usb/mixer_us16x08.c +@@ -151,6 +151,9 @@ static const char *const route_names[] = { + static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + unsigned char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; + + guard(mutex)(&chip->mutex); + snd_usb_ctl_msg(chip->dev, +@@ -165,6 +168,10 @@ static int snd_us16x08_recv_urb(struct snd_usb_audio *chip, + */ + static int snd_us16x08_send_urb(struct snd_usb_audio *chip, char *buf, int size) + { ++ CLASS(snd_usb_lock, pm)(chip); ++ if (pm.err < 0) ++ return -EIO; ++ + return snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + SND_US16X08_URB_REQUEST, SND_US16X08_URB_REQUESTTYPE, + 0, 0, buf, size); diff --git a/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch.sig new file mode 100644 index 0000000..cd8ab3f Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0514-sound-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch b/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch new file mode 100644 index 0000000..6a5a07c --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch @@ -0,0 +1,19 @@ +diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c +--- a/sound/soc/intel/boards/sof_sdw.c ++++ b/sound/soc/intel/boards/sof_sdw.c +@@ -845,6 +845,15 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { + }, + .driver_data = (void *)(SOC_SDW_PCH_DMIC), + }, ++ { ++ /* Dell XPS 13 DX13260 (Wildcat Lake), CS42L43 + 2x CS35L63 sidecar amps */ ++ .callback = sof_sdw_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53") ++ }, ++ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS), ++ }, + {} + }; + diff --git a/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch.sig new file mode 100644 index 0000000..1ccaf57 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0515-xps13-sof-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch b/pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch new file mode 100644 index 0000000..25fee81 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0516-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-muqss/0516-rt766-stream-config-type.patch.sig b/pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch.sig new file mode 100644 index 0000000..2b79ff3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0516-rt766-stream-config-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch b/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch new file mode 100644 index 0000000..47dae22 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch @@ -0,0 +1,39 @@ +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4226,6 +4226,7 @@ enum { + ALC294_FIXUP_ASUS_GU502_VERBS, + ALC294_FIXUP_ASUS_G513_PINS, + ALC285_FIXUP_ASUS_G533Z_PINS, ++ ALC285_FIXUP_ASUS_G733Z_VERBS, + ALC285_FIXUP_HP_GPIO_LED, + ALC285_FIXUP_HP_MUTE_LED, + ALC285_FIXUP_HP_SPECTRE_X360_MUTE_LED, +@@ -7129,6 +7130,19 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC287_FIXUP_TXNW2781_I2C, + }, ++ [ALC285_FIXUP_ASUS_G733Z_VERBS] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ /* Enables internal speaker */ ++ {0x17, AC_VERB_SET_CONNECT_SEL, 0x00}, ++ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, ++ {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, ++ {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, ++ { } ++ } ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7775,6 +7789,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x12a3, "Asus N7691ZM", ALC269_FIXUP_ASUS_N7601ZM), + SND_PCI_QUIRK(0x1043, 0x12af, "ASUS UX582ZS", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x12b4, "ASUS B3405CCA / P3405CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), ++ SND_PCI_QUIRK(0x1043, 0x12bf, "ASUS ROG Strix G733ZW", ALC285_FIXUP_ASUS_G733Z_VERBS), + SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), diff --git a/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig b/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig new file mode 100644 index 0000000..0cfe941 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0517-hda-realtek-rog-strix-g733zw-speakers.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch b/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch new file mode 100644 index 0000000..0d8e619 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch @@ -0,0 +1,17 @@ +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -619,6 +619,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SFA16-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "MDU"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A314-23P"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { diff --git a/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig b/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig new file mode 100644 index 0000000..62063af Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0518-asoc-amd-yc-acer-aspire-a314-23p.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch b/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch new file mode 100644 index 0000000..48c9872 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch @@ -0,0 +1,432 @@ +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 +@@ -7465,6 +7465,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x8890, "HP Elite Dragonfly Max G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8895, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8896, "HP EliteBook 855 G8 Notebook PC", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8898, "HP EliteBook 845 G8 Notebook PC", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), +@@ -7580,6 +7581,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), ++ SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 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), +diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c +--- a/sound/pci/rme9652/hdspm.c ++++ b/sound/pci/rme9652/hdspm.c +@@ -1071,14 +1071,8 @@ struct hdspm { + + + static const struct pci_device_id snd_hdspm_ids[] = { +- { +- .vendor = PCI_VENDOR_ID_XILINX, +- .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, +- .subvendor = PCI_ANY_ID, +- .subdevice = PCI_ANY_ID, +- .class = 0, +- .class_mask = 0, +- .driver_data = 0}, ++ { PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI) }, ++ { PCI_DEVICE(0x1d18, 0x3fc6) }, /* RME HDSPe AIO PCI express audio */ + {0,} + }; + +diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c +--- a/sound/usb/hiface/pcm.c ++++ b/sound/usb/hiface/pcm.c +@@ -24,7 +24,7 @@ + struct pcm_urb { + struct hiface_chip *chip; + +- struct urb instance; ++ struct urb *instance; + struct usb_anchor submitted; + u8 *buffer; + }; +@@ -193,7 +193,7 @@ static void hiface_pcm_stream_stop(struct pcm_runtime *rt) + if (!time) + usb_kill_anchored_urbs( + &rt->out_urbs[i].submitted); +- usb_kill_urb(&rt->out_urbs[i].instance); ++ usb_kill_urb(rt->out_urbs[i].instance); + } + + rt->stream_state = STREAM_DISABLED; +@@ -215,9 +215,9 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) + rt->stream_state = STREAM_STARTING; + for (i = 0; i < PCM_N_URBS; i++) { + memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE); +- usb_anchor_urb(&rt->out_urbs[i].instance, ++ usb_anchor_urb(rt->out_urbs[i].instance, + &rt->out_urbs[i].submitted); +- ret = usb_submit_urb(&rt->out_urbs[i].instance, ++ ret = usb_submit_urb(rt->out_urbs[i].instance, + GFP_ATOMIC); + if (ret) { + hiface_pcm_stream_stop(rt); +@@ -334,7 +334,7 @@ static void hiface_pcm_out_urb_handler(struct urb *usb_urb) + if (do_period_elapsed) + snd_pcm_period_elapsed(sub->instance); + +- ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC); ++ ret = usb_submit_urb(out_urb->instance, GFP_ATOMIC); + if (ret < 0) + goto out_fail; + +@@ -492,16 +492,18 @@ static int hiface_pcm_init_urb(struct pcm_urb *urb, + void (*handler)(struct urb *)) + { + urb->chip = chip; +- usb_init_urb(&urb->instance); ++ urb->instance = usb_alloc_urb(0, GFP_KERNEL); ++ if (!urb->instance) ++ return -ENOMEM; + + urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL); + if (!urb->buffer) + return -ENOMEM; + +- usb_fill_bulk_urb(&urb->instance, chip->dev, ++ usb_fill_bulk_urb(urb->instance, chip->dev, + usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer, + PCM_PACKET_SIZE, handler, urb); +- if (usb_urb_ep_type_check(&urb->instance)) ++ if (usb_urb_ep_type_check(urb->instance)) + return -EINVAL; + init_usb_anchor(&urb->submitted); + +@@ -520,24 +522,26 @@ void hiface_pcm_abort(struct hiface_chip *chip) + } + } + +-static void hiface_pcm_destroy(struct hiface_chip *chip) ++static void hiface_pcm_destroy(struct pcm_runtime *rt) + { +- struct pcm_runtime *rt = chip->pcm; + int i; + +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); ++ if (!rt) ++ return; + +- kfree(chip->pcm); +- chip->pcm = NULL; ++ if (rt->chip) ++ rt->chip->pcm = NULL; ++ ++ for (i = 0; i < PCM_N_URBS; i++) { ++ usb_free_urb(rt->out_urbs[i].instance); ++ kfree(rt->out_urbs[i].buffer); ++ } ++ kfree(rt); + } + + static void hiface_pcm_free(struct snd_pcm *pcm) + { +- struct pcm_runtime *rt = pcm->private_data; +- +- if (rt) +- hiface_pcm_destroy(rt->chip); ++ hiface_pcm_destroy(pcm->private_data); + } + + int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) +@@ -587,8 +591,6 @@ int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq) + return 0; + + error: +- for (i = 0; i < PCM_N_URBS; i++) +- kfree(rt->out_urbs[i].buffer); +- kfree(rt); ++ hiface_pcm_destroy(rt); + return ret; + } +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -109,10 +109,10 @@ struct ua101 { + unsigned int buffer_pos; + unsigned int queue_length; + struct ua101_urb { +- struct urb urb; +- struct usb_iso_packet_descriptor iso_frame_desc[1]; ++ struct urb *urb; + struct list_head ready_list; +- } *urbs[MAX_QUEUE_LENGTH]; ++ struct ua101 *ua; ++ } urbs[MAX_QUEUE_LENGTH]; + struct { + unsigned int size; + void *addr; +@@ -167,15 +167,15 @@ static void abort_usb_playback(struct ua101 *ua) + wake_up(&ua->alsa_playback_wait); + } + +-static void playback_urb_complete(struct urb *usb_urb) ++static void playback_urb_complete(struct urb *urb) + { +- struct ua101_urb *urb = (struct ua101_urb *)usb_urb; +- struct ua101 *ua = urb->urb.context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + +- if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ +- urb->urb.status == -ENODEV || /* device removed */ +- urb->urb.status == -ECONNRESET || /* unlinked */ +- urb->urb.status == -ESHUTDOWN)) { /* device disabled */ ++ if (unlikely(urb->status == -ENOENT || /* unlinked */ ++ urb->status == -ENODEV || /* device removed */ ++ urb->status == -ECONNRESET || /* unlinked */ ++ urb->status == -ESHUTDOWN)) { /* device disabled */ + abort_usb_playback(ua); + abort_alsa_playback(ua); + return; +@@ -184,18 +184,19 @@ static void playback_urb_complete(struct urb *usb_urb) + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { + /* append URB to FIFO */ + guard(spinlock_irqsave)(&ua->lock); +- list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); ++ list_add_tail(&ua_urb->ready_list, &ua->ready_playback_urbs); + if (ua->rate_feedback_count > 0) + queue_work(system_highpri_wq, &ua->playback_work); + ua->playback.substream->runtime->delay -= +- urb->urb.iso_frame_desc[0].length / ++ urb->iso_frame_desc[0].length / + ua->playback.frame_bytes; + } + } + + static void first_playback_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = playback_urb_complete; + playback_urb_complete(urb); +@@ -248,7 +249,8 @@ static void playback_work(struct work_struct *work) + { + struct ua101 *ua = container_of(work, struct ua101, playback_work); + unsigned int frames; +- struct ua101_urb *urb; ++ struct ua101_urb *ua_urb; ++ struct urb *urb; + bool do_period_elapsed = false; + int err; + +@@ -275,23 +277,24 @@ static void playback_work(struct work_struct *work) + ua->rate_feedback_count--; + + /* take URB out of FIFO */ +- urb = list_first_entry(&ua->ready_playback_urbs, +- struct ua101_urb, ready_list); +- list_del(&urb->ready_list); ++ ua_urb = list_first_entry(&ua->ready_playback_urbs, ++ struct ua101_urb, ready_list); ++ list_del(&ua_urb->ready_list); ++ urb = ua_urb->urb; + + /* fill packet with data or silence */ +- urb->urb.iso_frame_desc[0].length = ++ urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, +- &urb->urb, ++ urb, + frames); + else +- memset(urb->urb.transfer_buffer, 0, +- urb->urb.iso_frame_desc[0].length); ++ memset(urb->transfer_buffer, 0, ++ urb->iso_frame_desc[0].length); + + /* and off you go ... */ +- err = usb_submit_urb(&urb->urb, GFP_ATOMIC); ++ err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + abort_usb_playback(ua); + abort_alsa_playback(ua); +@@ -342,7 +345,8 @@ static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, + + static void capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + struct ua101_stream *stream = &ua->capture; + unsigned int frames, write_ptr; + bool do_period_elapsed; +@@ -413,7 +417,8 @@ static void capture_urb_complete(struct urb *urb) + + static void first_capture_urb_complete(struct urb *urb) + { +- struct ua101 *ua = urb->context; ++ struct ua101_urb *ua_urb = urb->context; ++ struct ua101 *ua = ua_urb->ua; + + urb->complete = capture_urb_complete; + capture_urb_complete(urb); +@@ -427,7 +432,7 @@ static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); ++ int err = usb_submit_urb(stream->urbs[i].urb, GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); +@@ -442,8 +447,8 @@ static void kill_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) +- if (stream->urbs[i]) +- usb_kill_urb(&stream->urbs[i]->urb); ++ if (stream->urbs[i].urb) ++ usb_kill_urb(stream->urbs[i].urb); + } + + static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) +@@ -508,7 +513,7 @@ static int start_usb_capture(struct ua101 *ua) + return err; + + clear_bit(CAPTURE_URB_COMPLETED, &ua->states); +- ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; ++ ua->capture.urbs[0].urb->complete = first_capture_urb_complete; + ua->rate_feedback_start = 0; + ua->rate_feedback_count = 0; + +@@ -550,7 +555,7 @@ static int start_usb_playback(struct ua101 *ua) + return err; + + clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); +- ua->playback.urbs[0]->urb.complete = ++ ua->playback.urbs[0].urb->complete = + first_playback_urb_complete; + scoped_guard(spinlock_irq, &ua->lock) { + INIT_LIST_HEAD(&ua->ready_playback_urbs); +@@ -580,7 +585,7 @@ static int start_usb_playback(struct ua101 *ua) + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + } +- urb = &ua->playback.urbs[i]->urb; ++ urb = ua->playback.urbs[i].urb; + urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + memset(urb->transfer_buffer, 0, +@@ -1059,7 +1064,7 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + void (*urb_complete)(struct urb *)) + { + unsigned max_packet_size = stream->max_packet_bytes; +- struct ua101_urb *urb; ++ struct urb *urb; + unsigned int b, u = 0; + + for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { +@@ -1070,23 +1075,24 @@ static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + while (size >= max_packet_size) { + if (u >= stream->queue_length) + goto bufsize_error; +- urb = kmalloc_obj(*urb); ++ urb = usb_alloc_urb(1, GFP_KERNEL); + if (!urb) + return -ENOMEM; +- usb_init_urb(&urb->urb); +- urb->urb.dev = ua->dev; +- urb->urb.pipe = stream->usb_pipe; +- urb->urb.transfer_flags = URB_NO_TRANSFER_DMA_MAP; +- urb->urb.transfer_buffer = addr; +- urb->urb.transfer_dma = dma; +- urb->urb.transfer_buffer_length = max_packet_size; +- urb->urb.number_of_packets = 1; +- urb->urb.interval = 1; +- urb->urb.context = ua; +- urb->urb.complete = urb_complete; +- urb->urb.iso_frame_desc[0].offset = 0; +- urb->urb.iso_frame_desc[0].length = max_packet_size; +- stream->urbs[u++] = urb; ++ urb->dev = ua->dev; ++ urb->pipe = stream->usb_pipe; ++ urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; ++ urb->transfer_buffer = addr; ++ urb->transfer_dma = dma; ++ urb->transfer_buffer_length = max_packet_size; ++ urb->number_of_packets = 1; ++ urb->interval = 1; ++ urb->context = &stream->urbs[u]; ++ urb->complete = urb_complete; ++ urb->iso_frame_desc[0].offset = 0; ++ urb->iso_frame_desc[0].length = max_packet_size; ++ stream->urbs[u].ua = ua; ++ stream->urbs[u].urb = urb; ++ u++; + size -= max_packet_size; + addr += max_packet_size; + dma += max_packet_size; +@@ -1104,8 +1110,8 @@ static void free_stream_urbs(struct ua101_stream *stream) + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { +- kfree(stream->urbs[i]); +- stream->urbs[i] = NULL; ++ usb_free_urb(stream->urbs[i].urb); ++ stream->urbs[i].urb = NULL; + } + } + +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -849,6 +849,27 @@ static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev) + return 0; + } + ++/* ++ * A post configuration device descriptor read is needed to make the CM1A ++ * operational after reenumeration. ++ */ ++static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) ++{ ++ struct usb_device_descriptor *desc __free(kfree) = kmalloc_obj(*desc); ++ int err; ++ ++ if (!desc) ++ return -ENOMEM; ++ ++ err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, sizeof(*desc)); ++ if (err < 0) { ++ dev_err(&dev->dev, "failed to read device descriptor: %d\n", err); ++ return err; ++ } ++ ++ return 0; ++} ++ + /* + * Some sound cards from Native Instruments are in fact compliant to the USB + * audio standard of version 2 and other approved USB standards, even though +@@ -1681,6 +1702,8 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); ++ case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ ++ return snd_usb_cm1a_boot_quirk(dev); + } + + return 0; +@@ -2390,6 +2413,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x050c, /* Behringer Flow 8 */ + QUIRK_FLAG_IFB_SILENCE_ON_EMPTY), ++ DEVICE_FLG(0x1397, 0x0510, /* Behringer UV1 */ ++ QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x152a, 0x85dd, /* SMSL USB DAC */ diff --git a/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch.sig new file mode 100644 index 0000000..59a91ae Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0522-sound-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch new file mode 100644 index 0000000..b65c5d4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch @@ -0,0 +1,1040 @@ +diff --git a/sound/hda/core/ext/controller.c b/sound/hda/core/ext/controller.c +--- a/sound/hda/core/ext/controller.c ++++ b/sound/hda/core/ext/controller.c +@@ -90,8 +90,10 @@ int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus) + + for (idx = 0; idx < link_count; idx++) { + hlink = kzalloc_obj(*hlink); +- if (!hlink) ++ if (!hlink) { ++ snd_hdac_ext_link_free_all(bus); + return -ENOMEM; ++ } + hlink->index = idx; + hlink->bus = bus; + hlink->ml_addr = bus->mlcap + AZX_ML_BASE + +diff --git a/sound/hda/core/ext/stream.c b/sound/hda/core/ext/stream.c +--- a/sound/hda/core/ext/stream.c ++++ b/sound/hda/core/ext/stream.c +@@ -102,8 +102,10 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + + for (i = 0; i < num_stream; i++) { + struct hdac_ext_stream *hext_stream = kzalloc_obj(*hext_stream); +- if (!hext_stream) ++ if (!hext_stream) { ++ snd_hdac_ext_stream_free_all(bus); + return -ENOMEM; ++ } + tag = ++stream_tag; + snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag); + idx++; +@@ -111,7 +113,6 @@ int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, + } + + return 0; +- + } + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); + +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "RB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN17-41"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -899,6 +906,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_BOARD_NAME, "TM2423"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "HP 255R 15.6 inch G10 Notebook PC"), ++ } ++ }, + {} + }; + +diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c +--- a/sound/soc/codecs/ab8500-codec.c ++++ b/sound/soc/codecs/ab8500-codec.c +@@ -14,17 +14,14 @@ + * for ST-Ericsson. + */ + +-#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include +-#include + #include + #include + #include +@@ -54,32 +51,9 @@ + /* Macrocell register definitions */ + #define AB8500_GPIO_DIR4_REG 0x13 /* Bank AB8500_MISC */ + +-/* Nr of FIR/IIR-coeff banks in ANC-block */ +-#define AB8500_NR_OF_ANC_COEFF_BANKS 2 +- +-/* Minimum duration to keep ANC IIR Init bit high or +-low before proceeding with the configuration sequence */ +-#define AB8500_ANC_SM_DELAY 2000 +- +-/* Sidetone states */ +-static const char * const enum_sid_state[] = { +- "Unconfigured", +- "Apply FIR", +- "FIR is configured", +-}; +-enum sid_state { +- SID_UNCONFIGURED = 0, +- SID_APPLY_FIR = 1, +- SID_FIR_CONFIGURED = 2, +-}; +- + /* Private data for AB8500 device-driver */ + struct ab8500_codec_drvdata { + struct regmap *regmap; +- struct mutex ctrl_lock; +- +- /* Sidetone */ +- enum sid_state sid_status; + }; + + static inline const char *amic_micbias_str(enum amic_micbias micbias) +@@ -642,9 +616,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { + NULL, 0), + /* Acoustical Noise Cancellation path */ + +- SND_SOC_DAPM_INPUT("ANC Configure Input"), +- SND_SOC_DAPM_OUTPUT("ANC Configure Output"), +- + SND_SOC_DAPM_MUX("ANC Source", + SND_SOC_NOPM, 0, 0, + dapm_anc_in_select), +@@ -687,10 +658,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { + {"Main Supply", NULL, "Audio Power"}, + {"Main Supply", NULL, "Audio Analog Power"}, + +- /* ANC Configure */ +- {"ANC Configure Input", NULL, "Main Supply"}, +- {"ANC Configure Output", NULL, "ANC Configure Input"}, +- + /* Powerup charge pump if DA1/2 is in use */ + + {"DA_IN1", NULL, "ab8500_0p"}, +@@ -972,75 +939,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes_mic2_vamicx[] = { + {"MIC2 V-AMICx Enable", NULL, "V-AMIC2"}, + }; + +-/* +- * Control-events +- */ +- +-static int sid_status_control_get(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- +- guard(mutex)(&drvdata->ctrl_lock); +- ucontrol->value.enumerated.item[0] = drvdata->sid_status; +- +- return 0; +-} +- +-/* Write sidetone FIR-coefficients configuration sequence */ +-static int sid_status_control_put(struct snd_kcontrol *kcontrol, +- struct snd_ctl_elem_value *ucontrol) +-{ +- struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(component->dev); +- unsigned int param, sidconf; +- int status = 1; +- +- dev_dbg(component->dev, "%s: Enter\n", __func__); +- +- if (ucontrol->value.enumerated.item[0] != SID_APPLY_FIR) { +- dev_err(component->dev, +- "%s: ERROR: This control supports '%s' only!\n", +- __func__, enum_sid_state[SID_APPLY_FIR]); +- return -EIO; +- } +- +- guard(mutex)(&drvdata->ctrl_lock); +- +- sidconf = snd_soc_component_read(component, AB8500_SIDFIRCONF); +- if (((sidconf & BIT(AB8500_SIDFIRCONF_FIRSIDBUSY)) != 0)) { +- if ((sidconf & BIT(AB8500_SIDFIRCONF_ENFIRSIDS)) == 0) { +- dev_err(component->dev, "%s: Sidetone busy while off!\n", +- __func__); +- status = -EPERM; +- } else { +- status = -EBUSY; +- } +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- return status; +- } +- +- snd_soc_component_write(component, AB8500_SIDFIRADR, 0); +- +- for (param = 0; param < AB8500_SID_FIR_COEFFS; param++) { +- snd_soc_component_write(component, AB8500_SIDFIRCOEF1, 0); +- snd_soc_component_write(component, AB8500_SIDFIRCOEF2, 0); +- } +- +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), +- BIT(AB8500_SIDFIRADR_FIRSIDSET)); +- snd_soc_component_update_bits(component, AB8500_SIDFIRADR, +- BIT(AB8500_SIDFIRADR_FIRSIDSET), 0); +- +- drvdata->sid_status = SID_FIR_CONFIGURED; +- +- dev_dbg(component->dev, "%s: Exit\n", __func__); +- +- return status; +-} +- + /* + * Controls - Non-DAPM ASoC + */ +@@ -1324,9 +1222,6 @@ static SOC_ENUM_SINGLE_DECL(soc_enum_bfifomast, + AB8500_FIFOCONF3, AB8500_FIFOCONF3_BFIFOMAST_SHIFT, + enum_slavemaster); + +-/* Sidetone */ +-static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_sidstate, enum_sid_state); +- + /* ANC */ + + static struct snd_kcontrol_new ab8500_ctrls[] = { +@@ -1617,8 +1512,6 @@ static struct snd_kcontrol_new ab8500_ctrls[] = { + AB8500_ANC_WARP_DELAY_MIN, AB8500_ANC_WARP_DELAY_MAX, 0), + + /* Sidetone */ +- SOC_ENUM_EXT("Sidetone Status", soc_enum_sidstate, +- sid_status_control_get, sid_status_control_put), + SOC_SINGLE_STROBE("Sidetone Reset", + AB8500_SIDFIRADR, AB8500_SIDFIRADR_FIRSIDSET, 0), + }; +@@ -2148,10 +2041,8 @@ static void ab8500_codec_of_probe(struct device *dev, struct device_node *np, + + static int ab8500_codec_probe(struct snd_soc_component *component) + { +- struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct device *dev = component->dev; + struct device_node *np = dev->of_node; +- struct ab8500_codec_drvdata *drvdata = dev_get_drvdata(dev); + struct ab8500_codec_platform_data codec_pdata; + int status; + +@@ -2184,10 +2075,6 @@ static int ab8500_codec_probe(struct snd_soc_component *component) + snd_soc_component_write(component, AB8500_SHORTCIRCONF, + BIT(AB8500_SHORTCIRCONF_HSZCDDIS)); + +- snd_soc_dapm_disable_pin(dapm, "ANC Configure Input"); +- +- mutex_init(&drvdata->ctrl_lock); +- + return status; + } + +@@ -2216,7 +2103,6 @@ static int ab8500_codec_driver_probe(struct platform_device *pdev) + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; +- drvdata->sid_status = SID_UNCONFIGURED; + dev_set_drvdata(&pdev->dev, drvdata); + + drvdata->regmap = devm_regmap_init(&pdev->dev, NULL, &pdev->dev, +diff --git a/sound/soc/codecs/cs35l56-sdw.c b/sound/soc/codecs/cs35l56-sdw.c +--- a/sound/soc/codecs/cs35l56-sdw.c ++++ b/sound/soc/codecs/cs35l56-sdw.c +@@ -386,11 +386,8 @@ static int __maybe_unused cs35l56_sdw_system_suspend(struct device *dev) + { + struct cs35l56_private *cs35l56 = dev_get_drvdata(dev); + +- if (!cs35l56->base.init_done) +- return 0; +- +- /* runtime_resume unmasks the interrupt */ +- cs35l56_mask_soundwire_interrupts(cs35l56); ++ if (cs35l56->sdw_attached) ++ cs35l56_mask_soundwire_interrupts(cs35l56); + + return cs35l56_system_suspend(dev); + } +diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c +--- a/sound/soc/codecs/cs35l56.c ++++ b/sound/soc/codecs/cs35l56.c +@@ -18,9 +18,11 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include + #include + #include +@@ -37,6 +39,13 @@ + #include "wm_adsp.h" + #include "cs35l56.h" + ++/* ++ * snd_soc_register_component() can call component_probe() on all instances ++ * in a card, so deferred registration must be protected across all instances. ++ */ ++static DEFINE_MUTEX(cs35l56_component_register_lock); ++static bool cs35l56_shutting_down; ++ + void cs35l56_mask_soundwire_interrupts(struct cs35l56_private *cs35l56) + { + /* +@@ -1957,6 +1966,11 @@ static void cs35l56_component_register_work(struct work_struct *work) + component_register_work); + int ret; + ++ guard(mutex)(&cs35l56_component_register_lock); ++ ++ if (cs35l56_shutting_down) ++ return; ++ + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(cs35l56->base.dev, pm_err); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm_err); + if (ret) { +@@ -2217,6 +2231,37 @@ EXPORT_NS_GPL_DEV_PM_OPS(cs35l56_pm_ops_i2c_spi, SND_SOC_CS35L56_CORE) = { + }; + #endif + ++static int cs35l56_reboot_notify(struct notifier_block *nb, ++ unsigned long action, void *data) ++{ ++ guard(mutex)(&cs35l56_component_register_lock); ++ cs35l56_shutting_down = true; ++ ++ return NOTIFY_DONE; ++} ++ ++static struct notifier_block cs35l56_reboot_notifier = { ++ .notifier_call = cs35l56_reboot_notify, ++}; ++ ++static int __init cs35l56_modinit(void) ++{ ++ /* ++ * Use reboot notifier to prevent race between shutdown and ++ * snd_soc_register_component(). Driver shutdown() callback would ++ * run too late, after device_shutdown() is already walking the ++ * device list that component registration can modify. ++ */ ++ return register_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_init(cs35l56_modinit); ++ ++static void __exit cs35l56_modexit(void) ++{ ++ unregister_reboot_notifier(&cs35l56_reboot_notifier); ++} ++module_exit(cs35l56_modexit); ++ + MODULE_DESCRIPTION("ASoC CS35L56 driver"); + MODULE_IMPORT_NS("SND_SOC_CS35L56_SHARED"); + MODULE_IMPORT_NS("SND_SOC_CS_AMP_LIB"); +diff --git a/sound/soc/codecs/cs35l56.h b/sound/soc/codecs/cs35l56.h +--- a/sound/soc/codecs/cs35l56.h ++++ b/sound/soc/codecs/cs35l56.h +@@ -32,6 +32,7 @@ struct sdw_slave; + struct cs35l56_private { + struct wm_adsp dsp; /* must be first member */ + struct cs35l56_base base; ++ struct work_struct component_register_work; + struct work_struct dsp_work; + struct workqueue_struct *dsp_wq; + struct snd_soc_component *component; +@@ -41,6 +42,7 @@ struct cs35l56_private { + const char *fallback_fw_suffix; + bool soft_resetting; + bool sdw_attached; ++ bool component_registered; + struct completion init_completion; + + int speaker_id; +diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c +--- a/sound/soc/codecs/es8326.c ++++ b/sound/soc/codecs/es8326.c +@@ -26,6 +26,7 @@ struct es8326_priv { + struct snd_soc_component *component; + struct delayed_work jack_detect_work; + struct delayed_work button_press_work; ++ struct delayed_work capture_pop_work; + struct snd_soc_jack *jack; + int irq; + /* The lock protects the situation that an irq is generated +@@ -628,6 +629,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_HP_DRIVER_REF, + 0x30, 0x00); + } else { ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, + 0x0F, 0x0F); + if (es8326->version > ES8326_VERSION_B) { +@@ -666,8 +668,9 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) + regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x70); + regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x00); + } +- regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, +- 0x0F, 0x00); ++ ++ queue_delayed_work(system_dfl_wq, &es8326->capture_pop_work, ++ msecs_to_jiffies(40)); + } + } + return 0; +@@ -773,6 +776,15 @@ static void es8326_disable_micbias(struct snd_soc_component *component) + snd_soc_dapm_mutex_unlock(dapm); + } + ++static void es8326_capture_pop_handler(struct work_struct *work) ++{ ++ struct es8326_priv *es8326 = ++ container_of(work, struct es8326_priv, capture_pop_work.work); ++ ++ regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, ++ 0x0F, 0x00); ++} ++ + /* + * For button detection, set the following in soundcard + * snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); +@@ -1140,6 +1152,7 @@ static int es8326_suspend(struct snd_soc_component *component) + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&es8326->jack_detect_work); ++ cancel_delayed_work_sync(&es8326->capture_pop_work); + es8326_disable_micbias(component); + es8326->calibrated = false; + regmap_write(es8326->regmap, ES8326_CLK_MUX, 0x2d); +@@ -1291,6 +1304,8 @@ static int es8326_i2c_probe(struct i2c_client *i2c) + es8326_jack_detect_handler); + INIT_DELAYED_WORK(&es8326->button_press_work, + es8326_jack_button_handler); ++ INIT_DELAYED_WORK(&es8326->capture_pop_work, ++ es8326_capture_pop_handler); + /* ES8316 is level-based while ES8326 is edge-based */ + ret = devm_request_threaded_irq(&i2c->dev, es8326->irq, NULL, es8326_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, +diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c +--- a/sound/soc/codecs/es8389.c ++++ b/sound/soc/codecs/es8389.c +@@ -812,6 +812,23 @@ static int es8389_pcm_hw_free(struct snd_pcm_substream *substream, + return 0; + } + ++static void es8389_standby(struct snd_soc_component *component) ++{ ++ struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ++ ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); ++ regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); ++ regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); ++ usleep_range(70000, 72000); ++ regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); ++ regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); ++ regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); ++ regmap_write(es8389->regmap, ES8389_RESET, 0x3E); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); ++ usleep_range(8000, 8500); ++ regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); ++} ++ + static int es8389_set_bias_level(struct snd_soc_component *component, + enum snd_soc_bias_level level) + { +@@ -834,18 +851,7 @@ static int es8389_set_bias_level(struct snd_soc_component *component, + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF1, 0x0f, 0x04); +- regmap_update_bits(es8389->regmap, ES8389_ADC_HPF2, 0x0f, 0x04); +- regmap_write(es8389->regmap, ES8389_CSM_JUMP, 0xD4); +- usleep_range(70000, 72000); +- regmap_write(es8389->regmap, ES8389_ANA_CTL1, 0x59); +- regmap_write(es8389->regmap, ES8389_ADC_EN, 0x00); +- regmap_write(es8389->regmap, ES8389_CLK_OFF1, 0x00); +- regmap_write(es8389->regmap, ES8389_RESET, 0x3E); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x80); +- usleep_range(8000, 8500); +- regmap_update_bits(es8389->regmap, ES8389_DAC_INV, 0x80, 0x00); +- ++ es8389_standby(component); + clk_disable_unprepare(es8389->mclk); + break; + case SND_SOC_BIAS_OFF: +@@ -1015,7 +1021,7 @@ static int es8389_suspend(struct snd_soc_component *component) + { + struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); + +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + regcache_cache_only(es8389->regmap, true); + regcache_mark_dirty(es8389->regmap); + +@@ -1084,7 +1090,7 @@ static int es8389_probe(struct snd_soc_component *component) + + es8389->hpf_freq = ES8389_HPF_DEFAULT; + es8389_init(component); +- es8389_set_bias_level(component, SND_SOC_BIAS_STANDBY); ++ es8389_standby(component); + + return 0; + } +diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c +--- a/sound/soc/codecs/rt1318-sdw.c ++++ b/sound/soc/codecs/rt1318-sdw.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include "rt1318-sdw.h" + +@@ -564,11 +565,10 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1318_sdw_priv *rt1318 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -582,23 +582,13 @@ static int rt1318_sdw_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt1318->sdw_slave, &stream_config, +diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c +--- a/sound/soc/codecs/rt1320-sdw.c ++++ b/sound/soc/codecs/rt1320-sdw.c +@@ -3246,7 +3246,7 @@ static int rt1320_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_component *component = dai->component; + struct rt1320_sdw_priv *rt1320 = + snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + struct sdw_port_config dmic_port_config[2]; + struct sdw_stream_runtime *sdw_stream; +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + #include + #include "rt712-sdca.h" + +@@ -1449,11 +1450,10 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id); +@@ -1471,7 +1471,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT712_AIF1) + port = 1; + else if (dai->id == RT712_AIF2) +@@ -1479,7 +1478,6 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT712_AIF1) + port = 4; + else if (dai->id == RT712_AIF3) +@@ -1488,13 +1486,8 @@ static int rt712_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt712->slave, &stream_config, +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + #include + + #include "rt721-sdca.h" +@@ -206,6 +207,7 @@ static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); ++ regmap_write(rt721->regmap, 0x2f5d, 0x1); + } + + static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) +@@ -1268,11 +1270,10 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt721_sdca_priv *rt721 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1291,7 +1292,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT721_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT721_AIF1) + port = 1; + else if (dai->id == RT721_AIF2) +@@ -1299,7 +1299,6 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT721_AIF1) + port = 2; + else if (dai->id == RT721_AIF3) +@@ -1307,13 +1306,9 @@ static int rt721_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt721->slave, &stream_config, +diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c +--- a/sound/soc/codecs/rt722-sdca.c ++++ b/sound/soc/codecs/rt722-sdca.c +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + #include + + #include "rt722-sdca.h" +@@ -1442,11 +1443,10 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -1465,7 +1465,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + * RT722_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; + if (dai->id == RT722_AIF1) + port = 1; + else if (dai->id == RT722_AIF2) +@@ -1473,7 +1472,6 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } else { +- direction = SDW_DATA_DIR_TX; + if (dai->id == RT722_AIF1) + port = 2; + else if (dai->id == RT722_AIF3) +@@ -1481,13 +1479,9 @@ static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + else + return -EINVAL; + } +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = params_channels(params); +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; + +- num_channels = params_channels(params); +- port_config.ch_mask = GENMASK(num_channels - 1, 0); ++ /* SoundWire specific configuration */ ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + retval = sdw_stream_add_slave(rt722->slave, &stream_config, +diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c +--- a/sound/soc/codecs/tas2783-sdw.c ++++ b/sound/soc/codecs/tas2783-sdw.c +@@ -1001,6 +1001,31 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, + /* SoundWire specific configuration */ + snd_sdw_params_to_config(substream, params, + &stream_config, &port_config); ++ ++ /* ++ * The two mono amps each render one channel of the stereo stream: ++ * snd_sdw_params_to_config() hands every codec the full mask for ++ * playback, which leaves the pair in mirror mode and one channel ++ * unreproduced. Claim a single channel instead, keyed off the ++ * machine-assigned component prefix rather than the SoundWire ++ * address, which is board-specific: soc_sdw_ti_amp.c names the amps ++ * tas2783-1..4. ++ * ++ * Which side an amp then renders does not follow from the bit that ++ * is set - sdw_compute_slave_ports() advances the payload offset by ++ * the popcount of ch_mask and never looks at which bit it is - but ++ * from the amp's position in the codec order of the DAI link, which ++ * on these boards matches the prefix numbering. ++ */ ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && ++ params_channels(params) == 2 && component->name_prefix) { ++ const char *idx_str = strrchr(component->name_prefix, '-'); ++ unsigned long idx; ++ ++ if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) ++ port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); ++ } ++ + /* port 1 for playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port_config.num = 1; +diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c +--- a/sound/soc/intel/avs/boards/hdaudio.c ++++ b/sound/soc/intel/avs/boards/hdaudio.c +@@ -15,6 +15,22 @@ + #include "../../../codecs/hda.h" + #include "../utils.h" + ++static int avs_link_startup(struct snd_pcm_substream *substream) ++{ ++ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); ++ const struct snd_soc_pcm_stream *stream_info; ++ struct snd_soc_dai *codec_dai; ++ ++ codec_dai = snd_soc_rtd_to_codec(rtd, 0); ++ stream_info = snd_soc_dai_get_pcm_stream(codec_dai, substream->stream); ++ ++ return snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, stream_info->sig_bits); ++} ++ ++static const struct snd_soc_ops avs_link_ops = { ++ .startup = avs_link_startup, ++}; ++ + static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, + struct snd_soc_dai_link **links) + { +@@ -43,6 +59,7 @@ static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int + dl[i].platforms = platform; + dl[i].num_platforms = 1; + dl[i].ignore_pmdown_time = 1; ++ dl[i].ops = &avs_link_ops; + + dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); +diff --git a/sound/soc/intel/avs/core.c b/sound/soc/intel/avs/core.c +--- a/sound/soc/intel/avs/core.c ++++ b/sound/soc/intel/avs/core.c +@@ -92,16 +92,28 @@ static int avs_hdac_bus_init_streams(struct hdac_bus *bus) + { + unsigned int cp_streams, pb_streams; + unsigned int gcap; ++ int ret; + + gcap = snd_hdac_chip_readw(bus, GCAP); + cp_streams = (gcap >> 8) & 0x0F; + pb_streams = (gcap >> 12) & 0x0F; + bus->num_streams = cp_streams + pb_streams; + +- snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); +- snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ ret = snd_hdac_ext_stream_init_all(bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE); ++ if (ret) ++ return ret; ++ ret = snd_hdac_ext_stream_init_all(bus, cp_streams, pb_streams, SNDRV_PCM_STREAM_PLAYBACK); ++ if (ret) ++ goto err; + +- return snd_hdac_bus_alloc_stream_pages(bus); ++ ret = snd_hdac_bus_alloc_stream_pages(bus); ++ if (ret) ++ goto err; ++ ++ return 0; ++err: ++ snd_hdac_ext_stream_free_all(bus); ++ return ret; + } + + static bool avs_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset) +@@ -383,6 +395,18 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + struct device *dev = &pci->dev; + int ret; + ++ ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); ++ if (!ipc) ++ return -ENOMEM; ++ ++ adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); ++ if (!adev->modcfg_buf) ++ return -ENOMEM; ++ ++ ret = avs_ipc_init(ipc, dev); ++ if (ret < 0) ++ return ret; ++ + ret = snd_hdac_ext_bus_init(&bus->core, dev, NULL, &soc_hda_ext_bus_ops); + if (ret < 0) + return ret; +@@ -394,17 +418,6 @@ static int avs_bus_init(struct avs_dev *adev, struct pci_dev *pci, const struct + bus->mixer_assigned = -1; + mutex_init(&bus->prepare_mutex); + +- ipc = devm_kzalloc(dev, sizeof(*ipc), GFP_KERNEL); +- if (!ipc) +- return -ENOMEM; +- ret = avs_ipc_init(ipc, dev); +- if (ret < 0) +- return ret; +- +- adev->modcfg_buf = devm_kzalloc(dev, AVS_MAILBOX_SIZE, GFP_KERNEL); +- if (!adev->modcfg_buf) +- return -ENOMEM; +- + adev->dev = dev; + adev->spec = (const struct avs_spec *)id->driver_data; + adev->ipc = ipc; +@@ -456,13 +469,14 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + + ret = pcim_request_all_regions(pci, "AVS HDAudio"); + if (ret < 0) +- return ret; ++ goto err_request_regions; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (!bus->remap_addr) { + dev_err(bus->dev, "ioremap error\n"); +- return -ENXIO; ++ ret = -ENXIO; ++ goto err_request_regions; + } + + adev->dsp_ba = pci_ioremap_bar(pci, 4); +@@ -526,6 +540,8 @@ static int avs_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) + iounmap(adev->dsp_ba); + err_remap_bar4: + iounmap(bus->remap_addr); ++err_request_regions: ++ snd_hdac_ext_bus_exit(bus); + return ret; + } + +diff --git a/sound/soc/intel/avs/debugfs.c b/sound/soc/intel/avs/debugfs.c +--- a/sound/soc/intel/avs/debugfs.c ++++ b/sound/soc/intel/avs/debugfs.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -236,15 +237,20 @@ static int strace_open(struct inode *inode, struct file *file) + if (!try_module_get(adev->dev->driver->owner)) + return -ENODEV; + +- if (kfifo_initialized(&adev->trace_fifo)) +- return -EBUSY; ++ if (kfifo_initialized(&adev->trace_fifo)) { ++ ret = -EBUSY; ++ goto err; ++ } + + ret = kfifo_alloc(&adev->trace_fifo, PAGE_SIZE, GFP_KERNEL); + if (ret < 0) +- return ret; ++ goto err; + + file->private_data = adev; + return 0; ++err: ++ module_put(adev->dev->driver->owner); ++ return ret; + } + + static int strace_release(struct inode *inode, struct file *file) +diff --git a/sound/soc/intel/avs/ipc.c b/sound/soc/intel/avs/ipc.c +--- a/sound/soc/intel/avs/ipc.c ++++ b/sound/soc/intel/avs/ipc.c +@@ -172,7 +172,7 @@ static void avs_dsp_exception_caught(struct avs_dev *adev, union avs_notify_msg + + /* Avoid deadlock as the exception may be the response to SET_D0IX. */ + if (current_work() != &ipc->d0ix_work.work) +- cancel_delayed_work_sync(&ipc->d0ix_work); ++ cancel_delayed_work(&ipc->d0ix_work); + ipc->in_d0ix = false; + /* Re-enabled on recovery completion. */ + pm_runtime_disable(adev->dev); +@@ -395,11 +395,11 @@ static int avs_dsp_do_send_msg(struct avs_dev *adev, struct avs_ipc_msg *request + struct avs_ipc *ipc = adev->ipc; + int ret; + ++ guard(mutex)(&ipc->msg_mutex); ++ + if (!ipc->ready) + return -EPERM; + +- guard(mutex)(&ipc->msg_mutex); +- + spin_lock(&ipc->rx_lock); + avs_ipc_msg_init(ipc, reply); + avs_dsp_send_tx(adev, request, true); +diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig +--- a/sound/soc/intel/boards/Kconfig ++++ b/sound/soc/intel/boards/Kconfig +@@ -533,12 +533,14 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH + select SND_SOC_CS35L56_SPI + select SND_SOC_CS35L56_SDW + select SND_SOC_ES9356 ++ imply SND_SOC_TAC5XX2_SDW + select SND_SOC_DMIC + select SND_SOC_INTEL_HDA_DSP_COMMON + imply SND_SOC_SDW_MOCKUP + help + Add support for Intel SoundWire-based platforms connected to +- MAX98373, RT700, RT711, RT1308 and RT715 ++ MAX98373, RT700, RT711, RT1308, RT715, TAC5XX2_SDW family (including ++ TAC5572, TAC5682). + If unsure select "N". + + endif +diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c +--- a/sound/soc/intel/boards/sof_rt5682.c ++++ b/sound/soc/intel/boards/sof_rt5682.c +@@ -908,6 +908,14 @@ static const struct platform_device_id board_ids[] = { + SOF_SSP_PORT_BT_OFFLOAD(2) | + SOF_BT_OFFLOAD_PRESENT), + }, ++ { ++ .name = "nvl_rt5682_def", ++ .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | ++ SOF_SSP_PORT_CODEC(0) | ++ SOF_SSP_PORT_AMP(1) | ++ SOF_SSP_PORT_BT_OFFLOAD(2) | ++ SOF_BT_OFFLOAD_PRESENT), ++ }, + { + .name = "ptl_rt5682_c1_h02", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | +diff --git a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-nvl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-nvl-match.c +@@ -51,10 +51,10 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_nvl_machines[] = { + }, + { + .comp_ids = &nvl_rt5682_rt5682s_hp, +- .drv_name = "sof_rt5682", +- .sof_tplg_filename = "sof-nvl-rt5682", /* the tplg suffix is added at run time */ +- .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | +- SND_SOC_ACPI_TPLG_INTEL_SSP_MSB, ++ .drv_name = "nvl_rt5682_def", ++ .sof_tplg_filename = "sof-nvl", /* the tplg suffix is added at run time */ ++ .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_AMP_NAME | ++ SND_SOC_ACPI_TPLG_INTEL_CODEC_NAME, + }, + /* place amp/hdmi-in only boards in the end of table */ + { diff --git a/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch.sig new file mode 100644 index 0000000..1e9594c Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0523-asoc-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch b/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch new file mode 100644 index 0000000..39c0ebf --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch @@ -0,0 +1,135 @@ +diff --git a/sound/core/init.c b/sound/core/init.c +--- a/sound/core/init.c ++++ b/sound/core/init.c +@@ -364,7 +364,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, + sound_debugfs_root); + #endif + #ifdef CONFIG_SND_CTL_DEBUG +- card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL); ++ card->value_buf = kmalloc_obj(*card->value_buf); + if (!card->value_buf) + return -ENOMEM; + #endif +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4304,6 +4304,7 @@ enum { + ALC287_FIXUP_LEGION_16ACHG6, + ALC287_FIXUP_CS35L41_I2C_2, + ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED, ++ ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, + ALC287_FIXUP_CS35L41_I2C_4, + ALC245_FIXUP_CS35L41_SPI_1, + ALC245_FIXUP_CS35L41_SPI_2, +@@ -6615,6 +6616,12 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_MUTE_LED, + }, ++ [ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS] = { ++ .type = HDA_FIXUP_FUNC, ++ .v.func = cs35l41_fixup_i2c_two, ++ .chained = true, ++ .chain_id = ALC245_FIXUP_HP_X360_MUTE_LEDS, ++ }, + [ALC287_FIXUP_CS35L41_I2C_4] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, +@@ -7372,6 +7379,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8158, "HP", ALC256_FIXUP_HP_HEADSET_MIC), + SND_PCI_QUIRK(0x103c, 0x820d, "HP Pavilion 15", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x8256, "HP", ALC221_FIXUP_HP_FRONT_MIC), ++ SND_PCI_QUIRK(0x103c, 0x8259, "HP OMEN 15-ax202nf", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x827e, "HP x360", ALC295_FIXUP_HP_X360), + SND_PCI_QUIRK(0x103c, 0x827f, "HP x360", ALC269_FIXUP_HP_MUTE_LED_MIC3), + SND_PCI_QUIRK(0x103c, 0x82bf, "HP G3 mini", ALC221_FIXUP_HP_MIC_NO_PRESENCE), +@@ -7582,6 +7590,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), +@@ -7674,7 +7683,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8dcd, "HP Victus 15-fa2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8d9b, "HP 17 Turbine OmniBook 7 UMA", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), +diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c +--- a/sound/usb/endpoint.c ++++ b/sound/usb/endpoint.c +@@ -449,7 +449,9 @@ static void push_back_to_ready_list(struct snd_usb_endpoint *ep, + struct snd_urb_ctx *ctx) + { + guard(spinlock_irqsave)(&ep->lock); +- list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); ++ /* ctx may still be linked: a stale completion racing a stop/restart. */ ++ if (list_empty(&ctx->ready_list)) ++ list_add_tail(&ctx->ready_list, &ep->ready_playback_urbs); + } + + /* +@@ -1037,6 +1039,7 @@ void snd_usb_endpoint_sync_pending_stop(struct snd_usb_endpoint *ep) + */ + static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + { ++ struct snd_urb_ctx *ctx, *n; + unsigned int i; + + if (!force && atomic_read(&ep->running)) +@@ -1046,7 +1049,9 @@ static int stop_urbs(struct snd_usb_endpoint *ep, bool force, bool keep_pending) + return 0; + + scoped_guard(spinlock_irqsave, &ep->lock) { +- INIT_LIST_HEAD(&ep->ready_playback_urbs); ++ /* Unlink each ctx; INIT_LIST_HEAD() alone would leave them looking linked. */ ++ list_for_each_entry_safe(ctx, n, &ep->ready_playback_urbs, ready_list) ++ list_del_init(&ctx->ready_list); + ep->next_packet_head = 0; + ep->next_packet_queued = 0; + } +diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c +--- a/sound/usb/implicit.c ++++ b/sound/usb/implicit.c +@@ -76,6 +76,7 @@ static const struct snd_usb_implicit_fb_match playback_implicit_fb_quirks[] = { + + /* Implicit feedback quirk table for capture: only FIXED type */ + static const struct snd_usb_implicit_fb_match capture_implicit_fb_quirks[] = { ++ IMPLICIT_FB_FIXED_DEV(0x1397, 0x0004, 0x01, 1), /* Behringer FCA1616 */ + {} /* terminator */ + }; + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -531,6 +531,15 @@ static const struct usbmix_name_map audient_id24_map[] = { + {} + }; + ++/* ++ * The GC553Pro returns no data for GET_CUR on its advertised mute control. ++ * SET_CUR succeeds but does not mute capture, so skip the control entirely. ++ */ ++static const struct usbmix_name_map avermedia_gc553pro_map[] = { ++ { 3, NULL, UAC_FU_MUTE }, ++ {} ++}; ++ + /* + * Control map entries + */ +@@ -577,6 +586,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0763, 0x2031), + .selector_map = c400_selectors, + }, ++ { ++ .id = USB_ID(0x07ca, 0x1553), ++ .map = avermedia_gc553pro_map, ++ }, + { + .id = USB_ID(0x08bb, 0x2702), + .map = linex_map, diff --git a/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch.sig new file mode 100644 index 0000000..ac1447b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0524-sound-fixes-4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch b/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch new file mode 100644 index 0000000..843a983 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch @@ -0,0 +1,549 @@ +diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h +--- a/include/sound/soc_sdw_utils.h ++++ b/include/sound/soc_sdw_utils.h +@@ -250,8 +250,6 @@ int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, +- struct snd_soc_dai *dai); + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix); + + /* MAXIM codec support */ +diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c +--- a/sound/soc/amd/acp/acp-sdw-sof-mach.c ++++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c +@@ -314,7 +314,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) + if (!sof_aux) + return -ENOMEM; + +- ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_devs); ++ ret = asoc_sdw_parse_sdw_endpoints(dev, ctx, sof_aux, sof_dais, sof_ends, &num_confs); + if (ret < 0) + return ret; + +@@ -390,7 +390,7 @@ static int mc_probe(struct platform_device *pdev) + ctx->private = amd_ctx; + card = &ctx->card; + card->dev = &pdev->dev; +- card->name = "amd-soundwire"; ++ card->name = "amd-sdw"; + card->owner = THIS_MODULE; + card->late_probe = asoc_sdw_card_late_probe; + +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -524,13 +524,13 @@ config SND_SOC_ADAU1977 + tristate + + config SND_SOC_ADAU1977_SPI +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - SPI" + depends on SPI_MASTER + select SND_SOC_ADAU1977 + select REGMAP_SPI + + config SND_SOC_ADAU1977_I2C +- tristate ++ tristate "Analog Devices ADAU1977/ADAU1978/ADAU1979 CODEC - I2C" + depends on I2C + select SND_SOC_ADAU1977 + select REGMAP_I2C +diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c +--- a/sound/soc/codecs/adau1977-i2c.c ++++ b/sound/soc/codecs/adau1977-i2c.c +@@ -34,9 +34,18 @@ static const struct i2c_device_id adau1977_i2c_ids[] = { + }; + MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids); + ++static const struct of_device_id adau1977_i2c_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, adau1977_i2c_of_match); ++ + static struct i2c_driver adau1977_i2c_driver = { + .driver = { + .name = "adau1977", ++ .of_match_table = adau1977_i2c_of_match, + }, + .probe = adau1977_i2c_probe, + .id_table = adau1977_i2c_ids, +diff --git a/sound/soc/codecs/adau1977-spi.c b/sound/soc/codecs/adau1977-spi.c +--- a/sound/soc/codecs/adau1977-spi.c ++++ b/sound/soc/codecs/adau1977-spi.c +@@ -53,18 +53,18 @@ static const struct spi_device_id adau1977_spi_ids[] = { + }; + MODULE_DEVICE_TABLE(spi, adau1977_spi_ids); + +-static const struct of_device_id adau1977_spi_of_match[] __maybe_unused = { +- { .compatible = "adi,adau1977" }, +- { .compatible = "adi,adau1978" }, +- { .compatible = "adi,adau1979" }, +- { }, ++static const struct of_device_id adau1977_spi_of_match[] = { ++ { .compatible = "adi,adau1977" }, ++ { .compatible = "adi,adau1978" }, ++ { .compatible = "adi,adau1979" }, ++ { }, + }; + MODULE_DEVICE_TABLE(of, adau1977_spi_of_match); + + static struct spi_driver adau1977_spi_driver = { + .driver = { + .name = "adau1977", +- .of_match_table = of_match_ptr(adau1977_spi_of_match), ++ .of_match_table = adau1977_spi_of_match, + }, + .probe = adau1977_spi_probe, + .id_table = adau1977_spi_ids, +diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c +--- a/sound/soc/codecs/rt712-sdca-sdw.c ++++ b/sound/soc/codecs/rt712-sdca-sdw.c +@@ -18,12 +18,16 @@ + static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201a ... 0x201f: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2230 ... 0x2232: + case 0x2f01 ... 0x2f0a: + case 0x2f35 ... 0x2f36: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x2f50: + case 0x2f54: + case 0x2f58 ... 0x2f5d: +@@ -48,6 +52,7 @@ static bool rt712_sdca_readable_register(struct device *dev, unsigned int reg) + static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case 0x004d: + case 0x201b: + case 0x201c: + case 0x201d: +@@ -56,6 +61,9 @@ static bool rt712_sdca_volatile_register(struct device *dev, unsigned int reg) + case 0x2230: + case 0x2f01: + case 0x2f35: ++ case 0x2f3a: ++ case 0x2f3d: ++ case 0x2f41: + case 0x320c: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT_GE49, RT712_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUNC_NUM_HID, RT712_SDCA_ENT_HID01, RT712_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... +diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c +--- a/sound/soc/codecs/rt712-sdca.c ++++ b/sound/soc/codecs/rt712-sdca.c +@@ -73,14 +73,57 @@ static int rt712_sdca_index_update_bits(struct rt712_sdca_priv *rt712, + return rt712_sdca_index_write(rt712, nid, reg, tmp); + } + ++static void rt712_sdca_clk_patch(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0017); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0001); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF1, 0x0081, 0x0081); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x0080, 0x0080); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF2, 0x001f, 0x0000); ++ regmap_update_bits(rt712->regmap, RT712_PLL2_CONF3, 0x0010, 0x0010); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++} ++ ++static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) ++{ ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0x0800, ++ 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_REG, 0x49, 0xf000, ++ 0x0000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x0c, 0xc000, ++ 0xc000); ++ rt712_sdca_index_update_bits(rt712, RT712_VENDOR_ANALOG_CTL, 0x00, 0xc000, ++ 0xc000); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0081); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x02); ++ usleep_range(1000, 1100); ++ regmap_write(rt712->regmap, RT712_SDW_ROOT_CLK, 0x03); ++ usleep_range(1000, 1100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); ++} ++ + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + { + unsigned int val, loop_rc = 0, loop_dc = 0; + struct device *dev; + struct regmap *regmap = rt712->regmap; ++ unsigned int clk_base; + int chk_cnt = 100; + int ret = 0; + ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ + mutex_lock(&rt712->calibrate_mutex); + dev = regmap_get_device(regmap); + +@@ -109,8 +152,35 @@ static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) + if (ret < 0) + goto _cali_fail_; + } +- if (loop_dc == chk_cnt) +- dev_err(dev, "%s, calibration time-out!\n", __func__); ++ ++ if (loop_dc == chk_cnt) { ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_FSM_CTL, 0x4100); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0x7883); ++ rt712_sdca_index_write(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, 0xf893); ++ rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ for (loop_dc = 0; loop_dc < chk_cnt && ++ (val & RT712_DAC_DC_CALI_TRIGGER); loop_dc++) { ++ usleep_range(10000, 11000); ++ ret = rt712_sdca_index_read(rt712, RT712_VENDOR_CALI, ++ RT712_DAC_DC_CALI_CTL1, &val); ++ ++ if (ret < 0) ++ goto _cali_fail_; ++ } ++ ++ if (loop_dc == chk_cnt) ++ dev_err(dev, "%s, calibration time-out!\n", __func__); ++ else ++ dev_dbg(dev, "%s, calibration success!\n", __func__); ++ } + + if (loop_dc == chk_cnt || loop_rc == chk_cnt) + ret = -ETIMEDOUT; +@@ -1759,9 +1829,13 @@ static void rt712_sdca_va_io_init(struct rt712_sdca_priv *rt712) + + static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + { +- int ret = 0; + unsigned int jack_func_status, mic_func_status, amp_func_status; + struct device *dev = &rt712->slave->dev; ++ unsigned int clk_base; ++ int ret = 0; ++ ++ regmap_read(rt712->regmap, RT712_SDW_ROOT_CLK, &clk_base); ++ dev_dbg(dev, "%s clk_base=%x", __func__, clk_base); + + regmap_read(rt712->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT712_SDCA_ENT0, RT712_SDCA_CTL_FUNC_STATUS, 0), &jack_func_status); +@@ -1773,6 +1847,12 @@ static void rt712_sdca_vb_io_init(struct rt712_sdca_priv *rt712) + __func__, jack_func_status, mic_func_status, amp_func_status); + + rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_JD_CTL3, 0x7778); ++ ++ if (clk_base == RT712_CLK_FREQ_24_576MHZ) { ++ rt712_sdca_clk_patch(rt712); ++ rt712_sdca_clk_patch2(rt712); ++ } ++ + /* DMIC */ + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt712->first_hw_init)) { + rt712_sdca_index_write(rt712, RT712_VENDOR_HDA_CTL, RT712_DMIC2_FU_IT_FLOAT_CTL, 0x1526); +diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h +--- a/sound/soc/codecs/rt712-sdca.h ++++ b/sound/soc/codecs/rt712-sdca.h +@@ -162,6 +162,16 @@ struct rt712_dmic_kctrl_priv { + #define RT712_EAPD_HIGH 0x2 + #define RT712_EAPD_LOW 0x0 + ++/* SDW clock root frequency */ ++#define RT712_SDW_ROOT_CLK 0x004d ++#define RT712_SDW_SCALE_CLK0 0x0062 ++#define RT712_SDW_SCALE_CLK1 0x0072 ++ ++/* PLL2 config */ ++#define RT712_PLL2_CONF1 0x2f3a ++#define RT712_PLL2_CONF2 0x2f3d ++#define RT712_PLL2_CONF3 0x2f41 ++ + /* RC Calibration register */ + #define RT712_RC_CAL 0x3201 + +@@ -254,6 +264,14 @@ enum rt712_sdca_version { + RT712_VB, + }; + ++enum { ++ RT712_CLK_FREQ_19_2_MHZ = 1, ++ RT712_CLK_FREQ_24MHZ = 2, ++ RT712_CLK_FREQ_24_576MHZ = 3, ++ RT712_CLK_FREQ_22_5792MHZ = 4, ++}; ++ ++ + int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave); + int rt712_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave); +diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c +--- a/sound/soc/codecs/rt721-sdca-sdw.c ++++ b/sound/soc/codecs/rt721-sdca-sdw.c +@@ -70,6 +70,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re + case 0x0310100: + case 0x2000000 ... 0x2000003: + case 0x2000013: ++ case 0x2000026: + case 0x200002c: + case 0x200003c: + case 0x2000046: +@@ -142,6 +143,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x200000d: + case 0x2000019: + case 0x2000020: ++ case 0x2000026: + case 0x200002c: + case 0x2000030: + case 0x2000046: +@@ -155,6 +157,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re + case 0x5810039: + case 0x5b10018: + case 0x5b10019: ++ case 0x6100006: + return true; + default: + return false; +diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c +--- a/sound/soc/codecs/rt721-sdca.c ++++ b/sound/soc/codecs/rt721-sdca.c +@@ -1497,6 +1497,15 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, + &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); + } + ++static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) ++{ ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_REG, ++ RT721_VD_HIDDEN_CTRL, RT721_HIDDEN_REG_SW_RESET, ++ RT721_HIDDEN_REG_SW_RESET); ++ rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, ++ RT721_HDA_LEGACY_RESET_CTL, 0x1, 0x1); ++} ++ + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + { + struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); +@@ -1530,9 +1539,17 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) + } + + pm_runtime_get_noresume(&slave->dev); ++ ++ if (!rt721->first_hw_init) ++ rt721_sdca_reset(rt721); ++ + rt721_sdca_dmic_preset(rt721); + rt721_sdca_amp_preset(rt721); + rt721_sdca_jack_preset(rt721); ++ ++ if (rt721->hs_jack && (!rt721->first_hw_init)) ++ rt721_sdca_jack_init(rt721); ++ + if (rt721->first_hw_init) { + regcache_cache_bypass(rt721->regmap, false); + regcache_mark_dirty(rt721->regmap); +diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c +--- a/sound/soc/codecs/wm_adsp.c ++++ b/sound/soc/codecs/wm_adsp.c +@@ -775,9 +775,12 @@ static int wm_adsp_request_firmware_file(struct wm_adsp *dsp, + s++; + } + ++ adsp_dbg(dsp, "Try '%s'\n", fw->filename); + ret = wm_adsp_firmware_request(&fw->firmware, fw->filename, cs_dsp->dev); + if (ret < 0) { +- adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ if (ret != -ENOENT) ++ adsp_dbg(dsp, "Failed to request '%s': %d\n", fw->filename, ret); ++ + kfree(fw->filename); + fw->filename = NULL; + if (ret != -ENOENT) +diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c +--- a/sound/soc/intel/boards/sof_es8336.c ++++ b/sound/soc/intel/boards/sof_es8336.c +@@ -360,6 +360,15 @@ static const struct dmi_system_id sof_es8336_quirk_table[] = { + .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | + SOC_ES8336_HEADSET_MIC1) + }, ++ { ++ .callback = sof_es8336_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "NDZ-WXX9"), ++ }, ++ .driver_data = (void *)(SOF_ES8336_HEADPHONE_GPIO | ++ SOC_ES8336_HEADSET_MIC1) ++ }, + {} + }; + +diff --git a/sound/soc/sdw_utils/soc_sdw_cs_amp.c b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +--- a/sound/soc/sdw_utils/soc_sdw_cs_amp.c ++++ b/sound/soc/sdw_utils/soc_sdw_cs_amp.c +@@ -14,7 +14,6 @@ + #include + #include + +-#define CS_AMP_CHANNELS_PER_AMP 4 + #define CS35L56_SPK_VOLUME_0DB 400 /* 0dB Max */ + + int asoc_sdw_cs35l56_volume_limit(struct snd_soc_card *card, const char *name_prefix) +@@ -64,51 +63,6 @@ int asoc_sdw_cs_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai + } + EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_rtd_init, "SND_SOC_SDW_UTILS"); + +-int asoc_sdw_cs_spk_feedback_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +-{ +- const struct snd_soc_dai_link *dai_link = rtd->dai_link; +- const struct snd_soc_dai_link_ch_map *ch_map; +- const struct snd_soc_dai_link_component *codec_dlc; +- struct snd_soc_dai *codec_dai; +- u8 ch_slot[8] = {}; +- unsigned int amps_per_bus, ch_per_amp, mask; +- int i, ret; +- +- WARN_ON(dai_link->num_cpus > ARRAY_SIZE(ch_slot)); +- +- /* +- * CS35L56 has 4 TX channels. When the capture is aggregated the +- * same bus slots will be allocated to all the amps on a bus. Only +- * one amp on that bus can be transmitting in each slot so divide +- * the available 4 slots between all the amps on a bus. +- */ +- amps_per_bus = dai_link->num_codecs / dai_link->num_cpus; +- if ((amps_per_bus == 0) || (amps_per_bus > CS_AMP_CHANNELS_PER_AMP)) { +- dev_err(rtd->card->dev, "Illegal num_codecs:%u / num_cpus:%u\n", +- dai_link->num_codecs, dai_link->num_cpus); +- return -EINVAL; +- } +- +- ch_per_amp = CS_AMP_CHANNELS_PER_AMP / amps_per_bus; +- +- for_each_rtd_ch_maps(rtd, i, ch_map) { +- codec_dlc = snd_soc_link_to_codec(rtd->dai_link, i); +- codec_dai = snd_soc_find_dai(codec_dlc); +- mask = GENMASK(ch_per_amp - 1, 0) << ch_slot[ch_map->cpu]; +- +- ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, mask, 4, 32); +- if (ret < 0) { +- dev_err(rtd->card->dev, "Failed to set TDM slot:%d\n", ret); +- return ret; +- } +- +- ch_slot[ch_map->cpu] += ch_per_amp; +- } +- +- return 0; +-} +-EXPORT_SYMBOL_NS(asoc_sdw_cs_spk_feedback_rtd_init, "SND_SOC_SDW_UTILS"); +- + int asoc_sdw_cs_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, +diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c +--- a/sound/soc/sdw_utils/soc_sdw_utils.c ++++ b/sound/soc/sdw_utils/soc_sdw_utils.c +@@ -811,7 +811,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -840,7 +839,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -869,7 +867,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -898,7 +895,6 @@ struct asoc_sdw_codec_info codec_info_list[] = { + .dai_name = "cs35l56-sdw1c", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_AMP_IN_DAI_ID}, +- .rtd_init = asoc_sdw_cs_spk_feedback_rtd_init, + }, + }, + .dai_num = 2, +@@ -1565,7 +1561,7 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link_ch_map *ch_maps; + int ch = params_channels(params); +- unsigned int ch_mask; ++ unsigned int cpu_ch_mask, codec_ch_mask; + int num_codecs; + int step; + int i; +@@ -1575,8 +1571,9 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + + /* Identical data will be sent to all codecs in playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- ch_mask = GENMASK(ch - 1, 0); ++ cpu_ch_mask = GENMASK(ch - 1, 0); + step = 0; ++ codec_ch_mask = 0; + } else { + num_codecs = rtd->dai_link->num_codecs; + +@@ -1586,17 +1583,24 @@ int asoc_sdw_hw_params(struct snd_pcm_substream *substream, + return -EINVAL; + } + +- ch_mask = GENMASK(ch / num_codecs - 1, 0); +- step = hweight_long(ch_mask); ++ cpu_ch_mask = GENMASK(ch / num_codecs - 1, 0); ++ step = hweight_long(cpu_ch_mask); ++ codec_ch_mask = cpu_ch_mask; + } + + /* + * The captured data will be combined from each cpu DAI if the dai + * link has more than one codec DAIs. Set codec channel mask and + * ASoC will set the corresponding channel numbers for each cpu dai. ++ * ++ * sdw_stream_add_slave() assigns different payload offsets to each ++ * codec in a capture stream, so that the same channels on each ++ * codec map to different channels on the CPU. + */ +- for_each_link_ch_maps(rtd->dai_link, i, ch_maps) +- ch_maps->cpu_ch_mask = ch_mask << (i * step); ++ for_each_link_ch_maps(rtd->dai_link, i, ch_maps) { ++ ch_maps->cpu_ch_mask = cpu_ch_mask << (i * step); ++ ch_maps->codec_ch_mask = codec_ch_mask; ++ } + + return 0; + } diff --git a/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch.sig b/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch.sig new file mode 100644 index 0000000..0b22640 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0525-asoc-fixes-3.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch b/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch new file mode 100644 index 0000000..3b71148 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch @@ -0,0 +1,13 @@ +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 +@@ -1197,7 +1197,8 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) + enable = true; + break; + default: +- break; ++ dev_err(sdev->dev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); ++ return -EINVAL; + } + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); diff --git a/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig b/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig new file mode 100644 index 0000000..086972e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0526-sof-amd-acp7x-suspend-unknown-revision.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch b/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch new file mode 100644 index 0000000..f9378d4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch @@ -0,0 +1,513 @@ +diff --git a/include/sound/core.h b/include/sound/core.h +--- a/include/sound/core.h ++++ b/include/sound/core.h +@@ -340,6 +340,15 @@ static inline void snd_card_unref(struct snd_card *card) + + DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T)) + ++/* ++ * For automatic error handling at probe time, assign the card like: ++ * struct snd_card *card __free(snd_card_free) = NULL; ++ * then create and process as usual. ++ * But, don't forget to clear to NULL at successful return for avoiding ++ * the unexpected cleanup! ++ */ ++DEFINE_FREE(snd_card_free, struct snd_card *, if (_T) snd_card_free(_T)) ++ + #define snd_card_set_dev(card, devptr) ((card)->dev = (devptr)) + + /* device.c */ +diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c +--- a/sound/core/seq/seq_compat.c ++++ b/sound/core/seq/seq_compat.c +@@ -44,7 +44,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned + return -EFAULT; + data->kernel = NULL; + +- err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ scoped_guard(mutex, &client->ioctl_mutex) { ++ err = snd_seq_kernel_client_ctl(client->number, cmd, data); ++ } + if (err < 0) + return err; + +diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c +--- a/sound/hda/codecs/realtek/alc269.c ++++ b/sound/hda/codecs/realtek/alc269.c +@@ -4025,6 +4025,7 @@ enum { + ALC269_FIXUP_SONY_VAIO, + ALC275_FIXUP_SONY_VAIO_GPIO2, + ALC269_FIXUP_DELL_M101Z, ++ ALC256_FIXUP_ASUS_PM3406CHA, + ALC269_FIXUP_SKU_IGNORE, + ALC269_FIXUP_ASUS_G73JW, + ALC269_FIXUP_ASUS_N7601ZM_PINS, +@@ -4124,6 +4125,7 @@ enum { + ALC285_FIXUP_HP_SPECTRE_X360_EB1, + ALC285_FIXUP_HP_SPECTRE_X360_DF1, + ALC285_FIXUP_HP_ENVY_X360, ++ ALC285_FIXUP_HP_ENVY_13AQ, + ALC288_FIXUP_DELL_HEADSET_MODE, + ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, + ALC288_FIXUP_DELL_XPS_13, +@@ -4199,6 +4201,7 @@ enum { + ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE, + ALC225_FIXUP_WYSE_AUTO_MUTE, + ALC225_FIXUP_WYSE_DISABLE_MIC_VREF, ++ ALC225_FIXUP_HP_AIO_SPK, + ALC286_FIXUP_ACER_AIO_HEADSET_MIC, + ALC256_FIXUP_ASUS_HEADSET_MIC, + ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, +@@ -4385,6 +4388,7 @@ enum { + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, + ALC285_LENOVO_DAC_RENAME, + ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ++ ALC256_FIXUP_IPASON_SMARTBOOK_S1, + }; + + /* A special fixup for Lenovo C940 and Yoga Duet 7; +@@ -4482,6 +4486,14 @@ static const struct hda_fixup alc269_fixups[] = { + {} + } + }, ++ [ALC256_FIXUP_ASUS_PM3406CHA] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x7f20 }, ++ {} ++ } ++ }, + [ALC269_FIXUP_SKU_IGNORE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_sku_ignore, +@@ -5720,6 +5732,14 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, ++ [ALC225_FIXUP_HP_AIO_SPK] = { ++ .type = HDA_FIXUP_VERBS, ++ .v.verbs = (const struct hda_verb[]) { ++ { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, ++ { 0x20, AC_VERB_SET_PROC_COEF, 0x0220 }, ++ { } ++ }, ++ }, + [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { +@@ -6401,6 +6421,15 @@ static const struct hda_fixup alc269_fixups[] = { + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, + }, ++ [ALC285_FIXUP_HP_ENVY_13AQ] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x14, 0x90170150 }, /* front speakers */ ++ { } ++ }, ++ .chained = true, ++ .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, ++ }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_ideapad_s740_coef, +@@ -7088,6 +7117,8 @@ static const struct hda_fixup alc269_fixups[] = { + [ALC236_FIXUP_DELL_HP_POP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_invalidate_dacs, ++ .chained = true, ++ .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE + }, + [ALC274_FIXUP_HP_89E9_GPIO] = { + .type = HDA_FIXUP_FUNC, +@@ -7150,6 +7181,13 @@ static const struct hda_fixup alc269_fixups[] = { + { } + } + }, ++ [ALC256_FIXUP_IPASON_SMARTBOOK_S1] = { ++ .type = HDA_FIXUP_PINS, ++ .v.pins = (const struct hda_pintbl[]) { ++ { 0x1b, 0x90170110 }, /* the real internal speaker */ ++ { } ++ }, ++ }, + }; + + static const struct hda_quirk alc269_fixup_tbl[] = { +@@ -7197,6 +7235,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), ++ SND_PCI_QUIRK(0x1025, 0x151e, "Acer Nitro AN515-45", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC287_FIXUP_ACER_MICMUTE_LED), +@@ -7210,8 +7249,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), ++ SND_PCI_QUIRK(0x1025, 0x173b, "Acer Swift SFG16-72", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), +@@ -7317,6 +7358,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), + SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), ++ SND_PCI_QUIRK(0x103c, 0x2164, "HP Pavilion 15 Notebook PC", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), +@@ -7399,9 +7441,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x854a, "HP EliteBook 830 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x854d, "HP EliteBook 840 G6", ALC285_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), ++ HDA_CODEC_QUIRK(0x103c, 0x85ba, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), + SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), + SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), + SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), ++ SND_PCI_QUIRK(0x103c, 0x85f1, "HP Laptop 15-dw1xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), +@@ -7409,6 +7453,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x86ad, "HP ENVY 13-aq1xxx", ALC285_FIXUP_HP_ENVY_13AQ), + SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), + SND_PCI_QUIRK(0x103c, 0x86c8, "HP Laptop 14s-dr1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), +@@ -7437,6 +7482,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), ++ SND_PCI_QUIRK(0x103c, 0x87b2, "HP Pavilion Gaming Laptop 15-ec1xxx", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x87b7, "HP Laptop 14-fq0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x87cb, "HP Pavilion 15-eg0xxx", ALC287_FIXUP_HP_GPIO_LED), +@@ -7497,6 +7543,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8975, "HP EliteBook x360 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x897d, "HP mt440 Mobile Thin Client U74", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8981, "HP Elite Dragonfly G3", ALC245_FIXUP_CS35L41_SPI_4), ++ SND_PCI_QUIRK(0x103c, 0x8987, "HP Pavilion 14-dv1001TU", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898a, "HP Pavilion 15-eg100", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x898e, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x898f, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7534,7 +7581,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), +- SND_PCI_QUIRK(0x103c, 0x8a31, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a36, "HP Pavilion Plus 14-eh0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8a3d, "HP Victus 15-fb0xxx (MB 8A3D)", ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT), +@@ -7595,6 +7642,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), + SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8bc7, "HP Pavilion 15-eh3174nw", ALC287_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bd4, "HP Victus 16-s0xxx (MB 8BD4)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), +@@ -7617,6 +7665,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), + SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), + SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), ++ SND_PCI_QUIRK(0x103c, 0x8c2f, "HP Victus 15-fb2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), +@@ -7726,6 +7775,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), ++ SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), +@@ -7829,6 +7879,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1652, "ASUS ROG Zephyrus Do 15 SE", ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), ++ SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), + SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), +@@ -7931,7 +7982,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x3ef0, "ASUS Strix G635LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f00, "ASUS Strix G815LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f10, "ASUS Strix G835LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C_ASUS), + SND_PCI_QUIRK(0x1043, 0x3f30, "ASUS Strix G815LR_LW", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3fd0, "ASUS B3605CVA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3ff0, "ASUS B5405CVA", ALC245_FIXUP_CS35L41_SPI_2), +@@ -7954,6 +8005,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), + SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), + SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE), ++ SND_PCI_QUIRK(0x10ec, 0x111e, "Higole F9B", ALC290_FIXUP_MONO_SPEAKERS), + SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), + SND_PCI_QUIRK(0x10ec, 0x119e, "Positivo SU C1400", ALC269_FIXUP_ASPIRE_HEADSET_MIC), + SND_PCI_QUIRK(0x10ec, 0x11bc, "VAIO VJFE-IL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), +@@ -8179,7 +8231,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), +- SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; + * use codec SSID to distinguish them + */ +@@ -8204,6 +8255,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), +@@ -8254,6 +8306,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x38d5, "Yoga S990-16 Pro IMH YC Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d6, "Yoga S990-16 Pro IMH VECO Quad", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38d7, "Lenovo Yoga 9 14IMH9", ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN), ++ SND_PCI_QUIRK(0x17aa, 0x38dc, "Lenovo ThinkBook G8", ALC285_FIXUP_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), +@@ -8273,7 +8326,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), + SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), +- SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), ++ SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), + SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), +@@ -8369,6 +8422,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), + SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), ++ SND_PCI_QUIRK(0x2782, 0x0206, "IPASON SmartBook S1", ALC256_FIXUP_IPASON_SMARTBOOK_S1), + SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), + SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), +diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c +--- a/sound/hda/codecs/realtek/realtek.c ++++ b/sound/hda/codecs/realtek/realtek.c +@@ -1292,6 +1292,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1407,6 +1408,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_write_coef_idx(codec, 0x45, 0xc489); + snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); +@@ -1562,6 +1564,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) + alc_process_coef_fw(codec, coef0225); + alc_hp_enable_unmute(codec, 75); + break; ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1663,6 +1666,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1780,6 +1784,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) + }; + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + break; +@@ -1881,6 +1886,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) + } + + switch (codec->core.vendor_id) { ++ case 0x10ec0235: + case 0x10ec0255: + alc_process_coef_fw(codec, coef0255); + msleep(300); +diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c +--- a/sound/hda/controllers/tegra.c ++++ b/sound/hda/controllers/tegra.c +@@ -374,7 +374,8 @@ static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) + * in powers of 2, next available ratio is 16 which can be + * used as a limiting factor here. + */ +- if (of_device_is_compatible(np, "nvidia,tegra30-hda")) ++ if (of_device_is_compatible(np, "nvidia,tegra30-hda") || ++ of_device_is_compatible(np, "nvidia,tegra194-hda")) + chip->bus.core.sdo_limit = 16; + + /* codec detection */ +diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c +--- a/sound/pci/ctxfi/cthw20k2.c ++++ b/sound/pci/ctxfi/cthw20k2.c +@@ -1895,6 +1895,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + + if (hw->model == CTSB1270) { + /* Set up the PCM4220 ADC on Titanium HD */ ++ data = hw_read_20kx(hw, GPIO_DATA); + data &= ~0x0C; + if (1 == info->msr) + data |= 0x00; /* Single Speed Mode 32-50kHz */ +@@ -1903,6 +1904,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) + else + data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */ + hw_write_20kx(hw, GPIO_DATA, data); ++ usleep_range(10000, 11000); + } + + hw_adc_start(hw); +diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c +--- a/sound/pci/ice1712/ice1712.c ++++ b/sound/pci/ice1712/ice1712.c +@@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) + { + static int dev; +- struct snd_card *card __free(snd_card_unref) = NULL; ++ struct snd_card *card __free(snd_card_free) = NULL; + struct snd_ice1712 *ice; + int pcm_dev = 0, err; + const struct snd_ice1712_card_info * const *tbl, *c; +diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c +--- a/sound/usb/caiaq/device.c ++++ b/sound/usb/caiaq/device.c +@@ -562,6 +562,9 @@ static int snd_probe(struct usb_interface *intf, + ret = init_card(caiaqdev(card)); + if (ret < 0) { + dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret); ++#ifdef CONFIG_SND_USB_CAIAQ_INPUT ++ snd_usb_caiaq_input_disconnect(caiaqdev(card)); ++#endif + snd_card_free(card); + return ret; + } +diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c +--- a/sound/usb/line6/playback.c ++++ b/sound/usb/line6/playback.c +@@ -182,6 +182,13 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) + + fsize *= bytes_per_frame; + ++ if (fsize > line6pcm->max_packet_size_out) { ++ dev_err(line6pcm->line6->ifcdev, ++ "playback packet too large: %d > %d\n", ++ fsize, line6pcm->max_packet_size_out); ++ return -EMSGSIZE; ++ } ++ + fout->offset = urb_size; + fout->length = fsize; + urb_size += fsize; +diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c +--- a/sound/usb/misc/ua101.c ++++ b/sound/usb/misc/ua101.c +@@ -1007,6 +1007,15 @@ static int detect_usb_format(struct ua101 *ua) + } + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->playback.max_packet_bytes = usb_endpoint_maxp(epd); ++ ++ if (ua->capture.max_packet_bytes / ua->capture.frame_bytes * ++ ua->playback.frame_bytes > ua->playback.max_packet_bytes) { ++ dev_err(&ua->dev->dev, ++ "playback packet size %u too small for %u capture frames\n", ++ ua->playback.max_packet_bytes, ++ ua->capture.max_packet_bytes / ua->capture.frame_bytes); ++ return -ENXIO; ++ } + return 0; + } + +diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c +--- a/sound/usb/mixer_maps.c ++++ b/sound/usb/mixer_maps.c +@@ -749,6 +749,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { + .id = USB_ID(0x0db0, 0xa073), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, ++ { /* MSI MAG B850M MORTAR WIFI */ ++ .id = USB_ID(0x0db0, 0xcc78), ++ .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, ++ }, + { /* MSI TRX40 */ + .id = USB_ID(0x0db0, 0x543d), + .map = trx40_mobo_map, +diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c +--- a/sound/usb/quirks.c ++++ b/sound/usb/quirks.c +@@ -1702,10 +1702,12 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, + switch (id) { + case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ + return snd_usb_motu_m_series_boot_quirk(dev); +- case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ +- return snd_usb_cm1a_boot_quirk(dev); + } + ++ /* Behringer devices may need explicit device descriptor read at boot */ ++ if (USB_ID_VENDOR(id) == 0x1397) ++ return snd_usb_cm1a_boot_quirk(dev); ++ + return 0; + } + +@@ -2366,6 +2368,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_CTL_MSG_DELAY_1M), + DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ + QUIRK_FLAG_ALWAYS_SET_RATE), ++ DEVICE_FLG(0x0b05, 0x1826, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), ++ DEVICE_FLG(0x0b05, 0x1827, /* ASUS SupremeFX Hi-Fi */ ++ QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ + QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ +@@ -2510,8 +2516,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x262a, 0x9302, /* ddHiFi TC44C */ + QUIRK_FLAG_DSD_RAW), +- DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ +- QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x2772, 0x0502, /* Musical Fidelity M6s DAC */ + 0), /* for avoiding QUIRK_FLAG_DSD_RAW with vendor match */ + DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ +@@ -2542,6 +2546,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_IGNORE_CTL_ERROR), + DEVICE_FLG(0x3255, 0x0000, /* Luxman D-10X */ + QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), ++ DEVICE_FLG(0x32bb, 0x0004, /* HiBy FC4 */ ++ QUIRK_FLAG_DSD_RAW), + DEVICE_FLG(0x3302, 0x17c2, /* TTGK Technology USB-C Audio */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ +@@ -2608,6 +2614,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2622, /* IAG Limited devices */ + QUIRK_FLAG_DSD_RAW), ++ VENDOR_FLG(0x2708, /* Audient devices */ ++ QUIRK_FLAG_IGNORE_CTL_ERROR), + VENDOR_FLG(0x2772, /* Musical Fidelity devices */ + QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x278b, /* Rotel? */ diff --git a/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch.sig b/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch.sig new file mode 100644 index 0000000..2654792 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0527-sound-fixes-5.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch b/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch new file mode 100644 index 0000000..2290ae4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch @@ -0,0 +1,523 @@ +diff --git a/drivers/spi/spi-cs42l43.c b/drivers/spi/spi-cs42l43.c +--- a/drivers/spi/spi-cs42l43.c ++++ b/drivers/spi/spi-cs42l43.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -215,12 +216,108 @@ static size_t cs42l43_spi_max_length(struct spi_device *spi) + return CS42L43_SPI_MAX_LENGTH; + } + ++/* ++ * Workaround needed for two speaker ID pins in one ACPI GpioIo() but ++ * the Linux-specific _DSD property only contains one pin. ++ * Create a temporary acpi_gpio_mapping pointing at both pins. ++ */ ++static const struct acpi_gpio_params cs42l43_2bit_speaker_id_from_one_gpioio_params[] = { ++ [0] = { ++ .crs_entry_index = 0, ++ .line_index = 0, ++ }, ++ [1] = { ++ .crs_entry_index = 0, ++ .line_index = 1, ++ }, ++}; ++ ++static const struct acpi_gpio_mapping cs42l43_2bit_speaker_id_from_one_gpioio_mapping[] = { ++ { ++ .name = "spk-id-quirk-gpios", ++ .data = cs42l43_2bit_speaker_id_from_one_gpioio_params, ++ .size = ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params), ++ }, ++ { } ++}; ++ ++static int cs42l43_get_2bit_speaker_id_from_one_gpioio(struct cs42l43_spi *priv, int *result) ++{ ++ struct fwnode_reference_args args; ++ struct acpi_device *adev; ++ struct gpio_desc *desc; ++ u32 spkid = 0; ++ int i, ret; ++ ++ /* Use the _DSD property to get the node containing the GpioIo() */ ++ ret = fwnode_property_get_reference_args(dev_fwnode(priv->dev), "spk-id-gpios", ++ NULL, 3, 0, &args); ++ if (ret) ++ return ret; ++ ++ struct fwnode_handle *fwnode __free(fwnode_handle) = args.fwnode; ++ ++ /* An acpi_gpio_mapping must be added to the node that contains the GpioIo() */ ++ adev = to_acpi_device_node(fwnode); ++ if (!adev) ++ return -EINVAL; ++ ++ ret = acpi_dev_add_driver_gpios(adev, cs42l43_2bit_speaker_id_from_one_gpioio_mapping); ++ if (ret) ++ return ret; ++ ++ /* gpiod_get_array() can't read from a mapping in a child node */ ++ for (i = 0; i < ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params); i++) { ++ desc = fwnode_gpiod_get_index(fwnode, "spk-id-quirk", i, GPIOD_IN, ++ dev_name(priv->dev)); ++ if (IS_ERR(desc)) { ++ ret = PTR_ERR(desc); ++ goto out; ++ } ++ ++ ret = gpiod_get_value_cansleep(desc); ++ gpiod_put(desc); ++ if (ret < 0) ++ goto out; ++ ++ spkid |= (u32)ret << i; ++ } ++ ++ dev_dbg(priv->dev, "spk-id-gpios = %u\n", spkid); ++ *result = spkid; ++ ret = 0; ++out: ++ acpi_dev_remove_driver_gpios(adev); ++ ++ return ret; ++} ++ ++static const struct dmi_system_id cs42l43_spk_id_quirks[] = { ++ { ++ .ident = "Dell XPS 13 DX13260", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53"), ++ }, ++ .driver_data = cs42l43_get_2bit_speaker_id_from_one_gpioio, ++ }, ++ { } ++}; ++ + static int cs42l43_get_speaker_id_gpios(struct cs42l43_spi *priv, int *result) + { ++ const struct dmi_system_id *dmi_id; + struct gpio_descs *descs; + u32 spkid; + int i, ret; + ++ dmi_id = dmi_first_match(cs42l43_spk_id_quirks); ++ if (dmi_id) { ++ int (*get_speaker_id)(struct cs42l43_spi *priv, int *result) = dmi_id->driver_data; ++ ++ return get_speaker_id(priv, result); ++ } ++ + descs = gpiod_get_array_optional(priv->dev, "spk-id", GPIOD_IN); + if (!descs) + return 0; +diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c +--- a/sound/soc/amd/acp-config.c ++++ b/sound/soc/amd/acp-config.c +@@ -51,6 +51,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83V9"), + }, + }, ++ { ++ /* Lenovo Yoga Pro 7 14ASP9 (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83HN"), ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), +@@ -63,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), + }, + }, ++ { ++ /* ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) */ ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook S14 UM5406GA"), ++ }, ++ }, + { + /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ + .matches = { +diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig +--- a/sound/soc/amd/acp/Kconfig ++++ b/sound/soc/amd/acp/Kconfig +@@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH + select SND_SOC_RT715_SDW + select SND_SOC_RT715_SDCA_SDW + select SND_SOC_RT722_SDCA_SDW ++ select SND_SOC_TAS2783_SDW + help + This option enables Legacy(No DSP) sound card support for SoundWire + enabled AMD platforms along with ACP PDM controller. +diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c +--- a/sound/soc/amd/acp/amd-acp70-acpi-match.c ++++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c +@@ -748,7 +748,27 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_only[] = { + {} + }; + ++static const struct snd_soc_acpi_link_adr acp70_tas2783a_l0_rt712_vb_l1[] = { ++ { ++ .mask = BIT(0), ++ .num_adr = ARRAY_SIZE(tas2783_2_adr), ++ .adr_d = tas2783_2_adr, ++ }, ++ { ++ .mask = BIT(1), ++ .num_adr = ARRAY_SIZE(rt712_vb_1_group1_adr), ++ .adr_d = rt712_vb_1_group1_adr, ++ }, ++ {} ++}; ++ + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { ++ { ++ .link_mask = BIT(0) | BIT(1), ++ .links = acp70_tas2783a_l0_rt712_vb_l1, ++ .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, ++ .drv_name = "amd_sdw", ++ }, + { + .link_mask = BIT(0), + .links = acp70_tas2783_2, +diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c +--- a/sound/soc/amd/yc/acp6x-mach.c ++++ b/sound/soc/amd/yc/acp6x-mach.c +@@ -248,6 +248,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "21J6"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "21JN"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +@@ -381,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), + } + }, ++ { ++ .driver_data = &acp6x_card, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83UU"), ++ } ++ }, + { + .driver_data = &acp6x_card, + .matches = { +diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig +--- a/sound/soc/codecs/Kconfig ++++ b/sound/soc/codecs/Kconfig +@@ -1989,9 +1989,7 @@ config SND_SOC_RT715_SDCA_SDW + config SND_SOC_RT766_SDCA_SDW + tristate "Realtek RT766 SDCA Codec - SDW" + depends on SOUNDWIRE +- depends on SND_SOC_SDCA +- select SND_SOC_SDCA_HID +- select SND_SOC_SDCA_IRQ ++ depends on SND_SOC_SDCA_CLASS + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + +diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c +--- a/sound/soc/codecs/lpass-wsa-macro.c ++++ b/sound/soc/codecs/lpass-wsa-macro.c +@@ -1841,6 +1841,7 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + int event) + { + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); ++ unsigned int gain; + u16 gain_reg; + u16 reg; + struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); +@@ -1882,6 +1883,8 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, + CDC_WSA_RX_PGA_HALF_DB_MASK, + CDC_WSA_RX_PGA_HALF_DB_ENABLE); + } ++ gain = snd_soc_component_read(component, gain_reg); ++ snd_soc_component_write(component, gain_reg, gain); + wsa_macro_config_ear_spkr_gain(component, wsa, + event, gain_reg); + break; +diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c +--- a/sound/soc/codecs/max98363.c ++++ b/sound/soc/codecs/max98363.c +@@ -200,7 +200,7 @@ static int max98363_sdw_dai_hw_params(struct snd_pcm_substream *substream, + struct max98363_priv *max98363 = + snd_soc_component_get_drvdata(component); + +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_runtime *stream; +diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c +--- a/sound/soc/codecs/rt1017-sdca-sdw.c ++++ b/sound/soc/codecs/rt1017-sdca-sdw.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -573,11 +574,10 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + { + struct snd_soc_component *component = dai->component; + struct rt1017_sdca_priv *rt1017 = snd_soc_component_get_drvdata(component); +- struct sdw_stream_config stream_config; ++ struct sdw_stream_config stream_config = {0}; + struct sdw_port_config port_config; +- enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; +- int retval, port, num_channels, ch_mask; ++ int retval, port; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); +@@ -591,28 +591,17 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + + /* SoundWire specific configuration */ + /* port 1 for playback */ +- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +- direction = SDW_DATA_DIR_RX; ++ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port = 1; +- } else { +- direction = SDW_DATA_DIR_TX; ++ else + port = 2; +- } + +- num_channels = params_channels(params); +- ch_mask = (1 << num_channels) - 1; +- +- stream_config.frame_rate = params_rate(params); +- stream_config.ch_count = num_channels; +- stream_config.bps = snd_pcm_format_width(params_format(params)); +- stream_config.direction = direction; +- +- port_config.ch_mask = ch_mask; ++ snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + port_config.num = port; + + dev_dbg(dai->dev, "frame_rate %d, ch_count %d, bps %d, direction %d, ch_mask %d, port: %d\n", +- params_rate(params), num_channels, snd_pcm_format_width(params_format(params)), +- direction, ch_mask, port); ++ stream_config.frame_rate, stream_config.ch_count, stream_config.bps, ++ stream_config.direction, port_config.ch_mask, port); + + retval = sdw_stream_add_slave(rt1017->sdw_slave, &stream_config, + &port_config, 1, sdw_stream); +diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c +--- a/sound/soc/codecs/rt274.c ++++ b/sound/soc/codecs/rt274.c +@@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, + unsigned int val = 0; + int d_len_code = 0, c_len_code = 0; + ++ /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ + switch (params_rate(params)) { +- /* bit 14 0:48K 1:44.1K */ + case 44100: ++ val |= BIT(14); ++ break; + case 48000: + break; + default: +diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c +--- a/sound/soc/codecs/rt286.c ++++ b/sound/soc/codecs/rt286.c +@@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) + + if (rt286->i2c->irq) + schedule_delayed_work(&rt286->jack_detect_work, +- msecs_to_jiffies(50)); ++ msecs_to_jiffies(1250)); + return 0; + } + +diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c +--- a/sound/soc/codecs/wm8903.c ++++ b/sound/soc/codecs/wm8903.c +@@ -676,7 +676,7 @@ SOC_ENUM("DRC Decay Rate", drc_decay), + SOC_ENUM("DRC FF Delay", drc_ff_delay), + SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), + SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +-SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), ++SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_1, 6, 3, 0, drc_tlv_max), + SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), + SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), + SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c +--- a/sound/soc/intel/boards/bytcr_rt5651.c ++++ b/sound/soc/intel/boards/bytcr_rt5651.c +@@ -410,6 +410,23 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, ++ { ++ /* Chuwi Hi8 (CWI513) with a BIOS using generic DMI strings */ ++ .callback = byt_rt5651_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), ++ DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), ++ /* Above matches are too generic, add bios-date match */ ++ DMI_MATCH(DMI_BIOS_DATE, "05/21/2016"), ++ }, ++ .driver_data = (void *)(BYT_RT5651_MCLK_EN | ++ BYT_RT5651_JD1_1 | ++ BYT_RT5651_OVCD_TH_2000UA | ++ BYT_RT5651_OVCD_SF_1P0 | ++ BYT_RT5651_IN2_MAP | ++ BYT_RT5651_HP_LR_SWAPPED | ++ BYT_RT5651_MONO_SPEAKER), ++ }, + { + /* Chuwi Vi8 Plus (CWI519) */ + .callback = byt_rt5651_quirk_cb, +diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +--- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +@@ -745,6 +745,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + .links = lnl_cs42l43_l0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-cs42l43-l0.tplg", ++ .machine_check = snd_soc_acpi_intel_force_function_topology, + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +@@ -53,6 +53,22 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) + } + EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + ++static const struct dmi_system_id force_function_topology_quirk_table[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83R0"), ++ }, ++ }, ++ {} ++}; ++ ++bool snd_soc_acpi_intel_force_function_topology(void *arg) ++{ ++ return !dmi_check_system(force_function_topology_quirk_table); ++} ++EXPORT_SYMBOL_NS(snd_soc_acpi_intel_force_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); ++ + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) + { + return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && +diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +--- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h ++++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +@@ -11,6 +11,7 @@ + + bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); + bool snd_soc_acpi_intel_no_function_topology(void *arg); ++bool snd_soc_acpi_intel_force_function_topology(void *arg); + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); + + #endif +diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c +--- a/sound/soc/qcom/lpass-cpu.c ++++ b/sound/soc/qcom/lpass-cpu.c +@@ -1234,8 +1234,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) + } + if (drvdata->mi2s_playback_sd_mode[dai_id] == + LPAIF_I2SCTL_MODE_QUAD01) { +- variant->dai_driver[dai_id].playback.channels_min = 4; +- variant->dai_driver[dai_id].playback.channels_max = 4; ++ variant->dai_driver[i].playback.channels_min = 4; ++ variant->dai_driver[i].playback.channels_max = 4; + } + } + +diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c +--- a/sound/soc/sdca/sdca_class_function.c ++++ b/sound/soc/sdca/sdca_class_function.c +@@ -388,20 +388,27 @@ static int class_function_probe(struct auxiliary_device *auxdev, + + ret = devm_pm_runtime_enable(dev); + if (ret) +- return ret; ++ goto err_pm; + + ret = class_function_boot(drv); + if (ret) +- return ret; ++ goto err_pm; + + ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais); +- if (ret) +- return dev_err_probe(dev, ret, "failed to register component\n"); ++ if (ret) { ++ dev_err_probe(dev, ret, "failed to register component\n"); ++ goto err_pm; ++ } + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return 0; ++ ++err_pm: ++ pm_runtime_put_sync(dev); ++ ++ return ret; + } + + static void class_function_remove(struct auxiliary_device *auxdev) +@@ -490,8 +497,6 @@ static int class_function_suspend(struct device *dev) + struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); + int ret; + +- drv->suspended = true; +- + /* Ensure runtime resume runs on resume */ + ret = pm_runtime_resume_and_get(dev); + if (ret) { +@@ -499,6 +504,8 @@ static int class_function_suspend(struct device *dev) + return ret; + } + ++ drv->suspended = true; ++ + sdca_irq_disable(drv->function, drv->core->irq_info); + + ret = pm_runtime_force_suspend(dev); +diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c +--- a/sound/soc/tegra/tegra186_asrc.c ++++ b/sound/soc/tegra/tegra186_asrc.c +@@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = { + tegra186_asrc_put_input_threshold), + + SOC_SINGLE_EXT("Stream6 Input Threshold", +- ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, ++ ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0, + tegra186_asrc_get_input_threshold, + tegra186_asrc_put_input_threshold), + diff --git a/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch.sig b/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch.sig new file mode 100644 index 0000000..ad208a3 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0528-asoc-fixes-4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0541-cvs-nova-lake-acpi-id.patch b/pkgbuilds/linux-omarchy-muqss/0541-cvs-nova-lake-acpi-id.patch new file mode 100644 index 0000000..2e401f7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -867,6 +867,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-muqss/0541-cvs-nova-lake-acpi-id.patch.sig b/pkgbuilds/linux-omarchy-muqss/0541-cvs-nova-lake-acpi-id.patch.sig new file mode 100644 index 0000000..2e58e13 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0541-cvs-nova-lake-acpi-id.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch b/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch new file mode 100644 index 0000000..6a78d2e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch @@ -0,0 +1,33 @@ +diff --git a/drivers/media/i2c/cvs/core.c b/drivers/media/i2c/cvs/core.c +--- a/drivers/media/i2c/cvs/core.c ++++ b/drivers/media/i2c/cvs/core.c +@@ -723,8 +723,6 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + } + + if (ctx->res == ICVS_FULLCAP) { +- struct gpio_desc *wake; +- + ctx->rst = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->rst)) { + ret = dev_err_probe(dev, PTR_ERR(ctx->rst), +@@ -732,14 +730,12 @@ static int cvs_core_probe(struct device *dev, struct i2c_client *i2c) + goto err_put_ipu; + } + +- wake = devm_gpiod_get(dev, "wake", GPIOD_IN); +- if (IS_ERR(wake)) { +- ret = dev_err_probe(dev, PTR_ERR(wake), +- "failed to get wake GPIO\n"); +- goto err_put_ipu; +- } +- +- ctx->irq = gpiod_to_irq(wake); ++ /* ++ * Do not request the line: another device's _CRS may list ++ * the same pin, and its driver would then fail with -EBUSY. ++ */ ++ ctx->irq = acpi_dev_gpio_irq_get_by(ACPI_COMPANION(dev), ++ "wake", 0); + if (ctx->irq < 0) { + ret = dev_err_probe(dev, ctx->irq, + "failed to get wake IRQ\n"); diff --git a/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig b/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig new file mode 100644 index 0000000..69eeeea Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0542-media-cvs-wake-irq-without-claiming-gpio.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch b/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch new file mode 100644 index 0000000..9ffdd20 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch @@ -0,0 +1,3293 @@ +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -259,6 +259,7 @@ config VIDEO_IMX335 + + config VIDEO_IMX355 + tristate "Sony IMX355 sensor support" ++ select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX355 camera. +@@ -287,6 +288,16 @@ config VIDEO_IMX415 + To compile this driver as a module, choose M here: the + module will be called imx415. + ++config VIDEO_IMX471 ++ tristate "Sony IMX471 sensor support" ++ select V4L2_CCI_I2C ++ help ++ This is a Video4Linux2 sensor driver for the Sony ++ IMX471 camera. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called imx471. ++ + config VIDEO_MAX9271_LIB + tristate + +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -61,6 +61,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o + obj-$(CONFIG_VIDEO_IMX355) += imx355.o + obj-$(CONFIG_VIDEO_IMX412) += imx412.o + obj-$(CONFIG_VIDEO_IMX415) += imx415.o ++obj-$(CONFIG_VIDEO_IMX471) += imx471.o + obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o + obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o + obj-$(CONFIG_VIDEO_KS0127) += ks0127.o +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 +@@ -31,6 +31,7 @@ + + #define PCI_DEVICE_ID_INTEL_IPU7 0x645d /* MTL / LNL */ + #define PCI_DEVICE_ID_INTEL_IPU7P5 0xb05d /* ARL / PTL */ ++#define PCI_DEVICE_ID_INTEL_IPU8 0xd719 /* NVL */ + + /* + * IPU7 PCI device IDs not covered by ipu6_pci_tbl in ipu6-pci-table.h. +@@ -39,6 +40,7 @@ + static const struct pci_device_id icvs_ipu7_tbl[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU7P5) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU8) }, + { } + }; + +diff --git a/drivers/media/i2c/cvs/icvs.h b/drivers/media/i2c/cvs/icvs.h +--- a/drivers/media/i2c/cvs/icvs.h ++++ b/drivers/media/i2c/cvs/icvs.h +@@ -432,7 +432,6 @@ enum icvs_state { + * @freq_ctrl: (future) frequency control pointer + * @pads: Local media pads (sink/source) + * @nr_of_lanes: Active CSI-2 lane count +- * @link_freq: Current link frequency (Hz) + * @ipu_link: PM runtime device link (IPU consumer, CVS supplier) + * @res: Resource capability (light/full) + * @caps: Reported device protocol capabilities +@@ -458,7 +457,6 @@ struct icvs { + struct v4l2_ctrl *freq_ctrl; + struct media_pad pads[ICVS_CSI_NUM_PADS]; + u32 nr_of_lanes; +- u64 link_freq; + struct device_link *ipu_link; + enum icvs_resources res; + struct icvs_dev_capabilities caps; +diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c +--- a/drivers/media/i2c/cvs/v4l2.c ++++ b/drivers/media/i2c/cvs/v4l2.c +@@ -46,6 +46,7 @@ static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { + /** + * csi_set_link_cfg - Program default CSI-2 link parameters + * @ctx: CVS device context ++ * @link_freq: Link frequency (Hz) + * + * Populates a HOST_SET_MIPI_CONFIG command using current lane count and + * link frequency, then submits it to the device. +@@ -53,12 +54,12 @@ static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { + * + * Return: 0 on success or negative errno. + */ +-static int csi_set_link_cfg(struct icvs *ctx) ++static int csi_set_link_cfg(struct icvs *ctx, u64 link_freq) + { + struct icvs_cmd cmd = { + .cmd_id = cpu_to_be16(ICVS_HOST_SET_MIPI_CONFIG), + .param.conf.nr_of_lanes = ctx->nr_of_lanes, +- .param.conf.link_freq = ctx->link_freq, ++ .param.conf.link_freq = link_freq, + }; + size_t cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf); + +@@ -91,7 +92,7 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev *remote_sd = + media_entity_to_v4l2_subdev(ctx->remote->entity); + struct device *dev = cvs_dev(ctx); +- s64 freq; ++ s64 link_freq; + int ret; + + /* cvs_set_link_owner(ICVS_CSI_LINK_HOST) */ +@@ -99,15 +100,14 @@ static int cvs_csi_enable_streams(struct v4l2_subdev *sd, + if (ret < 0) + return ret; + +- freq = v4l2_get_link_freq(ctx->remote, 0, 0); +- if (freq < 0) { +- ret = freq; ++ link_freq = v4l2_get_link_freq(ctx->remote, 0, 0); ++ if (link_freq < 0) { ++ ret = link_freq; + goto err_rpm_put; + } +- ctx->link_freq = freq; + + if (ctx->i2c_client) { +- ret = csi_set_link_cfg(ctx); ++ ret = csi_set_link_cfg(ctx, link_freq); + if (ret < 0) + goto err_rpm_put_sync; + } +@@ -345,7 +345,6 @@ static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + if (freq < 0) + return -EINVAL; + +- ctx->link_freq = freq; + cfg->link_freq = freq; + + return 0; +diff --git a/drivers/media/i2c/imx355.c b/drivers/media/i2c/imx355.c +--- a/drivers/media/i2c/imx355.c ++++ b/drivers/media/i2c/imx355.c +@@ -9,74 +9,97 @@ + #include + #include + #include ++#include + #include + #include + ++#include + #include + #include + #include + #include + +-#define IMX355_REG_MODE_SELECT 0x0100 ++#define IMX355_REG_MODE_SELECT CCI_REG8(0x0100) + #define IMX355_MODE_STANDBY 0x00 + #define IMX355_MODE_STREAMING 0x01 + + /* Chip ID */ +-#define IMX355_REG_CHIP_ID 0x0016 ++#define IMX355_REG_CHIP_ID CCI_REG16(0x0016) + #define IMX355_CHIP_ID 0x0355 + ++#define IMX355_REG_LANE_SEL CCI_REG8(0x0114) ++ ++/* PLL registers that depend on the external clock frequency */ ++#define IMX355_REG_EXTCLK_FREQ CCI_REG16(0x0136) ++#define IMX355_REG_PLL_OP_PREDIV CCI_REG8(0x030d) ++#define IMX355_REG_PLL_OP_MUL CCI_REG16(0x030e) ++#define IMX355_REG_PLL_IVT_PCK_DIV CCI_REG8(0x0301) ++#define IMX355_REG_PLL_IVT_SYSCK_DIV CCI_REG8(0x0303) ++#define IMX355_PLL_OP_PREDIV 2 ++#define IMX355_PLL_IVT_PCK_DIV 5 ++ + /* V_TIMING internal */ +-#define IMX355_REG_FLL 0x0340 ++#define IMX355_REG_FLL CCI_REG16(0x0340) + #define IMX355_FLL_MAX 0xffff ++#define IMX355_VBLANK_MIN 20 ++ ++#define IMX355_REG_LLP CCI_REG16(0x0342) ++#define IMX355_LLP_MAX 0xffff ++ ++#define IMX355_REG_X_ADD_START CCI_REG16(0x0344) ++#define IMX355_REG_Y_ADD_START CCI_REG16(0x0346) ++#define IMX355_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX355_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX355_REG_X_OUT_SIZE CCI_REG16(0x034c) ++#define IMX355_REG_Y_OUT_SIZE CCI_REG16(0x034e) + + /* Exposure control */ +-#define IMX355_REG_EXPOSURE 0x0202 ++#define IMX355_REG_EXPOSURE CCI_REG16(0x0202) + #define IMX355_EXPOSURE_MIN 1 + #define IMX355_EXPOSURE_STEP 1 + #define IMX355_EXPOSURE_DEFAULT 0x0282 ++#define IMX355_EXPOSURE_OFFSET 10 + + /* Analog gain control */ +-#define IMX355_REG_ANALOG_GAIN 0x0204 ++#define IMX355_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX355_ANA_GAIN_MIN 0 + #define IMX355_ANA_GAIN_MAX 960 + #define IMX355_ANA_GAIN_STEP 1 + #define IMX355_ANA_GAIN_DEFAULT 0 + + /* Digital gain control */ +-#define IMX355_REG_DPGA_USE_GLOBAL_GAIN 0x3070 +-#define IMX355_REG_DIG_GAIN_GLOBAL 0x020e ++#define IMX355_REG_DPGA_USE_GLOBAL_GAIN CCI_REG8(0x3070) ++#define IMX355_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) + #define IMX355_DGTL_GAIN_MIN 256 + #define IMX355_DGTL_GAIN_MAX 4095 + #define IMX355_DGTL_GAIN_STEP 1 + #define IMX355_DGTL_GAIN_DEFAULT 256 + + /* Test Pattern Control */ +-#define IMX355_REG_TEST_PATTERN 0x0600 ++#define IMX355_REG_TEST_PATTERN CCI_REG16(0x0600) + #define IMX355_TEST_PATTERN_DISABLED 0 + #define IMX355_TEST_PATTERN_SOLID_COLOR 1 + #define IMX355_TEST_PATTERN_COLOR_BARS 2 + #define IMX355_TEST_PATTERN_GRAY_COLOR_BARS 3 + #define IMX355_TEST_PATTERN_PN9 4 + ++#define IMX355_REG_REQ_LINK_BIT_RATE CCI_REG16(0x0820) ++ ++#define IMX355_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX355_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX355_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ + /* Flip Control */ +-#define IMX355_REG_ORIENTATION 0x0101 ++#define IMX355_REG_ORIENTATION CCI_REG8(0x0101) + +-/* default link frequency and external clock */ +-#define IMX355_LINK_FREQ_DEFAULT 360000000LL +-#define IMX355_EXT_CLK 19200000 +-#define IMX355_LINK_FREQ_INDEX 0 +- +-/* number of data lanes */ +-#define IMX355_DATA_LANES 4 +- +-struct imx355_reg { +- u16 address; +- u8 val; +-}; ++#define IMX355_PIXEL_ARRAY_TOP 0 ++#define IMX355_PIXEL_ARRAY_LEFT 0 ++#define IMX355_PIXEL_ARRAY_WIDTH 3280 ++#define IMX355_PIXEL_ARRAY_HEIGHT 2464 + + struct imx355_reg_list { + u32 num_of_regs; +- const struct imx355_reg *regs; ++ const struct cci_reg_sequence *regs; + }; + + /* Mode : resolution and related config&values */ +@@ -85,28 +108,58 @@ struct imx355_mode { + u32 width; + /* Frame height */ + u32 height; ++ struct v4l2_rect crop; + + /* V-timing */ + u32 fll_def; +- u32 fll_min; + + /* H-timing */ + u32 llp; + +- /* index of link frequency */ +- u32 link_freq_index; +- + /* Default register values */ + struct imx355_reg_list reg_list; + }; + ++struct imx355_clk_params { ++ u32 ext_clk; ++ u16 extclk_freq; /* External clock (MHz) in 8.8 fixed point) */ ++ u16 pll_op_mpy[2]; /* OP system PLL multiplier */ ++ u8 pll_op_prediv[2]; /* OP system pre PLL d */ ++}; ++ ++/* ++ * The clock tree is in single PLL mode, so PREDIV_VT and MPY_IVT do nothing. ++ * In 4 lane mode the MIPI rate is 360Mhz (720Mbit/s) and pixel rate is ++ * 288MPix/s. ++ * In 2 lane mode the MIPI rate is 444MHz (888Mbit/s) and pixel rate ++ * 177.6MPix/s with a 24MHz clock, and 441.6MHz (883.2Mbit/s) and 176.6MPix/s ++ * with a 19.2MHz clock. ++ */ ++static const struct imx355_clk_params imx355_clk_params[] = { ++ { ++ .ext_clk = 19200000, ++ .extclk_freq = 0x1333, ++ .pll_op_mpy = { 75, 92 }, ++ .pll_op_prediv = { 2, 2 } ++ }, ++ { ++ .ext_clk = 24000000, ++ .extclk_freq = 0x1800, ++ .pll_op_mpy = { 60, 111 }, ++ .pll_op_prediv = { 2, 3 } ++ }, ++}; ++ + struct imx355_hwcfg { ++ s64 link_freq_menu; + unsigned long link_freq_bitmap; ++ unsigned int num_lanes; + }; + + struct imx355 { + struct device *dev; + struct clk *clk; ++ struct regmap *regmap; + + struct v4l2_subdev sd; + struct media_pad pad; +@@ -114,24 +167,14 @@ struct imx355 { + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; +- struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + +- /* Current mode */ +- const struct imx355_mode *cur_mode; +- + struct imx355_hwcfg *hwcfg; +- +- /* +- * Mutex for serialized access: +- * Protect sensor set pad format and start/stop streaming safely. +- * Protect access to sensor v4l2 controls. +- */ +- struct mutex mutex; ++ const struct imx355_clk_params *clk_params; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data *supplies; +@@ -143,750 +186,147 @@ static const struct regulator_bulk_data imx355_supplies[] = { + { .supply = "dovdd" }, + }; + +-static const struct imx355_reg imx355_global_regs[] = { +- { 0x0136, 0x13 }, +- { 0x0137, 0x33 }, +- { 0x304e, 0x03 }, +- { 0x4348, 0x16 }, +- { 0x4350, 0x19 }, +- { 0x4408, 0x0a }, +- { 0x440c, 0x0b }, +- { 0x4411, 0x5f }, +- { 0x4412, 0x2c }, +- { 0x4623, 0x00 }, +- { 0x462c, 0x0f }, +- { 0x462d, 0x00 }, +- { 0x462e, 0x00 }, +- { 0x4684, 0x54 }, +- { 0x480a, 0x07 }, +- { 0x4908, 0x07 }, +- { 0x4909, 0x07 }, +- { 0x490d, 0x0a }, +- { 0x491e, 0x0f }, +- { 0x4921, 0x06 }, +- { 0x4923, 0x28 }, +- { 0x4924, 0x28 }, +- { 0x4925, 0x29 }, +- { 0x4926, 0x29 }, +- { 0x4927, 0x1f }, +- { 0x4928, 0x20 }, +- { 0x4929, 0x20 }, +- { 0x492a, 0x20 }, +- { 0x492c, 0x05 }, +- { 0x492d, 0x06 }, +- { 0x492e, 0x06 }, +- { 0x492f, 0x06 }, +- { 0x4930, 0x03 }, +- { 0x4931, 0x04 }, +- { 0x4932, 0x04 }, +- { 0x4933, 0x05 }, +- { 0x595e, 0x01 }, +- { 0x5963, 0x01 }, +- { 0x3030, 0x01 }, +- { 0x3031, 0x01 }, +- { 0x3045, 0x01 }, +- { 0x4010, 0x00 }, +- { 0x4011, 0x00 }, +- { 0x4012, 0x00 }, +- { 0x4013, 0x01 }, +- { 0x68a8, 0xfe }, +- { 0x68a9, 0xff }, +- { 0x6888, 0x00 }, +- { 0x6889, 0x00 }, +- { 0x68b0, 0x00 }, +- { 0x3058, 0x00 }, +- { 0x305a, 0x00 }, ++static const struct cci_reg_sequence imx355_global_regs[] = { ++ { CCI_REG8(0x304e), 0x03 }, ++ { CCI_REG8(0x4348), 0x16 }, ++ { CCI_REG8(0x4350), 0x19 }, ++ { CCI_REG8(0x4408), 0x0a }, ++ { CCI_REG8(0x440c), 0x0b }, ++ { CCI_REG8(0x4411), 0x5f }, ++ { CCI_REG8(0x4412), 0x2c }, ++ { CCI_REG8(0x4623), 0x00 }, ++ { CCI_REG8(0x462c), 0x0f }, ++ { CCI_REG8(0x462d), 0x00 }, ++ { CCI_REG8(0x462e), 0x00 }, ++ { CCI_REG8(0x4684), 0x54 }, ++ { CCI_REG8(0x480a), 0x07 }, ++ { CCI_REG8(0x4908), 0x07 }, ++ { CCI_REG8(0x4909), 0x07 }, ++ { CCI_REG8(0x490d), 0x0a }, ++ { CCI_REG8(0x491e), 0x0f }, ++ { CCI_REG8(0x4921), 0x06 }, ++ { CCI_REG8(0x4923), 0x28 }, ++ { CCI_REG8(0x4924), 0x28 }, ++ { CCI_REG8(0x4925), 0x29 }, ++ { CCI_REG8(0x4926), 0x29 }, ++ { CCI_REG8(0x4927), 0x1f }, ++ { CCI_REG8(0x4928), 0x20 }, ++ { CCI_REG8(0x4929), 0x20 }, ++ { CCI_REG8(0x492a), 0x20 }, ++ { CCI_REG8(0x492c), 0x05 }, ++ { CCI_REG8(0x492d), 0x06 }, ++ { CCI_REG8(0x492e), 0x06 }, ++ { CCI_REG8(0x492f), 0x06 }, ++ { CCI_REG8(0x4930), 0x03 }, ++ { CCI_REG8(0x4931), 0x04 }, ++ { CCI_REG8(0x4932), 0x04 }, ++ { CCI_REG8(0x4933), 0x05 }, ++ { CCI_REG8(0x595e), 0x01 }, ++ { CCI_REG8(0x5963), 0x01 }, ++ { CCI_REG8(0x3030), 0x01 }, ++ { CCI_REG8(0x3031), 0x01 }, ++ { CCI_REG8(0x3045), 0x01 }, ++ { CCI_REG8(0x4010), 0x00 }, ++ { CCI_REG8(0x4011), 0x00 }, ++ { CCI_REG8(0x4012), 0x00 }, ++ { CCI_REG8(0x4013), 0x01 }, ++ { CCI_REG8(0x68a8), 0xfe }, ++ { CCI_REG8(0x68a9), 0xff }, ++ { CCI_REG8(0x6888), 0x00 }, ++ { CCI_REG8(0x6889), 0x00 }, ++ { CCI_REG8(0x68b0), 0x00 }, ++ { CCI_REG8(0x3058), 0x00 }, ++ { CCI_REG8(0x305a), 0x00 }, ++ { CCI_REG8(0x0112), 0x0a }, ++ { CCI_REG8(0x0113), 0x0a }, ++ { IMX355_REG_PLL_IVT_PCK_DIV, IMX355_PLL_IVT_PCK_DIV }, ++ { CCI_REG8(0x0303), 0x01 }, ++ { CCI_REG8(0x0305), 0x02 }, ++ { CCI_REG8(0x0306), 0x00 }, ++ { CCI_REG8(0x0307), 0x78 }, ++ { CCI_REG8(0x030b), 0x01 }, ++ { IMX355_REG_PLL_OP_PREDIV, IMX355_PLL_OP_PREDIV }, ++ { CCI_REG8(0x0310), 0x00 }, ++ { CCI_REG8(0x0220), 0x00 }, ++ { CCI_REG8(0x0222), 0x01 }, ++ { CCI_REG8(0x3088), 0x04 }, ++ { CCI_REG8(0x6813), 0x02 }, ++ { CCI_REG8(0x6835), 0x07 }, ++ { CCI_REG8(0x6836), 0x01 }, ++ { CCI_REG8(0x6837), 0x04 }, ++ { CCI_REG8(0x684d), 0x07 }, ++ { CCI_REG8(0x684e), 0x01 }, ++ { CCI_REG8(0x684f), 0x04 }, + }; + +-static const struct imx355_reg_list imx355_global_setting = { +- .num_of_regs = ARRAY_SIZE(imx355_global_regs), +- .regs = imx355_global_regs, ++static const struct cci_reg_sequence mode_3268x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3268x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcb }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc4 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3264x2448_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3264x2448_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x08 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x08 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xc7 }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x97 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xc0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0x90 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_3280x2464_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_3280x2464_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x0a }, +- { 0x0341, 0x37 }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x0c }, +- { 0x034d, 0xd0 }, +- { 0x034e, 0x09 }, +- { 0x034f, 0xa0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1940x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1940x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x33 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x94 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1936x1096_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1936x1096_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa0 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xac }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2f }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xf3 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x90 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x48 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1924x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1924x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x2b }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x84 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1920x1080_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1920x1080_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x02 }, +- { 0x0345, 0xa8 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0xb4 }, +- { 0x0348, 0x0a }, +- { 0x0349, 0x27 }, +- { 0x034a, 0x06 }, +- { 0x034b, 0xeb }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x00 }, +- { 0x0901, 0x11 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x07 }, +- { 0x034d, 0x80 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0x38 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x1232_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x1232_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x04 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1640x922_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1640x922_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0x30 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x08 }, +- { 0x034b, 0x63 }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x06 }, +- { 0x034d, 0x68 }, +- { 0x034e, 0x03 }, +- { 0x034f, 0x9a }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1300x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1300x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x7f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x14 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1296x736_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1296x736_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x58 }, +- { 0x0346, 0x01 }, +- { 0x0347, 0xf0 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x77 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0xaf }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x10 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xe0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1284x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1284x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x6f }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x04 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_1280x720_regs[] = { ++ { CCI_REG8(0x0700), 0x00 }, ++ { CCI_REG8(0x0701), 0x10 }, + }; + +-static const struct imx355_reg mode_1280x720_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x07 }, +- { 0x0343, 0x2c }, +- { 0x0340, 0x05 }, +- { 0x0341, 0x1a }, +- { 0x0344, 0x01 }, +- { 0x0345, 0x68 }, +- { 0x0346, 0x02 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0b }, +- { 0x0349, 0x67 }, +- { 0x034a, 0x07 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x22 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x05 }, +- { 0x034d, 0x00 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0xd0 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x00 }, +- { 0x0701, 0x10 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, +-}; +- +-static const struct imx355_reg mode_820x616_regs[] = { +- { 0x0112, 0x0a }, +- { 0x0113, 0x0a }, +- { 0x0114, 0x03 }, +- { 0x0342, 0x0e }, +- { 0x0343, 0x58 }, +- { 0x0340, 0x02 }, +- { 0x0341, 0x8c }, +- { 0x0344, 0x00 }, +- { 0x0345, 0x00 }, +- { 0x0346, 0x00 }, +- { 0x0347, 0x00 }, +- { 0x0348, 0x0c }, +- { 0x0349, 0xcf }, +- { 0x034a, 0x09 }, +- { 0x034b, 0x9f }, +- { 0x0220, 0x00 }, +- { 0x0222, 0x01 }, +- { 0x0900, 0x01 }, +- { 0x0901, 0x44 }, +- { 0x0902, 0x00 }, +- { 0x034c, 0x03 }, +- { 0x034d, 0x34 }, +- { 0x034e, 0x02 }, +- { 0x034f, 0x68 }, +- { 0x0301, 0x05 }, +- { 0x0303, 0x01 }, +- { 0x0305, 0x02 }, +- { 0x0306, 0x00 }, +- { 0x0307, 0x78 }, +- { 0x030b, 0x01 }, +- { 0x030d, 0x02 }, +- { 0x030e, 0x00 }, +- { 0x030f, 0x4b }, +- { 0x0310, 0x00 }, +- { 0x0700, 0x02 }, +- { 0x0701, 0x78 }, +- { 0x0820, 0x0b }, +- { 0x0821, 0x40 }, +- { 0x3088, 0x04 }, +- { 0x6813, 0x02 }, +- { 0x6835, 0x07 }, +- { 0x6836, 0x01 }, +- { 0x6837, 0x04 }, +- { 0x684d, 0x07 }, +- { 0x684e, 0x01 }, +- { 0x684f, 0x04 }, ++static const struct cci_reg_sequence mode_820x616_regs[] = { ++ { CCI_REG8(0x0700), 0x02 }, ++ { CCI_REG8(0x0701), 0x78 }, + }; + + static const char * const imx355_test_pattern_menu[] = { +@@ -897,23 +337,19 @@ static const char * const imx355_test_pattern_menu[] = { + "Pseudorandom Sequence (PN9)", + }; + +-/* +- * When adding more than the one below, make sure the disallowed ones will +- * actually be disabled in the LINK_FREQ control. +- */ +-static const s64 link_freq_menu_items[] = { +- IMX355_LINK_FREQ_DEFAULT, +-}; +- + /* Mode configs */ + static const struct imx355_mode supported_modes[] = { + { + .width = 3280, + .height = 2464, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3280x2464_regs), + .regs = mode_3280x2464_regs, +@@ -922,10 +358,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3268, + .height = 2448, ++ .crop = { ++ .width = 3268, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3268x2448_regs), + .regs = mode_3268x2448_regs, +@@ -934,10 +374,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 3264, + .height = 2448, ++ .crop = { ++ .width = 3264, ++ .height = 2448, ++ .left = 8, ++ .top = 8, ++ }, + .fll_def = 2615, +- .fll_min = 2615, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3264x2448_regs), + .regs = mode_3264x2448_regs, +@@ -946,10 +390,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1940, + .height = 1096, ++ .crop = { ++ .width = 1940, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1940x1096_regs), + .regs = mode_1940x1096_regs, +@@ -958,10 +406,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1936, + .height = 1096, ++ .crop = { ++ .width = 1936, ++ .height = 1096, ++ .left = 672, ++ .top = 684, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1936x1096_regs), + .regs = mode_1936x1096_regs, +@@ -970,10 +422,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1924, + .height = 1080, ++ .crop = { ++ .width = 1924, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1924x1080_regs), + .regs = mode_1924x1080_regs, +@@ -982,10 +438,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, ++ .crop = { ++ .width = 1920, ++ .height = 1080, ++ .left = 680, ++ .top = 692, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1080_regs), + .regs = mode_1920x1080_regs, +@@ -994,10 +454,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 1232, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x1232_regs), + .regs = mode_1640x1232_regs, +@@ -1006,10 +470,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1640, + .height = 922, ++ .crop = { ++ .width = 3280, ++ .height = 1844, ++ .left = 0, ++ .top = 304, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1640x922_regs), + .regs = mode_1640x922_regs, +@@ -1018,10 +486,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1300, + .height = 736, ++ .crop = { ++ .width = 2600, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1300x736_regs), + .regs = mode_1300x736_regs, +@@ -1030,10 +502,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1296, + .height = 736, ++ .crop = { ++ .width = 2592, ++ .height = 1472, ++ .left = 344, ++ .top = 496, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1296x736_regs), + .regs = mode_1296x736_regs, +@@ -1042,10 +518,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1284, + .height = 720, ++ .crop = { ++ .width = 2568, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1284x720_regs), + .regs = mode_1284x720_regs, +@@ -1054,10 +534,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 1280, + .height = 720, ++ .crop = { ++ .width = 2560, ++ .height = 1440, ++ .left = 360, ++ .top = 512, ++ }, + .fll_def = 1306, +- .fll_min = 1306, + .llp = 1836, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x720_regs), + .regs = mode_1280x720_regs, +@@ -1066,10 +550,14 @@ static const struct imx355_mode supported_modes[] = { + { + .width = 820, + .height = 616, ++ .crop = { ++ .width = 3280, ++ .height = 2464, ++ .left = 0, ++ .top = 0, ++ }, + .fll_def = 652, +- .fll_min = 652, + .llp = 3672, +- .link_freq_index = IMX355_LINK_FREQ_INDEX, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_820x616_regs), + .regs = mode_820x616_regs, +@@ -1095,116 +583,28 @@ static u32 imx355_get_format_code(struct imx355 *imx355) + { MEDIA_BUS_FMT_SGBRG10_1X10, MEDIA_BUS_FMT_SBGGR10_1X10, }, + }; + +- lockdep_assert_held(&imx355->mutex); + code = codes[imx355->vflip->val][imx355->hflip->val]; + + return code; + } + +-/* Read registers up to 4 at a time */ +-static int imx355_read_reg(struct imx355 *imx355, u16 reg, u32 len, u32 *val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- struct i2c_msg msgs[2]; +- u8 addr_buf[2]; +- u8 data_buf[4] = { 0 }; +- int ret; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, addr_buf); +- /* Write register address */ +- msgs[0].addr = client->addr; +- msgs[0].flags = 0; +- msgs[0].len = ARRAY_SIZE(addr_buf); +- msgs[0].buf = addr_buf; +- +- /* Read data from register */ +- msgs[1].addr = client->addr; +- msgs[1].flags = I2C_M_RD; +- msgs[1].len = len; +- msgs[1].buf = &data_buf[4 - len]; +- +- ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); +- if (ret != ARRAY_SIZE(msgs)) +- return -EIO; +- +- *val = get_unaligned_be32(data_buf); +- +- return 0; +-} +- +-/* Write registers up to 4 at a time */ +-static int imx355_write_reg(struct imx355 *imx355, u16 reg, u32 len, u32 val) +-{ +- struct i2c_client *client = v4l2_get_subdevdata(&imx355->sd); +- u8 buf[6]; +- +- if (len > 4) +- return -EINVAL; +- +- put_unaligned_be16(reg, buf); +- put_unaligned_be32(val << (8 * (4 - len)), buf + 2); +- if (i2c_master_send(client, buf, len + 2) != len + 2) +- return -EIO; +- +- return 0; +-} +- +-/* Write a list of registers */ +-static int imx355_write_regs(struct imx355 *imx355, +- const struct imx355_reg *regs, u32 len) +-{ +- int ret; +- u32 i; +- +- for (i = 0; i < len; i++) { +- ret = imx355_write_reg(imx355, regs[i].address, 1, regs[i].val); +- if (ret) { +- dev_err_ratelimited(imx355->dev, +- "write reg 0x%4.4x return err %d", +- regs[i].address, ret); +- +- return ret; +- } +- } +- +- return 0; +-} +- +-/* Open sub-device */ +-static int imx355_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- struct v4l2_mbus_framefmt *try_fmt = +- v4l2_subdev_state_get_format(fh->state, 0); +- +- mutex_lock(&imx355->mutex); +- +- /* Initialize try_fmt */ +- try_fmt->width = imx355->cur_mode->width; +- try_fmt->height = imx355->cur_mode->height; +- try_fmt->code = imx355_get_format_code(imx355); +- try_fmt->field = V4L2_FIELD_NONE; +- +- mutex_unlock(&imx355->mutex); +- +- return 0; +-} +- + static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + { + struct imx355 *imx355 = container_of(ctrl->handler, + struct imx355, ctrl_handler); ++ const struct v4l2_mbus_framefmt *format = NULL; ++ struct v4l2_subdev_state *state; + s64 max; + int ret; + ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ format = v4l2_subdev_state_get_format(state, 0); ++ + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ +- max = imx355->cur_mode->height + ctrl->val - 10; ++ max = format->height + ctrl->val - IMX355_EXPOSURE_OFFSET; + __v4l2_ctrl_modify_range(imx355->exposure, + imx355->exposure->minimum, + max, imx355->exposure->step, max); +@@ -1221,31 +621,31 @@ static int imx355_set_ctrl(struct v4l2_ctrl *ctrl) + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + /* Analog gain = 1024/(1024 - ctrl->val) times */ +- ret = imx355_write_reg(imx355, IMX355_REG_ANALOG_GAIN, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_ANALOG_GAIN, ++ ctrl->val, NULL); + break; + case V4L2_CID_DIGITAL_GAIN: +- ret = imx355_write_reg(imx355, IMX355_REG_DIG_GAIN_GLOBAL, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); + break; + case V4L2_CID_EXPOSURE: +- ret = imx355_write_reg(imx355, IMX355_REG_EXPOSURE, 2, +- ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_EXPOSURE, ++ ctrl->val, NULL); + break; + case V4L2_CID_VBLANK: + /* Update FLL that meets expected vertical blanking */ +- ret = imx355_write_reg(imx355, IMX355_REG_FLL, 2, +- imx355->cur_mode->height + ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_FLL, ++ format->height + ctrl->val, NULL); + break; + case V4L2_CID_TEST_PATTERN: +- ret = imx355_write_reg(imx355, IMX355_REG_TEST_PATTERN, +- 2, ctrl->val); ++ ret = cci_write(imx355->regmap, IMX355_REG_TEST_PATTERN, ++ ctrl->val, NULL); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: +- ret = imx355_write_reg(imx355, IMX355_REG_ORIENTATION, 1, +- imx355->hflip->val | +- imx355->vflip->val << 1); ++ ret = cci_write(imx355->regmap, IMX355_REG_ORIENTATION, ++ imx355->hflip->val | imx355->vflip->val << 1, ++ NULL); + break; + default: + ret = -EINVAL; +@@ -1272,9 +672,7 @@ static int imx355_enum_mbus_code(struct v4l2_subdev *sd, + if (code->index > 0) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + code->code = imx355_get_format_code(imx355); +- mutex_unlock(&imx355->mutex); + + return 0; + } +@@ -1288,12 +686,9 @@ static int imx355_enum_frame_size(struct v4l2_subdev *sd, + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + +- mutex_lock(&imx355->mutex); + if (fse->code != imx355_get_format_code(imx355)) { +- mutex_unlock(&imx355->mutex); + return -EINVAL; + } +- mutex_unlock(&imx355->mutex); + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; +@@ -1311,36 +706,10 @@ static void imx355_update_pad_format(struct imx355 *imx355, + fmt->format.height = mode->height; + fmt->format.code = imx355_get_format_code(imx355); + fmt->format.field = V4L2_FIELD_NONE; +-} +- +-static int imx355_do_get_pad_format(struct imx355 *imx355, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct v4l2_mbus_framefmt *framefmt; +- +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- fmt->format = *framefmt; +- } else { +- imx355_update_pad_format(imx355, imx355->cur_mode, fmt); +- } +- +- return 0; +-} +- +-static int imx355_get_pad_format(struct v4l2_subdev *sd, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_format *fmt) +-{ +- struct imx355 *imx355 = to_imx355(sd); +- int ret; +- +- mutex_lock(&imx355->mutex); +- ret = imx355_do_get_pad_format(imx355, sd_state, fmt); +- mutex_unlock(&imx355->mutex); +- +- return ret; ++ fmt->format.colorspace = V4L2_COLORSPACE_RAW; ++ fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_601; ++ fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; ++ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + } + + static int +@@ -1351,13 +720,8 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + struct imx355 *imx355 = to_imx355(sd); + const struct imx355_mode *mode; + struct v4l2_mbus_framefmt *framefmt; +- s32 vblank_def; +- s32 vblank_min; ++ struct v4l2_rect *crop; + s64 h_blank; +- u64 pixel_rate; +- u32 height; +- +- mutex_lock(&imx355->mutex); + + /* + * Only one bayer order is supported. +@@ -1370,23 +734,25 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + width, height, + fmt->format.width, fmt->format.height); + imx355_update_pad_format(imx355, mode, fmt); +- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { +- framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); +- *framefmt = fmt->format; +- } else { +- imx355->cur_mode = mode; +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; +- do_div(pixel_rate, 10); +- __v4l2_ctrl_s_ctrl_int64(imx355->pixel_rate, pixel_rate); ++ framefmt = v4l2_subdev_state_get_format(sd_state, 0); ++ ++ *framefmt = fmt->format; ++ ++ crop = v4l2_subdev_state_get_crop(sd_state, 0); ++ crop->width = mode->crop.width; ++ crop->height = mode->crop.height; ++ crop->left = mode->crop.left; ++ crop->top = mode->crop.top; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Update limits and set FPS to default */ +- height = imx355->cur_mode->height; +- vblank_def = imx355->cur_mode->fll_def - height; +- vblank_min = imx355->cur_mode->fll_min - height; +- height = IMX355_FLL_MAX - height; +- __v4l2_ctrl_modify_range(imx355->vblank, vblank_min, height, 1, +- vblank_def); +- __v4l2_ctrl_s_ctrl(imx355->vblank, vblank_def); +- h_blank = mode->llp - imx355->cur_mode->width; ++ __v4l2_ctrl_modify_range(imx355->vblank, IMX355_VBLANK_MIN, ++ IMX355_FLL_MAX - mode->height, 1, ++ mode->fll_def - mode->height); ++ __v4l2_ctrl_s_ctrl(imx355->vblank, mode->fll_def - mode->height); ++ ++ h_blank = mode->llp - mode->width; ++ + /* + * Currently hblank is not changeable. + * So FPS control is done only by vblank. +@@ -1395,7 +761,42 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + h_blank, 1, h_blank); + } + +- mutex_unlock(&imx355->mutex); ++ return 0; ++} ++ ++static int imx355_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, 0); ++ return 0; ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = IMX355_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX355_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX355_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX355_PIXEL_ARRAY_HEIGHT; ++ ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx355_entity_init_state(struct v4l2_subdev *subdev, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { }; ++ ++ fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; ++ fmt.format.code = MEDIA_BUS_FMT_SRGGB10_1X10; ++ fmt.format.width = supported_modes[0].width; ++ fmt.format.height = supported_modes[0].height; ++ ++ imx355_set_pad_format(subdev, sd_state, &fmt); + + return 0; + } +@@ -1403,52 +804,98 @@ imx355_set_pad_format(struct v4l2_subdev *sd, + /* Start streaming */ + static int imx355_start_streaming(struct imx355 *imx355) + { +- const struct imx355_reg_list *reg_list; +- int ret; ++ const struct v4l2_mbus_framefmt *fmt; ++ struct v4l2_subdev_state *state; ++ const struct imx355_mode *mode; ++ int lane_idx = imx355->hwcfg->num_lanes == 4 ? 0 : 1; ++ struct v4l2_rect *crop; ++ u64 link_bitrate; ++ u8 binning_mode; ++ int ret = 0; + + /* Global Setting */ +- reg_list = &imx355_global_setting; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set global settings"); +- return ret; +- } ++ cci_multi_reg_write(imx355->regmap, imx355_global_regs, ++ ARRAY_SIZE(imx355_global_regs), &ret); + +- /* Apply default values of current mode */ +- reg_list = &imx355->cur_mode->reg_list; +- ret = imx355_write_regs(imx355, reg_list->regs, reg_list->num_of_regs); +- if (ret) { +- dev_err(imx355->dev, "failed to set mode"); +- return ret; +- } ++ /* Apply values of current mode */ ++ state = v4l2_subdev_get_locked_active_state(&imx355->sd); ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ crop = v4l2_subdev_state_get_crop(state, 0); ++ mode = v4l2_find_nearest_size(supported_modes, ++ ARRAY_SIZE(supported_modes), ++ width, height, fmt->width, fmt->height); ++ cci_multi_reg_write(imx355->regmap, mode->reg_list.regs, ++ mode->reg_list.num_of_regs, &ret); ++ ++ /* Set readout crop and size registers */ ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_START, crop->left, ++ &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_START, crop->top, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_ADD_END, ++ crop->width + crop->left - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_ADD_END, ++ crop->height + crop->top - 1, &ret); ++ cci_write(imx355->regmap, IMX355_REG_X_OUT_SIZE, fmt->width, &ret); ++ cci_write(imx355->regmap, IMX355_REG_Y_OUT_SIZE, fmt->height, &ret); ++ ++ binning_mode = ((crop->width / fmt->width) << 4) | ++ (crop->height / fmt->height); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_MODE, ++ binning_mode == 0x11 ? 0x00 : 0x01, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_TYPE, binning_mode, &ret); ++ cci_write(imx355->regmap, IMX355_REG_BINNING_WEIGHTING, 0x00, &ret); ++ ++ /* Set PLL registers for the external clock frequency */ ++ cci_write(imx355->regmap, IMX355_REG_EXTCLK_FREQ, ++ imx355->clk_params->extclk_freq, &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_MUL, ++ imx355->clk_params->pll_op_mpy[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_OP_PREDIV, ++ imx355->clk_params->pll_op_prediv[lane_idx], &ret); ++ cci_write(imx355->regmap, IMX355_REG_PLL_IVT_SYSCK_DIV, ++ lane_idx ? 2 : 1, &ret); ++ ++ /* Set MIPI configuration */ ++ cci_write(imx355->regmap, IMX355_REG_LANE_SEL, ++ imx355->hwcfg->num_lanes - 1, &ret); ++ ++ link_bitrate = imx355->link_freq->qmenu_int[imx355->link_freq->val] * ++ imx355->hwcfg->num_lanes * 2; ++ do_div(link_bitrate, 1000000); ++ cci_write(imx355->regmap, IMX355_REG_REQ_LINK_BIT_RATE, link_bitrate, ++ &ret); + + /* set digital gain control to all color mode */ +- ret = imx355_write_reg(imx355, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, 1); +- if (ret) +- return ret; ++ cci_write(imx355->regmap, IMX355_REG_DPGA_USE_GLOBAL_GAIN, 1, &ret); ++ ++ /* set line length */ ++ cci_write(imx355->regmap, IMX355_REG_LLP, ++ imx355->hblank->val + fmt->width, &ret); + + /* Apply customized values from user */ +- ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); +- if (ret) +- return ret; ++ if (!ret) ++ ret = __v4l2_ctrl_handler_setup(imx355->sd.ctrl_handler); + +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STREAMING); ++ cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, IMX355_MODE_STREAMING, ++ &ret); ++ ++ return ret; + } + + /* Stop streaming */ + static int imx355_stop_streaming(struct imx355 *imx355) + { +- return imx355_write_reg(imx355, IMX355_REG_MODE_SELECT, +- 1, IMX355_MODE_STANDBY); ++ return cci_write(imx355->regmap, IMX355_REG_MODE_SELECT, ++ IMX355_MODE_STANDBY, NULL); + } + + static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + { + struct imx355 *imx355 = to_imx355(sd); ++ struct v4l2_subdev_state *state; + int ret = 0; + +- mutex_lock(&imx355->mutex); ++ state = v4l2_subdev_lock_and_get_active_state(sd); + + if (enable) { + ret = pm_runtime_resume_and_get(imx355->dev); +@@ -1464,21 +911,21 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + goto err_rpm_put; + } else { + imx355_stop_streaming(imx355); +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + } + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx355->vflip, enable); + __v4l2_ctrl_grab(imx355->hflip, enable); + +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + + err_rpm_put: +- pm_runtime_put(imx355->dev); ++ pm_runtime_put_autosuspend(imx355->dev); + err_unlock: +- mutex_unlock(&imx355->mutex); ++ v4l2_subdev_unlock_state(state); + + return ret; + } +@@ -1487,14 +934,14 @@ static int imx355_set_stream(struct v4l2_subdev *sd, int enable) + static int imx355_identify_module(struct imx355 *imx355) + { + int ret; +- u32 val; ++ u64 val; + +- ret = imx355_read_reg(imx355, IMX355_REG_CHIP_ID, 2, &val); ++ ret = cci_read(imx355->regmap, IMX355_REG_CHIP_ID, &val, NULL); + if (ret) + return ret; + + if (val != IMX355_CHIP_ID) { +- dev_err(imx355->dev, "chip id mismatch: %x!=%x", ++ dev_err(imx355->dev, "chip id mismatch: %x!=%llx", + IMX355_CHIP_ID, val); + return -EIO; + } +@@ -1512,9 +959,10 @@ static const struct v4l2_subdev_video_ops imx355_video_ops = { + + static const struct v4l2_subdev_pad_ops imx355_pad_ops = { + .enum_mbus_code = imx355_enum_mbus_code, +- .get_fmt = imx355_get_pad_format, ++ .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx355_set_pad_format, + .enum_frame_size = imx355_enum_frame_size, ++ .get_selection = imx355_get_selection, + }; + + static const struct v4l2_subdev_ops imx355_subdev_ops = { +@@ -1528,7 +976,7 @@ static const struct media_entity_operations imx355_subdev_entity_ops = { + }; + + static const struct v4l2_subdev_internal_ops imx355_internal_ops = { +- .open = imx355_open, ++ .init_state = imx355_entity_init_state, + }; + + static int imx355_power_off(struct device *dev) +@@ -1582,13 +1030,11 @@ static int imx355_init_controls(struct imx355 *imx355) + { + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; ++ const struct imx355_mode *mode = &supported_modes[0]; + s64 exposure_max; + s64 vblank_def; +- s64 vblank_min; + s64 hblank; + u64 pixel_rate; +- const struct imx355_mode *mode; +- u32 max; + int ret; + + ctrl_hdlr = &imx355->ctrl_handler; +@@ -1596,40 +1042,34 @@ static int imx355_init_controls(struct imx355 *imx355) + if (ret) + return ret; + +- ctrl_hdlr->lock = &imx355->mutex; +- max = ARRAY_SIZE(link_freq_menu_items) - 1; + imx355->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_LINK_FREQ, max, 0, +- link_freq_menu_items); ++ V4L2_CID_LINK_FREQ, 0, 0, ++ &imx355->hwcfg->link_freq_menu); + if (imx355->link_freq) + imx355->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ +- pixel_rate = IMX355_LINK_FREQ_DEFAULT * 2 * 4; ++ pixel_rate = imx355->hwcfg->link_freq_menu * 2 * imx355->hwcfg->num_lanes; + do_div(pixel_rate, 10); +- /* By default, PIXEL_RATE is read only */ +- imx355->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_PIXEL_RATE, pixel_rate, +- pixel_rate, 1, pixel_rate); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, V4L2_CID_PIXEL_RATE, ++ pixel_rate, pixel_rate, 1, pixel_rate); + + /* Initialize vblank/hblank/exposure parameters based on current mode */ +- mode = imx355->cur_mode; + vblank_def = mode->fll_def - mode->height; +- vblank_min = mode->fll_min - mode->height; + imx355->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_VBLANK, vblank_min, ++ V4L2_CID_VBLANK, IMX355_VBLANK_MIN, + IMX355_FLL_MAX - mode->height, + 1, vblank_def); + + hblank = mode->llp - mode->width; +- imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, +- V4L2_CID_HBLANK, hblank, hblank, +- 1, hblank); ++ imx355->hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, ++ hblank, hblank, 1, hblank); + if (imx355->hblank) + imx355->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* fll >= exposure time + adjust parameter (default value is 10) */ +- exposure_max = mode->fll_def - 10; ++ exposure_max = mode->fll_def - IMX355_EXPOSURE_OFFSET; + imx355->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx355_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX355_EXPOSURE_MIN, exposure_max, +@@ -1683,14 +1123,17 @@ static int imx355_init_controls(struct imx355 *imx355) + return ret; + } + +-static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) ++static struct imx355_hwcfg *imx355_get_hwcfg(struct imx355 *imx355) + { ++ struct device *dev = imx355->dev; + struct imx355_hwcfg *cfg; + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; ++ const struct imx355_clk_params *clk = imx355->clk_params; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); ++ int lane_idx; + int ret; + + if (!fwnode) +@@ -1708,13 +1151,18 @@ static struct imx355_hwcfg *imx355_get_hwcfg(struct device *dev) + if (!cfg) + goto out_err; + +- if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX355_DATA_LANES) ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 && ++ bus_cfg.bus.mipi_csi2.num_data_lanes != 4) + goto out_err; + ++ cfg->num_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; ++ ++ lane_idx = cfg->num_lanes == 4 ? 0 : 1; ++ cfg->link_freq_menu = (clk->ext_clk * clk->pll_op_mpy[lane_idx]) / ++ (clk->pll_op_prediv[lane_idx] * 2); + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, +- link_freq_menu_items, +- ARRAY_SIZE(link_freq_menu_items), ++ &cfg->link_freq_menu, 1, + &cfg->link_freq_bitmap); + if (ret) + goto out_err; +@@ -1741,7 +1189,10 @@ static int imx355_probe(struct i2c_client *client) + + imx355->dev = &client->dev; + +- mutex_init(&imx355->mutex); ++ imx355->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(imx355->regmap)) ++ return dev_err_probe(imx355->dev, PTR_ERR(imx355->regmap), ++ "Unable to initialize I2C\n"); + + imx355->clk = devm_v4l2_sensor_clk_get(imx355->dev, NULL); + if (IS_ERR(imx355->clk)) +@@ -1749,7 +1200,13 @@ static int imx355_probe(struct i2c_client *client) + "failed to get clock\n"); + + freq = clk_get_rate(imx355->clk); +- if (freq != IMX355_EXT_CLK) ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx355_clk_params); i++) { ++ if (freq == imx355_clk_params[i].ext_clk) { ++ imx355->clk_params = &imx355_clk_params[i]; ++ break; ++ } ++ } ++ if (!imx355->clk_params) + return dev_err_probe(imx355->dev, -EINVAL, + "external clock %lu is not supported\n", + freq); +@@ -1760,7 +1217,7 @@ static int imx355_probe(struct i2c_client *client) + &imx355->supplies); + if (ret) { + dev_err_probe(imx355->dev, ret, "could not get regulators"); +- goto error_probe; ++ return ret; + } + + imx355->reset_gpio = devm_gpiod_get_optional(imx355->dev, "reset", +@@ -1768,22 +1225,21 @@ static int imx355_probe(struct i2c_client *client) + if (IS_ERR(imx355->reset_gpio)) { + ret = dev_err_probe(imx355->dev, PTR_ERR(imx355->reset_gpio), + "failed to get gpios"); +- goto error_probe; ++ return ret; + } + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx355->sd, client, &imx355_subdev_ops); + +- imx355->hwcfg = imx355_get_hwcfg(imx355->dev); ++ imx355->hwcfg = imx355_get_hwcfg(imx355); + if (!imx355->hwcfg) { + dev_err(imx355->dev, "failed to get hwcfg"); +- ret = -ENODEV; +- goto error_probe; ++ return -ENODEV; + } + + ret = imx355_power_on(imx355->dev); + if (ret) +- goto error_probe; ++ return ret; + + /* Check module identity */ + ret = imx355_identify_module(imx355); +@@ -1792,9 +1248,6 @@ static int imx355_probe(struct i2c_client *client) + goto error_power_off; + } + +- /* Set default mode to max resolution */ +- imx355->cur_mode = &supported_modes[0]; +- + ret = imx355_init_controls(imx355); + if (ret) { + dev_err(imx355->dev, "failed to init controls: %d", ret); +@@ -1816,24 +1269,36 @@ static int imx355_probe(struct i2c_client *client) + goto error_handler_free; + } + ++ imx355->sd.state_lock = imx355->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&imx355->sd); ++ if (ret < 0) { ++ dev_err_probe(imx355->dev, ret, "subdev init error\n"); ++ goto error_media_entity_free; ++ } ++ + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(imx355->dev); + pm_runtime_enable(imx355->dev); ++ pm_runtime_set_autosuspend_delay(imx355->dev, 1000); ++ pm_runtime_use_autosuspend(imx355->dev); + + ret = v4l2_async_register_subdev_sensor(&imx355->sd); + if (ret < 0) +- goto error_media_entity_runtime_pm; ++ goto error_subdev_cleanup_runtime_pm; + + pm_runtime_idle(imx355->dev); + + return 0; + +-error_media_entity_runtime_pm: ++error_subdev_cleanup_runtime_pm: + pm_runtime_disable(imx355->dev); + pm_runtime_set_suspended(imx355->dev); ++ pm_runtime_dont_use_autosuspend(imx355->dev); ++ v4l2_subdev_cleanup(&imx355->sd); ++error_media_entity_free: + media_entity_cleanup(&imx355->sd.entity); + + error_handler_free: +@@ -1842,9 +1307,6 @@ static int imx355_probe(struct i2c_client *client) + error_power_off: + imx355_power_off(imx355->dev); + +-error_probe: +- mutex_destroy(&imx355->mutex); +- + return ret; + } + +@@ -1854,6 +1316,7 @@ static void imx355_remove(struct i2c_client *client) + struct imx355 *imx355 = to_imx355(sd); + + v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + +@@ -1864,7 +1327,7 @@ static void imx355_remove(struct i2c_client *client) + pm_runtime_set_suspended(imx355->dev); + } + +- mutex_destroy(&imx355->mutex); ++ pm_runtime_dont_use_autosuspend(imx355->dev); + } + + static const struct acpi_device_id imx355_acpi_ids[] __maybe_unused = { +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +new file mode 100644 +--- /dev/null ++++ b/drivers/media/i2c/imx471.c +@@ -0,0 +1,957 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * imx471.c - imx471 sensor driver ++ * ++ * Copyright (C) 2025 Intel Corporation ++ * Copyright (C) 2026 Kate Hsuan ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define IMX471_REG_MODE_SELECT CCI_REG8(0x0100) ++#define IMX471_MODE_STANDBY 0x00 ++#define IMX471_MODE_STREAMING 0x01 ++ ++/* Chip ID */ ++#define IMX471_REG_CHIP_ID CCI_REG16(0x0016) ++#define IMX471_CHIP_ID 0x0471 ++ ++/* V_TIMING internal */ ++#define IMX471_REG_FLL CCI_REG16(0x0340) ++#define IMX471_FLL_MAX 0xffff ++ ++/* Exposure control */ ++#define IMX471_REG_EXPOSURE CCI_REG16(0x0202) ++#define IMX471_EXPOSURE_MIN 1 ++#define IMX471_EXPOSURE_STEP 1 ++#define IMX471_EXPOSURE_DEFAULT 1270 ++ ++/* Default exposure margin */ ++#define IMX471_EXPOSURE_MARGIN 18 ++ ++/* Analog gain control */ ++#define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) ++#define IMX471_ANA_GAIN_MIN 0 ++#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_STEP 1 ++#define IMX471_ANA_GAIN_DEFAULT 0 ++ ++/* Digital gain control */ ++#define IMX471_REG_DPGA_USE_GLOBAL_GAIN CCI_REG16(0x3ff9) ++#define IMX471_REG_DIG_GAIN_GLOBAL CCI_REG16(0x020e) ++#define IMX471_DGTL_GAIN_MIN 256 ++#define IMX471_DGTL_GAIN_MAX 4095 ++#define IMX471_DGTL_GAIN_STEP 1 ++#define IMX471_DGTL_GAIN_DEFAULT 256 ++ ++/* HFLIP and VFLIP control */ ++#define IMX471_REG_ORIENTATION CCI_REG8(0x0101) ++ ++/* Test Pattern Control */ ++#define IMX471_REG_TEST_PATTERN CCI_REG8(0x0600) ++ ++/* default link frequency and external clock */ ++#define IMX471_LINK_FREQ_DEFAULT 200000000LL ++#define IMX471_EXT_CLK 19200000 ++ ++/* PLL */ ++#define IMX471_REG_VTPXCK_DIV CCI_REG8(0x0301) ++#define IMX471_REG_VTSYCK_DIV CCI_REG8(0x0303) ++#define IMX471_REG_PREPLLCK_VT_DIV CCI_REG8(0x0305) ++#define IMX471_REG_PLL_VT_MPY CCI_REG16(0x0306) ++#define IMX471_REG_OPPXCK_DIV CCI_REG8(0x0309) ++#define IMX471_REG_OPSYCK_DIV CCI_REG8(0x030b) ++#define IMX471_REG_PLL_MULT_DRIV CCI_REG8(0x0310) ++#define IMX471_PLL_SINGLE 0 ++#define IMX471_PLL_DUAL 1 ++ ++/* IMX471 native and active pixel array size */ ++#define IMX471_NATIVE_WIDTH 4672 ++#define IMX471_NATIVE_HEIGHT 3512 ++#define IMX471_PIXEL_ARRAY_LEFT 8 ++#define IMX471_PIXEL_ARRAY_TOP 8 ++#define IMX471_PIXEL_ARRAY_WIDTH 4656 ++#define IMX471_PIXEL_ARRAY_HEIGHT 3496 ++ ++#define IMX471_REG_EXCK_FREQ CCI_REG16(0x0136) ++ ++#define IMX471_REG_CSI_DATA_FORMAT CCI_REG16(0x0112) ++#define IMX471_CSI_DATA_FORMAT_RAW10 0x0a0a ++ ++#define IMX471_REG_CSI_LANE_MODE CCI_REG8(0x0114) ++#define IMX471_CSI_2_LANE_MODE 1 ++#define IMX471_CSI_4_LANE_MODE 3 ++ ++#define IMX471_REG_X_ADD_STA CCI_REG16(0x0344) ++#define IMX471_REG_Y_ADD_STA CCI_REG16(0x0346) ++#define IMX471_REG_X_ADD_END CCI_REG16(0x0348) ++#define IMX471_REG_Y_ADD_END CCI_REG16(0x034a) ++#define IMX471_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) ++#define IMX471_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) ++#define IMX471_REG_X_EVEN_INC CCI_REG8(0x0381) ++#define IMX471_REG_X_ODD_INC CCI_REG8(0x0383) ++#define IMX471_REG_Y_EVEN_INC CCI_REG8(0x0385) ++#define IMX471_REG_Y_ODD_INC CCI_REG8(0x0387) ++ ++#define IMX471_REG_DIG_CROP_X_OFFSET CCI_REG16(0x0408) ++#define IMX471_REG_DIG_CROP_Y_OFFSET CCI_REG16(0x040a) ++#define IMX471_REG_DIG_CROP_WIDTH CCI_REG16(0x040c) ++#define IMX471_REG_DIG_CROP_HEIGHT CCI_REG16(0x040e) ++ ++/* Binning mode */ ++#define IMX471_REG_BINNING_MODE CCI_REG8(0x0900) ++#define IMX471_BINNING_NONE 0 ++#define IMX471_BINNING_ENABLE 1 ++#define IMX471_REG_BINNING_TYPE CCI_REG8(0x0901) ++#define IMX471_REG_BINNING_WEIGHTING CCI_REG8(0x0902) ++ ++#define to_imx471(_sd) container_of_const(_sd, struct imx471, sd) ++ ++static const char * const imx471_supply_name[] = { ++ "vana", ++}; ++ ++struct imx471_mode { ++ u32 width; ++ u32 height; ++ ++ /* V-timing */ ++ u32 fll_def; ++ u32 fll_min; ++ ++ /* H-timing */ ++ u32 llp; ++ ++ const struct cci_reg_sequence *default_mode_regs; ++ unsigned int default_mode_regs_length; ++}; ++ ++struct imx471 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ ++ struct v4l2_ctrl_handler ctrl_handler; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *exposure; ++ ++ struct gpio_desc *reset_gpio; ++ struct regulator_bulk_data supplies[ARRAY_SIZE(imx471_supply_name)]; ++ struct clk *img_clk; ++ ++ struct device *dev; ++ struct regmap *regmap; ++}; ++ ++static const struct cci_reg_sequence imx471_global_regs[] = { ++ { IMX471_REG_EXCK_FREQ, 0x1333 }, ++ { CCI_REG8(0x3c7e), 0x08 }, ++ { CCI_REG8(0x3c7f), 0x05 }, ++ { CCI_REG8(0x3e35), 0x00 }, ++ { CCI_REG8(0x3e36), 0x00 }, ++ { CCI_REG8(0x3e37), 0x00 }, ++ { CCI_REG8(0x3f7f), 0x01 }, ++ { CCI_REG8(0x4431), 0x04 }, ++ { CCI_REG8(0x531c), 0x01 }, ++ { CCI_REG8(0x531d), 0x02 }, ++ { CCI_REG8(0x531e), 0x04 }, ++ { CCI_REG8(0x5928), 0x00 }, ++ { CCI_REG8(0x5929), 0x2f }, ++ { CCI_REG8(0x592a), 0x00 }, ++ { CCI_REG8(0x592b), 0x85 }, ++ { CCI_REG8(0x592c), 0x00 }, ++ { CCI_REG8(0x592d), 0x32 }, ++ { CCI_REG8(0x592e), 0x00 }, ++ { CCI_REG8(0x592f), 0x88 }, ++ { CCI_REG8(0x5930), 0x00 }, ++ { CCI_REG8(0x5931), 0x3d }, ++ { CCI_REG8(0x5932), 0x00 }, ++ { CCI_REG8(0x5933), 0x93 }, ++ { CCI_REG8(0x5938), 0x00 }, ++ { CCI_REG8(0x5939), 0x24 }, ++ { CCI_REG8(0x593a), 0x00 }, ++ { CCI_REG8(0x593b), 0x7a }, ++ { CCI_REG8(0x593c), 0x00 }, ++ { CCI_REG8(0x593d), 0x24 }, ++ { CCI_REG8(0x593e), 0x00 }, ++ { CCI_REG8(0x593f), 0x7a }, ++ { CCI_REG8(0x5940), 0x00 }, ++ { CCI_REG8(0x5941), 0x2f }, ++ { CCI_REG8(0x5942), 0x00 }, ++ { CCI_REG8(0x5943), 0x85 }, ++ { CCI_REG8(0x5f0e), 0x6e }, ++ { CCI_REG8(0x5f11), 0xc6 }, ++ { CCI_REG8(0x5f17), 0x5e }, ++ { CCI_REG8(0x7990), 0x01 }, ++ { CCI_REG8(0x7993), 0x5d }, ++ { CCI_REG8(0x7994), 0x5d }, ++ { CCI_REG8(0x7995), 0xa1 }, ++ { CCI_REG8(0x799a), 0x01 }, ++ { CCI_REG8(0x799d), 0x00 }, ++ { CCI_REG8(0x8169), 0x01 }, ++ { CCI_REG8(0x8359), 0x01 }, ++ { CCI_REG8(0x9302), 0x1e }, ++ { CCI_REG8(0x9306), 0x1f }, ++ { CCI_REG8(0x930a), 0x26 }, ++ { CCI_REG8(0x930e), 0x23 }, ++ { CCI_REG8(0x9312), 0x23 }, ++ { CCI_REG8(0x9316), 0x2c }, ++ { CCI_REG8(0x9317), 0x19 }, ++ { CCI_REG8(0xb046), 0x01 }, ++ { CCI_REG8(0xb048), 0x01 }, ++}; ++ ++static const struct cci_reg_sequence mode_1928x1088_regs[] = { ++ { IMX471_REG_CSI_DATA_FORMAT, IMX471_CSI_DATA_FORMAT_RAW10 }, ++ { IMX471_REG_CSI_LANE_MODE, IMX471_CSI_4_LANE_MODE }, ++ { IMX471_REG_X_ADD_STA, 8 }, ++ { IMX471_REG_Y_ADD_STA, 408 }, ++ { IMX471_REG_X_ADD_END, 4647 }, ++ { IMX471_REG_Y_ADD_END, 3051 }, ++ { IMX471_REG_X_EVEN_INC, 1 }, ++ { IMX471_REG_X_ODD_INC, 1 }, ++ { IMX471_REG_Y_EVEN_INC, 1 }, ++ { IMX471_REG_Y_ODD_INC, 1 }, ++ { IMX471_REG_BINNING_MODE, IMX471_BINNING_ENABLE }, ++ { IMX471_REG_BINNING_TYPE, 0x22 }, ++ { IMX471_REG_BINNING_WEIGHTING, 0x08 }, ++ { IMX471_REG_DIG_CROP_X_OFFSET, 208 }, ++ { IMX471_REG_DIG_CROP_Y_OFFSET, 108 }, ++ { IMX471_REG_DIG_CROP_WIDTH, 1928 }, ++ { IMX471_REG_DIG_CROP_HEIGHT, 1088 }, ++ { IMX471_REG_X_OUTPUT_SIZE, 1928 }, ++ { IMX471_REG_Y_OUTPUT_SIZE, 1088 }, ++ { IMX471_REG_VTPXCK_DIV, 0x06 }, ++ { IMX471_REG_VTSYCK_DIV, 0x02 }, ++ { IMX471_REG_PREPLLCK_VT_DIV, 0x02 }, ++ { IMX471_REG_PLL_VT_MPY, 0x0079 }, ++ { IMX471_REG_OPSYCK_DIV, 0x01 }, ++ { CCI_REG8(0x030d), 0x02 }, ++ { CCI_REG8(0x030e), 0x00 }, ++ { CCI_REG8(0x030f), 0x53 }, ++ { IMX471_REG_PLL_MULT_DRIV, IMX471_PLL_DUAL }, ++ { CCI_REG8(0x3f4c), 0x81 }, ++ { CCI_REG8(0x3f4d), 0x81 }, ++ { CCI_REG8(0x3f78), 0x01 }, ++ { CCI_REG8(0x3f79), 0x31 }, ++ { CCI_REG8(0x3ffe), 0x00 }, ++ { CCI_REG8(0x3fff), 0x8a }, ++ { CCI_REG8(0x5f0a), 0xb6 }, ++}; ++ ++static const char * const imx471_test_pattern_menu[] = { ++ "Disabled", ++ "Solid Colour", ++ "Eight Vertical Colour Bars", ++ "Colour Bars With Fade to Grey", ++ "Pseudorandom Sequence (PN9)", ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ IMX471_LINK_FREQ_DEFAULT, ++}; ++ ++/* ++ * The Bayer formats for the flipping. ++ * - no flip ++ * - h flip ++ * - v flip ++ * - h and v flips ++ */ ++static const u32 imx471_hv_flips_bayer_order[] = { ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++}; ++ ++static const struct imx471_mode imx471_modes[] = { ++ { ++ .width = 1928, ++ .height = 1088, ++ .fll_def = 1308, ++ .fll_min = 1308, ++ .llp = 2328, ++ .default_mode_regs = mode_1928x1088_regs, ++ .default_mode_regs_length = ARRAY_SIZE(mode_1928x1088_regs), ++ }, ++}; ++ ++static int imx471_get_regulators(struct device *dev, struct imx471 *sensor) ++{ ++ for (unsigned int i = 0; i < ARRAY_SIZE(imx471_supply_name); i++) ++ sensor->supplies[i].supply = imx471_supply_name[i]; ++ ++ return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++} ++ ++static int imx471_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct imx471 *sensor = container_of_const(ctrl->handler, ++ struct imx471, ++ ctrl_handler); ++ struct v4l2_subdev_state *state = ++ v4l2_subdev_get_locked_active_state(&sensor->sd); ++ const struct v4l2_mbus_framefmt *format = ++ v4l2_subdev_state_get_format(state, 0); ++ int ret; ++ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ s64 exposure_max = format->height + ctrl->val - ++ IMX471_EXPOSURE_MARGIN; ++ ret = __v4l2_ctrl_modify_range(sensor->exposure, ++ sensor->exposure->minimum, ++ exposure_max, ++ sensor->exposure->step, ++ exposure_max); ++ if (ret) ++ return ret; ++ } ++ ++ if (!pm_runtime_get_if_in_use(sensor->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_ANALOG_GAIN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = cci_write(sensor->regmap, IMX471_REG_DIG_GAIN_GLOBAL, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = cci_write(sensor->regmap, IMX471_REG_EXPOSURE, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_VBLANK: ++ /* Update FLL that meets expected vertical blanking */ ++ ret = cci_write(sensor->regmap, IMX471_REG_FLL, ++ format->height + ctrl->val, NULL); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = cci_write(sensor->regmap, IMX471_REG_TEST_PATTERN, ++ ctrl->val, NULL); ++ break; ++ case V4L2_CID_HFLIP: ++ case V4L2_CID_VFLIP: ++ ret = cci_write(sensor->regmap, IMX471_REG_ORIENTATION, ++ sensor->hflip->val | sensor->vflip->val << 1, ++ NULL); ++ break; ++ default: ++ ret = -EINVAL; ++ dev_err(sensor->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n", ++ ctrl->id, ctrl->val); ++ break; ++ } ++ ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops imx471_ctrl_ops = { ++ .s_ctrl = imx471_set_ctrl, ++}; ++ ++static u32 imx471_get_format_code(struct imx471 *sensor) ++{ ++ unsigned int i; ++ ++ i = (sensor->vflip->val ? 2 : 0) | (sensor->hflip->val ? 1 : 0); ++ ++ return imx471_hv_flips_bayer_order[i]; ++} ++ ++static int imx471_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ ++ if (code->index >= (ARRAY_SIZE(imx471_hv_flips_bayer_order) / 4)) ++ return -EINVAL; ++ ++ code->code = imx471_get_format_code(sensor); ++ ++ return 0; ++} ++ ++static int imx471_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->index >= ARRAY_SIZE(imx471_modes)) ++ return -EINVAL; ++ ++ fse->min_width = imx471_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = imx471_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static void imx471_update_pad_format(struct imx471 *sensor, ++ const struct imx471_mode *mode, ++ struct v4l2_subdev_format *fmt) ++{ ++ fmt->format.code = imx471_get_format_code(sensor); ++ fmt->format.width = mode->width; ++ fmt->format.height = mode->height; ++ fmt->format.field = V4L2_FIELD_NONE; ++} ++ ++static int imx471_set_pad_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ int h_blank, ret; ++ ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->format.width, ++ fmt->format.height); ++ ++ imx471_update_pad_format(sensor, mode, fmt); ++ ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ return 0; ++ ++ if (media_entity_is_streaming(&sensor->sd.entity)) ++ return -EBUSY; ++ ++ ret = __v4l2_ctrl_modify_range(sensor->vblank, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ if (ret) ++ return ret; ++ ++ h_blank = mode->llp - mode->width; ++ /* ++ * Currently hblank is not changeable. ++ * So FPS control is done only by vblank. ++ */ ++ return __v4l2_ctrl_modify_range(sensor->hblank, h_blank, ++ h_blank, 1, h_blank); ++} ++ ++static int imx471_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); ++ break; ++ ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = IMX471_NATIVE_WIDTH; ++ sel->r.height = IMX471_NATIVE_HEIGHT; ++ return 0; ++ ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.top = IMX471_PIXEL_ARRAY_TOP; ++ sel->r.left = IMX471_PIXEL_ARRAY_LEFT; ++ sel->r.width = IMX471_PIXEL_ARRAY_WIDTH; ++ sel->r.height = IMX471_PIXEL_ARRAY_HEIGHT; ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++static int imx471_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_TRY, ++ .format = { ++ .code = MEDIA_BUS_FMT_SRGGB10_1X10, ++ .width = imx471_modes[0].width, ++ .height = imx471_modes[0].height, ++ }, ++ }; ++ ++ return imx471_set_pad_format(sd, sd_state, &fmt); ++} ++ ++static int imx471_identify_module(struct imx471 *sensor) ++{ ++ int ret; ++ u64 val; ++ ++ ret = cci_read(sensor->regmap, IMX471_REG_CHIP_ID, &val, NULL); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to read chip id\n"); ++ ++ if (val != IMX471_CHIP_ID) ++ return dev_err_probe(sensor->dev, -EIO, ++ "chip id mismatch: %x!=%llx\n", ++ IMX471_CHIP_ID, val); ++ ++ return 0; ++} ++ ++static int imx471_power_off(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ ++ clk_disable_unprepare(sensor->img_clk); ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ ++ return 0; ++} ++ ++static int imx471_power_on(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ if (ret < 0) { ++ dev_err(dev, "failed to enable regulators: %d\n", ret); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(sensor->img_clk); ++ if (ret < 0) { ++ regulator_bulk_disable(ARRAY_SIZE(imx471_supply_name), ++ sensor->supplies); ++ dev_err(dev, "failed to enable imaging clock: %d\n", ret); ++ return ret; ++ } ++ ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ ++ usleep_range(10000, 15000); ++ ++ return 0; ++} ++ ++static int imx471_enable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ const struct imx471_mode *mode; ++ struct v4l2_mbus_framefmt *fmt; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(sensor->dev); ++ if (ret) ++ return ret; ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_multi_reg_write(sensor->regmap, imx471_global_regs, ++ ARRAY_SIZE(imx471_global_regs), NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set global settings: %d\n", ++ ret); ++ goto error_powerdown; ++ } ++ ++ fmt = v4l2_subdev_state_get_format(state, 0); ++ mode = v4l2_find_nearest_size(imx471_modes, ARRAY_SIZE(imx471_modes), ++ width, height, fmt->width, fmt->height); ++ ++ ret = cci_multi_reg_write(sensor->regmap, mode->default_mode_regs, ++ mode->default_mode_regs_length, NULL); ++ if (ret) { ++ dev_err(sensor->dev, "failed to set mode: %d\n", ret); ++ goto error_powerdown; ++ } ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_DPGA_USE_GLOBAL_GAIN, 1, ++ NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = __v4l2_ctrl_handler_setup(&sensor->ctrl_handler); ++ if (ret) ++ goto error_powerdown; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STREAMING, NULL); ++ if (ret) ++ goto error_powerdown; ++ ++ __v4l2_ctrl_grab(sensor->vflip, true); ++ __v4l2_ctrl_grab(sensor->hflip, true); ++ ++ return ret; ++ ++error_powerdown: ++ pm_runtime_put(sensor->dev); ++ ++ return ret; ++} ++ ++static int imx471_disable_stream(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) ++{ ++ struct imx471 *sensor = to_imx471(sd); ++ int ret; ++ ++ ret = cci_write(sensor->regmap, IMX471_REG_MODE_SELECT, ++ IMX471_MODE_STANDBY, NULL); ++ pm_runtime_put(sensor->dev); ++ ++ if (ret) ++ dev_err(sensor->dev, ++ "failed to disable stream with return value: %d\n", ++ ret); ++ ++ __v4l2_ctrl_grab(sensor->vflip, false); ++ __v4l2_ctrl_grab(sensor->hflip, false); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops imx471_video_ops = { ++ .s_stream = v4l2_subdev_s_stream_helper, ++}; ++ ++static const struct v4l2_subdev_pad_ops imx471_pad_ops = { ++ .enum_mbus_code = imx471_enum_mbus_code, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .set_fmt = imx471_set_pad_format, ++ .get_selection = imx471_get_selection, ++ .enum_frame_size = imx471_enum_frame_size, ++ .enable_streams = imx471_enable_stream, ++ .disable_streams = imx471_disable_stream, ++}; ++ ++static const struct v4l2_subdev_ops imx471_subdev_ops = { ++ .video = &imx471_video_ops, ++ .pad = &imx471_pad_ops, ++}; ++ ++static const struct v4l2_subdev_internal_ops imx471_internal_ops = { ++ .init_state = imx471_init_state, ++}; ++ ++static int imx471_init_controls(struct imx471 *sensor) ++{ ++ const struct imx471_mode *mode = &imx471_modes[0]; ++ struct v4l2_fwnode_device_properties props; ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ struct v4l2_ctrl *link_freq; ++ s64 exposure_max, hblank; ++ u64 pixel_rate; ++ int ret; ++ ++ ret = v4l2_fwnode_device_parse(sensor->dev, &props); ++ if (ret) { ++ dev_err(sensor->dev, "failed to parse fwnode: %d\n", ret); ++ return ret; ++ } ++ ++ ctrl_hdlr = &sensor->ctrl_handler; ++ v4l2_ctrl_handler_init(ctrl_hdlr, 12); ++ ++ v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx471_ctrl_ops, &props); ++ ++ link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_LINK_FREQ, ++ ARRAY_SIZE(link_freq_menu_items) - 1, ++ 0, ++ link_freq_menu_items); ++ ++ /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ ++ pixel_rate = div_u64(IMX471_LINK_FREQ_DEFAULT * 2 * 4, 10); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_PIXEL_RATE, pixel_rate, ++ pixel_rate, 1, pixel_rate); ++ ++ sensor->vblank = v4l2_ctrl_new_std(ctrl_hdlr, ++ &imx471_ctrl_ops, ++ V4L2_CID_VBLANK, ++ mode->fll_min - mode->height, ++ IMX471_FLL_MAX - mode->height, ++ 1, ++ mode->fll_def - mode->height); ++ ++ hblank = mode->llp - mode->width; ++ sensor->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HBLANK, hblank, hblank, ++ 1, hblank); ++ ++ /* fll >= exposure time + adjust parameter (default value is 18) */ ++ exposure_max = mode->fll_def - IMX471_EXPOSURE_MARGIN; ++ sensor->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_EXPOSURE, ++ IMX471_EXPOSURE_MIN, exposure_max, ++ IMX471_EXPOSURE_STEP, ++ IMX471_EXPOSURE_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ IMX471_ANA_GAIN_MIN, IMX471_ANA_GAIN_MAX, ++ IMX471_ANA_GAIN_STEP, IMX471_ANA_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ IMX471_DGTL_GAIN_MIN, IMX471_DGTL_GAIN_MAX, ++ IMX471_DGTL_GAIN_STEP, IMX471_DGTL_GAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(imx471_test_pattern_menu) - 1, ++ 0, 0, imx471_test_pattern_menu); ++ ++ sensor->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ++ sensor->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx471_ctrl_ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ if (ctrl_hdlr->error) { ++ dev_err(sensor->dev, "%s control init failed: %d\n", ++ __func__, ctrl_hdlr->error); ++ goto error; ++ } ++ ++ link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; ++ ++ sensor->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++ ++error: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ ++ return ctrl_hdlr->error; ++} ++ ++static int imx471_check_hwcfg(struct imx471 *sensor) ++{ ++ struct v4l2_fwnode_endpoint bus_cfg = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY, ++ }; ++ struct fwnode_handle *ep, *fwnode = dev_fwnode(sensor->dev); ++ unsigned long link_freq_bitmap; ++ struct clk *clk; ++ int ret; ++ ++ clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(clk), ++ "can't get clock frequency\n"); ++ ++ if (clk_get_rate(clk) != IMX471_EXT_CLK) ++ return dev_err_probe(sensor->dev, -EINVAL, ++ "external clock %lu is not supported\n", ++ clk_get_rate(clk)); ++ ++ ep = fwnode_graph_get_endpoint_by_id(fwnode, 0, 0, 0); ++ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); ++ fwnode_handle_put(ep); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "parsing endpoint failed\n"); ++ ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { ++ ret = dev_err_probe(sensor->dev, -EINVAL, ++ "number of CSI2 data lanes %u is not supported\n", ++ bus_cfg.bus.mipi_csi2.num_data_lanes); ++ goto done_endpoint_free; ++ } ++ ++ ret = v4l2_link_freq_to_bitmap(sensor->dev, bus_cfg.link_frequencies, ++ bus_cfg.nr_of_link_frequencies, ++ link_freq_menu_items, ++ ARRAY_SIZE(link_freq_menu_items), ++ &link_freq_bitmap); ++ ++done_endpoint_free: ++ v4l2_fwnode_endpoint_free(&bus_cfg); ++ ++ return ret; ++} ++ ++static int imx471_probe(struct i2c_client *client) ++{ ++ struct imx471 *sensor; ++ int ret; ++ ++ sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return dev_err_probe(&client->dev, -ENOMEM, ++ "failed to allocate memory\n"); ++ ++ sensor->dev = &client->dev; ++ ++ ret = imx471_check_hwcfg(sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to check hwcfg: %d\n", ret); ++ ++ ret = imx471_get_regulators(sensor->dev, sensor); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to get regulators\n"); ++ ++ sensor->reset_gpio = devm_gpiod_get_optional(sensor->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset_gpio), ++ "failed to get reset gpio\n"); ++ ++ sensor->img_clk = devm_v4l2_sensor_clk_get(sensor->dev, NULL); ++ if (IS_ERR(sensor->img_clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->img_clk), ++ "failed to get imaging clock\n"); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &imx471_subdev_ops); ++ ++ sensor->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(sensor->regmap)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->regmap), ++ "failed to initialize CCI\n"); ++ ++ ret = imx471_power_on(sensor->dev); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "failed to power on\n"); ++ ++ ret = imx471_identify_module(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to find sensor: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ ret = imx471_init_controls(sensor); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, "failed to init controls: %d\n", ++ ret); ++ goto error_power_off; ++ } ++ ++ sensor->sd.internal_ops = &imx471_internal_ops; ++ sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) { ++ dev_err_probe(sensor->dev, ret, ++ "failed to init entity pads: %d\n", ret); ++ goto error_v4l2_ctrl_handler_free; ++ } ++ ++ sensor->sd.state_lock = sensor->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&sensor->sd); ++ if (ret < 0) { ++ dev_err_probe(sensor->dev, ret, "failed to init subdev: %d\n", ++ ret); ++ goto error_media_entity_pm; ++ } ++ ++ pm_runtime_set_active(sensor->dev); ++ pm_runtime_enable(sensor->dev); ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret < 0) ++ goto error_v4l2_subdev_cleanup; ++ ++ pm_runtime_idle(sensor->dev); ++ ++ return 0; ++ ++error_v4l2_subdev_cleanup: ++ pm_runtime_disable(sensor->dev); ++ pm_runtime_set_suspended(sensor->dev); ++ v4l2_subdev_cleanup(&sensor->sd); ++ ++error_media_entity_pm: ++ media_entity_cleanup(&sensor->sd.entity); ++ ++error_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); ++ ++error_power_off: ++ imx471_power_off(sensor->dev); ++ ++ return ret; ++} ++ ++static void imx471_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ ++ v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_ctrl_handler_free(sd->ctrl_handler); ++ ++ pm_runtime_disable(&client->dev); ++ ++ if (!pm_runtime_status_suspended(&client->dev)) { ++ imx471_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++ } ++} ++ ++static DEFINE_RUNTIME_DEV_PM_OPS(imx471_pm_ops, imx471_power_off, ++ imx471_power_on, NULL); ++ ++static const struct acpi_device_id imx471_acpi_ids[] __maybe_unused = { ++ { "SONY471A" }, ++ { "TBE20A0" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(acpi, imx471_acpi_ids); ++ ++static struct i2c_driver imx471_i2c_driver = { ++ .driver = { ++ .name = "imx471", ++ .acpi_match_table = ACPI_PTR(imx471_acpi_ids), ++ .pm = pm_sleep_ptr(&imx471_pm_ops), ++ }, ++ .probe = imx471_probe, ++ .remove = imx471_remove, ++}; ++module_i2c_driver(imx471_i2c_driver); ++ ++MODULE_AUTHOR("Jimmy Su "); ++MODULE_AUTHOR("Serin Yeh "); ++MODULE_AUTHOR("Kate Hsuan "); ++MODULE_DESCRIPTION("Sony imx471 sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c +--- a/drivers/media/i2c/ov2740.c ++++ b/drivers/media/i2c/ov2740.c +@@ -1241,41 +1241,42 @@ static int ov2740_nvmem_read(void *priv, unsigned int off, void *val, + return ret; + } + ++static const struct regmap_config regmap_config = { ++ .val_bits = 8, ++ .reg_bits = 16, ++ .disable_locking = true, ++}; ++ + static int ov2740_register_nvmem(struct i2c_client *client, + struct ov2740 *ov2740) + { + struct nvm_data *nvm; +- struct regmap_config regmap_config = { }; +- struct nvmem_config nvmem_config = { }; + struct regmap *regmap; + struct device *dev = ov2740->dev; ++ struct nvmem_config nvmem_config = { ++ .name = dev_name(dev), ++ .dev = dev, ++ .read_only = true, ++ .root_only = true, ++ .owner = THIS_MODULE, ++ .compat = true, ++ .base_dev = dev, ++ .reg_read = ov2740_nvmem_read, ++ .stride = 1, ++ .word_size = 1, ++ .size = CUSTOMER_USE_OTP_SIZE, ++ }; + + nvm = devm_kzalloc(dev, sizeof(*nvm), GFP_KERNEL); + if (!nvm) + return -ENOMEM; + +- regmap_config.val_bits = 8; +- regmap_config.reg_bits = 16; +- regmap_config.disable_locking = true; + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvm->regmap = regmap; +- +- nvmem_config.name = dev_name(dev); +- nvmem_config.dev = dev; +- nvmem_config.read_only = true; +- nvmem_config.root_only = true; +- nvmem_config.owner = THIS_MODULE; +- nvmem_config.compat = true; +- nvmem_config.base_dev = dev; +- nvmem_config.reg_read = ov2740_nvmem_read; +- nvmem_config.reg_write = NULL; + nvmem_config.priv = nvm; +- nvmem_config.stride = 1; +- nvmem_config.word_size = 1; +- nvmem_config.size = CUSTOMER_USE_OTP_SIZE; + + nvm->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvm->nvmem)) +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -49,6 +49,8 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { ++ /* Himax HM1092 */ ++ IPU_SENSOR_CONFIG("HIMX1092", 2, 180000000, 180480000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +@@ -97,6 +99,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* Sony IMX471 */ + IPU_SENSOR_CONFIG("SONY471A", 1, 200000000), ++ /* Sony IMX471 (found on Lenovo X1 Carbon G14) */ ++ IPU_SENSOR_CONFIG("TBE20A0", 1, 200000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), + }; +@@ -194,8 +198,8 @@ static struct acpi_device *ipu_bridge_get_ivsc_acpi_dev(struct acpi_device *adev + for (i = 0; i < ARRAY_SIZE(ivsc_acpi_ids); i++) { + const struct acpi_device_id *acpi_id = &ivsc_acpi_ids[i]; + struct acpi_device *consumer, *ivsc_adev; +- + acpi_handle handle = acpi_device_handle(ACPI_PTR(adev)); ++ + for_each_acpi_dev_match(ivsc_adev, acpi_id->id, NULL, -1) + /* camera sensor depends on IVSC in DSDT if exist */ + for_each_acpi_consumer_dev(ivsc_adev, consumer) diff --git a/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch.sig b/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch.sig new file mode 100644 index 0000000..2f69b33 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0543-media-laptop-cameras.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch b/pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch new file mode 100644 index 0000000..abda19b --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch @@ -0,0 +1,49 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -138,6 +138,45 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "OVTI02C1", + }, ++ /* ++ * The first four characters of DMI_BOARD_NAME identify the Lenovo ++ * machine type/model. For example, a DMI_BOARD_NAME starting with ++ * "21Q6" indicates a ThinkPad X9-15. ++ * ++ * Reference: https://psref.lenovo.com/ ++ */ ++ { ++ /* Lenovo X9-14 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21QA"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-14 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21QB"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-15 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21Q6"), ++ }, ++ .driver_data = "SONY471A", ++ }, ++ { ++ /* Lenovo X9-15 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BOARD_NAME, "21Q7"), ++ }, ++ .driver_data = "SONY471A", ++ }, + {} /* Terminating entry */ + }; + diff --git a/pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig b/pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig new file mode 100644 index 0000000..3b2eaec Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0544-ipu-bridge-lenovo-x9-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch b/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch new file mode 100644 index 0000000..7c15eb7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch @@ -0,0 +1,17 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -138,6 +138,13 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "OVTI02C1", + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 16 (Dell 16 Premium) DA16250"), ++ }, ++ .driver_data = "OVTI02C1", ++ }, + /* + * The first four characters of DMI_BOARD_NAME identify the Lenovo + * machine type/model. For example, a DMI_BOARD_NAME starting with diff --git a/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig b/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig new file mode 100644 index 0000000..1c5aff2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch b/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch new file mode 100644 index 0000000..a42d186 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch @@ -0,0 +1,18 @@ +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -184,6 +184,14 @@ static const struct dmi_system_id upside_down_sensor_dmi_ids[] = { + }, + .driver_data = "SONY471A", + }, ++ { ++ /* Samsung Galaxy Book5 Pro 360 */ ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "960QHA"), ++ }, ++ .driver_data = "OVTI02E1", ++ }, + {} /* Terminating entry */ + }; + diff --git a/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig b/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig new file mode 100644 index 0000000..5376099 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch b/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch new file mode 100644 index 0000000..db38adc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch @@ -0,0 +1,28 @@ +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -164,6 +164,24 @@ static const struct int3472_gpio_map int3472_gpio_map[] = { + .con_id = "dvdd", + .enable_time_us = 45 * USEC_PER_MSEC, + }, ++ { /* imx471 expects "vana" as con_id for power enable */ ++ .hid = "SONY471A", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, ++ { ++ /* ++ * imx471 (on Lenovo ThinkPads X1 G14) expects "vana" as con_id ++ * for power enable ++ */ ++ .hid = "TBE20A0", ++ .type_from = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .type_to = INT3472_GPIO_TYPE_POWER_ENABLE, ++ .con_id = "vana", ++ .enable_time_us = GPIO_REGULATOR_ENABLE_TIME, ++ }, + }; + + static void int3472_get_con_id_and_polarity(struct int3472_discrete_device *int3472, u8 *type, diff --git a/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch.sig b/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch.sig new file mode 100644 index 0000000..b2651bd Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0547-int3472-imx471-vana-power-enable.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch b/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch new file mode 100644 index 0000000..b041d90 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch @@ -0,0 +1,37 @@ +diff --git a/drivers/media/i2c/cvs/v4l2.c b/drivers/media/i2c/cvs/v4l2.c +--- a/drivers/media/i2c/cvs/v4l2.c ++++ b/drivers/media/i2c/cvs/v4l2.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -400,7 +401,7 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_async_connection *asc) + { + struct icvs *ctx = notifier_to_csi(notifier); +- int pad; ++ int pad, ret; + + pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, + MEDIA_PAD_FL_SOURCE); +@@ -409,9 +410,13 @@ static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, + + ctx->remote = &sd->entity.pads[pad]; + +- return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, +- ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | +- MEDIA_LNK_FL_IMMUTABLE); ++ ret = media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, ++ ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | ++ MEDIA_LNK_FL_IMMUTABLE); ++ if (ret) ++ return ret; ++ ++ return v4l2_device_register_subdev_nodes(sd->v4l2_dev); + } + + /** diff --git a/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch.sig b/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch.sig new file mode 100644 index 0000000..b58a4dd Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0548-media-cvs-sensor-node-in-bound-callback.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch b/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch new file mode 100644 index 0000000..a11c4f2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/media/i2c/imx471.c b/drivers/media/i2c/imx471.c +--- a/drivers/media/i2c/imx471.c ++++ b/drivers/media/i2c/imx471.c +@@ -41,7 +41,7 @@ + /* Analog gain control */ + #define IMX471_REG_ANALOG_GAIN CCI_REG16(0x0204) + #define IMX471_ANA_GAIN_MIN 0 +-#define IMX471_ANA_GAIN_MAX 800 ++#define IMX471_ANA_GAIN_MAX 960 + #define IMX471_ANA_GAIN_STEP 1 + #define IMX471_ANA_GAIN_DEFAULT 0 + diff --git a/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch.sig b/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch.sig new file mode 100644 index 0000000..283ab9e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0549-media-imx471-analog-gain-max-960.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch b/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch new file mode 100644 index 0000000..d8548c7 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch @@ -0,0 +1,3869 @@ +diff --git a/drivers/input/input.c b/drivers/input/input.c +--- a/drivers/input/input.c ++++ b/drivers/input/input.c +@@ -318,7 +318,7 @@ static int input_get_disposition(struct input_dev *dev, + static void input_event_dispose(struct input_dev *dev, int disposition, + unsigned int type, unsigned int code, int value) + { +- if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) ++ if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event && dev->ready) + dev->event(dev, type, code, value); + + if (disposition & INPUT_PASS_TO_HANDLERS) { +@@ -568,6 +568,75 @@ void input_release_device(struct input_handle *handle) + } + EXPORT_SYMBOL(input_release_device); + ++#define INPUT_DO_TOGGLE(dev, type, bits, on) \ ++ do { \ ++ int i; \ ++ bool active; \ ++ \ ++ if (!test_bit(EV_##type, dev->evbit)) \ ++ break; \ ++ \ ++ for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ ++ active = test_bit(i, dev->bits); \ ++ if (!active && !on) \ ++ continue; \ ++ \ ++ dev->event(dev, EV_##type, i, on ? active : 0); \ ++ } \ ++ } while (0) ++ ++/* ++ * Iterate through the logical state of the input device (LEDs, sounds, ++ * auto-repeat) and explicitly push that state down to the hardware ++ * via dev->event() to match the current logical state (if activate is true), ++ * or forcibly turn off all feedback like LEDs and sounds during teardown ++ * or suspend (if activate is false). ++ * ++ * Primarily used as a state-replay mechanism after a device is opened ++ * or uninhibited, as events might have been dropped by the core while the ++ * hardware was not marked as ready. ++ */ ++static void input_dev_toggle(struct input_dev *dev, bool activate) ++{ ++ if (!dev->event || !dev->ready) ++ return; ++ ++ INPUT_DO_TOGGLE(dev, LED, led, activate); ++ INPUT_DO_TOGGLE(dev, SND, snd, activate); ++ ++ if (activate && test_bit(EV_REP, dev->evbit)) { ++ dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); ++ dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); ++ } ++} ++ ++static int input_start_device(struct input_dev *dev) ++{ ++ int error; ++ ++ lockdep_assert_held(&dev->mutex); ++ ++ if (dev->users++ == 0 && !dev->inhibited) { ++ if (dev->open) { ++ error = dev->open(dev); ++ if (error) { ++ dev->users--; ++ return error; ++ } ++ } ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ dev->ready = true; ++ input_dev_toggle(dev, true); ++ } ++ ++ if (dev->poller) ++ input_dev_poller_start(dev->poller); ++ } ++ ++ return 0; ++} ++ + /** + * input_open_device - open input device + * @handle: handle through which device is being accessed +@@ -586,21 +655,9 @@ int input_open_device(struct input_handle *handle) + + handle->open++; + +- if (handle->handler->passive_observer) +- return 0; +- +- if (dev->users++ || dev->inhibited) { +- /* +- * Device is already opened and/or inhibited, +- * so we can exit immediately and report success. +- */ +- return 0; +- } +- +- if (dev->open) { +- error = dev->open(dev); ++ if (!handle->handler->passive_observer) { ++ error = input_start_device(dev); + if (error) { +- dev->users--; + handle->open--; + /* + * Make sure we are not delivering any more +@@ -611,8 +668,8 @@ int input_open_device(struct input_handle *handle) + } + } + +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ if (handle->open == 1 && handle->handler->start) ++ handle->handler->start(handle); + } + + return 0; +@@ -651,6 +708,12 @@ void input_close_device(struct input_handle *handle) + if (!--dev->users && !dev->inhibited) { + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ + if (dev->close) + dev->close(dev); + } +@@ -1702,37 +1765,6 @@ static int input_dev_uevent(const struct device *device, struct kobj_uevent_env + return 0; + } + +-#define INPUT_DO_TOGGLE(dev, type, bits, on) \ +- do { \ +- int i; \ +- bool active; \ +- \ +- if (!test_bit(EV_##type, dev->evbit)) \ +- break; \ +- \ +- for_each_set_bit(i, dev->bits##bit, type##_CNT) { \ +- active = test_bit(i, dev->bits); \ +- if (!active && !on) \ +- continue; \ +- \ +- dev->event(dev, EV_##type, i, on ? active : 0); \ +- } \ +- } while (0) +- +-static void input_dev_toggle(struct input_dev *dev, bool activate) +-{ +- if (!dev->event) +- return; +- +- INPUT_DO_TOGGLE(dev, LED, led, activate); +- INPUT_DO_TOGGLE(dev, SND, snd, activate); +- +- if (activate && test_bit(EV_REP, dev->evbit)) { +- dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]); +- dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]); +- } +-} +- + /** + * input_reset_device() - reset/restore the state of input device + * @dev: input device whose state needs to be reset +@@ -1756,52 +1788,73 @@ static int input_inhibit_device(struct input_dev *dev) + { + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (dev->inhibited) + return 0; + + if (dev->users) { +- if (dev->close) +- dev->close(dev); + if (dev->poller) + input_dev_poller_stop(dev->poller); ++ ++ scoped_guard(spinlock_irq, &dev->event_lock) { ++ input_dev_toggle(dev, false); ++ dev->ready = false; ++ } ++ ++ if (dev->close) ++ dev->close(dev); + } + + scoped_guard(spinlock_irq, &dev->event_lock) { + input_mt_release_slots(dev); + input_dev_release_keys(dev); + input_handle_event(dev, EV_SYN, SYN_REPORT, 1); +- input_dev_toggle(dev, false); ++ dev->inhibited = true; + } + +- dev->inhibited = true; +- + return 0; + } + + static int input_uninhibit_device(struct input_dev *dev) + { ++ struct input_handle *handle; + int error; + + guard(mutex)(&dev->mutex); + ++ if (dev->going_away) ++ return -ENODEV; ++ + if (!dev->inhibited) + return 0; + ++ dev->inhibited = false; ++ + if (dev->users) { + if (dev->open) { + error = dev->open(dev); +- if (error) ++ if (error) { ++ dev->inhibited = true; + return error; ++ } + } +- if (dev->poller) +- input_dev_poller_start(dev->poller); ++ scoped_guard(spinlock_irq, &dev->event_lock) ++ dev->ready = true; + } + +- dev->inhibited = false; +- + scoped_guard(spinlock_irq, &dev->event_lock) + input_dev_toggle(dev, true); + ++ if (dev->users && dev->poller) ++ input_dev_poller_start(dev->poller); ++ ++ list_for_each_entry(handle, &dev->h_list, d_node) { ++ if (handle->open && handle->handler->start) ++ handle->handler->start(handle); ++ } ++ + return 0; + } + +@@ -2655,9 +2708,6 @@ int input_register_handle(struct input_handle *handle) + */ + list_add_tail_rcu(&handle->h_node, &handler->h_list); + +- if (handler->start) +- handler->start(handle); +- + return 0; + } + EXPORT_SYMBOL(input_register_handle); +diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig +--- a/drivers/input/keyboard/Kconfig ++++ b/drivers/input/keyboard/Kconfig +@@ -119,44 +119,6 @@ config KEYBOARD_ATKBD + To compile this driver as a module, choose M here: the + module will be called atkbd. + +-config KEYBOARD_ATKBD_HP_KEYCODES +- bool "Use HP keyboard scancodes" +- depends on PARISC && KEYBOARD_ATKBD +- default y +- help +- Say Y here if you have a PA-RISC machine and want to use an AT or +- PS/2 keyboard, and your keyboard uses keycodes that are specific to +- PA-RISC keyboards. +- +- Say N if you use a standard keyboard. +- +-config KEYBOARD_ATKBD_RDI_KEYCODES +- bool "Use PrecisionBook keyboard scancodes" +- depends on KEYBOARD_ATKBD_HP_KEYCODES +- default n +- help +- If you have an RDI PrecisionBook, say Y here if you want to use its +- built-in keyboard (as opposed to an external keyboard). +- +- The PrecisionBook has five keys that conflict with those used by most +- AT and PS/2 keyboards. These are as follows: +- +- PrecisionBook Standard AT or PS/2 +- +- F1 F12 +- Left Ctrl Left Alt +- Caps Lock Left Ctrl +- Right Ctrl Caps Lock +- Left 102nd key (the key to the right of Left Shift) +- +- If you say N here, and use the PrecisionBook keyboard, then each key +- in the left-hand column will be interpreted as the corresponding key +- in the right-hand column. +- +- If you say Y here, and use an external keyboard, then each key in the +- right-hand column will be interpreted as the key shown in the +- left-hand column. +- + config KEYBOARD_QT1050 + tristate "Microchip AT42QT1050 Touch Sensor Chip" + depends on I2C +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -73,13 +73,6 @@ MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard conne + #define ATKBD_KEYMAP_SIZE 512 + + static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { +-#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES +- +-/* XXX: need a more general approach */ +- +-#include "hpps2atkbd.h" /* include the keyboard scancodes */ +- +-#else + 0, 67, 65, 63, 61, 59, 60, 88,183, 68, 66, 64, 62, 15, 41,117, + 184, 56, 42, 93, 29, 16, 2, 0,185, 0, 44, 31, 30, 17, 3, 0, + 186, 46, 45, 32, 18, 5, 4, 95,187, 57, 47, 33, 20, 19, 6,183, +@@ -99,7 +92,6 @@ static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + + 0, 0, 0, 65, 99, +-#endif + }; + + static const unsigned short atkbd_set3_keycode[ATKBD_KEYMAP_SIZE] = { +diff --git a/drivers/input/keyboard/cap11xx.c b/drivers/input/keyboard/cap11xx.c +--- a/drivers/input/keyboard/cap11xx.c ++++ b/drivers/input/keyboard/cap11xx.c +@@ -5,6 +5,8 @@ + * (c) 2014 Daniel Mack + */ + ++#include ++#include + #include + #include + #include +@@ -16,63 +18,43 @@ + #include + #include + ++#define CAP1114_REG_BUTTON_STATUS1 0x03 ++#define CAP1114_REG_BUTTON_STATUS2 0x04 ++#define CAP1114_REG_CONFIG2 0x40 ++#define CAP1114_REG_CONFIG2_VOL_UP_DOWN BIT(1) ++#define CAP1114_REG_LED_OUTPUT_CONTROL1 0x73 ++ + #define CAP11XX_REG_MAIN_CONTROL 0x00 + #define CAP11XX_REG_MAIN_CONTROL_GAIN_SHIFT (6) + #define CAP11XX_REG_MAIN_CONTROL_GAIN_MASK (0xc0) + #define CAP11XX_REG_MAIN_CONTROL_DLSEEP BIT(4) +-#define CAP11XX_REG_GENERAL_STATUS 0x02 + #define CAP11XX_REG_SENSOR_INPUT 0x03 +-#define CAP11XX_REG_NOISE_FLAG_STATUS 0x0a ++#define CAP1114_REG_BUTTON_STATUS2 0x04 + #define CAP11XX_REG_SENOR_DELTA(X) (0x10 + (X)) + #define CAP11XX_REG_SENSITIVITY_CONTROL 0x1f + #define CAP11XX_REG_SENSITIVITY_CONTROL_DELTA_SENSE_MASK 0x70 +-#define CAP11XX_REG_CONFIG 0x20 +-#define CAP11XX_REG_SENSOR_ENABLE 0x21 +-#define CAP11XX_REG_SENSOR_CONFIG 0x22 +-#define CAP11XX_REG_SENSOR_CONFIG2 0x23 +-#define CAP11XX_REG_SAMPLING_CONFIG 0x24 +-#define CAP11XX_REG_CALIBRATION 0x26 +-#define CAP11XX_REG_INT_ENABLE 0x27 + #define CAP11XX_REG_REPEAT_RATE 0x28 + #define CAP11XX_REG_SIGNAL_GUARD_ENABLE 0x29 +-#define CAP11XX_REG_MT_CONFIG 0x2a +-#define CAP11XX_REG_MT_PATTERN_CONFIG 0x2b +-#define CAP11XX_REG_MT_PATTERN 0x2d +-#define CAP11XX_REG_RECALIB_CONFIG 0x2f + #define CAP11XX_REG_SENSOR_THRESH(X) (0x30 + (X)) +-#define CAP11XX_REG_SENSOR_NOISE_THRESH 0x38 +-#define CAP11XX_REG_STANDBY_CHANNEL 0x40 +-#define CAP11XX_REG_STANDBY_CONFIG 0x41 +-#define CAP11XX_REG_STANDBY_SENSITIVITY 0x42 +-#define CAP11XX_REG_STANDBY_THRESH 0x43 + #define CAP11XX_REG_CONFIG2 0x44 + #define CAP11XX_REG_CONFIG2_ALT_POL BIT(6) +-#define CAP11XX_REG_SENSOR_BASE_CNT(X) (0x50 + (X)) +-#define CAP11XX_REG_LED_POLARITY 0x73 + #define CAP11XX_REG_LED_OUTPUT_CONTROL 0x74 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG 0x80 + #define CAP11XX_REG_CALIB_SENSITIVITY_CONFIG2 0x81 +- +-#define CAP11XX_REG_LED_DUTY_CYCLE_1 0x90 +-#define CAP11XX_REG_LED_DUTY_CYCLE_2 0x91 +-#define CAP11XX_REG_LED_DUTY_CYCLE_3 0x92 + #define CAP11XX_REG_LED_DUTY_CYCLE_4 0x93 + +-#define CAP11XX_REG_LED_DUTY_MIN_MASK (0x0f) +-#define CAP11XX_REG_LED_DUTY_MIN_MASK_SHIFT (0) + #define CAP11XX_REG_LED_DUTY_MAX_MASK (0xf0) +-#define CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT (4) + #define CAP11XX_REG_LED_DUTY_MAX_VALUE (15) + +-#define CAP11XX_REG_SENSOR_CALIB (0xb1 + (X)) +-#define CAP11XX_REG_SENSOR_CALIB_LSB1 0xb9 +-#define CAP11XX_REG_SENSOR_CALIB_LSB2 0xba + #define CAP11XX_REG_PRODUCT_ID 0xfd + #define CAP11XX_REG_MANUFACTURER_ID 0xfe + #define CAP11XX_REG_REVISION 0xff + + #define CAP11XX_MANUFACTURER_ID 0x5d + ++#define CAP11XX_T_RST_FILT_MIN_US 10000 ++#define CAP11XX_T_RST_ON_MIN_MS 400 ++ + #ifdef CONFIG_LEDS_CLASS + struct cap11xx_led { + struct cap11xx_priv *priv; +@@ -85,6 +67,7 @@ struct cap11xx_priv { + struct regmap *regmap; + struct device *dev; + struct input_dev *idev; ++ struct gpio_desc *reset_gpio; + const struct cap11xx_hw_model *model; + + struct cap11xx_led *leds; +@@ -101,47 +84,32 @@ struct cap11xx_priv { + + struct cap11xx_hw_model { + u8 product_id; ++ u8 led_output_control_reg_base; ++ u8 sensor_input_reg_base; + unsigned int num_channels; + unsigned int num_leds; ++ unsigned int num_sensor_thresholds; + bool has_gain; ++ bool has_grouped_sensors; + bool has_irq_config; ++ bool has_repeat_en; + bool has_sensitivity_control; + bool has_signal_guard; + }; + + static const struct reg_default cap11xx_reg_defaults[] = { + { CAP11XX_REG_MAIN_CONTROL, 0x00 }, +- { CAP11XX_REG_GENERAL_STATUS, 0x00 }, +- { CAP11XX_REG_SENSOR_INPUT, 0x00 }, +- { CAP11XX_REG_NOISE_FLAG_STATUS, 0x00 }, + { CAP11XX_REG_SENSITIVITY_CONTROL, 0x2f }, +- { CAP11XX_REG_CONFIG, 0x20 }, +- { CAP11XX_REG_SENSOR_ENABLE, 0x3f }, +- { CAP11XX_REG_SENSOR_CONFIG, 0xa4 }, +- { CAP11XX_REG_SENSOR_CONFIG2, 0x07 }, +- { CAP11XX_REG_SAMPLING_CONFIG, 0x39 }, +- { CAP11XX_REG_CALIBRATION, 0x00 }, +- { CAP11XX_REG_INT_ENABLE, 0x3f }, + { CAP11XX_REG_REPEAT_RATE, 0x3f }, +- { CAP11XX_REG_MT_CONFIG, 0x80 }, +- { CAP11XX_REG_MT_PATTERN_CONFIG, 0x00 }, +- { CAP11XX_REG_MT_PATTERN, 0x3f }, +- { CAP11XX_REG_RECALIB_CONFIG, 0x8a }, + { CAP11XX_REG_SENSOR_THRESH(0), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(1), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(2), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(3), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(4), 0x40 }, + { CAP11XX_REG_SENSOR_THRESH(5), 0x40 }, +- { CAP11XX_REG_SENSOR_NOISE_THRESH, 0x01 }, +- { CAP11XX_REG_STANDBY_CHANNEL, 0x00 }, +- { CAP11XX_REG_STANDBY_CONFIG, 0x39 }, +- { CAP11XX_REG_STANDBY_SENSITIVITY, 0x02 }, +- { CAP11XX_REG_STANDBY_THRESH, 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(6), 0x40 }, ++ { CAP11XX_REG_SENSOR_THRESH(7), 0x40 }, + { CAP11XX_REG_CONFIG2, 0x40 }, +- { CAP11XX_REG_LED_POLARITY, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB1, 0x00 }, +- { CAP11XX_REG_SENSOR_CALIB_LSB2, 0x00 }, + }; + + static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) +@@ -149,6 +117,12 @@ static bool cap11xx_volatile_reg(struct device *dev, unsigned int reg) + switch (reg) { + case CAP11XX_REG_MAIN_CONTROL: + case CAP11XX_REG_SENSOR_INPUT: ++ /* ++ * CAP1114_REG_BUTTON_STATUS1 (CAP11XX_REG_SENSOR_INPUT) and ++ * CAP1114_REG_BUTTON_STATUS2 is volatile for the CAP1114, ++ * which supports more than 8 touch channels. ++ */ ++ case CAP1114_REG_BUTTON_STATUS2: + case CAP11XX_REG_SENOR_DELTA(0): + case CAP11XX_REG_SENOR_DELTA(1): + case CAP11XX_REG_SENOR_DELTA(2): +@@ -257,8 +231,8 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + + if (!of_property_read_u32_array(node, "microchip,input-threshold", +- priv->thresholds, priv->model->num_channels)) { +- for (i = 0; i < priv->model->num_channels; i++) { ++ priv->thresholds, priv->model->num_sensor_thresholds)) { ++ for (i = 0; i < priv->model->num_sensor_thresholds; i++) { + if (priv->thresholds[i] > 127) { + dev_err(dev, "Invalid input-threshold value %u\n", + priv->thresholds[i]); +@@ -273,10 +247,13 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + } + } + +- if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", +- priv->calib_sensitivities, +- priv->model->num_channels)) { +- if (priv->model->has_sensitivity_control) { ++ if (of_property_present(node, "microchip,calib-sensitivity")) { ++ if (!priv->model->has_sensitivity_control) { ++ dev_warn(dev, ++ "This model doesn't support 'calib-sensitivity'\n"); ++ } else if (!of_property_read_u32_array(node, "microchip,calib-sensitivity", ++ priv->calib_sensitivities, ++ priv->model->num_channels)) { + for (i = 0; i < priv->model->num_channels; i++) { + if (!is_power_of_2(priv->calib_sensitivities[i]) || + priv->calib_sensitivities[i] > 4) { +@@ -296,32 +273,31 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + if (error) + return error; + } +- } else { +- dev_warn(dev, +- "This model doesn't support 'calib-sensitivity'\n"); + } + } + +- for (i = 0; i < priv->model->num_channels; i++) { +- if (!of_property_read_u32_index(node, "microchip,signal-guard", +- i, &u32_val)) { +- if (u32_val > 1) +- return -EINVAL; +- if (u32_val) +- priv->signal_guard_inputs_mask |= 0x01 << i; +- } +- } +- +- if (priv->signal_guard_inputs_mask) { +- if (priv->model->has_signal_guard) { +- error = regmap_write(priv->regmap, +- CAP11XX_REG_SIGNAL_GUARD_ENABLE, +- priv->signal_guard_inputs_mask); +- if (error) +- return error; +- } else { ++ if (of_property_present(node, "microchip,signal-guard")) { ++ if (!priv->model->has_signal_guard) { + dev_warn(dev, + "This model doesn't support 'signal-guard'\n"); ++ } else { ++ for (i = 0; i < priv->model->num_channels; i++) { ++ if (!of_property_read_u32_index(node, "microchip,signal-guard", ++ i, &u32_val)) { ++ if (u32_val > 1) ++ return -EINVAL; ++ if (u32_val) ++ priv->signal_guard_inputs_mask |= 0x01 << i; ++ } ++ } ++ ++ if (priv->signal_guard_inputs_mask) { ++ error = regmap_write(priv->regmap, ++ CAP11XX_REG_SIGNAL_GUARD_ENABLE, ++ priv->signal_guard_inputs_mask); ++ if (error) ++ return error; ++ } + } + } + +@@ -332,10 +308,23 @@ static int cap11xx_init_keys(struct cap11xx_priv *priv) + of_property_read_u32_array(node, "linux,keycodes", + priv->keycodes, priv->model->num_channels); + +- /* Disable autorepeat. The Linux input system has its own handling. */ +- error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); +- if (error) +- return error; ++ /* ++ * CAP1114 needs dedicated configuration to split ++ * grouped sensors into independent inputs. ++ */ ++ if (priv->model->has_grouped_sensors) { ++ error = regmap_set_bits(priv->regmap, CAP1114_REG_CONFIG2, ++ CAP1114_REG_CONFIG2_VOL_UP_DOWN); ++ if (error) ++ return error; ++ } ++ ++ if (priv->model->has_repeat_en) { ++ /* Disable autorepeat. The Linux input system has its own handling. */ ++ error = regmap_write(priv->regmap, CAP11XX_REG_REPEAT_RATE, 0); ++ if (error) ++ return error; ++ } + + return 0; + } +@@ -354,10 +343,25 @@ static irqreturn_t cap11xx_thread_func(int irq_num, void *data) + if (ret < 0) + goto out; + +- ret = regmap_read(priv->regmap, CAP11XX_REG_SENSOR_INPUT, &status); ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base, &status); + if (ret < 0) + goto out; + ++ if (priv->model->num_channels > 8) { ++ unsigned int status2; ++ ++ ret = regmap_read(priv->regmap, priv->model->sensor_input_reg_base + 1, &status2); ++ if (ret < 0) ++ goto out; ++ ++ /* ++ * CAP1114 STATUS1 register only contains data for the first 6 channels. ++ * the remaining channels is stored in STATUS2. ++ */ ++ status &= GENMASK(5, 0); ++ status |= FIELD_PREP(GENMASK(13, 6), status2); ++ } ++ + for (i = 0; i < priv->idev->keycodemax; i++) + input_report_key(priv->idev, priv->keycodes[i], + status & (1 << i)); +@@ -407,10 +411,16 @@ static int cap11xx_led_set(struct led_classdev *cdev, + * limitation. Brightness levels per LED are either + * 0 (OFF) and 1 (ON). + */ +- return regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, +- BIT(led->reg), +- value ? BIT(led->reg) : 0); ++ if (led->reg >= 8) ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ BIT(led->reg - 8), ++ value ? BIT(led->reg - 8) : 0); ++ else ++ return regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base, ++ BIT(led->reg), ++ value ? BIT(led->reg) : 0); + } + + static int cap11xx_init_leds(struct device *dev, +@@ -420,6 +430,7 @@ static int cap11xx_init_leds(struct device *dev, + struct cap11xx_led *led; + int cnt = of_get_child_count(node); + int error; ++ u32 duty_val; + + if (!num_leds || !cnt) + return 0; +@@ -433,15 +444,26 @@ static int cap11xx_init_leds(struct device *dev, + + priv->leds = led; + ++ /* Set all LEDs to off */ + error = regmap_update_bits(priv->regmap, +- CAP11XX_REG_LED_OUTPUT_CONTROL, 0xff, 0); ++ priv->model->led_output_control_reg_base, ++ GENMASK(min(num_leds, 8) - 1, 0), 0); + if (error) + return error; + ++ if (num_leds > 8) { ++ error = regmap_update_bits(priv->regmap, ++ priv->model->led_output_control_reg_base + 1, ++ GENMASK(num_leds - 8 - 1, 0), 0); ++ if (error) ++ return error; ++ } ++ ++ duty_val = FIELD_PREP(CAP11XX_REG_LED_DUTY_MAX_MASK, ++ CAP11XX_REG_LED_DUTY_MAX_VALUE); ++ + error = regmap_update_bits(priv->regmap, CAP11XX_REG_LED_DUTY_CYCLE_4, +- CAP11XX_REG_LED_DUTY_MAX_MASK, +- CAP11XX_REG_LED_DUTY_MAX_VALUE << +- CAP11XX_REG_LED_DUTY_MAX_MASK_SHIFT); ++ CAP11XX_REG_LED_DUTY_MAX_MASK, duty_val); + if (error) + return error; + +@@ -510,9 +532,20 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + ++ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(priv->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), ++ "Failed to get 'reset' GPIO\n"); ++ ++ if (priv->reset_gpio) { ++ usleep_range(CAP11XX_T_RST_FILT_MIN_US, CAP11XX_T_RST_FILT_MIN_US * 2); ++ gpiod_set_value_cansleep(priv->reset_gpio, 0); ++ msleep(CAP11XX_T_RST_ON_MIN_MS); ++ } ++ + error = regmap_read(priv->regmap, CAP11XX_REG_PRODUCT_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read product ID\n"); + + if (val != cap->product_id) { + dev_err(dev, "Product ID: Got 0x%02x, expected 0x%02x\n", +@@ -522,7 +555,7 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + + error = regmap_read(priv->regmap, CAP11XX_REG_MANUFACTURER_ID, &val); + if (error) +- return error; ++ return dev_err_probe(dev, error, "Failed to read manufacturer ID\n"); + + if (val != CAP11XX_MANUFACTURER_ID) { + dev_err(dev, "Manufacturer ID: Got 0x%02x, expected 0x%02x\n", +@@ -531,11 +564,8 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + error = regmap_read(priv->regmap, CAP11XX_REG_REVISION, &rev); +- if (error < 0) +- return error; +- +- dev_info(dev, "CAP11XX detected, model %s, revision 0x%02x\n", +- id->name, rev); ++ if (error) ++ return dev_err_probe(dev, error, "Failed to read revision\n"); + + priv->model = cap; + +@@ -599,47 +629,79 @@ static int cap11xx_i2c_probe(struct i2c_client *i2c_client) + } + + static const struct cap11xx_hw_model cap1106_model = { +- .product_id = 0x55, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x55, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, ++}; ++ ++static const struct cap11xx_hw_model cap1114_model = { ++ .product_id = 0x3a, ++ .num_channels = 14, .num_leds = 11, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP1114_REG_LED_OUTPUT_CONTROL1, ++ .sensor_input_reg_base = CAP1114_REG_BUTTON_STATUS1, ++ .has_grouped_sensors = true, + }; + + static const struct cap11xx_hw_model cap1126_model = { +- .product_id = 0x53, .num_channels = 6, .num_leds = 2, ++ .product_id = 0x53, ++ .num_channels = 6, .num_leds = 2, .num_sensor_thresholds = 6, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1188_model = { +- .product_id = 0x50, .num_channels = 8, .num_leds = 8, ++ .product_id = 0x50, ++ .num_channels = 8, .num_leds = 8, .num_sensor_thresholds = 8, ++ .led_output_control_reg_base = CAP11XX_REG_LED_OUTPUT_CONTROL, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, + .has_irq_config = true, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1203_model = { +- .product_id = 0x6d, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6d, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1206_model = { +- .product_id = 0x67, .num_channels = 6, .num_leds = 0, ++ .product_id = 0x67, ++ .num_channels = 6, .num_leds = 0, .num_sensor_thresholds = 6, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, ++ .has_repeat_en = true, + }; + + static const struct cap11xx_hw_model cap1293_model = { +- .product_id = 0x6f, .num_channels = 3, .num_leds = 0, ++ .product_id = 0x6f, ++ .num_channels = 3, .num_leds = 0, .num_sensor_thresholds = 3, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct cap11xx_hw_model cap1298_model = { +- .product_id = 0x71, .num_channels = 8, .num_leds = 0, ++ .product_id = 0x71, ++ .num_channels = 8, .num_leds = 0, .num_sensor_thresholds = 8, ++ .sensor_input_reg_base = CAP11XX_REG_SENSOR_INPUT, + .has_gain = true, ++ .has_repeat_en = true, + .has_sensitivity_control = true, + .has_signal_guard = true, + }; + + static const struct of_device_id cap11xx_dt_ids[] = { + { .compatible = "microchip,cap1106", .data = &cap1106_model }, ++ { .compatible = "microchip,cap1114", .data = &cap1114_model }, + { .compatible = "microchip,cap1126", .data = &cap1126_model }, + { .compatible = "microchip,cap1188", .data = &cap1188_model }, + { .compatible = "microchip,cap1203", .data = &cap1203_model }, +@@ -652,6 +714,7 @@ MODULE_DEVICE_TABLE(of, cap11xx_dt_ids); + + static const struct i2c_device_id cap11xx_i2c_ids[] = { + { .name = "cap1106", .driver_data = (kernel_ulong_t)&cap1106_model }, ++ { .name = "cap1114", .driver_data = (kernel_ulong_t)&cap1114_model }, + { .name = "cap1126", .driver_data = (kernel_ulong_t)&cap1126_model }, + { .name = "cap1188", .driver_data = (kernel_ulong_t)&cap1188_model }, + { .name = "cap1203", .driver_data = (kernel_ulong_t)&cap1203_model }, +diff --git a/drivers/input/keyboard/charlieplex_keypad.c b/drivers/input/keyboard/charlieplex_keypad.c +--- a/drivers/input/keyboard/charlieplex_keypad.c ++++ b/drivers/input/keyboard/charlieplex_keypad.c +@@ -77,7 +77,9 @@ static int charlieplex_keypad_scan_line(struct charlieplex_keypad *keypad, + int err; + + /* Activate only one line as output at a time. */ +- gpiod_direction_output(line_gpios->desc[oline], 1); ++ err = gpiod_direction_output(line_gpios->desc[oline], 1); ++ if (err) ++ return err; + + if (keypad->settling_time_us) + fsleep(keypad->settling_time_us); +diff --git a/drivers/input/keyboard/hpps2atkbd.h b/drivers/input/keyboard/hpps2atkbd.h +deleted file mode 100644 +--- a/drivers/input/keyboard/hpps2atkbd.h ++++ /dev/null +@@ -1,110 +0,0 @@ +-/* +- * drivers/input/keyboard/hpps2atkbd.h +- * +- * Copyright (c) 2004 Helge Deller +- * Copyright (c) 2002 Laurent Canet +- * Copyright (c) 2002 Thibaut Varene +- * Copyright (c) 2000 Xavier Debacker +- * +- * HP PS/2 AT-compatible Keyboard, found in PA/RISC Workstations & Laptops +- * +- * This file is subject to the terms and conditions of the GNU General Public +- * License. See the file "COPYING" in the main directory of this archive +- * for more details. +- */ +- +- +-/* Is the keyboard an RDI PrecisionBook? */ +-#ifndef CONFIG_KEYBOARD_ATKBD_RDI_KEYCODES +-# define CONFLICT(x,y) x +-#else +-# define CONFLICT(x,y) y +-#endif +- +-/* sadly RDI (Tadpole) decided to ship a different keyboard layout +- than HP for their PS/2 laptop keyboard which leads to conflicting +- keycodes between a normal HP PS/2 keyboard and a RDI Precisionbook. +- HP: RDI: */ +-#define C_07 CONFLICT( KEY_F12, KEY_F1 ) +-#define C_11 CONFLICT( KEY_LEFTALT, KEY_LEFTCTRL ) +-#define C_14 CONFLICT( KEY_LEFTCTRL, KEY_CAPSLOCK ) +-#define C_58 CONFLICT( KEY_CAPSLOCK, KEY_RIGHTCTRL ) +-#define C_61 CONFLICT( KEY_102ND, KEY_LEFT ) +- +-/* Raw SET 2 scancode table */ +- +-/* 00 */ KEY_RESERVED, KEY_F9, KEY_RESERVED, KEY_F5, KEY_F3, KEY_F1, KEY_F2, C_07, +-/* 08 */ KEY_ESC, KEY_F10, KEY_F8, KEY_F6, KEY_F4, KEY_TAB, KEY_GRAVE, KEY_F2, +-/* 10 */ KEY_RESERVED, C_11, KEY_LEFTSHIFT, KEY_RESERVED, C_14, KEY_Q, KEY_1, KEY_F3, +-/* 18 */ KEY_RESERVED, KEY_LEFTALT, KEY_Z, KEY_S, KEY_A, KEY_W, KEY_2, KEY_F4, +-/* 20 */ KEY_RESERVED, KEY_C, KEY_X, KEY_D, KEY_E, KEY_4, KEY_3, KEY_F5, +-/* 28 */ KEY_RESERVED, KEY_SPACE, KEY_V, KEY_F, KEY_T, KEY_R, KEY_5, KEY_F6, +-/* 30 */ KEY_RESERVED, KEY_N, KEY_B, KEY_H, KEY_G, KEY_Y, KEY_6, KEY_F7, +-/* 38 */ KEY_RESERVED, KEY_RIGHTALT, KEY_M, KEY_J, KEY_U, KEY_7, KEY_8, KEY_F8, +-/* 40 */ KEY_RESERVED, KEY_COMMA, KEY_K, KEY_I, KEY_O, KEY_0, KEY_9, KEY_F9, +-/* 48 */ KEY_RESERVED, KEY_DOT, KEY_SLASH, KEY_L, KEY_SEMICOLON, KEY_P, KEY_MINUS, KEY_F10, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_APOSTROPHE,KEY_RESERVED, KEY_LEFTBRACE, KEY_EQUAL, KEY_F11, KEY_SYSRQ, +-/* 58 */ C_58, KEY_RIGHTSHIFT,KEY_ENTER, KEY_RIGHTBRACE,KEY_BACKSLASH, KEY_BACKSLASH,KEY_F12, KEY_SCROLLLOCK, +-/* 60 */ KEY_DOWN, C_61, KEY_PAUSE, KEY_UP, KEY_DELETE, KEY_END, KEY_BACKSPACE, KEY_INSERT, +-/* 68 */ KEY_RESERVED, KEY_KP1, KEY_RIGHT, KEY_KP4, KEY_KP7, KEY_PAGEDOWN, KEY_HOME, KEY_PAGEUP, +-/* 70 */ KEY_KP0, KEY_KPDOT, KEY_KP2, KEY_KP5, KEY_KP6, KEY_KP8, KEY_ESC, KEY_NUMLOCK, +-/* 78 */ KEY_F11, KEY_KPPLUS, KEY_KP3, KEY_KPMINUS, KEY_KPASTERISK,KEY_KP9, KEY_SCROLLLOCK,KEY_102ND, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RIGHTALT, 255, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_CAPSLOCK, KEY_RESERVED, KEY_LEFTMETA, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTMETA, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_COMPOSE, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPSLASH, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_KPENTER, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_END, KEY_RESERVED, KEY_LEFT, KEY_HOME, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_INSERT, KEY_DELETE, KEY_DOWN, KEY_RESERVED, KEY_RIGHT, KEY_UP, KEY_RESERVED, KEY_PAUSE, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_PAGEDOWN, KEY_RESERVED, KEY_SYSRQ, KEY_PAGEUP, KEY_RESERVED, KEY_RESERVED, +- +-/* These are offset for escaped keycodes: */ +- +-/* 00 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_F7, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 08 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 10 */ KEY_RESERVED, KEY_RIGHTALT, KEY_RESERVED, KEY_RESERVED, KEY_RIGHTCTRL, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 18 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 20 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 28 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 30 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 38 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 40 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 48 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 50 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 58 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 60 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 68 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 70 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 78 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 80 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 88 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 90 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* 98 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* a8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* b8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* c8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* d8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* e8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f0 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, +-/* f8 */ KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +- +-#undef CONFLICT +-#undef C_07 +-#undef C_11 +-#undef C_14 +-#undef C_58 +-#undef C_61 +- +diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c +--- a/drivers/input/keyboard/matrix_keypad.c ++++ b/drivers/input/keyboard/matrix_keypad.c +@@ -16,7 +16,6 @@ + #include + #include + #include +-#include + #include + #include + #include +diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c +--- a/drivers/input/keyboard/pmic8xxx-keypad.c ++++ b/drivers/input/keyboard/pmic8xxx-keypad.c +@@ -462,15 +462,9 @@ static int pmic8xxx_kp_enable(struct pmic8xxx_kp *kp) + + static int pmic8xxx_kp_disable(struct pmic8xxx_kp *kp) + { +- int rc; +- + kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN; + +- rc = regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); +- if (rc < 0) +- return rc; +- +- return rc; ++ return regmap_write(kp->regmap, KEYP_CTRL, kp->ctrl_reg); + } + + static int pmic8xxx_kp_open(struct input_dev *dev) +diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c +--- a/drivers/input/keyboard/samsung-keypad.c ++++ b/drivers/input/keyboard/samsung-keypad.c +@@ -142,7 +142,7 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + unsigned int row_state[SAMSUNG_MAX_COLS]; + bool key_down; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + do { + readl(keypad->base + SAMSUNG_KEYIFSTSCLR); +@@ -158,8 +158,6 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) + + } while (key_down && !keypad->stopped); + +- pm_runtime_put(&keypad->pdev->dev); +- + return IRQ_HANDLED; + } + +@@ -167,7 +165,7 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Tell IRQ thread that it may poll the device. */ + keypad->stopped = false; +@@ -182,14 +180,14 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) + /* KEYIFCOL reg clear. */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); + +- pm_runtime_put(&keypad->pdev->dev); ++ enable_irq(keypad->irq); + } + + static void samsung_keypad_stop(struct samsung_keypad *keypad) + { + unsigned int val; + +- pm_runtime_get_sync(&keypad->pdev->dev); ++ guard(pm_runtime_active)(&keypad->pdev->dev); + + /* Signal IRQ thread to stop polling and disable the handler. */ + keypad->stopped = true; +@@ -205,14 +203,6 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); +- +- /* +- * Now that chip should not generate interrupts we can safely +- * re-enable the handler. +- */ +- enable_irq(keypad->irq); +- +- pm_runtime_put(&keypad->pdev->dev); + } + + static int samsung_keypad_open(struct input_dev *input_dev) +@@ -412,7 +402,8 @@ static int samsung_keypad_probe(struct platform_device *pdev) + } + + error = devm_request_threaded_irq(&pdev->dev, keypad->irq, NULL, +- samsung_keypad_irq, IRQF_ONESHOT, ++ samsung_keypad_irq, ++ IRQF_ONESHOT | IRQF_NO_AUTOEN, + dev_name(&pdev->dev), keypad); + if (error) { + dev_err(&pdev->dev, "failed to register keypad interrupt\n"); +@@ -492,15 +483,17 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, + + val = readl(keypad->base + SAMSUNG_KEYIFCON); + if (enable) { ++ enable_irq_wake(keypad->irq); + val |= SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- enable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); + } else { + val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; +- if (device_may_wakeup(&keypad->pdev->dev)) +- disable_irq_wake(keypad->irq); ++ writel(val, keypad->base + SAMSUNG_KEYIFCON); ++ disable_irq_wake(keypad->irq); ++ ++ if (!input_device_enabled(keypad->input_dev)) ++ writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); + } +- writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); + } +@@ -516,7 +509,8 @@ static int samsung_keypad_suspend(struct device *dev) + if (input_device_enabled(input_dev)) + samsung_keypad_stop(keypad); + +- samsung_keypad_toggle_wakeup(keypad, true); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, true); + + return 0; + } +@@ -529,7 +523,8 @@ static int samsung_keypad_resume(struct device *dev) + + guard(mutex)(&input_dev->mutex); + +- samsung_keypad_toggle_wakeup(keypad, false); ++ if (device_may_wakeup(dev)) ++ samsung_keypad_toggle_wakeup(keypad, false); + + if (input_device_enabled(input_dev)) + samsung_keypad_start(keypad); +diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c +--- a/drivers/input/keyboard/snvs_pwrkey.c ++++ b/drivers/input/keyboard/snvs_pwrkey.c +@@ -39,6 +39,9 @@ struct pwrkey_drv_data { + int keycode; + int keystate; /* 1:pressed */ + int wakeup; ++ bool suspended; /* Track suspend state */ ++ bool pending_press; /* Key pressed during suspend, report from timer callback */ ++ spinlock_t lock; /* Protects keystate, suspended and pending_press */ + struct timer_list check_timer; + struct input_dev *input; + u8 minor_rev; +@@ -49,14 +52,38 @@ static void imx_imx_snvs_check_for_events(struct timer_list *t) + struct pwrkey_drv_data *pdata = timer_container_of(pdata, t, + check_timer); + struct input_dev *input = pdata->input; ++ bool state_changed = false; ++ bool pending_press; + u32 state; + + regmap_read(pdata->snvs, SNVS_HPSR_REG, &state); + state = state & SNVS_HPSR_BTN ? 1 : 0; + +- /* only report new event if status changed */ +- if (state ^ pdata->keystate) { +- pdata->keystate = state; ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ pending_press = pdata->pending_press; ++ if (pending_press) { ++ pdata->pending_press = false; ++ pdata->keystate = 1; ++ } ++ /* only report new event if status changed */ ++ if (state ^ pdata->keystate) { ++ pdata->keystate = state; ++ state_changed = true; ++ } ++ } ++ ++ /* ++ * Report a press event latched during suspend. If the key is still ++ * held, state_changed will be 0 (keystate already set to 1 above), ++ * so no duplicate press is reported. If already released, ++ * state_changed will fire next to report the release. ++ */ ++ if (pending_press) { ++ input_report_key(input, pdata->keycode, 1); ++ input_sync(input); ++ } ++ ++ if (state_changed) { + input_event(input, EV_KEY, pdata->keycode, state); + input_sync(input); + pm_relax(pdata->input->dev.parent); +@@ -92,8 +119,17 @@ static irqreturn_t imx_snvs_pwrkey_interrupt(int irq, void *dev_id) + input_sync(input); + pm_relax(input->dev.parent); + } else { ++ /* ++ * If the key is pressed during suspend, latch it so ++ * the timer callback can report the press event in ++ * softirq context, avoiding out-of-order events. ++ */ ++ scoped_guard(spinlock_irqsave, &pdata->lock) { ++ if (pdata->suspended) ++ pdata->pending_press = true; ++ } + mod_timer(&pdata->check_timer, +- jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); ++ jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); + } + } + +@@ -112,6 +148,7 @@ static void imx_snvs_pwrkey_act(void *pdata) + + static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; + struct pwrkey_drv_data *pdata; + struct input_dev *input; + struct device_node *np; +@@ -122,37 +159,35 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + u32 vid; + + /* Get SNVS register Page */ +- np = pdev->dev.of_node; ++ np = dev->of_node; + if (!np) +- return -ENODEV; ++ return dev_err_probe(dev, -ENODEV, "Device tree node not found\n"); + +- pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->snvs = syscon_regmap_lookup_by_phandle(np, "regmap"); +- if (IS_ERR(pdata->snvs)) { +- dev_err(&pdev->dev, "Can't get snvs syscon\n"); +- return PTR_ERR(pdata->snvs); +- } ++ if (IS_ERR(pdata->snvs)) ++ return dev_err_probe(dev, PTR_ERR(pdata->snvs), "Can't get snvs syscon\n"); + + if (of_property_read_u32(np, "linux,keycode", &pdata->keycode)) { + pdata->keycode = KEY_POWER; +- dev_warn(&pdev->dev, "KEY_POWER without setting in dts\n"); ++ dev_warn(dev, "KEY_POWER without setting in dts\n"); + } + +- clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); +- if (IS_ERR(clk)) { +- dev_err(&pdev->dev, "Failed to get snvs clock (%pe)\n", clk); +- return PTR_ERR(clk); +- } ++ clk = devm_clk_get_optional_enabled(dev, NULL); ++ if (IS_ERR(clk)) ++ return dev_err_probe(dev, PTR_ERR(clk), ++ "Failed to get snvs clock (%pe)\n", clk); + + pdata->wakeup = of_property_read_bool(np, "wakeup-source"); + + pdata->irq = platform_get_irq(pdev, 0); + if (pdata->irq < 0) +- return -EINVAL; ++ return pdata->irq; + ++ spin_lock_init(&pdata->lock); + error = of_property_read_u32(np, "power-off-time-sec", &val); + if (!error) { + switch (val) { +@@ -165,9 +200,8 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + bpt = (val / 5) - 1; + break; + default: +- dev_err(&pdev->dev, +- "power-off-time-sec %d out of range\n", val); +- return -EINVAL; ++ return dev_err_probe(dev, -EINVAL, ++ "power-off-time-sec %d out of range\n", val); + } + + regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, SNVS_LPCR_BPT_MASK, +@@ -184,11 +218,9 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + + timer_setup(&pdata->check_timer, imx_imx_snvs_check_for_events, 0); + +- input = devm_input_allocate_device(&pdev->dev); +- if (!input) { +- dev_err(&pdev->dev, "failed to allocate the input device\n"); +- return -ENOMEM; +- } ++ input = devm_input_allocate_device(dev); ++ if (!input) ++ return dev_err_probe(dev, -ENOMEM, "failed to allocate the input device\n"); + + input->name = pdev->name; + input->phys = "snvs-pwrkey/input0"; +@@ -197,37 +229,57 @@ static int imx_snvs_pwrkey_probe(struct platform_device *pdev) + input_set_capability(input, EV_KEY, pdata->keycode); + + /* input customer action to cancel release timer */ +- error = devm_add_action(&pdev->dev, imx_snvs_pwrkey_act, pdata); +- if (error) { +- dev_err(&pdev->dev, "failed to register remove action\n"); +- return error; +- } ++ error = devm_add_action(dev, imx_snvs_pwrkey_act, pdata); ++ if (error) ++ return dev_err_probe(dev, error, "failed to register remove action\n"); + + pdata->input = input; + platform_set_drvdata(pdev, pdata); + +- error = devm_request_irq(&pdev->dev, pdata->irq, +- imx_snvs_pwrkey_interrupt, +- 0, pdev->name, pdev); +- if (error) { +- dev_err(&pdev->dev, "interrupt not available.\n"); +- return error; +- } ++ error = devm_request_irq(dev, pdata->irq, ++ imx_snvs_pwrkey_interrupt, ++ 0, pdev->name, pdev); ++ if (error) ++ return dev_err_probe(dev, error, "interrupt not available.\n"); + + error = input_register_device(input); +- if (error < 0) { +- dev_err(&pdev->dev, "failed to register input device\n"); +- return error; +- } ++ if (error < 0) ++ return dev_err_probe(dev, error, "failed to register input device\n"); + +- device_init_wakeup(&pdev->dev, pdata->wakeup); +- error = dev_pm_set_wake_irq(&pdev->dev, pdata->irq); ++ device_init_wakeup(dev, pdata->wakeup); ++ error = dev_pm_set_wake_irq(dev, pdata->irq); + if (error) +- dev_err(&pdev->dev, "irq wake enable failed.\n"); ++ dev_err(dev, "irq wake enable failed.\n"); + + return 0; + } + ++static int imx_snvs_pwrkey_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = true; ++ ++ return 0; ++} ++ ++static int imx_snvs_pwrkey_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); ++ ++ guard(spinlock_irq)(&pdata->lock); ++ pdata->suspended = false; ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(imx_snvs_pwrkey_pm_ops, ++ imx_snvs_pwrkey_suspend, ++ imx_snvs_pwrkey_resume); ++ + static const struct of_device_id imx_snvs_pwrkey_ids[] = { + { .compatible = "fsl,sec-v4.0-pwrkey" }, + { /* sentinel */ } +@@ -238,6 +290,7 @@ static struct platform_driver imx_snvs_pwrkey_driver = { + .driver = { + .name = "snvs_pwrkey", + .of_match_table = imx_snvs_pwrkey_ids, ++ .pm = pm_ptr(&imx_snvs_pwrkey_pm_ops), + }, + .probe = imx_snvs_pwrkey_probe, + }; +diff --git a/drivers/input/keyboard/tca8418_keypad.c b/drivers/input/keyboard/tca8418_keypad.c +--- a/drivers/input/keyboard/tca8418_keypad.c ++++ b/drivers/input/keyboard/tca8418_keypad.c +@@ -254,7 +254,8 @@ static int tca8418_configure(struct tca8418_keypad *keypad_data, + return error; + + error = tca8418_write_byte(keypad_data, REG_CFG, +- CFG_INT_CFG | CFG_OVR_FLOW_IEN | CFG_KE_IEN); ++ CFG_INT_CFG | CFG_OVR_FLOW_IEN | ++ CFG_OVR_FLOW_M | CFG_KE_IEN); + + return error; + } +diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig +--- a/drivers/input/misc/Kconfig ++++ b/drivers/input/misc/Kconfig +@@ -842,6 +842,18 @@ config INPUT_IQS7222 + To compile this driver as a module, choose M here: the + module will be called iqs7222. + ++config INPUT_ISA1200_HAPTIC ++ tristate "Imagis ISA1200 haptic feedback unit" ++ depends on I2C ++ select INPUT_FF_MEMLESS ++ select REGMAP_I2C ++ help ++ Say Y to enable support for the Imagis ISA1200 haptic ++ feedback unit. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called isa1200. ++ + config INPUT_CMA3000 + tristate "VTI CMA3000 Tri-axis accelerometer" + help +diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile +--- a/drivers/input/misc/Makefile ++++ b/drivers/input/misc/Makefile +@@ -49,6 +49,7 @@ obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o + obj-$(CONFIG_INPUT_IQS269A) += iqs269a.o + obj-$(CONFIG_INPUT_IQS626A) += iqs626a.o + obj-$(CONFIG_INPUT_IQS7222) += iqs7222.o ++obj-$(CONFIG_INPUT_ISA1200_HAPTIC) += isa1200.o + obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o + obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o + obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c +--- a/drivers/input/misc/ims-pcu.c ++++ b/drivers/input/misc/ims-pcu.c +@@ -2234,6 +2234,7 @@ static const struct usb_device_id ims_pcu_id_table[] = { + }, + { } + }; ++MODULE_DEVICE_TABLE(usb, ims_pcu_id_table); + + static const struct attribute_group *ims_pcu_sysfs_groups[] = { + &ims_pcu_attr_group, +diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c +new file mode 100644 +--- /dev/null ++++ b/drivers/input/misc/isa1200.c +@@ -0,0 +1,533 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * System control (LDO regulator) ++ * ++ * LDO voltage to register mapping is linear, but it is split in two parts: ++ * 2.3V - 3.0V map to 0x08 - 0x0f; 3.1V - 3.8V map to 0x00 - 0x7 ++ */ ++ ++#define ISA1200_SCTRL 0x00 ++#define ISA1200_LDO_VOLTAGE_BASE 0x08 ++#define ISA1200_LDO_VOLTAGE_STEP 100000 ++#define ISA1200_LDO_VOLTAGE_2V3 23 ++#define ISA1200_LDO_VOLTAGE_3V1 31 ++#define ISA1200_LDO_VOLTAGE_MIN 2300000 ++#define ISA1200_LDO_VOLTAGE_MAX 3800000 ++ ++/* ++ * The output frequency is calculated with this formula: ++ * ++ * base clock frequency ++ * fout = ----------------------------------------- ++ * (128 - PWM_FREQ) * 2 * PLLDIV * PWM_PERIOD ++ * ++ * The base clock frequency is the clock frequency provided on the ++ * clock input to the chip, divided by the value in HCTRL0 ++ * ++ * PWM_FREQ is configured in register HCTRL4, it is common to set this ++ * to 0 to get only two variables to calculate. ++ * ++ * PLLDIV is configured in register HCTRL3 (bits 7..4, so 0..15) ++ * PWM_PERIOD is configured in register HCTRL6 ++ * Further the duty cycle can be configured in HCTRL5 ++ */ ++ ++/* ++ * HCTRL0 configures clock or PWM input and selects the divider for ++ * the clock input. ++ */ ++#define ISA1200_HCTRL0 0x30 ++#define ISA1200_HCTRL0_HAP_ENABLE BIT(7) ++#define ISA1200_HCTRL0_PWM_GEN_MODE BIT(4) ++#define ISA1200_HCTRL0_PWM_INPUT_MODE BIT(3) ++#define ISA1200_HCTRL0_CLKDIV_128 128 ++ ++/* ++ * HCTRL1 configures the motor type and clock sourse ++ */ ++#define ISA1200_HCTRL1 0x31 ++#define ISA1200_HCTRL1_EXT_CLOCK BIT(7) ++#define ISA1200_HCTRL1_DAC_INVERT BIT(6) ++#define ISA1200_HCTRL1_MODE(n) (((n) & 1) << 5) ++ ++/* HCTRL2 controls software reset of the chip */ ++#define ISA1200_HCTRL2 0x32 ++#define ISA1200_HCTRL2_SW_RESET BIT(0) ++ ++/* ++ * HCTRL3 controls the PLL divisor ++ * ++ * Bits [0,1] are always set to 1 (we don't know what they are ++ * used for) and bit 4 and upward control the PLL divisor. ++ */ ++#define ISA1200_HCTRL3 0x33 ++#define ISA1200_HCTRL3_DEFAULT 0x03 ++#define ISA1200_HCTRL3_PLLDIV(n) (((n) & 0xf) << 4) ++ ++/* HCTRL4 controls the PWM frequency of external channel */ ++#define ISA1200_HCTRL4 0x34 ++ ++/* HCTRL5 controls the PWM high duty cycle of internal channel */ ++#define ISA1200_HCTRL5 0x35 ++ ++/* HCTRL6 controls the PWM period of internal channel */ ++#define ISA1200_HCTRL6 0x36 ++#define ISA1200_HCTRL6_PERIOD_SCALE 100 ++ ++/* The use for these registers is unknown but they exist */ ++#define ISA1200_HCTRL7 0x37 ++#define ISA1200_HCTRL8 0x38 ++#define ISA1200_HCTRL9 0x39 ++#define ISA1200_HCTRLA 0x3a ++#define ISA1200_HCTRLB 0x3b ++#define ISA1200_HCTRLC 0x3c ++#define ISA1200_HCTRLD 0x3d ++ ++#define ISA1200_EN_PINS_MAX 2 ++ ++static const struct regulator_bulk_data isa1200_supplies[] = { ++ { .supply = "vdd" }, { .supply = "vddp" }, ++}; ++ ++struct isa1200_config { ++ u32 ldo_voltage; ++ u32 mode; ++ u32 clkdiv; ++ u32 plldiv; ++ u32 freq; ++ u32 period; ++ u32 duty; ++}; ++ ++struct isa1200 { ++ struct input_dev *input; ++ struct regmap *map; ++ ++ struct clk *clk; ++ struct pwm_device *pwm; ++ struct gpio_descs *enable_gpios; ++ struct regulator_bulk_data *supplies; ++ ++ struct work_struct play_work; ++ struct isa1200_config config; ++ ++ int level; ++ bool suspended; ++ bool active; ++}; ++ ++static const struct regmap_config isa1200_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = ISA1200_HCTRLD, ++}; ++ ++static void isa1200_start(struct isa1200 *isa) ++{ ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &isa->input->dev; ++ struct pwm_state state; ++ u8 hctrl0 = 0, hctrl1 = 0; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ int err; ++ ++ if (!isa->active) { ++ err = regulator_bulk_enable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ if (err) { ++ dev_err(dev, "failed to enable supplies (%d)\n", err); ++ return; ++ } ++ ++ err = clk_prepare_enable(isa->clk); ++ if (err) { ++ dev_err(dev, "failed to enable clock (%d)\n", err); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ return; ++ } ++ ++ bitmap_fill(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ usleep_range(200, 300); ++ } ++ ++ regmap_write(isa->map, ISA1200_SCTRL, config->ldo_voltage); ++ ++ if (isa->clk) { ++ hctrl0 = ISA1200_HCTRL0_PWM_GEN_MODE; ++ hctrl1 = ISA1200_HCTRL1_EXT_CLOCK; ++ } ++ ++ if (isa->pwm) { ++ hctrl0 = ISA1200_HCTRL0_PWM_INPUT_MODE; ++ hctrl1 = 0; ++ } ++ ++ hctrl0 |= __ffs(config->clkdiv / ISA1200_HCTRL0_CLKDIV_128); ++ hctrl1 |= ISA1200_HCTRL1_DAC_INVERT; ++ hctrl1 |= ISA1200_HCTRL1_MODE(config->mode); ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ regmap_write(isa->map, ISA1200_HCTRL1, hctrl1); ++ ++ /* Make sure to de-assert software reset */ ++ regmap_write(isa->map, ISA1200_HCTRL2, 0x00); ++ ++ /* PLL divisor */ ++ regmap_write(isa->map, ISA1200_HCTRL3, ++ ISA1200_HCTRL3_PLLDIV(config->plldiv) | ++ ISA1200_HCTRL3_DEFAULT); ++ ++ /* Frequency */ ++ regmap_write(isa->map, ISA1200_HCTRL4, config->freq); ++ /* Duty cycle */ ++ regmap_write(isa->map, ISA1200_HCTRL5, config->period >> 1); ++ /* Period */ ++ regmap_write(isa->map, ISA1200_HCTRL6, config->period); ++ ++ hctrl0 |= ISA1200_HCTRL0_HAP_ENABLE; ++ regmap_write(isa->map, ISA1200_HCTRL0, hctrl0); ++ ++ if (isa->clk) ++ regmap_write(isa->map, ISA1200_HCTRL5, config->duty); ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = config->duty; ++ state.enabled = true; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ isa->active = true; ++} ++ ++static void isa1200_stop(struct isa1200 *isa) ++{ ++ struct pwm_state state; ++ DECLARE_BITMAP(values, ISA1200_EN_PINS_MAX); ++ ++ if (!isa->active) ++ return; ++ ++ if (isa->pwm) { ++ pwm_get_state(isa->pwm, &state); ++ state.duty_cycle = 0; ++ state.enabled = false; ++ pwm_apply_might_sleep(isa->pwm, &state); ++ } ++ ++ regmap_write(isa->map, ISA1200_HCTRL0, 0x00); ++ ++ bitmap_zero(values, ISA1200_EN_PINS_MAX); ++ gpiod_multi_set_value_cansleep(isa->enable_gpios, values); ++ ++ clk_disable_unprepare(isa->clk); ++ regulator_bulk_disable(ARRAY_SIZE(isa1200_supplies), ++ isa->supplies); ++ ++ isa->active = false; ++} ++ ++static void isa1200_play_work(struct work_struct *work) ++{ ++ struct isa1200 *isa = container_of(work, struct isa1200, play_work); ++ ++ if (!READ_ONCE(isa->suspended)) { ++ if (isa->level) ++ isa1200_start(isa); ++ else ++ isa1200_stop(isa); ++ } ++} ++ ++static int isa1200_vibrator_play_effect(struct input_dev *input, void *data, ++ struct ff_effect *effect) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ int level; ++ ++ /* ++ * TODO: we currently only support rumble. ++ * The ISA1200 can control two motors and some devices ++ * also have two motors mounted. ++ */ ++ level = effect->u.rumble.strong_magnitude; ++ if (!level) ++ level = effect->u.rumble.weak_magnitude; ++ ++ dev_dbg(&input->dev, "FF effect type %d level %d\n", ++ effect->type, level); ++ ++ if (isa->level != level) { ++ isa->level = level; ++ if (!READ_ONCE(isa->suspended)) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static void isa1200_vibrator_close(struct input_dev *input) ++{ ++ struct isa1200 *isa = input_get_drvdata(input); ++ ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ isa->level = 0; ++} ++ ++static int isa1200_of_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa = i2c_get_clientdata(client); ++ struct isa1200_config *config = &isa->config; ++ struct device *dev = &client->dev; ++ struct fwnode_handle *ldo_node; ++ int err; ++ ++ isa->clk = devm_clk_get_optional(dev, NULL); ++ if (IS_ERR(isa->clk)) ++ return dev_err_probe(dev, PTR_ERR(isa->clk), ++ "failed to get clock\n"); ++ ++ isa->pwm = devm_pwm_get(dev, NULL); ++ if (IS_ERR(isa->pwm)) { ++ err = PTR_ERR(isa->pwm); ++ if (err == -ENODEV || err == -EINVAL) ++ isa->pwm = NULL; ++ else ++ return dev_err_probe(dev, err, "getting PWM\n"); ++ } ++ ++ if (!isa->clk && !isa->pwm) ++ return dev_err_probe(dev, -EINVAL, ++ "clock or PWM are required, none were provided\n"); ++ ++ err = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(isa1200_supplies), ++ isa1200_supplies, &isa->supplies); ++ if (err) ++ return dev_err_probe(dev, err, "failed to get supplies\n"); ++ ++ isa->enable_gpios = devm_gpiod_get_array_optional(dev, "control", ++ GPIOD_OUT_LOW); ++ if (IS_ERR(isa->enable_gpios)) ++ return dev_err_probe(dev, PTR_ERR(isa->enable_gpios), ++ "failed to get enable gpios\n"); ++ ++ if (isa->enable_gpios && isa->enable_gpios->ndescs > ISA1200_EN_PINS_MAX) ++ return dev_err_probe(dev, -EINVAL, "too many enable gpios\n"); ++ ++ ldo_node = device_get_named_child_node(dev, "ldo"); ++ if (!ldo_node) ++ return dev_err_probe(dev, -ENODEV, ++ "failed to get embedded LDO node\n"); ++ ++ err = fwnode_property_read_u32(ldo_node, "regulator-min-microvolt", ++ &config->ldo_voltage); ++ fwnode_handle_put(ldo_node); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get ldo voltage\n"); ++ ++ config->ldo_voltage = clamp(config->ldo_voltage, ++ ISA1200_LDO_VOLTAGE_MIN, ++ ISA1200_LDO_VOLTAGE_MAX); ++ ++ config->ldo_voltage /= ISA1200_LDO_VOLTAGE_STEP; ++ if (config->ldo_voltage < ISA1200_LDO_VOLTAGE_3V1) ++ config->ldo_voltage = config->ldo_voltage - ++ ISA1200_LDO_VOLTAGE_2V3 + ++ ISA1200_LDO_VOLTAGE_BASE; ++ else ++ config->ldo_voltage -= ISA1200_LDO_VOLTAGE_3V1; ++ ++ config->mode = 0; /* LRA_MODE */ ++ device_property_read_u32(dev, "imagis,mode", &config->mode); ++ ++ config->clkdiv = ISA1200_HCTRL0_CLKDIV_128; ++ device_property_read_u32(dev, "imagis,clk-div", &config->clkdiv); ++ if (!config->clkdiv) ++ return dev_err_probe(dev, -EINVAL, "clk-div cannot be zero\n"); ++ ++ config->clkdiv = clamp(config->clkdiv, ISA1200_HCTRL0_CLKDIV_128, ++ ISA1200_HCTRL0_CLKDIV_128 << 3); ++ ++ err = device_property_read_u32(dev, "imagis,pll-div", &config->plldiv); ++ if (err || !config->plldiv) ++ config->plldiv = 1; ++ ++ config->period = 0; ++ config->freq = 0; ++ config->duty = 0; ++ ++ if (isa->clk) { ++ err = device_property_read_u32(dev, "imagis,period-ns", ++ &config->period); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to get period\n"); ++ ++ /* ++ * TODO: The scale value is arbitrary, but it fits observations ++ * quite well, and the exact conversion method is unknown. ++ * The period property value returned above is the HCTRL6 ++ * register value set by the vendor code, multiplied by 100. ++ */ ++ config->period /= ISA1200_HCTRL6_PERIOD_SCALE; ++ config->duty = config->period >> 1; ++ } ++ ++ if (isa->pwm) { ++ struct pwm_state state; ++ ++ pwm_init_state(isa->pwm, &state); ++ ++ if (!state.period) ++ return dev_err_probe(dev, -EINVAL, ++ "PWM period cannot be zero\n"); ++ ++ config->freq = div64_u64(NANO, state.period * config->clkdiv); ++ config->duty = state.period >> 1; ++ ++ err = pwm_apply_might_sleep(isa->pwm, &state); ++ if (err) ++ return dev_err_probe(dev, err, ++ "failed to apply initial PWM state\n"); ++ } ++ ++ /* ++ * TODO: If device is using a clock, this property should return the ++ * value written to the HCTRL5 register by downstrem code. It likely ++ * needs to be converted into a meaningful duty cycle value, though ++ * unfortunately the exact conversion mechanism is unknown. If the ++ * device uses PWM, this property will return the correct duty cycle ++ * in nanoseconds. ++ */ ++ device_property_read_u32(dev, "imagis,duty-cycle-ns", &config->duty); ++ ++ return 0; ++} ++ ++static int isa1200_probe(struct i2c_client *client) ++{ ++ struct isa1200 *isa; ++ struct device *dev = &client->dev; ++ int err; ++ ++ isa = devm_kzalloc(dev, sizeof(*isa), GFP_KERNEL); ++ if (!isa) ++ return -ENOMEM; ++ ++ isa->input = devm_input_allocate_device(dev); ++ if (!isa->input) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, isa); ++ ++ err = isa1200_of_probe(client); ++ if (err) ++ return err; ++ ++ isa->map = devm_regmap_init_i2c(client, &isa1200_regmap_config); ++ if (IS_ERR(isa->map)) ++ return dev_err_probe(dev, PTR_ERR(isa->map), ++ "failed to initialize register map\n"); ++ ++ INIT_WORK(&isa->play_work, isa1200_play_work); ++ ++ isa->input->name = "isa1200-haptic"; ++ isa->input->id.bustype = BUS_I2C; ++ isa->input->close = isa1200_vibrator_close; ++ ++ isa->active = false; ++ ++ input_set_drvdata(isa->input, isa); ++ ++ /* TODO: this hardware can likely support more than rumble */ ++ input_set_capability(isa->input, EV_FF, FF_RUMBLE); ++ ++ err = input_ff_create_memless(isa->input, NULL, ++ isa1200_vibrator_play_effect); ++ if (err) ++ return dev_err_probe(dev, err, "failed to create FF dev\n"); ++ ++ err = input_register_device(isa->input); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register input dev\n"); ++ ++ return 0; ++} ++ ++static int isa1200_suspend(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, true); ++ cancel_work_sync(&isa->play_work); ++ isa1200_stop(isa); ++ } ++ ++ return 0; ++} ++ ++static int isa1200_resume(struct device *dev) ++{ ++ struct isa1200 *isa = dev_get_drvdata(dev); ++ ++ guard(mutex)(&isa->input->mutex); ++ ++ if (input_device_enabled(isa->input)) { ++ WRITE_ONCE(isa->suspended, false); ++ if (isa->level) ++ schedule_work(&isa->play_work); ++ } ++ ++ return 0; ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(isa1200_pm_ops, isa1200_suspend, isa1200_resume); ++ ++static const struct of_device_id isa1200_of_match[] = { ++ { .compatible = "imagis,isa1200" }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, isa1200_of_match); ++ ++static struct i2c_driver isa1200_i2c_driver = { ++ .driver = { ++ .name = "isa1200", ++ .of_match_table = isa1200_of_match, ++ .pm = pm_sleep_ptr(&isa1200_pm_ops), ++ }, ++ .probe = isa1200_probe, ++}; ++module_i2c_driver(isa1200_i2c_driver); ++ ++MODULE_AUTHOR("Linus Walleij "); ++MODULE_AUTHOR("Svyatoslav Ryhel "); ++MODULE_DESCRIPTION("Imagis ISA1200 haptic feedback unit"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/mouse/elan_i2c.h b/drivers/input/mouse/elan_i2c.h +--- a/drivers/input/mouse/elan_i2c.h ++++ b/drivers/input/mouse/elan_i2c.h +@@ -102,7 +102,8 @@ struct elan_transport_ops { + int (*prepare_fw_update)(struct i2c_client *client, u16 ic_type, + u8 iap_version, u16 fw_page_size); + int (*write_fw_block)(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx); ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx); + int (*finish_fw_update)(struct i2c_client *client, + struct completion *reset_done); + +diff --git a/drivers/input/mouse/elan_i2c_core.c b/drivers/input/mouse/elan_i2c_core.c +--- a/drivers/input/mouse/elan_i2c_core.c ++++ b/drivers/input/mouse/elan_i2c_core.c +@@ -16,27 +16,27 @@ + */ + + #include ++#include + #include + #include + #include + #include + #include ++#include + #include + #include + #include +-#include +-#include +-#include +-#include +-#include +-#include +-#include + #include +-#include ++#include ++#include + #include + #include + #include + #include ++#include ++#include ++#include ++#include + #include + + #include "elan_i2c.h" +@@ -86,6 +86,7 @@ struct elan_tp_data { + u16 ic_type; + u16 fw_validpage_count; + u16 fw_page_size; ++ u16 fw_page_delay; + u32 fw_signature_address; + + u8 min_baseline; +@@ -127,8 +128,11 @@ static u32 elan_i2c_lookup_quirks(u16 ic_type, u16 product_id) + } + + static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, +- u32 *signature_address, u16 *page_size) ++ u32 *signature_address, u16 *page_size, ++ u16 *page_delay) + { ++ *page_delay = 30; ++ + switch (ic_type) { + case 0x00: + case 0x06: +@@ -164,6 +168,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + break; + case 0x19: + *validpage_count = 2032; ++ *page_delay = 10; + break; + default: + /* unknown ic type clear value */ +@@ -179,6 +184,7 @@ static int elan_get_fwinfo(u16 ic_type, u8 iap_version, u16 *validpage_count, + if ((ic_type == 0x14 || ic_type == 0x15) && iap_version >= 2) { + *validpage_count /= 8; + *page_size = ETP_FW_PAGE_SIZE_512; ++ *page_delay = 50; + } else if (ic_type >= 0x0D && iap_version >= 1) { + *validpage_count /= 2; + *page_size = ETP_FW_PAGE_SIZE_128; +@@ -368,7 +374,8 @@ static int elan_query_device_info(struct elan_tp_data *data) + error = elan_get_fwinfo(data->ic_type, data->iap_version, + &data->fw_validpage_count, + &data->fw_signature_address, +- &data->fw_page_size); ++ &data->fw_page_size, ++ &data->fw_page_delay); + if (error) + dev_warn(&data->client->dev, + "unexpected iap version %#04x (ic type: %#04x), firmware update will not work\n", +@@ -479,14 +486,16 @@ static int elan_query_device_parameters(struct elan_tp_data *data) + ********************************************************** + */ + static int elan_write_fw_block(struct elan_tp_data *data, u16 page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 page_delay, const u8 *page, u16 checksum, ++ int idx) + { + int retry = ETP_RETRY_COUNT; + int error; + + do { + error = data->ops->write_fw_block(data->client, page_size, +- page, checksum, idx); ++ page_delay, page, checksum, ++ idx); + if (!error) + return 0; + +@@ -525,7 +534,8 @@ static int __elan_update_firmware(struct elan_tp_data *data, + checksum += ((page[j + 1] << 8) | page[j]); + + error = elan_write_fw_block(data, data->fw_page_size, +- page, checksum, i); ++ data->fw_page_delay, page, checksum, ++ i); + if (error) { + dev_err(dev, "write page %d fail: %d\n", i, error); + return error; +diff --git a/drivers/input/mouse/elan_i2c_i2c.c b/drivers/input/mouse/elan_i2c_i2c.c +--- a/drivers/input/mouse/elan_i2c_i2c.c ++++ b/drivers/input/mouse/elan_i2c_i2c.c +@@ -625,7 +625,8 @@ static int elan_i2c_prepare_fw_update(struct i2c_client *client, u16 ic_type, + } + + static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + u8 val[3]; +@@ -650,7 +651,7 @@ static int elan_i2c_write_fw_block(struct i2c_client *client, u16 fw_page_size, + } + + /* Wait for F/W to update one page ROM data. */ +- msleep(fw_page_size == ETP_FW_PAGE_SIZE_512 ? 50 : 35); ++ msleep(fw_page_delay); + + error = elan_i2c_read_cmd(client, ETP_I2C_IAP_CTRL_CMD, val); + if (error) { +diff --git a/drivers/input/mouse/elan_i2c_smbus.c b/drivers/input/mouse/elan_i2c_smbus.c +--- a/drivers/input/mouse/elan_i2c_smbus.c ++++ b/drivers/input/mouse/elan_i2c_smbus.c +@@ -416,7 +416,8 @@ static int elan_smbus_prepare_fw_update(struct i2c_client *client, u16 ic_type, + + + static int elan_smbus_write_fw_block(struct i2c_client *client, u16 fw_page_size, +- const u8 *page, u16 checksum, int idx) ++ u16 fw_page_delay, const u8 *page, u16 checksum, ++ int idx) + { + struct device *dev = &client->dev; + int error; +diff --git a/drivers/input/mouse/focaltech.c b/drivers/input/mouse/focaltech.c +--- a/drivers/input/mouse/focaltech.c ++++ b/drivers/input/mouse/focaltech.c +@@ -78,8 +78,8 @@ struct focaltech_finger_state { + * Absolute position (from the bottom left corner) of the + * finger. + */ +- unsigned int x; +- unsigned int y; ++ int x; ++ int y; + }; + + /* +@@ -108,7 +108,7 @@ struct focaltech_hw_state { + }; + + struct focaltech_data { +- unsigned int x_max, y_max; ++ int x_max, y_max; + struct focaltech_hw_state state; + }; + +@@ -126,17 +126,16 @@ static void focaltech_report_state(struct psmouse *psmouse) + input_mt_slot(dev, i); + input_mt_report_slot_state(dev, MT_TOOL_FINGER, active); + if (active) { +- unsigned int clamped_x, clamped_y; + /* + * The touchpad might report invalid data, so we clamp + * the resulting values so that we do not confuse +- * userspace. ++ * userspace or accumulate coordinate wind-up. + */ +- clamped_x = clamp(finger->x, 0U, priv->x_max); +- clamped_y = clamp(finger->y, 0U, priv->y_max); +- input_report_abs(dev, ABS_MT_POSITION_X, clamped_x); ++ finger->x = clamp(finger->x, 0, priv->x_max); ++ finger->y = clamp(finger->y, 0, priv->y_max); ++ input_report_abs(dev, ABS_MT_POSITION_X, finger->x); + input_report_abs(dev, ABS_MT_POSITION_Y, +- priv->y_max - clamped_y); ++ priv->y_max - finger->y); + input_report_abs(dev, ABS_TOOL_WIDTH, state->width); + } + } +diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c +--- a/drivers/input/mouse/psmouse-base.c ++++ b/drivers/input/mouse/psmouse-base.c +@@ -256,9 +256,8 @@ static inline void __psmouse_set_state(struct psmouse *psmouse, enum psmouse_sta + */ + void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + { +- serio_pause_rx(psmouse->ps2dev.serio); ++ guard(serio_pause_rx)(psmouse->ps2dev.serio); + __psmouse_set_state(psmouse, new_state); +- serio_continue_rx(psmouse->ps2dev.serio); + } + + /* +@@ -267,7 +266,15 @@ void psmouse_set_state(struct psmouse *psmouse, enum psmouse_state new_state) + */ + static int psmouse_handle_byte(struct psmouse *psmouse) + { +- psmouse_ret_t rc = psmouse->protocol_handler(psmouse); ++ psmouse_ret_t rc; ++ ++ /* protocol_handler is NULL when device is being disconnected */ ++ if (unlikely(!psmouse->protocol_handler)) { ++ psmouse->pktcnt = 0; ++ return 0; ++ } ++ ++ rc = psmouse->protocol_handler(psmouse); + + switch (rc) { + case PSMOUSE_BAD_DATA: +@@ -484,13 +491,16 @@ static int psmouse_poll(struct psmouse *psmouse) + PSMOUSE_CMD_POLL | (psmouse->pktsize << 8)); + } + +-static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) ++static bool psmouse_check_pnp_id(const char *p, const char * const ids[]) + { +- int i; ++ const char * const *id; ++ size_t len; + +- for (i = 0; ids[i]; i++) +- if (!strcasecmp(id, ids[i])) ++ for (id = ids; *id; id++) { ++ len = strlen(*id); ++ if (!strncasecmp(p, *id, len) && (p[len] == ' ' || p[len] == '\0')) + return true; ++ } + + return false; + } +@@ -501,28 +511,26 @@ static bool psmouse_check_pnp_id(const char *id, const char * const ids[]) + bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]) + { + struct serio *serio = psmouse->ps2dev.serio; +- char *p, *fw_id_copy, *save_ptr; +- bool found = false; ++ const char *p = serio->firmware_id; + +- if (strncmp(serio->firmware_id, "PNP: ", 5)) ++ if (!strstarts(p, "PNP: ")) + return false; + +- fw_id_copy = kstrndup(&serio->firmware_id[5], +- sizeof(serio->firmware_id) - 5, +- GFP_KERNEL); +- if (!fw_id_copy) +- return false; ++ p += 5; ++ while (*p) { ++ p = skip_spaces(p); ++ if (!*p) ++ break; + +- save_ptr = fw_id_copy; +- while ((p = strsep(&fw_id_copy, " ")) != NULL) { +- if (psmouse_check_pnp_id(p, ids)) { +- found = true; ++ if (psmouse_check_pnp_id(p, ids)) ++ return true; ++ ++ p = strchr(p, ' '); ++ if (!p) + break; +- } + } + +- kfree(save_ptr); +- return found; ++ return false; + } + + /* +@@ -1311,10 +1319,10 @@ static void psmouse_resync(struct work_struct *work) + bool failed = false, enabled = false; + int i; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (psmouse->state != PSMOUSE_RESYNCING) +- goto out; ++ return; + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1392,8 +1400,6 @@ static void psmouse_resync(struct work_struct *work) + + if (parent) + psmouse_activate(parent); +- out: +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1404,7 +1410,7 @@ static void psmouse_cleanup(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ guard(mutex)(&psmouse_mutex); + + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); +@@ -1440,8 +1446,6 @@ static void psmouse_cleanup(struct serio *serio) + + psmouse_activate(parent); + } +- +- mutex_unlock(&psmouse_mutex); + } + + /* +@@ -1452,20 +1456,20 @@ static void psmouse_disconnect(struct serio *serio) + struct psmouse *psmouse = psmouse_from_serio(serio); + struct psmouse *parent = NULL; + +- mutex_lock(&psmouse_mutex); ++ disable_work_sync(&psmouse->resync_work); ++ ++ guard(mutex)(&psmouse_mutex); + + psmouse_set_state(psmouse, PSMOUSE_CMD_MODE); + +- /* make sure we don't have a resync in progress */ +- mutex_unlock(&psmouse_mutex); +- disable_work_sync(&psmouse->resync_work); +- mutex_lock(&psmouse_mutex); +- + if (serio->parent && serio->id.type == SERIO_PS_PSTHRU) { + parent = psmouse_from_serio(serio->parent); + psmouse_deactivate(parent); + } + ++ scoped_guard(serio_pause_rx, serio) ++ psmouse->protocol_handler = NULL; ++ + if (psmouse->disconnect) + psmouse->disconnect(psmouse); + +@@ -1484,8 +1488,6 @@ static void psmouse_disconnect(struct serio *serio) + + if (parent) + psmouse_activate(parent); +- +- mutex_unlock(&psmouse_mutex); + } + + static int psmouse_switch_protocol(struct psmouse *psmouse, +@@ -1654,14 +1656,12 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + enum psmouse_type type; + int rc = -1; + +- mutex_lock(&psmouse_mutex); ++ lockdep_assert_held(&psmouse_mutex); + + if (fast_reconnect) { + reconnect_handler = psmouse->fast_reconnect; +- if (!reconnect_handler) { +- rc = -ENOENT; +- goto out_unlock; +- } ++ if (!reconnect_handler) ++ return -ENOENT; + } else { + reconnect_handler = psmouse->reconnect; + } +@@ -1713,18 +1713,20 @@ static int __psmouse_reconnect(struct serio *serio, bool fast_reconnect) + if (parent) + psmouse_activate(parent); + +-out_unlock: +- mutex_unlock(&psmouse_mutex); + return rc; + } + + static int psmouse_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, false); + } + + static int psmouse_fast_reconnect(struct serio *serio) + { ++ guard(mutex)(&psmouse_mutex); ++ + return __psmouse_reconnect(serio, true); + } + +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -164,6 +164,7 @@ static const char * const topbuttonpad_pnp_ids[] = { + #ifdef CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS + static const char * const smbus_pnp_ids[] = { + /* all of the topbuttonpad_pnp_ids are valid, we just add some extras */ ++ "DLL0597", /* Dell Inspiron 3521 */ + "DLL060d", /* Dell Precision M3800 */ + "LEN0048", /* X1 Carbon 3 */ + "LEN0046", /* X250 */ +diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c +--- a/drivers/input/mouse/synaptics_i2c.c ++++ b/drivers/input/mouse/synaptics_i2c.c +@@ -261,7 +261,7 @@ static s32 synaptics_i2c_reg_set(struct i2c_client *client, u16 reg, u8 val) + if (error) + return error; + +- return error; ++ return 0; + } + + static s32 synaptics_i2c_word_get(struct i2c_client *client, u16 reg) +diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c +--- a/drivers/input/rmi4/rmi_f12.c ++++ b/drivers/input/rmi4/rmi_f12.c +@@ -155,6 +155,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + offset += 4; + } + ++ /* When platform data are provided, we're done */ ++ if (sensor->x_mm && sensor->y_mm) ++ return 0; ++ + /* + * Use the Query DPM feature when the resolution query register + * exists. +@@ -171,8 +175,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + } + dpm_resolution = buf[0]; + +- sensor->x_mm = sensor->max_x / dpm_resolution; +- sensor->y_mm = sensor->max_y / dpm_resolution; ++ if (!sensor->x_mm) ++ sensor->x_mm = sensor->max_x / dpm_resolution; ++ if (!sensor->y_mm) ++ sensor->y_mm = sensor->max_y / dpm_resolution; + } else { + if (rmi_register_desc_has_subpacket(item, 3)) { + rx_receivers = buf[offset]; +@@ -184,8 +190,10 @@ static int rmi_f12_read_sensor_tuning(struct f12_data *f12) + if (rmi_register_desc_has_subpacket(item, 4)) + offset += 1; + +- sensor->x_mm = (pitch_x * rx_receivers) >> 12; +- sensor->y_mm = (pitch_y * tx_receivers) >> 12; ++ if (!sensor->x_mm) ++ sensor->x_mm = (pitch_x * rx_receivers) >> 12; ++ if (!sensor->y_mm) ++ sensor->y_mm = (pitch_y * tx_receivers) >> 12; + } + + rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: x_mm: %d y_mm: %d\n", __func__, +diff --git a/drivers/input/rmi4/rmi_smbus.c b/drivers/input/rmi4/rmi_smbus.c +--- a/drivers/input/rmi4/rmi_smbus.c ++++ b/drivers/input/rmi4/rmi_smbus.c +@@ -177,13 +177,8 @@ static int smb_block_read(struct rmi_transport_dev *xport, + struct rmi_smb_xport *rmi_smb = + container_of(xport, struct rmi_smb_xport, xport); + struct i2c_client *client = rmi_smb->client; +- int retval; + +- retval = i2c_smbus_read_block_data(client, commandcode, buf); +- if (retval < 0) +- return retval; +- +- return retval; ++ return i2c_smbus_read_block_data(client, commandcode, buf); + } + + static int rmi_smb_read_block(struct rmi_transport_dev *xport, u16 rmiaddr, +diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig +--- a/drivers/input/serio/Kconfig ++++ b/drivers/input/serio/Kconfig +@@ -106,6 +106,33 @@ config SERIO_GSCPS2 + To compile this driver as a module, choose M here: the + module will be called gscps2. + ++config SERIO_GSCPS2_RDI_KEYCODES ++ bool "Use PrecisionBook keyboard scancodes" ++ depends on SERIO_GSCPS2 ++ default n ++ help ++ If you have an RDI PrecisionBook, say Y here if you want to use its ++ built-in keyboard (as opposed to an external keyboard). ++ ++ The PrecisionBook has five keys that conflict with those used by most ++ AT and PS/2 keyboards. These are as follows: ++ ++ PrecisionBook Standard AT or PS/2 ++ ++ F1 F12 ++ Left Ctrl Left Alt ++ Caps Lock Left Ctrl ++ Right Ctrl Caps Lock ++ Left 102nd key (the key to the right of Left Shift) ++ ++ If you say N here, and use the PrecisionBook keyboard, then each key ++ in the left-hand column will be interpreted as the corresponding key ++ in the right-hand column. ++ ++ If you say Y here, and use an external keyboard, then each key in the ++ right-hand column will be interpreted as the key shown in the ++ left-hand column. ++ + config HP_SDC + tristate "HP System Device Controller i8042 Support" + depends on (GSC || HP300) && SERIO +diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c +--- a/drivers/input/serio/gscps2.c ++++ b/drivers/input/serio/gscps2.c +@@ -22,18 +22,15 @@ + * was usable/enabled ?) + */ + +-#include +-#include +-#include +-#include ++#include + #include + #include +-#include +-#include ++#include + #include ++#include ++#include + + #include +-#include + #include + + MODULE_AUTHOR("Laurent Canet , Thibaut Varene , Helge Deller "); +@@ -80,6 +77,116 @@ MODULE_LICENSE("GPL"); + #define GSC_ID_KEYBOARD 0 /* device ID values */ + #define GSC_ID_MOUSE 1 + ++#ifndef CONFIG_SERIO_GSCPS2_RDI_KEYCODES ++# define CONFLICT(x, y) x ++#else ++# define CONFLICT(x, y) y ++#endif ++ ++/* ++ * Sadly RDI (Tadpole) decided to ship a different keyboard layout ++ * than HP for their PS/2 laptop keyboard which leads to conflicting ++ * keycodes between a normal HP PS/2 keyboard and a RDI PrecisionBook. ++ * HP: RDI: ++ */ ++#define C_07 CONFLICT(KEY_F12, KEY_F1) ++#define C_11 CONFLICT(KEY_LEFTALT, KEY_LEFTCTRL) ++#define C_14 CONFLICT(KEY_LEFTCTRL, KEY_CAPSLOCK) ++#define C_58 CONFLICT(KEY_CAPSLOCK, KEY_RIGHTCTRL) ++#define C_61 CONFLICT(KEY_102ND, KEY_LEFT) ++ ++/* ++ * Special keycode value recognized by atkbd (ATKBD_KEY_NULL) to silently ++ * discard scancodes without generating input events or "unknown key" warnings. ++ */ ++#define KEY_NULL 255 ++ ++#define KEYMAP_ENTRY(scancode, keycode) (((scancode) << 16) | (keycode)) ++ ++static const u32 gscps2_keymap[] = { ++ KEYMAP_ENTRY(0x01, KEY_F9), KEYMAP_ENTRY(0x03, KEY_F5), ++ KEYMAP_ENTRY(0x04, KEY_F3), KEYMAP_ENTRY(0x05, KEY_F1), ++ KEYMAP_ENTRY(0x06, KEY_F2), KEYMAP_ENTRY(0x07, C_07), ++ KEYMAP_ENTRY(0x08, KEY_ESC), KEYMAP_ENTRY(0x09, KEY_F10), ++ KEYMAP_ENTRY(0x0a, KEY_F8), KEYMAP_ENTRY(0x0b, KEY_F6), ++ KEYMAP_ENTRY(0x0c, KEY_F4), KEYMAP_ENTRY(0x0d, KEY_TAB), ++ KEYMAP_ENTRY(0x0e, KEY_GRAVE), KEYMAP_ENTRY(0x0f, KEY_F2), ++ KEYMAP_ENTRY(0x11, C_11), KEYMAP_ENTRY(0x12, KEY_LEFTSHIFT), ++ KEYMAP_ENTRY(0x14, C_14), KEYMAP_ENTRY(0x15, KEY_Q), ++ KEYMAP_ENTRY(0x16, KEY_1), KEYMAP_ENTRY(0x17, KEY_F3), ++ KEYMAP_ENTRY(0x19, KEY_LEFTALT), KEYMAP_ENTRY(0x1a, KEY_Z), ++ KEYMAP_ENTRY(0x1b, KEY_S), KEYMAP_ENTRY(0x1c, KEY_A), ++ KEYMAP_ENTRY(0x1d, KEY_W), KEYMAP_ENTRY(0x1e, KEY_2), ++ KEYMAP_ENTRY(0x1f, KEY_F4), KEYMAP_ENTRY(0x21, KEY_C), ++ KEYMAP_ENTRY(0x22, KEY_X), KEYMAP_ENTRY(0x23, KEY_D), ++ KEYMAP_ENTRY(0x24, KEY_E), KEYMAP_ENTRY(0x25, KEY_4), ++ KEYMAP_ENTRY(0x26, KEY_3), KEYMAP_ENTRY(0x27, KEY_F5), ++ KEYMAP_ENTRY(0x29, KEY_SPACE), KEYMAP_ENTRY(0x2a, KEY_V), ++ KEYMAP_ENTRY(0x2b, KEY_F), KEYMAP_ENTRY(0x2c, KEY_T), ++ KEYMAP_ENTRY(0x2d, KEY_R), KEYMAP_ENTRY(0x2e, KEY_5), ++ KEYMAP_ENTRY(0x2f, KEY_F6), KEYMAP_ENTRY(0x31, KEY_N), ++ KEYMAP_ENTRY(0x32, KEY_B), KEYMAP_ENTRY(0x33, KEY_H), ++ KEYMAP_ENTRY(0x34, KEY_G), KEYMAP_ENTRY(0x35, KEY_Y), ++ KEYMAP_ENTRY(0x36, KEY_6), KEYMAP_ENTRY(0x37, KEY_F7), ++ KEYMAP_ENTRY(0x39, KEY_RIGHTALT), KEYMAP_ENTRY(0x3a, KEY_M), ++ KEYMAP_ENTRY(0x3b, KEY_J), KEYMAP_ENTRY(0x3c, KEY_U), ++ KEYMAP_ENTRY(0x3d, KEY_7), KEYMAP_ENTRY(0x3e, KEY_8), ++ KEYMAP_ENTRY(0x3f, KEY_F8), KEYMAP_ENTRY(0x41, KEY_COMMA), ++ KEYMAP_ENTRY(0x42, KEY_K), KEYMAP_ENTRY(0x43, KEY_I), ++ KEYMAP_ENTRY(0x44, KEY_O), KEYMAP_ENTRY(0x45, KEY_0), ++ KEYMAP_ENTRY(0x46, KEY_9), KEYMAP_ENTRY(0x47, KEY_F9), ++ KEYMAP_ENTRY(0x49, KEY_DOT), KEYMAP_ENTRY(0x4a, KEY_SLASH), ++ KEYMAP_ENTRY(0x4b, KEY_L), KEYMAP_ENTRY(0x4c, KEY_SEMICOLON), ++ KEYMAP_ENTRY(0x4d, KEY_P), KEYMAP_ENTRY(0x4e, KEY_MINUS), ++ KEYMAP_ENTRY(0x4f, KEY_F10), KEYMAP_ENTRY(0x52, KEY_APOSTROPHE), ++ KEYMAP_ENTRY(0x54, KEY_LEFTBRACE), KEYMAP_ENTRY(0x55, KEY_EQUAL), ++ KEYMAP_ENTRY(0x56, KEY_F11), KEYMAP_ENTRY(0x57, KEY_SYSRQ), ++ KEYMAP_ENTRY(0x58, C_58), KEYMAP_ENTRY(0x59, KEY_RIGHTSHIFT), ++ KEYMAP_ENTRY(0x5a, KEY_ENTER), KEYMAP_ENTRY(0x5b, KEY_RIGHTBRACE), ++ KEYMAP_ENTRY(0x5c, KEY_BACKSLASH), KEYMAP_ENTRY(0x5d, KEY_BACKSLASH), ++ KEYMAP_ENTRY(0x5e, KEY_F12), KEYMAP_ENTRY(0x5f, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x60, KEY_DOWN), KEYMAP_ENTRY(0x61, C_61), ++ KEYMAP_ENTRY(0x62, KEY_PAUSE), KEYMAP_ENTRY(0x63, KEY_UP), ++ KEYMAP_ENTRY(0x64, KEY_DELETE), KEYMAP_ENTRY(0x65, KEY_END), ++ KEYMAP_ENTRY(0x66, KEY_BACKSPACE), KEYMAP_ENTRY(0x67, KEY_INSERT), ++ KEYMAP_ENTRY(0x69, KEY_KP1), KEYMAP_ENTRY(0x6a, KEY_RIGHT), ++ KEYMAP_ENTRY(0x6b, KEY_KP4), KEYMAP_ENTRY(0x6c, KEY_KP7), ++ KEYMAP_ENTRY(0x6d, KEY_PAGEDOWN), KEYMAP_ENTRY(0x6e, KEY_HOME), ++ KEYMAP_ENTRY(0x6f, KEY_PAGEUP), KEYMAP_ENTRY(0x70, KEY_KP0), ++ KEYMAP_ENTRY(0x71, KEY_KPDOT), KEYMAP_ENTRY(0x72, KEY_KP2), ++ KEYMAP_ENTRY(0x73, KEY_KP5), KEYMAP_ENTRY(0x74, KEY_KP6), ++ KEYMAP_ENTRY(0x75, KEY_KP8), KEYMAP_ENTRY(0x76, KEY_ESC), ++ KEYMAP_ENTRY(0x77, KEY_NUMLOCK), KEYMAP_ENTRY(0x78, KEY_F11), ++ KEYMAP_ENTRY(0x79, KEY_KPPLUS), KEYMAP_ENTRY(0x7a, KEY_KP3), ++ KEYMAP_ENTRY(0x7b, KEY_KPMINUS), KEYMAP_ENTRY(0x7c, KEY_KPASTERISK), ++ KEYMAP_ENTRY(0x7d, KEY_KP9), KEYMAP_ENTRY(0x7e, KEY_SCROLLLOCK), ++ KEYMAP_ENTRY(0x7f, KEY_102ND), KEYMAP_ENTRY(0x91, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x92, KEY_NULL), KEYMAP_ENTRY(0x94, KEY_RIGHTCTRL), ++ KEYMAP_ENTRY(0x9d, KEY_CAPSLOCK), KEYMAP_ENTRY(0x9f, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0xa7, KEY_RIGHTMETA), KEYMAP_ENTRY(0xaf, KEY_COMPOSE), ++ KEYMAP_ENTRY(0xca, KEY_KPSLASH), KEYMAP_ENTRY(0xda, KEY_KPENTER), ++ KEYMAP_ENTRY(0xe9, KEY_END), KEYMAP_ENTRY(0xeb, KEY_LEFT), ++ KEYMAP_ENTRY(0xec, KEY_HOME), KEYMAP_ENTRY(0xf0, KEY_INSERT), ++ KEYMAP_ENTRY(0xf1, KEY_DELETE), KEYMAP_ENTRY(0xf2, KEY_DOWN), ++ KEYMAP_ENTRY(0xf4, KEY_RIGHT), KEYMAP_ENTRY(0xf5, KEY_UP), ++ KEYMAP_ENTRY(0xf7, KEY_PAUSE), KEYMAP_ENTRY(0xfa, KEY_PAGEDOWN), ++ KEYMAP_ENTRY(0xfc, KEY_SYSRQ), KEYMAP_ENTRY(0xfd, KEY_PAGEUP), ++ ++ /* Escaped keycodes */ ++ KEYMAP_ENTRY(0x103, KEY_F7), KEYMAP_ENTRY(0x10b, KEY_LEFTMETA), ++ KEYMAP_ENTRY(0x10c, KEY_RIGHTMETA), KEYMAP_ENTRY(0x111, KEY_RIGHTALT), ++ KEYMAP_ENTRY(0x114, KEY_RIGHTCTRL), ++}; ++ ++static const struct property_entry gscps2_props[] = { ++ PROPERTY_ENTRY_U32_ARRAY("linux,keymap", gscps2_keymap), ++ { } ++}; ++ ++static const struct software_node gscps2_keyboard_node = { ++ .name = "gscps2-keyboard", ++ .properties = gscps2_props, ++}; + + static irqreturn_t gscps2_interrupt(int irq, void *dev); + +@@ -398,6 +505,17 @@ static int __init gscps2_probe(struct parisc_device *dev) + goto fail; + #endif + ++ if (ps2port->id == GSC_ID_KEYBOARD) { ++ ret = device_add_software_node(&serio->dev, ++ &gscps2_keyboard_node); ++ if (ret) { ++ dev_err(&dev->dev, ++ "failed to add software node for keyboard: %d\n", ++ ret); ++ goto fail; ++ } ++ } ++ + pr_info("serio: %s port at 0x%08lx irq %d @ %s\n", + ps2port->port->name, + hpa, +@@ -411,11 +529,16 @@ static int __init gscps2_probe(struct parisc_device *dev) + return 0; + + fail: ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&serio->dev); ++ + free_irq(dev->irq, ps2port); + + fail_miserably: + iounmap(ps2port->addr); ++#if 0 + release_mem_region(dev->hpa.start, GSC_STATUS + 4); ++#endif + + fail_nomem: + kfree(ps2port); +@@ -434,6 +557,9 @@ static void __exit gscps2_remove(struct parisc_device *dev) + { + struct gscps2port *ps2port = dev_get_drvdata(&dev->dev); + ++ if (ps2port->id == GSC_ID_KEYBOARD) ++ device_remove_software_node(&ps2port->port->dev); ++ + serio_unregister_port(ps2port->port); + free_irq(dev->irq, ps2port); + gscps2_flush(ps2port); +@@ -465,16 +591,25 @@ static struct parisc_driver parisc_ps2_driver __refdata = { + + static int __init gscps2_init(void) + { +- register_parisc_driver(&parisc_ps2_driver); +- return 0; ++ int error; ++ ++ error = software_node_register(&gscps2_keyboard_node); ++ if (error) ++ return error; ++ ++ error = register_parisc_driver(&parisc_ps2_driver); ++ if (error) ++ software_node_unregister(&gscps2_keyboard_node); ++ ++ return error; + } + + static void __exit gscps2_exit(void) + { + unregister_parisc_driver(&parisc_ps2_driver); ++ software_node_unregister(&gscps2_keyboard_node); + } + + + module_init(gscps2_init); + module_exit(gscps2_exit); +- +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -3,6 +3,7 @@ + #define _I8042_ACPIPNPIO_H + + #include ++#include + + #ifdef CONFIG_X86 + #include +@@ -1486,17 +1487,21 @@ static char i8042_pnp_aux_name[32]; + + static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size) + { +- strscpy(dst, "PNP:", dst_size); ++ struct seq_buf sb; ++ ++ seq_buf_init(&sb, dst, dst_size); ++ seq_buf_printf(&sb, "PNP:"); + + while (id) { +- strlcat(dst, " ", dst_size); +- strlcat(dst, id->id, dst_size); ++ seq_buf_printf(&sb, " %s", id->id); + id = id->next; + } + } + + static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1506,11 +1511,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev,0)) + i8042_pnp_kbd_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); +- strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); +- } ++ scnprintf(i8042_pnp_kbd_name, sizeof(i8042_pnp_kbd_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id, + sizeof(i8042_kbd_firmware_id)); + i8042_kbd_fwnode = dev_fwnode(&dev->dev); +@@ -1524,6 +1526,8 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id * + + static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did) + { ++ const char *name = pnp_dev_name(dev); ++ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + +@@ -1533,11 +1537,8 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id * + if (pnp_irq_valid(dev, 0)) + i8042_pnp_aux_irq = pnp_irq(dev, 0); + +- strscpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); +- if (strlen(pnp_dev_name(dev))) { +- strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); +- strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); +- } ++ scnprintf(i8042_pnp_aux_name, sizeof(i8042_pnp_aux_name), "%s%s%s", ++ did->id, strlen(name) ? ":" : "", name); + i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id, + sizeof(i8042_aux_firmware_id)); + +diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c +--- a/drivers/input/touchscreen/elo.c ++++ b/drivers/input/touchscreen/elo.c +@@ -257,7 +257,7 @@ static int elo_command_10(struct elo *elo, unsigned char *packet) + + static int elo_setup_10(struct elo *elo) + { +- static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; ++ static const char * const elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; + struct input_dev *dev = elo->dev; + unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD }; + +@@ -273,7 +273,7 @@ static int elo_setup_10(struct elo *elo) + + dev_info(&elo->serio->dev, + "%sTouch touchscreen, fw: %02x.%02x, features: 0x%02x, controller: 0x%02x\n", +- elo_types[(packet[1] -'0') & 0x03], ++ elo_types[(packet[1] - '0') & 0x03], + packet[5], packet[4], packet[3], packet[7]); + + return 0; +diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c +--- a/drivers/input/touchscreen/inexio.c ++++ b/drivers/input/touchscreen/inexio.c +@@ -81,7 +81,9 @@ static irqreturn_t inexio_interrupt(struct serio *serio, + if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0]) + inexio_process_data(pinexio); + else +- printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]); ++ dev_dbg(&serio->dev, ++ "unknown/unsynchronized data from device, byte %x\n", ++ pinexio->data[0]); + + return IRQ_HANDLED; + } +diff --git a/drivers/input/touchscreen/iqs5xx.c b/drivers/input/touchscreen/iqs5xx.c +--- a/drivers/input/touchscreen/iqs5xx.c ++++ b/drivers/input/touchscreen/iqs5xx.c +@@ -785,7 +785,8 @@ static int iqs5xx_fw_file_parse(struct i2c_client *client, + switch (rec_type) { + case IQS5XX_REC_TYPE_DATA: + if (rec_addr < IQS5XX_CHKSM || +- rec_addr > IQS5XX_PMAP_END) { ++ rec_addr > IQS5XX_PMAP_END || ++ rec_len > IQS5XX_PMAP_END + 1 - rec_addr) { + dev_err(&client->dev, + "Invalid address at record %u\n", + rec_num); +diff --git a/drivers/input/touchscreen/mms114.c b/drivers/input/touchscreen/mms114.c +--- a/drivers/input/touchscreen/mms114.c ++++ b/drivers/input/touchscreen/mms114.c +@@ -4,6 +4,8 @@ + // Copyright (c) 2012 Samsung Electronics Co., Ltd. + // Author: Joonyoung Shim + ++#include ++#include + #include + #include + #include +@@ -52,21 +54,13 @@ + #define MMS114_TYPE_TOUCHSCREEN 1 + #define MMS114_TYPE_TOUCHKEY 2 + +-enum mms_type { +- TYPE_MMS114 = 114, +- TYPE_MMS134S = 134, +- TYPE_MMS136 = 136, +- TYPE_MMS152 = 152, +- TYPE_MMS345L = 345, +-}; +- + struct mms114_data { ++ const struct mms_chip *chip; + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *core_reg; + struct regulator *io_reg; + struct touchscreen_properties props; +- enum mms_type type; + unsigned int contact_threshold; + unsigned int moving_threshold; + +@@ -77,9 +71,23 @@ struct mms114_data { + u8 cache_mode_control; + }; + ++struct mms_chip { ++ const char *name; ++ int event_size; ++ bool has_config_regs; ++ int (*get_version)(struct mms114_data *data); ++}; ++ ++#define MMS114_FLAGS_ID_MASK GENMASK(3, 0) ++#define MMS114_FLAGS_TYPE_MASK GENMASK(6, 5) ++#define MMS114_FLAGS_PRESSED_MASK BIT(7) ++ ++#define MMS114_XY_HI_X_MASK GENMASK(3, 0) ++#define MMS114_XY_HI_Y_MASK GENMASK(7, 4) ++ + struct mms114_touch { +- u8 id:4, reserved_bit4:1, type:2, pressed:1; +- u8 x_hi:4, y_hi:4; ++ u8 flags; ++ u8 xy_hi; + u8 x_lo; + u8 y_lo; + u8 width; +@@ -87,16 +95,16 @@ struct mms114_touch { + u8 reserved[2]; + } __packed; + +-static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, +- unsigned int len, u8 *val) ++static int __mms114_read_reg(struct mms114_data *data, u8 reg, ++ unsigned int len, void *val) + { + struct i2c_client *client = data->client; + struct i2c_msg xfer[2]; +- u8 buf = reg & 0xff; ++ u8 buf = reg; + int error; + +- if (reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL) +- BUG(); ++ if (WARN_ON(reg <= MMS114_MODE_CONTROL && reg + len > MMS114_MODE_CONTROL)) ++ return -EINVAL; + + /* Write register */ + xfer[0].addr = client->addr; +@@ -116,12 +124,12 @@ static int __mms114_read_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c transfer failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + return 0; + } + +-static int mms114_read_reg(struct mms114_data *data, unsigned int reg) ++static int mms114_read_reg(struct mms114_data *data, u8 reg) + { + u8 val; + int error; +@@ -133,15 +141,14 @@ static int mms114_read_reg(struct mms114_data *data, unsigned int reg) + return error < 0 ? error : val; + } + +-static int mms114_write_reg(struct mms114_data *data, unsigned int reg, +- unsigned int val) ++static int mms114_write_reg(struct mms114_data *data, u8 reg, u8 val) + { + struct i2c_client *client = data->client; + u8 buf[2]; + int error; + +- buf[0] = reg & 0xff; +- buf[1] = val & 0xff; ++ buf[0] = reg; ++ buf[1] = val; + + error = i2c_master_send(client, buf, 2); + if (error != 2) { +@@ -149,7 +156,7 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + "%s: i2c send failed (%d)\n", __func__, error); + return error < 0 ? error : -EIO; + } +- udelay(MMS114_I2C_DELAY); ++ usleep_range(MMS114_I2C_DELAY, MMS114_I2C_DELAY + 50); + + if (reg == MMS114_MODE_CONTROL) + data->cache_mode_control = val; +@@ -157,32 +164,119 @@ static int mms114_write_reg(struct mms114_data *data, unsigned int reg, + return 0; + } + ++static int mms114_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[6]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", ++ buf[0], buf[1], buf[3]); ++ return 0; ++} ++ ++static int mms152_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int group; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ group = i2c_smbus_read_byte_data(data->client, MMS152_COMPAT_GROUP); ++ if (group < 0) ++ return group; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", ++ buf[0], buf[1], buf[2], group); ++ return 0; ++} ++ ++static int mms345l_get_version(struct mms114_data *data) ++{ ++ struct device *dev = &data->client->dev; ++ u8 buf[3]; ++ int error; ++ ++ error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); ++ if (error) ++ return error; ++ ++ dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", ++ buf[0], buf[1], buf[2]); ++ return 0; ++} ++ ++static const struct mms_chip mms114_descriptor = { ++ .name = "MMS114", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms134s_descriptor = { ++ .name = "MMS134S", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms136_descriptor = { ++ .name = "MMS136", ++ .event_size = MMS136_EVENT_SIZE, ++ .has_config_regs = true, ++ .get_version = mms114_get_version, ++}; ++ ++static const struct mms_chip mms152_descriptor = { ++ .name = "MMS152", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms152_get_version, ++}; ++ ++static const struct mms_chip mms345l_descriptor = { ++ .name = "MMS345L", ++ .event_size = MMS114_EVENT_SIZE, ++ .has_config_regs = false, ++ .get_version = mms345l_get_version, ++}; ++ + static void mms114_process_mt(struct mms114_data *data, struct mms114_touch *touch) + { + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; +- unsigned int id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + unsigned int x; + unsigned int y; + +- if (touch->id == 0 || touch->id > MMS114_MAX_TOUCH) { +- dev_err(&client->dev, "Wrong touch id (%d)\n", touch->id); ++ if (id == 0 || id > MMS114_MAX_TOUCH) { ++ dev_err(&client->dev, "Wrong touch id (%d)\n", id); + return; + } + +- id = touch->id - 1; +- x = touch->x_lo | touch->x_hi << 8; +- y = touch->y_lo | touch->y_hi << 8; ++ id--; ++ x = touch->x_lo | FIELD_GET(MMS114_XY_HI_X_MASK, touch->xy_hi) << 8; ++ y = touch->y_lo | FIELD_GET(MMS114_XY_HI_Y_MASK, touch->xy_hi) << 8; + + dev_dbg(&client->dev, + "id: %d, type: %d, pressed: %d, x: %d, y: %d, width: %d, strength: %d\n", +- id, touch->type, touch->pressed, ++ id, type, pressed, + x, y, touch->width, touch->strength); + + input_mt_slot(input_dev, id); +- input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, touch->pressed); ++ input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, pressed); + +- if (touch->pressed) { ++ if (pressed) { + touchscreen_report_pos(input_dev, &data->props, x, y, true); + input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, touch->width); + input_report_abs(input_dev, ABS_MT_PRESSURE, touch->strength); +@@ -195,21 +289,23 @@ static void mms114_process_touchkey(struct mms114_data *data, + struct i2c_client *client = data->client; + struct input_dev *input_dev = data->input_dev; + unsigned int keycode_id; ++ unsigned int id = FIELD_GET(MMS114_FLAGS_ID_MASK, touch->flags); ++ bool pressed = FIELD_GET(MMS114_FLAGS_PRESSED_MASK, touch->flags); + +- if (touch->id == 0) ++ if (id == 0) + return; + +- if (touch->id > data->num_keycodes) { ++ if (id > data->num_keycodes) { + dev_err(&client->dev, "Wrong touch id for touchkey (%d)\n", +- touch->id); ++ id); + return; + } + +- keycode_id = touch->id - 1; ++ keycode_id = id - 1; + dev_dbg(&client->dev, "keycode id: %d, pressed: %d\n", keycode_id, +- touch->pressed); ++ pressed); + +- input_report_key(input_dev, data->keycodes[keycode_id], touch->pressed); ++ input_report_key(input_dev, data->keycodes[keycode_id], pressed); + } + + static irqreturn_t mms114_interrupt(int irq, void *dev_id) +@@ -218,8 +314,8 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + struct i2c_client *client = data->client; + struct mms114_touch touch[MMS114_MAX_TOUCH]; + struct mms114_touch *t; ++ int event_size = data->chip->event_size; + int packet_size; +- int event_size; + int touch_size; + int index; + int error; +@@ -234,23 +330,17 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + goto out; + } + +- /* MMS136 has slightly different event size */ +- if (data->type == TYPE_MMS134S || data->type == TYPE_MMS136) +- event_size = MMS136_EVENT_SIZE; +- else +- event_size = MMS114_EVENT_SIZE; +- + touch_size = packet_size / event_size; + +- error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, +- (u8 *)touch); +- if (error < 0) ++ error = __mms114_read_reg(data, MMS114_INFORMATION, packet_size, touch); ++ if (error) + goto out; + + for (index = 0; index < touch_size; index++) { + t = (struct mms114_touch *)((u8 *)touch + index * event_size); ++ unsigned int type = FIELD_GET(MMS114_FLAGS_TYPE_MASK, t->flags); + +- switch (t->type) { ++ switch (type) { + case MMS114_TYPE_TOUCHSCREEN: + mms114_process_mt(data, t); + break; +@@ -261,7 +351,7 @@ static irqreturn_t mms114_interrupt(int irq, void *dev_id) + + default: + dev_err(&client->dev, "Wrong touch type (%d)\n", +- t->type); ++ type); + break; + } + } +@@ -290,65 +380,17 @@ static int mms114_set_active(struct mms114_data *data, bool active) + return mms114_write_reg(data, MMS114_MODE_CONTROL, val); + } + +-static int mms114_get_version(struct mms114_data *data) +-{ +- struct device *dev = &data->client->dev; +- u8 buf[6]; +- int group; +- int error; +- +- switch (data->type) { +- case TYPE_MMS345L: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x\n", +- buf[0], buf[1], buf[2]); +- break; +- +- case TYPE_MMS152: +- error = __mms114_read_reg(data, MMS152_FW_REV, 3, buf); +- if (error) +- return error; +- +- group = i2c_smbus_read_byte_data(data->client, +- MMS152_COMPAT_GROUP); +- if (group < 0) +- return group; +- +- dev_info(dev, "TSP FW Rev: bootloader 0x%x / core 0x%x / config 0x%x, Compat group: %c\n", +- buf[0], buf[1], buf[2], group); +- break; +- +- case TYPE_MMS114: +- case TYPE_MMS134S: +- case TYPE_MMS136: +- error = __mms114_read_reg(data, MMS114_TSP_REV, 6, buf); +- if (error) +- return error; +- +- dev_info(dev, "TSP Rev: 0x%x, HW Rev: 0x%x, Firmware Ver: 0x%x\n", +- buf[0], buf[1], buf[3]); +- break; +- } +- +- return 0; +-} +- + static int mms114_setup_regs(struct mms114_data *data) + { + const struct touchscreen_properties *props = &data->props; + int val; + int error; + +- error = mms114_get_version(data); +- if (error < 0) ++ error = data->chip->get_version(data); ++ if (error) + return error; + +- /* MMS114, MMS134S and MMS136 have configuration and power on registers */ +- if (data->type != TYPE_MMS114 && data->type != TYPE_MMS134S && +- data->type != TYPE_MMS136) ++ if (!data->chip->has_config_regs) + return 0; + + error = mms114_set_active(data, true); +@@ -366,21 +408,21 @@ static int mms114_setup_regs(struct mms114_data *data) + if (error < 0) + return error; + +- val = props->max_x & 0xff; ++ val = props->max_y & 0xff; + error = mms114_write_reg(data, MMS114_Y_RESOLUTION, val); + if (error < 0) + return error; + + if (data->contact_threshold) { + error = mms114_write_reg(data, MMS114_CONTACT_THRESHOLD, +- data->contact_threshold); ++ data->contact_threshold); + if (error < 0) + return error; + } + + if (data->moving_threshold) { + error = mms114_write_reg(data, MMS114_MOVING_THRESHOLD, +- data->moving_threshold); ++ data->moving_threshold); + if (error < 0) + return error; + } +@@ -466,9 +508,9 @@ static int mms114_parse_legacy_bindings(struct mms114_data *data) + } + + device_property_read_u32(dev, "contact-threshold", +- &data->contact_threshold); ++ &data->contact_threshold); + device_property_read_u32(dev, "moving-threshold", +- &data->moving_threshold); ++ &data->moving_threshold); + + if (device_property_read_bool(dev, "x-invert")) + props->invert_x = true; +@@ -484,7 +526,6 @@ static int mms114_probe(struct i2c_client *client) + { + struct mms114_data *data; + struct input_dev *input_dev; +- const void *match_data; + int error; + int i; + +@@ -504,12 +545,10 @@ static int mms114_probe(struct i2c_client *client) + data->client = client; + data->input_dev = input_dev; + +- match_data = device_get_match_data(&client->dev); +- if (!match_data) ++ data->chip = i2c_get_match_data(client); ++ if (!data->chip) + return -EINVAL; + +- data->type = (enum mms_type)match_data; +- + data->num_keycodes = device_property_count_u32(&client->dev, + "linux,keycodes"); + if (data->num_keycodes == -EINVAL) { +@@ -521,7 +560,7 @@ static int mms114_probe(struct i2c_client *client) + return data->num_keycodes; + } else if (data->num_keycodes > MMS114_MAX_TOUCHKEYS) { + dev_warn(&client->dev, +- "Found %d linux,keycodes but max is %d, ignoring the rest\n", ++ "Found %d linux,keycodes but max is %d, ignoring the rest\n", + data->num_keycodes, MMS114_MAX_TOUCHKEYS); + data->num_keycodes = MMS114_MAX_TOUCHKEYS; + } +@@ -566,8 +605,7 @@ static int mms114_probe(struct i2c_client *client) + 0, data->props.max_y, 0, 0); + } + +- if (data->type == TYPE_MMS114 || data->type == TYPE_MMS134S || +- data->type == TYPE_MMS136) { ++ if (data->chip->has_config_regs) { + /* + * The firmware handles movement and pressure fuzz, so + * don't duplicate that in software. +@@ -582,8 +620,8 @@ static int mms114_probe(struct i2c_client *client) + } + + input_dev->name = devm_kasprintf(&client->dev, GFP_KERNEL, +- "MELFAS MMS%d Touchscreen", +- data->type); ++ "MELFAS %s Touchscreen", ++ data->chip->name); + if (!input_dev->name) + return -ENOMEM; + +@@ -679,29 +717,22 @@ static int mms114_resume(struct device *dev) + static DEFINE_SIMPLE_DEV_PM_OPS(mms114_pm_ops, mms114_suspend, mms114_resume); + + static const struct i2c_device_id mms114_id[] = { +- { .name = "mms114" }, ++ { .name = "mms114", .driver_data = (kernel_ulong_t)&mms114_descriptor }, ++ { .name = "mms134s", .driver_data = (kernel_ulong_t)&mms134s_descriptor }, ++ { .name = "mms136", .driver_data = (kernel_ulong_t)&mms136_descriptor }, ++ { .name = "mms152", .driver_data = (kernel_ulong_t)&mms152_descriptor }, ++ { .name = "mms345l", .driver_data = (kernel_ulong_t)&mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(i2c, mms114_id); + + #ifdef CONFIG_OF + static const struct of_device_id mms114_dt_match[] = { +- { +- .compatible = "melfas,mms114", +- .data = (void *)TYPE_MMS114, +- }, { +- .compatible = "melfas,mms134s", +- .data = (void *)TYPE_MMS134S, +- }, { +- .compatible = "melfas,mms136", +- .data = (void *)TYPE_MMS136, +- }, { +- .compatible = "melfas,mms152", +- .data = (void *)TYPE_MMS152, +- }, { +- .compatible = "melfas,mms345l", +- .data = (void *)TYPE_MMS345L, +- }, ++ { .compatible = "melfas,mms114", .data = &mms114_descriptor }, ++ { .compatible = "melfas,mms134s", .data = &mms134s_descriptor }, ++ { .compatible = "melfas,mms136", .data = &mms136_descriptor }, ++ { .compatible = "melfas,mms152", .data = &mms152_descriptor }, ++ { .compatible = "melfas,mms345l", .data = &mms345l_descriptor }, + { } + }; + MODULE_DEVICE_TABLE(of, mms114_dt_match); +@@ -722,4 +753,4 @@ module_i2c_driver(mms114_driver); + /* Module information */ + MODULE_AUTHOR("Joonyoung Shim "); + MODULE_DESCRIPTION("MELFAS mms114 Touchscreen driver"); +-MODULE_LICENSE("GPL v2"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/input/touchscreen/sur40.c b/drivers/input/touchscreen/sur40.c +--- a/drivers/input/touchscreen/sur40.c ++++ b/drivers/input/touchscreen/sur40.c +@@ -128,8 +128,8 @@ struct sur40_image_header { + /* polling interval (ms) */ + #define POLL_INTERVAL 1 + +-/* maximum number of contacts FIXME: this is a guess? */ +-#define MAX_CONTACTS 64 ++/* maximum number of contacts */ ++#define MAX_CONTACTS 52 + + /* control commands */ + #define SUR40_GET_VERSION 0xb0 /* 12 bytes string */ +diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c +--- a/drivers/input/touchscreen/wacom_w8001.c ++++ b/drivers/input/touchscreen/wacom_w8001.c +@@ -417,6 +417,13 @@ static int w8001_detect(struct w8001 *w8001) + return 0; + } + ++static void w8001_append_suffix(char *dest, const char *suffix, size_t dest_sz) ++{ ++ size_t used = strnlen(dest, dest_sz); ++ ++ strscpy(dest + used, suffix, dest_sz - used); ++} ++ + static int w8001_setup_pen(struct w8001 *w8001, char *basename, + size_t basename_sz) + { +@@ -453,7 +460,7 @@ static int w8001_setup_pen(struct w8001 *w8001, char *basename, + } + + w8001->id = 0x90; +- strlcat(basename, " Penabled", basename_sz); ++ w8001_append_suffix(basename, " Penabled", basename_sz); + + return 0; + } +@@ -503,14 +510,14 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + case 2: + w8001->pktlen = W8001_PKTLEN_TOUCH93; + w8001->id = 0x93; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + break; + + case 1: + case 3: + case 4: + w8001->pktlen = W8001_PKTLEN_TOUCH9A; +- strlcat(basename, " 1FG", basename_sz); ++ w8001_append_suffix(basename, " 1FG", basename_sz); + w8001->id = 0x9a; + break; + +@@ -534,7 +541,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + input_abs_set_res(dev, ABS_MT_POSITION_X, touch.panel_res); + input_abs_set_res(dev, ABS_MT_POSITION_Y, touch.panel_res); + +- strlcat(basename, " 2FG", basename_sz); ++ w8001_append_suffix(basename, " 2FG", basename_sz); + if (w8001->max_pen_x && w8001->max_pen_y) + w8001->id = 0xE3; + else +@@ -542,7 +549,7 @@ static int w8001_setup_touch(struct w8001 *w8001, char *basename, + break; + } + +- strlcat(basename, " Touchscreen", basename_sz); ++ w8001_append_suffix(basename, " Touchscreen", basename_sz); + + return 0; + } +diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c +--- a/drivers/mfd/rohm-bd71828.c ++++ b/drivers/mfd/rohm-bd71828.c +@@ -5,7 +5,8 @@ + * ROHM BD718[15/28/79] and BD72720 PMIC driver + */ + +-#include ++#include ++#include + #include + #include + #include +@@ -18,6 +19,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -37,19 +39,6 @@ + }, \ + } + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +- .wakeup = 1, +-}; +- +-static const struct gpio_keys_platform_data bd71828_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd71828-pwrkey", +-}; +- + static const struct resource bd71815_rtc_irqs[] = { + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC0, "bd70528-rtc-alm-0"), + DEFINE_RES_IRQ_NAMED(BD71815_INT_RTC1, "bd70528-rtc-alm-1"), +@@ -174,10 +163,6 @@ static struct mfd_cell bd71828_mfd_cells[] = { + .name = "bd71828-rtc", + .resources = bd71828_rtc_irqs, + .num_resources = ARRAY_SIZE(bd71828_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, + }; + +@@ -242,11 +227,8 @@ static const struct mfd_cell bd72720_mfd_cells[] = { + .name = "bd72720-rtc", + .resources = bd72720_rtc_irqs, + .num_resources = ARRAY_SIZE(bd72720_rtc_irqs), +- }, { +- .name = "gpio-keys", +- .platform_data = &bd71828_powerkey_data, +- .pdata_size = sizeof(bd71828_powerkey_data), + }, ++ /* Power button is registered separately */ + }; + + static const struct regmap_range bd71815_volatile_ranges[] = { +@@ -877,6 +859,84 @@ static int set_clk_mode(struct device *dev, struct regmap *regmap, + OUT32K_MODE_CMOS); + } + ++static const struct property_entry bd71828_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd71828-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd71828_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ PROPERTY_ENTRY_BOOL("wakeup-source"), ++ { } ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd71828_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd71828_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd71828_i2c_register_pwrbutton(struct device *dev, int button_irq, ++ struct irq_domain *irq_domain) ++{ ++ const struct resource res[] = { ++ DEFINE_RES_IRQ_NAMED(button_irq, "bd71828-pwrkey"), ++ }; ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = res, ++ .num_resources = ARRAY_SIZE(res), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd71828_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd71828_powerkey_props; ++ ++ ret = bd71828_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd71828_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static struct i2c_client *bd71828_dev; + static void bd71828_power_off(void) + { +@@ -929,6 +989,7 @@ static struct regmap *bd72720_do_regmaps(struct i2c_client *i2c) + static int bd71828_i2c_probe(struct i2c_client *i2c) + { + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + struct regmap *regmap = NULL; + const struct regmap_config *regmap_config; +@@ -1022,23 +1083,23 @@ static int bd71828_i2c_probe(struct i2c_client *i2c) + "Failed to enable main level IRQs\n"); + } + } +- if (button_irq) { +- ret = regmap_irq_get_virq(irq_data, button_irq); +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, +- "Failed to get the power-key IRQ\n"); +- +- button.irq = ret; +- } + + ret = set_clk_mode(&i2c->dev, regmap, clkmode_reg); + if (ret) + return ret; + ++ irq_domain = regmap_irq_get_domain(irq_data); ++ + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, mfd, cells, +- NULL, 0, regmap_irq_get_domain(irq_data)); ++ NULL, 0, irq_domain); + if (ret) +- return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ ++ if (button_irq) { ++ ret = bd71828_i2c_register_pwrbutton(&i2c->dev, button_irq, irq_domain); ++ if (ret) ++ return ret; ++ } + + if (of_device_is_system_power_controller(i2c->dev.of_node) && + chip_type == ROHM_CHIP_TYPE_BD71828) { +diff --git a/drivers/mfd/rohm-bd718x7.c b/drivers/mfd/rohm-bd718x7.c +--- a/drivers/mfd/rohm-bd718x7.c ++++ b/drivers/mfd/rohm-bd718x7.c +@@ -7,7 +7,8 @@ + // Datasheet for BD71837MWV available from + // https://www.rohm.com/datasheet/BD71837MWV/bd71837mwv-e + +-#include ++#include ++#include + #include + #include + #include +@@ -15,37 +16,16 @@ + #include + #include + #include ++#include + #include + #include + +-static struct gpio_keys_button button = { +- .code = KEY_POWER, +- .gpio = -1, +- .type = EV_KEY, +-}; +- +-static struct gpio_keys_platform_data bd718xx_powerkey_data = { +- .buttons = &button, +- .nbuttons = 1, +- .name = "bd718xx-pwrkey", +-}; +- + static struct mfd_cell bd71837_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71837-clk", }, + { .name = "bd71837-pmic", }, + }; + + static struct mfd_cell bd71847_mfd_cells[] = { +- { +- .name = "gpio-keys", +- .platform_data = &bd718xx_powerkey_data, +- .pdata_size = sizeof(bd718xx_powerkey_data), +- }, + { .name = "bd71847-clk", }, + { .name = "bd71847-pmic", }, + }; +@@ -125,10 +105,89 @@ static int bd718xx_init_press_duration(struct regmap *regmap, + return 0; + } + ++static const struct property_entry bd718xx_powerkey_parent_props[] = { ++ PROPERTY_ENTRY_STRING("label", "bd718xx-pwrkey"), ++ { } ++}; ++ ++static const struct property_entry bd718xx_powerkey_props[] = { ++ PROPERTY_ENTRY_U32("linux,code", KEY_POWER), ++ { } ++}; ++ ++static const struct resource bd718xx_powerkey_resources[] = { ++ DEFINE_RES_IRQ_NAMED(BD718XX_INT_PWRBTN_S, "bd718xx-pwrkey"), ++}; ++ ++#define GPIO_KEYS 0 /* Node corresponding to gpio-keys device itself */ ++#define PWRON_KEY 1 /* Node describing power button in gpio-keys */ ++ ++static int bd718xx_i2c_register_swnodes(const struct software_node *nodes) ++{ ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ return software_node_register_node_group(node_group); ++} ++ ++static void bd718xx_i2c_unregister_swnodes(void *data) ++{ ++ const struct software_node *nodes = data; ++ const struct software_node * const node_group[] = { ++ &nodes[GPIO_KEYS], &nodes[PWRON_KEY], NULL ++ }; ++ ++ software_node_unregister_node_group(node_group); ++} ++ ++static int bd718xx_i2c_register_pwrbutton(struct device *dev, ++ struct irq_domain *irq_domain) ++{ ++ struct mfd_cell gpio_keys_cell = { ++ .name = "gpio-keys", ++ .resources = bd718xx_powerkey_resources, ++ .num_resources = ARRAY_SIZE(bd718xx_powerkey_resources), ++ }; ++ struct software_node *nodes; ++ int ret; ++ ++ nodes = devm_kcalloc(dev, 2, sizeof(*nodes), GFP_KERNEL); ++ if (!nodes) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].name = devm_kasprintf(dev, GFP_KERNEL, "%s-power-key", dev_name(dev)); ++ if (!nodes[GPIO_KEYS].name) ++ return -ENOMEM; ++ ++ nodes[GPIO_KEYS].properties = bd718xx_powerkey_parent_props; ++ ++ nodes[PWRON_KEY].parent = &nodes[GPIO_KEYS]; ++ nodes[PWRON_KEY].properties = bd718xx_powerkey_props; ++ ++ ret = bd718xx_i2c_register_swnodes(nodes); ++ if (ret) ++ return ret; ++ ++ ret = devm_add_action_or_reset(dev, bd718xx_i2c_unregister_swnodes, nodes); ++ if (ret) ++ return ret; ++ ++ gpio_keys_cell.swnode = &nodes[GPIO_KEYS]; ++ ++ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, &gpio_keys_cell, 1, ++ NULL, 0, irq_domain); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to register power-button"); ++ ++ return 0; ++} ++ + static int bd718xx_i2c_probe(struct i2c_client *i2c) + { + struct regmap *regmap; + struct regmap_irq_chip_data *irq_data; ++ struct irq_domain *irq_domain; + int ret; + unsigned int chip_type; + struct mfd_cell *mfd; +@@ -169,20 +228,18 @@ static int bd718xx_i2c_probe(struct i2c_client *i2c) + if (ret) + return ret; + +- ret = regmap_irq_get_virq(irq_data, BD718XX_INT_PWRBTN_S); +- +- if (ret < 0) +- return dev_err_probe(&i2c->dev, ret, "Failed to get the IRQ\n"); +- +- button.irq = ret; ++ irq_domain = regmap_irq_get_domain(irq_data); + + ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, +- mfd, cells, NULL, 0, +- regmap_irq_get_domain(irq_data)); ++ mfd, cells, NULL, 0, irq_domain); + if (ret) +- dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); ++ return dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n"); + +- return ret; ++ ret = bd718xx_i2c_register_pwrbutton(&i2c->dev, irq_domain); ++ if (ret) ++ return ret; ++ ++ return 0; + } + + static const struct of_device_id bd718xx_of_match[] = { +diff --git a/include/linux/input.h b/include/linux/input.h +--- a/include/linux/input.h ++++ b/include/linux/input.h +@@ -117,7 +117,8 @@ enum input_clock_type { + * user opens device and dev->close() is called when the very + * last user closes the device + * @going_away: marks devices that are in a middle of unregistering and +- * causes input_open_device*() fail with -ENODEV. ++ * causes input_open_device() and input_inhibit/uninhibit_device() ++ * to fail with -ENODEV. + * @dev: driver model's view of this device + * @h_list: list of input handles associated with the device. When + * accessing the list dev->mutex must be held +@@ -128,11 +129,14 @@ enum input_clock_type { + * @devres_managed: indicates that devices is managed with devres framework + * and needs not be explicitly unregistered or freed. + * @timestamp: storage for a timestamp set by input_set_timestamp called +- * by a driver ++ * by a driver + * @inhibited: indicates that the input device is inhibited. If that is +- * the case then input core ignores any events generated by the device. +- * Device's close() is called when it is being inhibited and its open() +- * is called when it is being uninhibited. ++ * the case then input core ignores any events generated by the device. ++ * Device's close() is called when it is being inhibited and its open() ++ * is called when it is being uninhibited. ++ * @ready: indicates that the device has been successfully opened and is ++ * prepared to process events (like LEDs or sounds) sent from the ++ * input core. + */ + struct input_dev { + const char *name; +@@ -209,6 +213,7 @@ struct input_dev { + ktime_t timestamp[INPUT_CLK_MAX]; + + bool inhibited; ++ bool ready; + }; + #define to_input_dev(d) container_of(d, struct input_dev, dev) + +@@ -284,8 +289,9 @@ struct input_handle; + * @connect: called when attaching a handler to an input device + * @disconnect: disconnects a handler from input device + * @start: starts handler for given handle. This function is called by +- * input core right after connect() method and also when a process +- * that "grabbed" a device releases it ++ * input core when device is open and ready to process events, ++ * and also when device is uninhibited or when a process that "grabbed" ++ * a device releases it + * @passive_observer: set to %true by drivers only interested in observing + * data stream from devices if there are other users present. Such + * drivers will not result in starting underlying hardware device +diff --git a/include/linux/input/elan-i2c-ids.h b/include/linux/input/elan-i2c-ids.h +--- a/include/linux/input/elan-i2c-ids.h ++++ b/include/linux/input/elan-i2c-ids.h +@@ -18,7 +18,7 @@ + #ifndef __ELAN_I2C_IDS_H + #define __ELAN_I2C_IDS_H + +-#include ++#include + + static const struct acpi_device_id elan_acpi_id[] = { + { "ELAN0000", 0 }, diff --git a/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch.sig new file mode 100644 index 0000000..9e9a221 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0560-input-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch new file mode 100644 index 0000000..b067aaa --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch @@ -0,0 +1,22 @@ +diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c +--- a/drivers/input/touchscreen/tsc2007_core.c ++++ b/drivers/input/touchscreen/tsc2007_core.c +@@ -221,7 +221,6 @@ static int tsc2007_get_pendown_state_gpio(struct device *dev) + static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + { + u32 val32; +- u64 val64; + + if (!device_property_read_u32(dev, "ti,max-rt", &val32)) + ts->max_rt = val32; +@@ -237,8 +236,8 @@ static int tsc2007_probe_properties(struct device *dev, struct tsc2007 *ts) + if (!device_property_read_u32(dev, "ti,fuzzz", &val32)) + ts->fuzzz = val32; + +- if (!device_property_read_u64(dev, "ti,poll-period", &val64)) +- ts->poll_period = msecs_to_jiffies(val64); ++ if (!device_property_read_u32(dev, "ti,poll-period", &val32)) ++ ts->poll_period = msecs_to_jiffies(val32); + else + ts->poll_period = msecs_to_jiffies(1); + diff --git a/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch.sig new file mode 100644 index 0000000..27751f1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0561-input-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch new file mode 100644 index 0000000..47e03fa --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch @@ -0,0 +1,483 @@ +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h +--- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h +@@ -12,11 +12,15 @@ + + #include + #include ++#include + #include "amd_sfh_hid.h" + + #define PCI_DEVICE_ID_AMD_MP2 0x15E4 + #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A + ++/* The BAR 2 size must cover the highest register offset (0x10958) */ ++#define AMD_SFH_MIN_BAR_SIZE SZ_128K ++ + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) + #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) + +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +@@ -451,6 +451,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i + if (rc) + return rc; + ++ if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { ++ dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); ++ return -ENODEV; ++ } ++ ++ if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { ++ dev_err(&pdev->dev, "BAR 2 is too small\n"); ++ return -EINVAL; ++ } ++ + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); + if (rc) + return rc; +diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c +--- a/drivers/hid/bpf/hid_bpf_dispatch.c ++++ b/drivers/hid/bpf/hid_bpf_dispatch.c +@@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) + + static int + __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, +- enum hid_report_type rtype) ++ enum hid_report_type rtype, bool hw_request) + { + struct hid_report_enum *report_enum; + struct hid_report *report; +@@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, + + report_len = hid_report_len(report); + ++ /* unnumbered reports need to have a report ID reserved in the first byte */ ++ if (hw_request && report_enum->numbered == 0) ++ report_len += 1; ++ + if (*buf__sz > report_len) + *buf__sz = report_len; + +@@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); + if (ret) + return ret; + +@@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); + if (ret) + return ret; + +@@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b + return -EDEADLOCK; + + /* check arguments */ +- ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); ++ ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); + if (ret) + return ret; + +diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c +--- a/drivers/hid/hid-alps.c ++++ b/drivers/hid/hid-alps.c +@@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) + return 1; + + case U1_SP_ABSOLUTE_REPORT_ID: ++ if (!hdata->input2) ++ return 0; + sp_x = get_unaligned_le16(data+2); + sp_y = get_unaligned_le16(data+4); + +@@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + goto exit; + } + +- data->input2 = input2; + input2->phys = input->phys; + input2->name = "DualPoint Stick"; + input2->id.bustype = BUS_I2C; +@@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) + __set_bit(INPUT_PROP_POINTER, input2->propbit); + __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); + +- if (input_register_device(data->input2)) { ++ if (input_register_device(input2)) { + input_free_device(input2); + ret = -ENOENT; + goto exit; + } ++ data->input2 = input2; + } + + exit: +@@ -823,6 +825,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) + return 0; + } + ++static void alps_remove(struct hid_device *hdev) ++{ ++ struct alps_dev *data = hid_get_drvdata(hdev); ++ ++ /* ++ * input2 ("DualPoint Stick") is allocated separately and is not ++ * tracked in hdev->inputs, so the default remove path ++ * (hid_hw_stop -> hidinput_disconnect) does not unregister it. ++ * ++ * Stop the device first so that no URB callback can touch input2 ++ * while it is being unregistered, then drop it explicitly. ++ */ ++ hid_hw_stop(hdev); ++ ++ if (data->input2) ++ input_unregister_device(data->input2); ++} ++ + static const struct hid_device_id alps_id[] = { + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, + USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, +@@ -845,6 +865,7 @@ static struct hid_driver alps_driver = { + .input_configured = alps_input_configured, + .resume = pm_ptr(alps_post_resume), + .reset_resume = pm_ptr(alps_post_reset), ++ .remove = alps_remove, + }; + + module_hid_driver(alps_driver); +diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c +--- a/drivers/hid/hid-corsair-void.c ++++ b/drivers/hid/hid-corsair-void.c +@@ -51,20 +51,23 @@ + /* Receiver report information: (ID 102) */ + /* -------------------------------------------------------------------------- */ + /* +- * When queried, the recevier responds with 4 bytes to describe the firmware +- * The first 2 bytes are for the receiver, the second 2 are the headset ++ * When queried, the receiver responds with 5 bytes to describe the firmware ++ * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset + * The headset firmware version will be 0 if no headset is connected + * +- * 0: Recevier firmware major version ++ * 0: Report ID ++ * 102 for the firmware packet ++ * ++ * 1: Receiver firmware major version + * Major version of the receiver's firmware + * +- * 1: Recevier firmware minor version ++ * 2: Receiver firmware minor version + * Minor version of the receiver's firmware + * +- * 2: Headset firmware major version ++ * 3: Headset firmware major version + * Major version of the headset's firmware + * +- * 3: Headset firmware minor version ++ * 4: Headset firmware minor version + * Minor version of the headset's firmware + */ + /* -------------------------------------------------------------------------- */ +diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c +--- a/drivers/hid/hid-elecom.c ++++ b/drivers/hid/hid-elecom.c +@@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: + case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: +- case USB_DEVICE_ID_ELECOM_M_XT4DRBK: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: + /* + * Report descriptor format: + * 12: button bit count +@@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, + mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); + break; + case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: ++ case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: + /* + * Report descriptor format: + * 22: button bit count +@@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -472,7 +472,8 @@ + #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc + #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c +-#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd ++#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e + #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe + #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff + #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d +@@ -759,6 +760,7 @@ + #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a + #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 + #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 ++#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db + #define USB_DEVICE_ID_ITE8595 0x8595 + #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 + +@@ -1283,6 +1285,9 @@ + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 + #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 + ++#define USB_VENDOR_ID_SDINNOVATION 0x36ae ++#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab ++ + #define USB_VENDOR_ID_SEMICO 0x1a2c + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 + #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 +diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c +--- a/drivers/hid/hid-logitech-hidpp.c ++++ b/drivers/hid/hid-logitech-hidpp.c +@@ -4656,6 +4656,8 @@ static const struct hid_device_id hidpp_devices[] = { + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, + { /* Logitech G502 Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, ++ { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ ++ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, + { /* Logitech G703 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, + { /* Logitech G703 Hero Gaming Mouse over USB */ +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) + #define MT_QUIRK_YOGABOOK9I BIT(24) + #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) ++#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_APPLE_TOUCHBAR 0x0114 + #define MT_CLS_YOGABOOK9I 0x0115 + #define MT_CLS_EGALAX_P80H84 0x0116 ++#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 + #define MT_CLS_SIS 0x0457 + + #define MT_DEFAULT_MAXCONTACT 10 +@@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_ASUS_CUSTOM_UP }, ++ { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ .quirks = MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS | ++ MT_QUIRK_CONFIDENCE | ++ MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, ++ .export_all_inputs = true }, + { .name = MT_CLS_VTL, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_CONTACT_CNT_ACCURATE | +@@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { + + static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + { ++ struct mt_device *td = hid_get_drvdata(hdev); + int ret; + u32 size = hid_report_len(report); + u8 *buf; +@@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) + dev_warn(&hdev->dev, "failed to fetch feature %d\n", + report->id); + } else { +- /* The report ID in the request and the response should match */ +- if (report->id != buf[0]) { ++ /* ++ * The report ID in the request and the response should match. ++ * Some firmware (e.g. the ASUS ROG Z13 Folio ++ * touchpad) returns a mismatched ID on this specific fetch; ++ * tolerate it only for devices explicitly flagged as such. ++ */ ++ if (report->id != buf[0] && ++ !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { + hid_err(hdev, "Returned feature report did not match the request\n"); + goto free; + } +@@ -2726,6 +2745,12 @@ static const struct hid_device_id mt_devices[] = { + HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, + I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + ++ /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ ++ { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, ++ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, ++ USB_VENDOR_ID_ASUSTEK, ++ USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, ++ + /* Generic MT device */ + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, + +diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c +--- a/drivers/hid/hid-oxp.c ++++ b/drivers/hid/hid-oxp.c +@@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, + + static void oxp_hid_remove(struct hid_device *hdev) + { +- cancel_delayed_work(&drvdata.oxp_rgb_queue); +- cancel_delayed_work(&drvdata.oxp_btn_queue); +- cancel_delayed_work(&drvdata.oxp_mcu_init); ++ cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_btn_queue); ++ cancel_delayed_work_sync(&drvdata.oxp_mcu_init); + hid_hw_close(hdev); + hid_hw_stop(hdev); + } +diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c +--- a/drivers/hid/hid-quirks.c ++++ b/drivers/hid/hid-quirks.c +@@ -183,6 +183,7 @@ static const struct hid_device_id hid_quirks[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, + { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, +@@ -422,13 +423,14 @@ static const struct hid_device_id hid_have_special_driver[] = { + #endif + #if IS_ENABLED(CONFIG_HID_ELECOM) + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, +- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, +- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, +diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c +--- a/drivers/hid/hid-roccat.c ++++ b/drivers/hid/hid-roccat.c +@@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + return temp; + } + +- mutex_unlock(&devices_lock); +- + init_waitqueue_head(&device->wait); + INIT_LIST_HEAD(&device->readers); + mutex_init(&device->readers_lock); +@@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report + device->cbuf_end = 0; + device->report_size = report_size; + ++ mutex_unlock(&devices_lock); + return minor; + } + EXPORT_SYMBOL_GPL(roccat_connect); +@@ -369,15 +368,12 @@ void roccat_disconnect(int minor) + + mutex_lock(&devices_lock); + device = devices[minor]; +- mutex_unlock(&devices_lock); + + device->exist = 0; /* TODO exist maybe not needed */ + + device_destroy(device->dev->class, MKDEV(roccat_major, minor)); + +- mutex_lock(&devices_lock); + devices[minor] = NULL; +- mutex_unlock(&devices_lock); + + if (device->open) { + hid_hw_close(device->hid); +@@ -385,6 +381,8 @@ void roccat_disconnect(int minor) + } else { + roccat_free_device(device); + } ++ ++ mutex_unlock(&devices_lock); + } + EXPORT_SYMBOL_GPL(roccat_disconnect); + +diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c +--- a/drivers/hid/hid-winwing.c ++++ b/drivers/hid/hid-winwing.c +@@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) + static int winwing_play_effect(struct input_dev *dev, void *context, + struct ff_effect *effect) + { +- struct winwing_drv_data *data = (struct winwing_drv_data *) context; ++ struct hid_device *hdev = input_get_drvdata(dev); ++ struct winwing_drv_data *data = hid_get_drvdata(hdev); + + if (effect->type != FF_RUMBLE) + return 0; +@@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) + + input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); + +- return input_ff_create_memless(hidinput->input, data, ++ /* ++ * input_ff_create_memless() takes ownership of the context pointer ++ * and frees it on teardown; do not hand it the devm-managed drvdata. ++ * winwing_play_effect() fetches it from the input device instead. ++ */ ++ return input_ff_create_memless(hidinput->input, NULL, + winwing_play_effect); + } + +diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c +--- a/drivers/hid/i2c-hid/i2c-hid-core.c ++++ b/drivers/hid/i2c-hid/i2c-hid-core.c +@@ -123,9 +123,11 @@ static const struct i2c_hid_quirks { + __u32 quirks; + } i2c_hid_quirks[] = { + { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, +- I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, + { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, ++ { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, ++ I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, +diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c +--- a/drivers/hid/wacom_sys.c ++++ b/drivers/hid/wacom_sys.c +@@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, + + /* Queue events which have invalid tool type or serial number */ + for (i = 0; i < report->maxfield; i++) { +- for (j = 0; j < report->field[i]->maxusage; j++) { +- struct hid_field *field = report->field[i]; ++ struct hid_field *field = report->field[i]; ++ ++ for (j = 0; j < field->report_count; j++) { + struct hid_usage *usage = &field->usage[j]; + unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); + unsigned int offset; diff --git a/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch.sig new file mode 100644 index 0000000..03c4b82 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0562-hid-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch new file mode 100644 index 0000000..1682da2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch @@ -0,0 +1,88 @@ +diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c +--- a/drivers/input/keyboard/atkbd.c ++++ b/drivers/input/keyboard/atkbd.c +@@ -1938,6 +1938,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { + }, + .callback = atkbd_deactivate_fixup, + }, ++ { ++ /* Lenovo IdeaPad Slim 3 15IWC11 (83RR) */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"), ++ }, ++ .callback = atkbd_deactivate_fixup, ++ }, + { + /* Xiaomi Book Pro 14 (TM2424) */ + .matches = { +diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c +--- a/drivers/input/mouse/synaptics.c ++++ b/drivers/input/mouse/synaptics.c +@@ -192,6 +192,7 @@ static const char * const smbus_pnp_ids[] = { + "LEN2054", /* E480 */ + "LEN2055", /* E580 */ + "LEN2058", /* E490 */ ++ "LEN205b", /* T490 */ + "LEN2068", /* T14 Gen 1 */ + "SYN1221", /* TUXEDO InfinityBook Pro 14 v5 */ + "SYN3003", /* HP EliteBook 850 G1 */ +@@ -1839,8 +1840,10 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, + return -ENXIO; + } + +- /* Disable intertouch on known-broken board revisions */ +- if (info->board_id == 2722) { ++ /* Disable intertouch on known-broken T440p board revisions */ ++ if (info->board_id == 2722 && ++ psmouse_matches_pnp_id(psmouse, ++ (const char * const []){"LEN0036", NULL})) { + psmouse_info(psmouse, + "Disabling intertouch for board id %u\n", + info->board_id); +diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h +--- a/drivers/input/serio/i8042-acpipnpio.h ++++ b/drivers/input/serio/i8042-acpipnpio.h +@@ -671,6 +671,15 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { + }, + .driver_data = (void *)(SERIO_QUIRK_NOAUX) + }, ++ { ++ /* Fujitsu LIFEBOOK U7410 */ ++ /* Mux mode also disturbs the EC keyboard, causing spurious ACKs */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU CLIENT COMPUTING LIMITED"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U7410"), ++ }, ++ .driver_data = (void *)(SERIO_QUIRK_NOMUX) ++ }, + { + /* Gigabyte M912 */ + .matches = { +diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c +--- a/drivers/input/touchscreen/s6sy761.c ++++ b/drivers/input/touchscreen/s6sy761.c +@@ -500,10 +500,22 @@ static int s6sy761_suspend(struct device *dev) + static int s6sy761_resume(struct device *dev) + { + struct s6sy761_data *sdata = dev_get_drvdata(dev); ++ int err; ++ ++ err = s6sy761_power_on(sdata); ++ if (err) ++ return err; + + enable_irq(sdata->client->irq); + +- return s6sy761_power_on(sdata); ++ guard(mutex)(&sdata->input->mutex); ++ ++ if (!input_device_enabled(sdata->input)) ++ return 0; ++ ++ err = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON); ++ ++ return err; + } + + static const struct dev_pm_ops s6sy761_pm_ops = { diff --git a/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch.sig new file mode 100644 index 0000000..5059200 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0563-input-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch b/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch new file mode 100644 index 0000000..01748b9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch @@ -0,0 +1,11 @@ +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -373,6 +373,7 @@ static const struct acpi_device_id i2c_acpi_force_100khz_device_ids[] = { + * the device works without issues on Windows at what is expected to be + * a 400KHz frequency. The root cause of the issue is not known. + */ ++ { "ASUE140D", 0 }, + { "DLL0945", 0 }, + { "ELAN0678", 0 }, + { "ELAN06FA", 0 }, diff --git a/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch.sig b/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch.sig new file mode 100644 index 0000000..d5acafd Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0565-i2c-asue140d-touchpad-100khz.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch b/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch new file mode 100644 index 0000000..d7870a2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c +--- a/drivers/hid/hid-asus.c ++++ b/drivers/hid/hid-asus.c +@@ -1480,12 +1480,14 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + return ret; + } + +- for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { +- if (asus_has_report_id(hdev, asus_report_id_init[r])) { +- ret = asus_kbd_init(hdev, asus_report_id_init[r]); +- if (ret < 0) +- hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", +- asus_report_id_init[r], ret); ++ if (!drvdata->tp) { ++ for (int r = 0; r < ARRAY_SIZE(asus_report_id_init); r++) { ++ if (asus_has_report_id(hdev, asus_report_id_init[r])) { ++ ret = asus_kbd_init(hdev, asus_report_id_init[r]); ++ if (ret < 0) ++ hid_warn(hdev, "Failed to initialize 0x%x: %d.\n", ++ asus_report_id_init[r], ret); ++ } + } + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig b/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig new file mode 100644 index 0000000..0d7e5e4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch b/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch new file mode 100644 index 0000000..7a0d9ca --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch @@ -0,0 +1,1131 @@ +diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c +--- a/drivers/net/thunderbolt/main.c ++++ b/drivers/net/thunderbolt/main.c +@@ -1362,7 +1362,7 @@ static void tbnet_generate_mac(struct net_device *dev) + dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + } + +-static int tbnet_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbnet_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct net_device *dev; +@@ -1482,7 +1482,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tbnet_pm_ops, tbnet_suspend, tbnet_resume); + + static const struct tb_service_id tbnet_ids[] = { + { TB_SERVICE("network", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbnet_ids); + +diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c +--- a/drivers/thunderbolt/debugfs.c ++++ b/drivers/thunderbolt/debugfs.c +@@ -136,13 +136,13 @@ static void *validate_and_copy_from_user(const void __user *user_buf, + if (!access_ok(user_buf, *count)) + return ERR_PTR(-EFAULT); + +- buf = (void *)get_zeroed_page(GFP_KERNEL); ++ buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + nbytes = min_t(size_t, *count, PAGE_SIZE); + if (copy_from_user(buf, user_buf, nbytes)) { +- free_page((unsigned long)buf); ++ kfree(buf); + return ERR_PTR(-EFAULT); + } + +@@ -265,7 +265,7 @@ static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -406,7 +406,7 @@ static ssize_t port_sb_regs_write(struct file *file, const char __user *user_buf + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -439,7 +439,7 @@ static ssize_t retimer_sb_regs_write(struct file *file, + out: + pm_runtime_mark_last_busy(&rt->dev); + pm_runtime_put_autosuspend(&rt->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +@@ -652,7 +652,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf, + margining->ber_level = val; + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + out_unlock: + mutex_unlock(&tb->lock); + +@@ -829,7 +829,7 @@ margining_lanes_write(struct file *file, const char __user *user_buf, + } + } + +- free_page((unsigned long)buf); ++ kfree(buf); + + if (lane == -1) + return -EINVAL; +@@ -958,7 +958,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + else + goto err_free; + +- free_page((unsigned long)buf); ++ kfree(buf); + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) { + if (!margining->software) +@@ -970,7 +970,7 @@ margining_error_counter_write(struct file *file, const char __user *user_buf, + return count; + + err_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return -EINVAL; + } + +@@ -1116,7 +1116,7 @@ static ssize_t margining_mode_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1503,7 +1503,7 @@ static ssize_t margining_test_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1569,7 +1569,7 @@ static ssize_t margining_margin_write(struct file *file, + mutex_unlock(&tb->lock); + + out_free: +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1624,7 +1624,7 @@ static ssize_t margining_eye_write(struct file *file, + ret = -EINVAL; + } + +- free_page((unsigned long)buf); ++ kfree(buf); + return ret ? ret : count; + } + +@@ -1934,7 +1934,7 @@ static ssize_t counters_write(struct file *file, const char __user *user_buf, + out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); +- free_page((unsigned long)buf); ++ kfree(buf); + + return ret < 0 ? ret : count; + } +diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c +--- a/drivers/thunderbolt/dma_test.c ++++ b/drivers/thunderbolt/dma_test.c +@@ -636,7 +636,7 @@ static void dma_test_debugfs_init(struct tb_service *svc) + debugfs_create_file("test", 0200, debugfs_dir, svc, &test_fops); + } + +-static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int dma_test_probe(struct tb_service *svc) + { + struct tb_xdomain *xd = tb_service_parent(svc); + struct dma_test *dt; +@@ -689,7 +689,7 @@ static const struct dev_pm_ops dma_test_pm_ops = { + + static const struct tb_service_id dma_test_ids[] = { + { TB_SERVICE("dma_test", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, dma_test_ids); + +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -77,12 +77,10 @@ static int tb_service_probe(struct device *dev) + { + struct tb_service *svc = tb_to_service(dev); + struct tb_service_driver *driver; +- const struct tb_service_id *id; + + driver = container_of(dev->driver, struct tb_service_driver, driver); +- id = __tb_service_match(dev, &driver->driver); + +- return driver->probe(svc, id); ++ return driver->probe(svc); + } + + static void tb_service_remove(struct device *dev) +@@ -790,6 +788,21 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + transmit_ring, receive_path, receive_ring); + } + ++static void tb_domain_reset_interface(struct tb *tb) ++{ ++ struct tb_nhi *nhi = tb->nhi; ++ ++ if (!nhi->ops->reset_interface) ++ return; ++ ++ guard(mutex)(&tb->lock); ++ ++ /* The reset clears the ring state so stop the control channel */ ++ tb_ctl_stop(tb->ctl); ++ nhi->ops->reset_interface(nhi); ++ tb_ctl_start(tb->ctl); ++} ++ + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -812,11 +825,20 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { ++ int ret; ++ + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); ++ if (ret) ++ return ret; ++ ++ if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) ++ tb_domain_reset_interface(tb); ++ ++ return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -235,6 +235,12 @@ static void ring_write_descriptors(struct tb_ring *ring) + { + struct ring_frame *frame, *n; + struct ring_desc *descriptor; ++ u32 flags; ++ ++ flags = RING_DESC_POSTED; ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ flags |= RING_DESC_INTERRUPT; ++ + list_for_each_entry_safe(frame, n, &ring->queue, list) { + if (ring_full(ring)) + break; +@@ -242,7 +248,7 @@ static void ring_write_descriptors(struct tb_ring *ring) + descriptor = &ring->descriptors[ring->head]; + descriptor->phys = frame->buffer_phy; + descriptor->time = 0; +- descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; ++ descriptor->flags = flags; + if (ring->is_tx) { + descriptor->length = frame->size; + descriptor->eof = frame->eof; +@@ -339,8 +345,9 @@ EXPORT_SYMBOL_GPL(__tb_ring_enqueue); + * @ring: Ring to poll + * + * This function can be called when @start_poll callback of the @ring +- * has been called. It will read one completed frame from the ring and +- * return it to the caller. ++ * has been called or the ring is created with %RING_FLAG_NO_INTERRUPT. ++ * It will read one completed frame from the ring and return it to the ++ * caller. + * + * Return: Pointer to &struct ring_frame, %NULL if there is no more + * completed frames. +@@ -538,6 +545,12 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", + transmit ? "TX" : "RX", hop, size); + ++ if ((flags & RING_FLAG_NO_INTERRUPT) && start_poll) { ++ dev_WARN(nhi->dev, ++ "start_poll() and NO_INTERRUPT cannot be used at the same time\n"); ++ return NULL; ++ } ++ + ring = kzalloc_obj(*ring); + if (!ring) + return NULL; +@@ -568,7 +581,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + if (!ring->descriptors) + goto err_free_ring; + +- if (nhi->ops->request_ring_irq) { ++ if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_descs; + } +@@ -701,7 +714,8 @@ void tb_ring_start(struct tb_ring *ring) + ring_iowrite32options(ring, flags, 0); + } + +- ring_interrupt_active(ring, true); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, true); + ring->running = true; + err: + spin_unlock(&ring->lock); +@@ -761,7 +775,8 @@ void tb_ring_stop(struct tb_ring *ring) + RING_TYPE(ring), ring->hop); + goto err; + } +- ring_interrupt_active(ring, false); ++ if (!(ring->flags & RING_FLAG_NO_INTERRUPT)) ++ ring_interrupt_active(ring, false); + + ring_iowrite32options(ring, 0, 0); + ring_iowrite64desc(ring, 0, 0); +@@ -1160,6 +1175,32 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + ++/** ++ * nhi_reset_interface() - Reset the host interface ++ * @nhi: Host interface to reset ++ * ++ * Brings the registers in the memory BAR back to their default state and ++ * clears the End-to-End Flow Control state. The caller is responsible for ++ * stopping the control channel over the reset because it clears the ring ++ * state as well. ++ */ ++void nhi_reset_interface(struct tb_nhi *nhi) ++{ ++ u32 val; ++ ++ val = ioread32(nhi->iobase + REG_CAPS); ++ /* Only v1 host interfaces implement the reset */ ++ if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) ++ return; ++ ++ dev_dbg(nhi->dev, "issuing host interface reset\n"); ++ ++ iowrite32(REG_HOST_INTERFACE_RESET_RST, ++ nhi->iobase + REG_HOST_INTERFACE_RESET); ++ /* Wait for tHIReset (10 ms) to complete */ ++ usleep_range(10000, 20000); ++} ++ + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +@@ -1235,6 +1276,8 @@ int nhi_probe(struct tb_nhi *nhi) + + dev_dbg(dev, "NHI initialized, starting thunderbolt\n"); + ++ nhi->host_reset = host_reset; ++ + res = tb_domain_add(tb, host_reset); + if (res) { + /* +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,6 +36,8 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); ++void nhi_reset_interface(struct tb_nhi *nhi); ++ + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -52,6 +54,7 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook ++ * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -66,6 +69,7 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); ++ void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -116,11 +120,24 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 ++#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b ++#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c ++#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d ++#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e ++ + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) ++#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,6 +115,10 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + ++/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ ++#define REG_HOST_INTERFACE_RESET 0x39858 ++#define REG_HOST_INTERFACE_RESET_RST BIT(0) ++ + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,6 +62,27 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } ++ } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { ++ switch (pdev->device) { ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: ++ case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: ++ /* ++ * These AMD hosts may hang the Tx ring when the ++ * DMA paths are torn down so they need the host ++ * interface reset after each teardown. ++ */ ++ nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; ++ break; ++ } + } + } + +@@ -112,7 +133,6 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +- struct device *dev = &pdev->dev; + int res, irq, nvec; + + ida_init(&nhi_pci->msix_ida); +@@ -139,7 +159,7 @@ static int nhi_pci_init_msi(struct tb_nhi *nhi) + res = devm_request_irq(&pdev->dev, irq, nhi_msi, + IRQF_NO_SUSPEND, "thunderbolt", nhi); + if (res) +- return dev_err_probe(dev, res, "request_irq failed, aborting\n"); ++ return res; + } + + return 0; +@@ -230,7 +250,7 @@ static void nhi_pci_ring_release_msix(struct tb_ring *ring) + ring->irq = 0; + } + +-static void nhi_pci_shutdown(struct tb_nhi *nhi) ++static void nhi_pci_release_irq(struct tb_nhi *nhi) + { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); +@@ -256,9 +276,10 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, +- .shutdown = nhi_pci_shutdown, ++ .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -424,7 +445,7 @@ static int icl_nhi_resume(struct tb_nhi *nhi) + + static void icl_nhi_shutdown(struct tb_nhi *nhi) + { +- nhi_pci_shutdown(nhi); ++ nhi_pci_release_irq(nhi); + + icl_nhi_force_power(nhi, false); + } +@@ -442,6 +463,7 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, ++ .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +@@ -479,11 +501,19 @@ static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) + return nhi_probe(&nhi_pci->nhi); + } + +-static void nhi_pci_remove(struct pci_dev *pdev) ++static void nhi_pci_do_remove(struct pci_dev *pdev, bool reset) + { + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + ++ /* ++ * On system shutdown/reboot force a host router reset so the ++ * connection manager asserts DPR on connected Thunderbolt 3 devices ++ * before the router tree is removed (see tb_stop()). ++ */ ++ if (reset) ++ nhi->host_reset = true; ++ + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); +@@ -493,6 +523,16 @@ static void nhi_pci_remove(struct pci_dev *pdev) + nhi_shutdown(nhi); + } + ++static void nhi_pci_remove(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, false); ++} ++ ++static void nhi_pci_shutdown(struct pci_dev *pdev) ++{ ++ nhi_pci_do_remove(pdev, true); ++} ++ + static struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and +@@ -593,7 +633,7 @@ static struct pci_driver nhi_driver = { + .id_table = nhi_ids, + .probe = nhi_pci_probe, + .remove = nhi_pci_remove, +- .shutdown = nhi_pci_remove, ++ .shutdown = nhi_pci_shutdown, + .driver.pm = &nhi_pm_ops, + }; + +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -9,10 +9,12 @@ + + #define pr_fmt(fmt) "tbstream: " fmt + ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -128,6 +130,7 @@ struct tbstream_ring { + * @out_hopid: Out HopID + * @ring_size: Size of the rings + * @throttling: Interrupt throttling rate in ns ++ * @busy_poll: Instead of interrupts, busy poll the rings + * @users: Number of times @cdev has been opened + * @closed: CLOSE packet was received + * @removed: Userspace removed the ConfigFS group underneath. +@@ -147,6 +150,7 @@ struct tbstream_dev { + int out_hopid; + unsigned int ring_size; + unsigned int throttling; ++ bool busy_poll; + int users; + bool closed; + bool removed; +@@ -536,10 +540,39 @@ tbstream_dev_send_data(struct tbstream_dev *sdev, struct iov_iter *from, + return tb_ring_tx(sdev->tx_ring.ring, &sf->frame); + } + ++static void ++tbstream_dev_poll_ring(struct tbstream_dev *sdev, struct tbstream_ring *ring) ++{ ++ struct ring_frame *frame; ++ ++ if (!sdev->busy_poll) ++ return; ++ ++ while ((frame = tb_ring_poll(ring->ring))) ++ frame->callback(ring->ring, frame, false); ++} ++ + static int tbstream_dev_send_close(struct tbstream_dev *sdev) + { + struct tbstream_frame *sf; + ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling it's the write(2) path that ++ * advances the completions so it is possible that the ++ * ring is full at this point. Advance the ring here so ++ * that there is room for the CLOSE packet to be sent. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ } ++ + sf = tbstream_dev_alloc_tx(sdev, TBSTREAM_CLOSE, NULL, SZ_256); + if (IS_ERR(sf)) + return PTR_ERR(sf); +@@ -549,12 +582,15 @@ static int tbstream_dev_send_close(struct tbstream_dev *sdev) + static int tbstream_dev_start(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd = tbstream_dev_xdomain(sdev); ++ unsigned int flags = RING_FLAG_FRAME | RING_FLAG_E2E; + u16 sof_mask, eof_mask; + struct tb_ring *ring; + int ret, e2e_tx_hop; + +- ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E); ++ if (sdev->busy_poll) ++ flags |= RING_FLAG_NO_INTERRUPT; ++ ++ ring = tb_ring_alloc_tx(xd->tb->nhi, -1, sdev->ring_size, flags); + if (!ring) + return -ENOMEM; + sdev->tx_ring.ring = ring; +@@ -567,9 +603,8 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + sof_mask = BIT(TBSTREAM_FRAME_START); + eof_mask = BIT(TBSTREAM_DATA) | BIT(TBSTREAM_CLOSE); + +- ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, +- RING_FLAG_FRAME | RING_FLAG_E2E, e2e_tx_hop, +- sof_mask, eof_mask, NULL, NULL); ++ ring = tb_ring_alloc_rx(xd->tb->nhi, -1, sdev->ring_size, flags, ++ e2e_tx_hop, sof_mask, eof_mask, NULL, NULL); + if (!ring) { + ret = -ENOMEM; + goto err_free_tx_buffers; +@@ -607,15 +642,43 @@ static int tbstream_dev_start(struct tbstream_dev *sdev) + return ret; + } + ++static bool tbstream_dev_tx_drained(const struct tbstream_dev *sdev) ++{ ++ const struct tbstream_ring *ring = &sdev->tx_ring; ++ ++ /* ++ * Everything is completed when number of free TX slots is back ++ * to the maximum. ++ */ ++ return ring->prod - ring->cons == tb_ring_size(ring->ring) - 1; ++} ++ + static void tbstream_dev_stop(struct tbstream_dev *sdev) + { + struct tb_xdomain *xd; + +- /* Wait for the ring to complete any outstanding frames */ +- tb_ring_flush(sdev->tx_ring.ring, 500); +- tb_ring_stop(sdev->tx_ring.ring); +- tb_ring_flush(sdev->rx_ring.ring, 500); +- tb_ring_stop(sdev->rx_ring.ring); ++ if (sdev->busy_poll) { ++ /* ++ * When busy polling we must advance the ring ourselves ++ * to push all outstanding frames on the wire. ++ */ ++ ktime_t timeout = ktime_add_ms(ktime_get(), 500); ++ ++ do { ++ if (tbstream_dev_tx_drained(sdev)) ++ break; ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); ++ fsleep(15); ++ } while (ktime_before(ktime_get(), timeout)); ++ ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } else { ++ tb_ring_flush(sdev->tx_ring.ring, 500); ++ tb_ring_stop(sdev->tx_ring.ring); ++ tb_ring_flush(sdev->rx_ring.ring, 500); ++ tb_ring_stop(sdev->rx_ring.ring); ++ } + + xd = tbstream_dev_xdomain(sdev); + if (xd) { +@@ -633,10 +696,24 @@ static void tbstream_dev_stop(struct tbstream_dev *sdev) + sdev->tx_ring.ring = NULL; + } + ++/* Use only with read_iter/write_iter() to handle nowait */ ++static int tbstream_dev_lock(struct tbstream_dev *sdev, bool nowait) ++{ ++ if (nowait) { ++ if (!mutex_trylock(&sdev->lock)) ++ return -EAGAIN; ++ } else { ++ if (mutex_lock_interruptible(&sdev->lock)) ++ return -ERESTARTSYS; ++ } ++ return 0; ++} ++ + static ssize_t + tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -645,31 +722,50 @@ tbstream_dev_fops_read_iter(struct kiocb *kiocb, struct iov_iter *to) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->rx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->rx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ /* When busy polling, advance any completions manually */ ++ tbstream_dev_poll_ring(sdev, &sdev->rx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return 0; ++ } ++ ++ if (tbstream_ring_available(&sdev->rx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->rx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return 0; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -729,6 +825,7 @@ static ssize_t + tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + { + struct file *file = kiocb->ki_filp; ++ bool nowait = file->f_flags & O_NONBLOCK || kiocb->ki_flags & IOCB_NOWAIT; + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + size_t nbytes; + int ret; +@@ -737,31 +834,49 @@ tbstream_dev_fops_write_iter(struct kiocb *kiocb, struct iov_iter *from) + if (ret) + return ret; + +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; ++ ret = tbstream_dev_lock(sdev, nowait); ++ if (ret) ++ return ret; + +- while (!tbstream_ring_available(&sdev->tx_ring)) { +- mutex_unlock(&sdev->lock); +- +- if (file->f_flags & O_NONBLOCK) +- return -EAGAIN; +- ret = wait_event_interruptible(sdev->wait, +- tbstream_ring_available(&sdev->tx_ring) || +- tbstream_dev_valid(sdev) != 0 || +- tbstream_dev_closed(sdev) || +- tbstream_dev_removed(sdev)); +- if (ret) +- return ret; ++ for (;;) { ++ tbstream_dev_poll_ring(sdev, &sdev->tx_ring); + + ret = tbstream_dev_valid(sdev); ++ if (ret) { ++ mutex_unlock(&sdev->lock); ++ return ret; ++ } ++ ++ if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) { ++ mutex_unlock(&sdev->lock); ++ return -ENXIO; ++ } ++ ++ if (tbstream_ring_available(&sdev->tx_ring)) ++ break; ++ ++ mutex_unlock(&sdev->lock); ++ ++ if (nowait) ++ return -EAGAIN; ++ ++ if (sdev->busy_poll) { ++ if (signal_pending(current)) ++ return -ERESTARTSYS; ++ cond_resched(); ++ } else { ++ ret = wait_event_interruptible(sdev->wait, ++ tbstream_ring_available(&sdev->tx_ring) || ++ tbstream_dev_valid(sdev) != 0 || ++ tbstream_dev_closed(sdev) || ++ tbstream_dev_removed(sdev)); ++ if (ret) ++ return ret; ++ } ++ ++ ret = tbstream_dev_lock(sdev, nowait); + if (ret) + return ret; +- +- if (tbstream_dev_closed(sdev) || tbstream_dev_removed(sdev)) +- return -ENXIO; +- +- if (mutex_lock_interruptible(&sdev->lock)) +- return -ERESTARTSYS; + } + + nbytes = 0; +@@ -795,6 +910,13 @@ tbstream_dev_fops_poll(struct file *file, struct poll_table_struct *wait) + struct tbstream_dev *sdev = to_tbstream_dev(file->private_data); + __poll_t mask = 0; + ++ /* ++ * Without interrupts there is nothing that can wake us up so ++ * return failure instead. ++ */ ++ if (sdev->busy_poll) ++ return EPOLLERR; ++ + poll_wait(file, &sdev->wait, wait); + guard(mutex)(&sdev->lock); + if (tbstream_dev_valid(sdev) != 0) { +@@ -904,6 +1026,35 @@ tbstream_dev_from_group(struct config_group *group) + return container_of(group, struct tbstream_dev, group); + } + ++static ssize_t tbstream_dev_busy_poll_show(struct config_item *item, char *buf) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ ++ return sysfs_emit(buf, "%u\n", sdev->busy_poll); ++} ++ ++static ssize_t ++tbstream_dev_busy_poll_store(struct config_item *item, const char *buf, ++ size_t count) ++{ ++ struct config_group *group = to_config_group(item); ++ struct tbstream_dev *sdev = tbstream_dev_from_group(group); ++ bool busy_poll; ++ int ret; ++ ++ ret = kstrtobool(buf, &busy_poll); ++ if (ret) ++ return ret; ++ ++ guard(mutex)(&sdev->lock); ++ if (sdev->users) ++ return -EBUSY; ++ sdev->busy_poll = busy_poll; ++ return count; ++} ++CONFIGFS_ATTR(tbstream_dev_, busy_poll); ++ + static ssize_t tbstream_dev_index_show(struct config_item *item, char *buf) + { + struct config_group *group = to_config_group(item); +@@ -1210,6 +1361,7 @@ tbstream_dev_throttling_store(struct config_item *item, const char *buf, + CONFIGFS_ATTR(tbstream_dev_, throttling); + + static struct configfs_attribute *tbstream_dev_attrs[] = { ++ &tbstream_dev_attr_busy_poll, + &tbstream_dev_attr_index, + &tbstream_dev_attr_in_hopid, + &tbstream_dev_attr_out_hopid, +@@ -1542,7 +1694,7 @@ static void tbstream_group_detach_stream(struct tbstream *stream) + config_group_put(&sg->group); + } + +-static int tbstream_probe(struct tb_service *svc, const struct tb_service_id *id) ++static int tbstream_probe(struct tb_service *svc) + { + struct tbstream *stream; + +@@ -1632,7 +1784,7 @@ static const struct dev_pm_ops tbstream_pm_ops = { + + static const struct tb_service_id tbstream_ids[] = { + { TB_SERVICE("stream", 1) }, +- { }, ++ { } + }; + MODULE_DEVICE_TABLE(tbsvc, tbstream_ids); + +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -682,7 +682,16 @@ int tb_port_disable(struct tb_port *port) + return __tb_port_enable(port, false); + } + +-static int tb_port_reset(struct tb_port *port) ++/** ++ * tb_port_reset() - Reset the port ++ * @port: Port to reset ++ * ++ * Resets @port. For USB4 ports this issues a USB4 port reset and for ++ * legacy ports the link controller port is reset. ++ * ++ * Return: %0 on success, negative errno otherwise. ++ */ ++int tb_port_reset(struct tb_port *port) + { + if (tb_switch_is_usb4(port->sw)) + return port->cap_usb4 ? usb4_port_reset(port) : 0; +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -2941,7 +2941,9 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, + static void tb_stop(struct tb *tb) + { + struct tb_cm *tcm = tb_priv(tb); ++ struct tb_nhi *nhi = tb->nhi; + struct tb_tunnel *tunnel; ++ struct tb_port *port; + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); +@@ -2956,6 +2958,25 @@ static void tb_stop(struct tb *tb) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } ++ /* ++ * Signal disconnect to connected devices before the router tree is ++ * removed below. A Thunderbolt 3 device directly connected to a USB4 ++ * host otherwise never receives a disconnect indication, leaving ++ * firmware to poll the dead link for up to ~60 s which on some ++ * platforms turns the shutdown into a warm reset. Asserting ++ * PORT_CS_19.DPR drives SBTX low (USB4 spec section 6.9) so the device ++ * detects SBRX low and goes to Uninitialized Unplugged immediately. ++ */ ++ if (nhi->host_reset) { ++ tb_switch_for_each_port(tb->root_switch, port) { ++ if (!tb_port_is_null(port) || !tb_port_has_remote(port)) ++ continue; ++ if (tb_switch_is_usb4(port->remote->sw)) ++ continue; ++ if (tb_port_reset(port)) ++ tb_port_dbg(port, "downstream port reset failed, continuing\n"); ++ } ++ } + tb_switch_remove(tb->root_switch); + tb->root_switch = NULL; + tcm->hotplug_active = false; /* signal tb_handle_hotplug to quit */ +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -1103,6 +1103,7 @@ int tb_port_clear_counter(struct tb_port *port, int counter); + int tb_port_unlock(struct tb_port *port); + int tb_port_enable(struct tb_port *port); + int tb_port_disable(struct tb_port *port); ++int tb_port_reset(struct tb_port *port); + int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid); + void tb_port_release_in_hopid(struct tb_port *port, int hopid); + int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid); +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -48,6 +48,9 @@ + #define TB_DP_AUX_PRIORITY 2 + #define TB_DP_AUX_WEIGHT 1 + ++/* struct tb_regs_hop::initial_credits is 7 bits wide */ ++#define TB_MAX_CREDITS 127 ++ + /* Minimum number of credits needed for PCIe path */ + #define TB_MIN_PCIE_CREDITS 6U + /* +@@ -1778,8 +1781,7 @@ static int tb_dma_reserve_credits(struct tb_path_hop *hop, unsigned int credits) + if (available < TB_MIN_DMA_CREDITS) + return -ENOSPC; + +- while (credits > available) +- credits--; ++ credits = min(credits, available); + + tb_port_dbg(port, "reserving %u credits for DMA path\n", + credits); +@@ -1908,7 +1910,7 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + struct tb_tunnel *tunnel; + size_t npaths = 0, i = 0; + struct tb_path *path; +- int credits; ++ unsigned int credits; + + /* Ring 0 is reserved for control channel */ + if (WARN_ON(!receive_ring || !transmit_ring)) +@@ -1931,6 +1933,11 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, + tunnel->destroy = tb_dma_destroy; + + credits = min_not_zero(dma_credits, nhi->sw->max_dma_credits); ++ if (credits > TB_MAX_CREDITS) { ++ tb_tunnel_dbg(tunnel, "%u credits do not fit a hop, using %u\n", ++ credits, TB_MAX_CREDITS); ++ credits = TB_MAX_CREDITS; ++ } + + if (receive_ring > 0) { + path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -968,6 +968,9 @@ tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, + */ + int tb_register_service_driver(struct tb_service_driver *drv) + { ++ if (!drv->probe) ++ return -EINVAL; ++ + drv->driver.bus = &tb_bus_type; + return driver_register(&drv->driver); + } +@@ -1811,6 +1814,7 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; ++ tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -465,7 +465,7 @@ static inline struct tb_service *tb_to_service(struct device *dev) + */ + struct tb_service_driver { + struct device_driver driver; +- int (*probe)(struct tb_service *svc, const struct tb_service_id *id); ++ int (*probe)(struct tb_service *svc); + void (*remove)(struct tb_service *svc); + void (*shutdown)(struct tb_service *svc); + const struct tb_service_id *id_table; +@@ -514,6 +514,11 @@ void tb_service_properties_changed(struct tb_service *svc); + * @hop_count: Number of rings (end point hops) supported by NHI. + * @quirks: NHI specific quirks if any + * @domain_released: Completed when domain has been fully released ++ * @host_reset: Host router was reset on driver load, or forced on system ++ * shutdown/reboot. When set, tb_stop() asserts DPR on connected ++ * downstream ports to signal disconnect before tearing down the ++ * router tree. Only Thunderbolt 3 devices are reset; USB4 ++ * routers are skipped. + */ + struct tb_nhi { + spinlock_t lock; +@@ -528,6 +533,7 @@ struct tb_nhi { + u32 hop_count; + unsigned long quirks; + struct completion domain_released; ++ bool host_reset; + }; + + /** +@@ -592,6 +598,8 @@ struct tb_ring { + #define RING_FLAG_FRAME BIT(1) + /* Enable end-to-end flow control */ + #define RING_FLAG_E2E BIT(2) ++/* Do not enable interrupt for the ring */ ++#define RING_FLAG_NO_INTERRUPT BIT(3) + + struct ring_frame; + typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); diff --git a/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch.sig new file mode 100644 index 0000000..f52f6aa Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0601-thunderbolt-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch new file mode 100644 index 0000000..f2e51c0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch @@ -0,0 +1,557 @@ +diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c +--- a/drivers/thunderbolt/ctl.c ++++ b/drivers/thunderbolt/ctl.c +@@ -73,7 +73,6 @@ struct tb_ctl { + #define tb_ctl_dbg_once(ctl, format, arg...) \ + dev_dbg_once((ctl)->nhi->dev, format, ## arg) + +-static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); + /* Serializes access to request kref_get/put */ + static DEFINE_MUTEX(tb_cfg_request_lock); + +@@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req) + static int tb_cfg_request_enqueue(struct tb_ctl *ctl, + struct tb_cfg_request *req) + { ++ tb_cfg_request_get(req); ++ + WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)); + WARN_ON(req->ctl); + +- mutex_lock(&ctl->request_queue_lock); ++ guard(mutex)(&ctl->request_queue_lock); + if (!ctl->running) { +- mutex_unlock(&ctl->request_queue_lock); ++ tb_cfg_request_put(req); + return -ENOTCONN; + } + req->ctl = ctl; + list_add_tail(&req->list, &ctl->request_queue); + set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- mutex_unlock(&ctl->request_queue_lock); + return 0; + } + ++static bool tb_cfg_request_is_active(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++} ++ ++static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req) ++{ ++ return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++} ++ + static void tb_cfg_request_dequeue(struct tb_cfg_request *req) + { + struct tb_ctl *ctl = req->ctl; + +- mutex_lock(&ctl->request_queue_lock); +- if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { +- mutex_unlock(&ctl->request_queue_lock); +- return; ++ guard(mutex)(&ctl->request_queue_lock); ++ if (tb_cfg_request_is_active(req)) { ++ list_del(&req->list); ++ clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); ++ tb_cfg_request_put(req); + } +- +- list_del(&req->list); +- clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +- if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) +- wake_up(&tb_cfg_request_cancel_queue); +- mutex_unlock(&ctl->request_queue_lock); +-} +- +-static bool tb_cfg_request_is_active(struct tb_cfg_request *req) +-{ +- return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + } + + static struct tb_cfg_request * +@@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) + mutex_lock(&pkg->ctl->request_queue_lock); + list_for_each_entry(iter, &pkg->ctl->request_queue, list) { + tb_cfg_request_get(iter); +- if (iter->match(iter, pkg)) { ++ if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) { + req = iter; + break; + } +@@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req); + + if (req) { +- if (req->copy(req, pkg)) +- schedule_work(&req->work); ++ scoped_guard(mutex, &pkg->ctl->request_queue_lock) { ++ if (!tb_cfg_request_is_canceled(req) && ++ req->copy(req, pkg)) ++ schedule_work(&req->work); ++ } + tb_cfg_request_put(req); + } + +@@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work) + { + struct tb_cfg_request *req = container_of(work, typeof(*req), work); + +- if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) ++ if (!tb_cfg_request_is_canceled(req)) + req->callback(req->callback_data); + + tb_cfg_request_dequeue(req); +- tb_cfg_request_put(req); + } + + /** +@@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + INIT_WORK(&req->work, tb_cfg_request_work); + INIT_LIST_HEAD(&req->list); + +- tb_cfg_request_get(req); + ret = tb_cfg_request_enqueue(ctl, req); + if (ret) +- goto err_put; ++ return ret; + + ret = tb_ctl_tx(ctl, req->request, req->request_size, + req->request_type); +@@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + + err_dequeue: + tb_cfg_request_dequeue(req); +-err_put: +- tb_cfg_request_put(req); +- + return ret; + } + +@@ -588,9 +586,10 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, + */ + void tb_cfg_request_cancel(struct tb_cfg_request *req, int err) + { +- set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); +- schedule_work(&req->work); +- wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req)); ++ scoped_guard(mutex, &req->ctl->request_queue_lock) ++ set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); ++ cancel_work_sync(&req->work); ++ tb_cfg_request_dequeue(req); + req->result.err = err; + } + +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + INIT_LIST_HEAD(&ring->in_flight); + INIT_WORK(&ring->work, ring_work); + init_waitqueue_head(&ring->wait); ++ lockdep_register_key(&ring->lock_key); ++ lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0); + + ring->nhi = nhi; + ring->hop = hop; +@@ -599,6 +602,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + ring->size * sizeof(*ring->descriptors), + ring->descriptors, ring->descriptors_dma); + err_free_ring: ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + + return NULL; +@@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring) + * to finish before freeing the ring. + */ + flush_work(&ring->work); ++ lockdep_unregister_key(&ring->lock_key); + kfree(ring); + } + EXPORT_SYMBOL_GPL(tb_ring_free); +diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c +--- a/drivers/thunderbolt/switch.c ++++ b/drivers/thunderbolt/switch.c +@@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + { + int port_max_hopid; + struct ida *ida; ++ int ret; + + if (in) { + port_max_hopid = port->config.max_in_hop_id; +@@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, + if (max_hopid < 0 || max_hopid > port_max_hopid) + max_hopid = port_max_hopid; + +- return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); ++ if (ret >= 0) ++ tb_switch_get(port->sw); ++ ++ return ret; + } + + /** +@@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid) + void tb_port_release_in_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->in_hopids, hopid); ++ tb_switch_put(port->sw); + } + + /** +@@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid) + void tb_port_release_out_hopid(struct tb_port *port, int hopid) + { + ida_free(&port->out_hopids, hopid); ++ tb_switch_put(port->sw); + } + + static inline bool tb_switch_is_reachable(const struct tb_switch *parent, +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, + const char *reason); + static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port, + int retry, unsigned long delay); ++static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data); + + static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) + { +@@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, + + switch (port->config.type) { + case TB_TYPE_DP_HDMI_IN: +- tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); ++ tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids, ++ tb_dp_tunnel_active, tb); + tb_increase_tmu_accuracy(tunnel); + break; + +@@ -1910,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + struct tb *tb = data; + + mutex_lock(&tb->lock); ++ ++ /* ++ * If the DPRX read was canceled the tunnel is already being torn ++ * down by whoever canceled it. Do not touch the adapters here ++ * because the routers may be gone by now. ++ */ ++ if (tunnel->dprx_canceled) { ++ tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n"); ++ mutex_unlock(&tb->lock); ++ return; ++ } ++ + if (tb_tunnel_is_active(tunnel)) { + int consumed_up, consumed_down, ret; + +@@ -1964,8 +1978,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) + tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); + } + mutex_unlock(&tb->lock); +- +- tb_domain_put(tb); + } + + static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, +@@ -2026,8 +2038,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + available_up, available_down); + + tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, +- available_down, tb_dp_tunnel_active, +- tb_domain_get(tb)); ++ available_down, tb_dp_tunnel_active, tb); + if (!tunnel) { + tb_port_dbg(out, "could not allocate DP tunnel\n"); + goto err_reclaim_usb; +@@ -2048,7 +2059,6 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + tb_tunnel_put(tunnel); + err_reclaim_usb: + tb_reclaim_usb3_bandwidth(tb, in, out); +- tb_domain_put(tb); + err_detach_group: + tb_detach_bandwidth_group(in); + err_dealloc_dp: +@@ -2950,11 +2960,12 @@ static void tb_stop(struct tb *tb) + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +- * DMA tunnels require the driver to be functional so we +- * tear them down. Other protocol tunnels can be left +- * intact. ++ * DMA tunnels and DP tunnels which are not yet active require ++ * the driver to be functional so we tear them down. ++ * Other protocol tunnels can be left intact. + */ +- if (tb_tunnel_is_dma(tunnel)) ++ if (tb_tunnel_is_dma(tunnel) || ++ (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel))) + tb_tunnel_deactivate(tunnel); + tb_tunnel_put(tunnel); + } +@@ -3274,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work) + struct tb *tb = tcm_to_tb(tcm); + + mutex_lock(&tb->lock); +- if (tb->root_switch) ++ if (tb->root_switch) { + tb_free_unplugged_children(tb->root_switch); ++ tb_free_unplugged_xdomains(tb->root_switch); ++ } + mutex_unlock(&tb->lock); +- +- tb_free_unplugged_xdomains(tb->root_switch); + } + + static int tb_runtime_resume(struct tb *tb) +diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c +--- a/drivers/thunderbolt/tunnel.c ++++ b/drivers/thunderbolt/tunnel.c +@@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, + goto err_deactivate; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1093,8 +1094,14 @@ static void tb_dp_dprx_work(struct work_struct *work) + struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work); + struct tb *tb = tunnel->tb; + ++ /* ++ * The DPRX read can be canceled while this work is waiting for ++ * tb->lock. Check the flag only once it is held: while the lock is ++ * held the tunnel cannot be torn down under us and the adapters are ++ * safe to access. ++ */ ++ mutex_lock(&tb->lock); + if (!tunnel->dprx_canceled) { +- mutex_lock(&tb->lock); + if (tb_dp_is_usb4(tunnel->src_port->sw) && + tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { + if (ktime_before(ktime_get(), tunnel->dprx_timeout)) { +@@ -1106,41 +1113,42 @@ static void tb_dp_dprx_work(struct work_struct *work) + } else { + tb_tunnel_set_active(tunnel, true); + } +- mutex_unlock(&tb->lock); + } ++ mutex_unlock(&tb->lock); + +- if (tunnel->callback) +- tunnel->callback(tunnel, tunnel->callback_data); ++ tunnel->callback(tunnel, tunnel->callback_data); + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); + } + + static int tb_dp_dprx_start(struct tb_tunnel *tunnel) + { + /* +- * Bump up the reference to keep the tunnel around. It will be +- * dropped in tb_dp_dprx_stop() once the tunnel is deactivated. ++ * Bump up the references to keep the tunnel and the domain around ++ * until the work has run or has been canceled. + */ + tb_tunnel_get(tunnel); ++ tb_domain_get(tunnel->tb); + + tunnel->dprx_started = true; ++ tunnel->dprx_canceled = false; ++ tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); ++ queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); + +- if (tunnel->callback) { +- tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); +- queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); +- return -EINPROGRESS; +- } +- +- return tb_dp_is_usb4(tunnel->src_port->sw) ? +- tb_dp_wait_dprx(tunnel, dprx_timeout) : 0; ++ return -EINPROGRESS; + } + + static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) + { ++ struct tb *tb = tunnel->tb; ++ + if (tunnel->dprx_started) { + tunnel->dprx_started = false; + tunnel->dprx_canceled = true; +- if (cancel_delayed_work(&tunnel->dprx_work)) ++ if (cancel_delayed_work(&tunnel->dprx_work)) { + tb_tunnel_put(tunnel); ++ tb_domain_put(tb); ++ } + } + } + +@@ -1582,20 +1590,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) + * @tb: Pointer to the domain structure + * @in: DP in adapter + * @alloc_hopid: Allocate HopIDs from visited ports ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * If @in adapter is active, follows the tunnel to the DP out adapter + * and back. Returns the discovered tunnel or %NULL if there was no +- * tunnel. ++ * tunnel. See tb_tunnel_alloc_dp() for @callback. + * + * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. + */ + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid) ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data) + { + struct tb_tunnel *tunnel; + struct tb_port *port; + struct tb_path *path; + ++ if (WARN_ON(!callback)) ++ return NULL; ++ + if (!tb_dp_port_is_enabled(in)) + return NULL; + +@@ -1611,6 +1627,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; + tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; + tunnel->src_port = in; ++ tunnel->callback = callback; ++ tunnel->callback_data = callback_data; ++ INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work); + + path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1, + &tunnel->dst_port, "Video", alloc_hopid); +@@ -1656,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + + tb_dp_dump(tunnel); + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +@@ -1677,16 +1697,16 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, + * %0 if no available bandwidth. + * @max_down: Maximum available downstream bandwidth for the DP tunnel. + * %0 if no available bandwidth. +- * @callback: Optional callback that is called when the DP tunnel is +- * fully activated (or there is an error) +- * @callback_data: Optional data for @callback ++ * @callback: Callback that is called when the DP tunnel is fully ++ * activated (or there is an error) ++ * @callback_data: Data for @callback + * + * Allocates a tunnel between @in and @out that is capable of tunneling +- * Display Port traffic. If @callback is not %NULL it will be called +- * after tb_tunnel_activate() once the tunnel has been fully activated. +- * It can call tb_tunnel_is_active() to check if activation was +- * successful (or if it returns %false there was some sort of issue). +- * The @callback is called without @tb->lock held. ++ * Display Port traffic. The @callback is called after tb_tunnel_activate() ++ * once the tunnel has been fully activated. It can call ++ * tb_tunnel_is_active() to check if activation was successful (or if it ++ * returns %false there was some sort of issue). The @callback is called ++ * without @tb->lock held. + * + * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. + */ +@@ -1701,7 +1721,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_path *path; + bool pm_support; + +- if (WARN_ON(!in->cap_adap || !out->cap_adap)) ++ if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback)) + return NULL; + + tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); +@@ -2288,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, + tb_usb3_reclaim_available_bandwidth; + } + ++ tb_tunnel_set_active(tunnel, true); + tb_tunnel_dbg(tunnel, "discovered\n"); + return tunnel; + +diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h +--- a/drivers/thunderbolt/tunnel.h ++++ b/drivers/thunderbolt/tunnel.h +@@ -66,8 +66,8 @@ enum tb_tunnel_state { + * @dprx_canceled: Was DPRX capabilities read poll canceled + * @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes + * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read +- * @callback: Optional callback called when DP tunnel is fully activated +- * @callback_data: Optional data for @callback ++ * @callback: Callback called when DP tunnel is fully activated ++ * @callback_data: Data for @callback + * @paths: All paths required by the tunnel + */ + struct tb_tunnel { +@@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, + bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, + int *reserved_down); + struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, +- bool alloc_hopid); ++ bool alloc_hopid, ++ void (*callback)(struct tb_tunnel *, void *), ++ void *callback_data); + struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, + struct tb_port *out, int link_nr, + int max_up, int max_down, +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -1814,7 +1814,6 @@ static void tb_xdomain_state_work(struct work_struct *work) + tb_xdomain_failed(xd); + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; +- tb_xdomain_queue_properties_changed(xd); + } + break; + +diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h +--- a/include/linux/thunderbolt.h ++++ b/include/linux/thunderbolt.h +@@ -22,6 +22,7 @@ struct device; + #include + #include + #include ++#include + #include + #include + #include +@@ -565,6 +566,7 @@ struct tb_nhi { + * @interval_nsec: Interval counter if interrupt throttling is to be + * used with this ring (in ns) + * @wait: Used to signal that the ring may be empty now ++ * @lock_key: Lock validator class key per-ring + */ + struct tb_ring { + spinlock_t lock; +@@ -590,6 +592,7 @@ struct tb_ring { + void *poll_data; + unsigned int interval_nsec; + wait_queue_head_t wait; ++ struct lock_class_key lock_key; + }; + + /* Leave ring interrupt enabled on suspend */ diff --git a/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch.sig new file mode 100644 index 0000000..2c4aa9c Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0602-thunderbolt-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch b/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch new file mode 100644 index 0000000..171a4b4 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch @@ -0,0 +1,5629 @@ +diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c +--- a/drivers/power/supply/power_supply_core.c ++++ b/drivers/power/supply/power_supply_core.c +@@ -477,6 +477,159 @@ struct power_supply *power_supply_get_by_name(const char *name) + } + EXPORT_SYMBOL_GPL(power_supply_get_by_name); + ++static bool power_supply_is_system_battery(struct power_supply *psy) ++{ ++ union power_supply_propval val; ++ ++ if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) ++ return false; ++ ++ if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE, ++ &val)) ++ if (val.intval == POWER_SUPPLY_SCOPE_DEVICE) ++ return false; ++ ++ return true; ++} ++ ++static int __power_supply_get_num_system_batteries(struct power_supply *epsy, ++ void *data) ++{ ++ int *count = data; ++ ++ if (power_supply_is_system_battery(epsy)) ++ (*count)++; ++ ++ return 0; ++} ++ ++static int power_supply_get_num_system_batteries(struct device *dev) ++{ ++ int ret, count = 0; ++ ++ ret = power_supply_for_each_psy(&count, ++ __power_supply_get_num_system_batteries); ++ ++ dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret); ++ ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ ++struct psy_get_supplies_data { ++ int cnt; ++ int size; ++ struct power_supply **psys; ++}; ++ ++static int ++__power_supply_populate_system_batteries_array(struct power_supply *epsy, ++ void *_data) ++{ ++ struct psy_get_supplies_data *data = _data; ++ ++ if (power_supply_is_system_battery(epsy)) { ++ if (data->size <= data->cnt) ++ return -EOVERFLOW; ++ ++ get_device(&epsy->dev); ++ data->psys[data->cnt] = epsy; ++ atomic_inc(&epsy->use_cnt); ++ data->cnt++; ++ } ++ ++ return 0; ++} ++ ++static int ++power_supply_populate_system_batteries_array(struct device *dev, int size, ++ struct power_supply **batteries) ++{ ++ int ret; ++ ++ struct psy_get_supplies_data data = { ++ .cnt = 0, ++ .size = size, ++ .psys = batteries, ++ }; ++ ++ ret = power_supply_for_each_psy(&data, ++ __power_supply_populate_system_batteries_array); ++ ++ dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n", ++ __func__, data.cnt, data.size, ret); ++ ++ if (ret < 0 || !data.cnt) { ++ power_supply_put_system_batteries(batteries, data.cnt); ++ return ret; ++ } ++ ++ return data.cnt; ++} ++ ++/** ++ * power_supply_get_system_batteries() - Fetches references to battery type ++ * power supplies in the system. ++ * @dev: Pointer to device requesting the power supply refs. ++ * @psys: Pointer to an array of power supply refs. ++ * ++ * Helper function to get handles to battery type power supplies in the system. ++ * If acquiring a ref to a power supply fails, then the search for battery type ++ * power supplies will abort and the acquired power supply references will be ++ * released. ++ * ++ * Return: Indicates the number of battery type power supplies returned on ++ * success or a negative error code on failure. ++ * ++ * Call power_supply_put_system_batteries() after use to cleanup resources. ++ */ ++int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ int ret; ++ ++ if (!psys) ++ return -EINVAL; ++ ++ ret = power_supply_get_num_system_batteries(dev); ++ if (ret <= 0) { ++ *psys = NULL; ++ return ret; ++ } ++ ++ *psys = kzalloc_objs(**psys, ret); ++ if (!*psys) ++ return -ENOMEM; ++ ++ ret = power_supply_populate_system_batteries_array(dev, ret, *psys); ++ if (ret <= 0) ++ *psys = NULL; ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(power_supply_get_system_batteries); ++ ++/** ++ * power_supply_put_system_batteries() - Cleanup resources allocated by ++ * power_supply_get_system_batteries() ++ * @psys: Array of power supply references to release and free. ++ * @count: Number of elements in the array. ++ */ ++void power_supply_put_system_batteries(struct power_supply **psys, int count) ++{ ++ int i; ++ ++ for (i = 0; i < count; i++) { ++ if (psys[i]) ++ power_supply_put(psys[i]); ++ } ++ ++ kfree(psys); ++} ++EXPORT_SYMBOL_GPL(power_supply_put_system_batteries); ++ + /** + * power_supply_put() - Drop reference obtained with power_supply_get_by_name + * @psy: Reference to put +diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c +--- a/drivers/usb/cdns3/cdns3-plat.c ++++ b/drivers/usb/cdns3/cdns3-plat.c +@@ -147,12 +147,12 @@ static int cdns3_plat_probe(struct platform_device *pdev) + cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); + if (IS_ERR(cdns->usb2_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy), +- "Failed to get cdn3,usb2-phy\n"); ++ "Failed to get cdns3,usb2-phy\n"); + + cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); + if (IS_ERR(cdns->usb3_phy)) + return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy), +- "Failed to get cdn3,usb3-phy\n"); ++ "Failed to get cdns3,usb3-phy\n"); + + ret = phy_init(cdns->usb2_phy); + if (ret) +diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c +--- a/drivers/usb/core/config.c ++++ b/drivers/usb/core/config.c +@@ -151,7 +151,7 @@ static void usb_parse_ss_endpoint_companion(struct device *ddev, int cfgno, + usb_endpoint_xfer_int(&ep->desc)) && + desc->bmAttributes != 0) { + dev_notice(ddev, "%s endpoint with bmAttributes = %d in config %d interface %d altsetting %d ep 0x%X: setting to zero\n", +- usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Bulk", ++ usb_endpoint_xfer_control(&ep->desc) ? "Control" : "Interrupt", + desc->bmAttributes, + cfgno, inum, asnum, ep->desc.bEndpointAddress); + ep->ss_ep_comp.bmAttributes = 0; +diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c +--- a/drivers/usb/core/devices.c ++++ b/drivers/usb/core/devices.c +@@ -37,6 +37,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -408,7 +409,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + return 0; + /* allocate 2^1 pages = 8K (on i386); + * should be more than enough for one device */ +- pages_start = (char *)__get_free_pages(GFP_NOIO, 1); ++ pages_start = kmalloc(PAGE_SIZE << 1, GFP_NOIO); + if (!pages_start) + return -ENOMEM; + +@@ -479,7 +480,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + if (length > *nbytes) + length = *nbytes; + if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + return -EFAULT; + } + *nbytes -= length; +@@ -490,7 +491,7 @@ static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, + } else + *skip_bytes -= length; + +- free_pages((unsigned long)pages_start, 1); ++ kfree(pages_start); + + /* Now look at all of this device's children. */ + usb_hub_for_each_child(usbdev, chix, childdev) { +diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c +--- a/drivers/usb/core/devio.c ++++ b/drivers/usb/core/devio.c +@@ -2329,6 +2329,13 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + if (!connected(ps)) + return -ENODEV; + ++ if (ps->dev->state != USB_STATE_CONFIGURED) ++ return -EHOSTUNREACH; ++ ++ intf = usb_ifnum_to_if(ps->dev, ctl->ifno); ++ if (!intf) ++ return -EINVAL; ++ + /* alloc buffer */ + size = _IOC_SIZE(ctl->ioctl_code); + if (size > 0) { +@@ -2345,11 +2352,7 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) + } + } + +- if (ps->dev->state != USB_STATE_CONFIGURED) +- retval = -EHOSTUNREACH; +- else if (!(intf = usb_ifnum_to_if(ps->dev, ctl->ifno))) +- retval = -EINVAL; +- else switch (ctl->ioctl_code) { ++ switch (ctl->ioctl_code) { + + /* disconnect kernel driver from interface */ + case USBDEVFS_DISCONNECT: +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -623,11 +623,11 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, + mutex_lock(&hub->status_mutex); + ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len); + if (ret < len) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_port_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->port.wPortStatus); + *change = le16_to_cpu(hub->status->port.wPortChange); +@@ -995,11 +995,11 @@ static int hub_hub_status(struct usb_hub *hub, + mutex_lock(&hub->status_mutex); + ret = get_hub_status(hub->hdev, &hub->status->hub); + if (ret < (int)sizeof(hub->status->hub)) { +- if (ret != -ENODEV) +- dev_err(hub->intfdev, +- "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; ++ if (ret != -ENODEV) ++ dev_dbg(hub->intfdev, ++ "get_hub_status failed: err = %d\n", ret); + } else { + *status = le16_to_cpu(hub->status->hub.wHubStatus); + *change = le16_to_cpu(hub->status->hub.wHubChange); +diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c +--- a/drivers/usb/dwc2/core_intr.c ++++ b/drivers/usb/dwc2/core_intr.c +@@ -334,9 +334,8 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) + + /** + * dwc2_wakeup_from_lpm_l1 - Exit the device from LPM L1 state +- * + * @hsotg: Programming view of DWC_otg controller +- * ++ * @remotewakeup: Whether this is a remote wakeup + */ + void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup) + { +diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c +--- a/drivers/usb/dwc3/core.c ++++ b/drivers/usb/dwc3/core.c +@@ -2188,22 +2188,89 @@ static void dwc3_vbus_draw_work(struct work_struct *work) + ret, dwc->current_limit); + } + +-static struct power_supply *dwc3_get_usb_power_supply(struct dwc3 *dwc) ++static int dwc3_psy_notifier(struct notifier_block *nb, ++ unsigned long event, void *data) + { +- struct power_supply *usb_psy; +- const char *usb_psy_name; ++ struct dwc3 *dwc = container_of(nb, struct dwc3, psy_nb); ++ struct power_supply *psy = data; ++ unsigned long flags; ++ ++ if (dwc->usb_psy) ++ return NOTIFY_DONE; ++ ++ if (strcmp(psy->desc->name, dwc->usb_psy_name) != 0) ++ return NOTIFY_DONE; ++ ++ /* Explicitly get the reference for this psy */ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return NOTIFY_DONE; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * The USB power_supply may already be set. This can happen if notifier ++ * callbacks for the USB power_supply race, or if a previous notifier ++ * callback has already successfully fetched and associated the instance. ++ * In such cases, release the newly acquired reference and ignore ++ * subsequent notifications until the notifier is unregistered. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return NOTIFY_DONE; ++ } ++ ++ dwc->usb_psy = psy; ++ if (dwc->current_limit != DWC3_CURRENT_UNSPECIFIED) ++ schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ return NOTIFY_OK; ++} ++ ++static void dwc3_get_usb_power_supply(struct dwc3 *dwc) ++{ ++ struct power_supply *psy; ++ unsigned long flags; + int ret; + +- ret = device_property_read_string(dwc->dev, "usb-psy-name", &usb_psy_name); ++ ret = device_property_read_string(dwc->dev, "usb-psy-name", &dwc->usb_psy_name); + if (ret < 0) +- return NULL; +- +- usb_psy = power_supply_get_by_name(usb_psy_name); +- if (!usb_psy) +- return ERR_PTR(-EPROBE_DEFER); ++ return; + + INIT_WORK(&dwc->vbus_draw_work, dwc3_vbus_draw_work); +- return usb_psy; ++ ++ dwc->current_limit = DWC3_CURRENT_UNSPECIFIED; ++ dwc->psy_nb.notifier_call = dwc3_psy_notifier; ++ ret = power_supply_reg_notifier(&dwc->psy_nb); ++ if (ret) { ++ dev_err(dwc->dev, "Failed to register power supply notifier: %d\n", ret); ++ dwc->psy_nb.notifier_call = NULL; ++ return; ++ } ++ ++ psy = power_supply_get_by_name(dwc->usb_psy_name); ++ if (!psy) ++ return; ++ ++ /* Unregister the notifier now that we have the power supply */ ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ dwc->psy_nb.notifier_call = NULL; ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ /* ++ * It is possible that the notifier callback ran before we reached here ++ * and successfully fetched the power supply. In that case we need to ++ * release the above reference. ++ */ ++ if (dwc->usb_psy) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ power_supply_put(psy); ++ return; ++ } ++ ++ dwc->usb_psy = psy; ++ spin_unlock_irqrestore(&dwc->lock, flags); + } + + int dwc3_core_probe(const struct dwc3_probe_data *data) +@@ -2251,9 +2318,9 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + + dwc3_get_software_properties(dwc, &data->properties); + +- dwc->usb_psy = dwc3_get_usb_power_supply(dwc); +- if (IS_ERR(dwc->usb_psy)) +- return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n"); ++ spin_lock_init(&dwc->lock); ++ ++ dwc3_get_usb_power_supply(dwc); + + if (!data->ignore_clocks_and_resets) { + dwc->reset = devm_reset_control_array_get_optional_shared(dev); +@@ -2305,7 +2372,6 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + dwc->num_usb3_ports = 1; + } + +- spin_lock_init(&dwc->lock); + mutex_init(&dwc->mutex); + + pm_runtime_get_noresume(dev); +@@ -2373,6 +2439,8 @@ int dwc3_core_probe(const struct dwc3_probe_data *data) + err_assert_reset: + reset_control_assert(dwc->reset); + err_put_psy: ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); + if (dwc->usb_psy) + power_supply_put(dwc->usb_psy); + +@@ -2429,6 +2497,9 @@ void dwc3_core_remove(struct dwc3 *dwc) + + dwc3_free_event_buffers(dwc); + ++ if (dwc->psy_nb.notifier_call) ++ power_supply_unreg_notifier(&dwc->psy_nb); ++ + if (dwc->usb_psy) { + cancel_work_sync(&dwc->vbus_draw_work); + power_supply_put(dwc->usb_psy); +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -722,7 +722,6 @@ struct dwc3_event_buffer { + * @cancelled_list: list of cancelled requests for this endpoint + * @pending_list: list of pending requests for this endpoint + * @started_list: list of started requests on this endpoint +- * @regs: pointer to first endpoint register + * @trb_pool: array of transaction buffers + * @trb_pool_dma: dma address of @trb_pool + * @trb_enqueue: enqueue 'pointer' into TRB array +@@ -1059,6 +1058,8 @@ struct dwc3_glue_ops { + * @role_switch_default_mode: default operation mode of controller while + * usb role is USB_ROLE_NONE. + * @usb_psy: pointer to power supply interface. ++ * @usb_psy_name: name of the USB power supply ++ * @psy_nb: power supply notifier block + * @vbus_draw_work: Work to set the vbus drawing limit + * @current_limit: How much current to draw from vbus, in milliAmperes. + * @usb2_phy: pointer to USB2 PHY +@@ -1251,9 +1252,13 @@ struct dwc3 { + enum usb_dr_mode role_switch_default_mode; + + struct power_supply *usb_psy; ++ const char *usb_psy_name; ++ struct notifier_block psy_nb; + struct work_struct vbus_draw_work; + unsigned int current_limit; + ++#define DWC3_CURRENT_UNSPECIFIED UINT_MAX ++ + u32 fladj; + u32 ref_clk_per; + u32 irq_gadget; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -205,7 +205,9 @@ static int dwc3_ti_init(struct dwc3_am62 *am62) + + dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); + +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + + /* Set mode valid bit to indicate role is valid */ + reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); +@@ -361,14 +363,19 @@ static int dwc3_ti_resume_common(struct device *dev) + { + struct dwc3_am62 *am62 = dev_get_drvdata(dev); + u32 reg; ++ int ret; + + reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); + if (reg != USBSS_DEBUG_CFG_DISABLED) { + /* lost power/context */ +- dwc3_ti_init(am62); ++ ret = dwc3_ti_init(am62); ++ if (ret) ++ return ret; + } else { + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); +- clk_prepare_enable(am62->usb2_refclk); ++ ret = clk_prepare_enable(am62->usb2_refclk); ++ if (ret) ++ return ret; + } + + if (device_may_wakeup(dev)) { +diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c +--- a/drivers/usb/dwc3/dwc3-qcom.c ++++ b/drivers/usb/dwc3/dwc3-qcom.c +@@ -602,7 +602,7 @@ static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on) + pm_runtime_mark_last_busy(qcom->dev); + } + +-struct dwc3_glue_ops dwc3_qcom_glue_ops = { ++static struct dwc3_glue_ops dwc3_qcom_glue_ops = { + .pre_set_role = dwc3_qcom_set_role_notifier, + .pre_run_stop = dwc3_qcom_run_stop_notifier, + }; +diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c +--- a/drivers/usb/dwc3/dwc3-xilinx.c ++++ b/drivers/usb/dwc3/dwc3-xilinx.c +@@ -98,18 +98,10 @@ static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data) + + dwc3_xlnx_mask_phy_rst(priv_data, false); + +- /* Assert and De-assert reset */ +- ret = reset_control_assert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to assert Reset\n"); +- return ret; +- } +- +- ret = reset_control_deassert(crst); +- if (ret < 0) { +- dev_err_probe(dev, ret, "failed to De-assert Reset\n"); +- return ret; +- } ++ /* assert and deassert reset */ ++ ret = reset_control_reset(crst); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to assert and deassert reset\n"); + + dwc3_xlnx_mask_phy_rst(priv_data, true); + dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG); +@@ -194,7 +186,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) + } + + if (priv_data->usb3_phy) { +- /* Set PIPE Power Present signal in FPD Power Present Register*/ ++ /* Set PIPE Power Present signal in FPD Power Present Register */ + writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT); + /* Set the PIPE Clock Select bit in FPD PIPE Clock register */ + writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK); +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -3129,15 +3129,26 @@ static void dwc3_gadget_set_ssp_rate(struct usb_gadget *g, + static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) + { + struct dwc3 *dwc = gadget_to_dwc(g); ++ unsigned long flags; + + if (dwc->usb2_phy) + return usb_phy_set_power(dwc->usb2_phy, mA); + +- if (!dwc->usb_psy) +- return -EOPNOTSUPP; ++ spin_lock_irqsave(&dwc->lock, flags); ++ if (!dwc->usb_psy) { ++ if (!dwc->psy_nb.notifier_call) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -EOPNOTSUPP; ++ } ++ dwc->current_limit = mA; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ dev_dbg(dwc->dev, "Stored VBUS draw: %u mA (power supply not ready)\n", mA); ++ return 0; ++ } + + dwc->current_limit = mA; + schedule_work(&dwc->vbus_draw_work); ++ spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; + } +diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c +--- a/drivers/usb/dwc3/host.c ++++ b/drivers/usb/dwc3/host.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + #include "../host/xhci-port.h" + #include "../host/xhci-ext-caps.h" +@@ -46,9 +47,9 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) + return; + } + +- op_regs_base = HC_LENGTH(readl(xhci_regs)); ++ op_regs_base = FIELD_GET(HC_LENGTH, readl(xhci_regs)); + reg = readl(xhci_regs + XHCI_HCSPARAMS1); +- port_num = HCS_MAX_PORTS(reg); ++ port_num = FIELD_GET(HCS_MAX_PORTS, reg); + + for (i = 1; i <= port_num; i++) { + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); +diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c +--- a/drivers/usb/gadget/configfs.c ++++ b/drivers/usb/gadget/configfs.c +@@ -125,11 +125,11 @@ static int usb_string_copy(const char *s, char **s_copy) + if (copy) { + str = copy; + } else { +- str = kmalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); ++ str = kzalloc(USB_MAX_STRING_WITH_NULL_LEN, GFP_KERNEL); + if (!str) + return -ENOMEM; + } +- strcpy(str, s); ++ memcpy(str, s, ret + 1); + if (str[ret - 1] == '\n') + str[ret - 1] = '\0'; + *s_copy = str; +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -159,7 +159,9 @@ struct ffs_epfile { + struct mutex mutex; + + struct ffs_data *ffs; +- struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_ep *ep; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ ++ struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ + + /* + * Buffer for holding data from partial reads which may happen since +@@ -219,12 +221,13 @@ struct ffs_epfile { + struct ffs_buffer *read_buffer; + #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) + +- char name[5]; ++ char name[8]; + + unsigned char in; /* P: ffs->eps_lock */ + unsigned char isoc; /* P: ffs->eps_lock */ + +- unsigned char _pad; ++ u8 zlp_enabled; /* P: ffs->eps_lock */ ++ bool is_rw_proxy; + + /* Protects dmabufs */ + struct mutex dmabufs_mutex; +@@ -991,9 +994,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, + return ret; + } + +-static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) ++static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) + { +- struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; + +@@ -1020,17 +1022,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + char *data = NULL; + ssize_t ret, data_len = -EINVAL; + int halt; ++ bool is_rw_proxy = epfile->is_rw_proxy; + + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + +- ep = ffs_epfile_wait_ep(file); ++ /* Proxy to base endpoint if rw_proxy */ ++ if (is_rw_proxy) ++ epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; ++ ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + /* Do we halt? */ +- halt = (!io_data->read == !epfile->in); ++ halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); + if (halt && epfile->isoc) + return -EINVAL; + +@@ -1127,6 +1134,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1178,6 +1187,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) + req->buf = data; + req->num_sgs = 0; + } ++ ++ req->zero = !io_data->read ? epfile->zlp_enabled : 0; + req->length = data_len; + + io_data->buf = data; +@@ -1666,7 +1677,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + priv = attach->importer_priv; + +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + goto err_attachment_put; +@@ -1732,6 +1743,7 @@ static int ffs_dmabuf_transfer(struct file *file, + + /* Now that the dma_fence is in place, queue the transfer. */ + ++ usb_req->zero = epfile->zlp_enabled; + usb_req->length = req->length; + usb_req->buf = NULL; + usb_req->sg = priv->sgt->sgl; +@@ -1779,10 +1791,14 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; + int ret; ++ __u32 enable_zlp = 0; + + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + ++ if (epfile->is_rw_proxy) ++ return -ENOTTY; ++ + switch (code) { + case FUNCTIONFS_DMABUF_ATTACH: + { +@@ -1811,12 +1827,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + + return ffs_dmabuf_transfer(file, &req); + } ++ /* ++ * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace ++ * to configure ZLP behavior immediately without blocking indefinitely ++ * while waiting for the USB host to connect and enable the endpoint. ++ */ ++ case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: ++ if (!epfile->in) ++ return -EINVAL; ++ ++ if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) ++ return -EFAULT; ++ ++ spin_lock_irq(&epfile->ffs->eps_lock); ++ epfile->zlp_enabled = !!enable_zlp; ++ spin_unlock_irq(&epfile->ffs->eps_lock); ++ ++ return 0; + default: + break; + } + + /* Wait for endpoint to be enabled */ +- ep = ffs_epfile_wait_ep(file); ++ ep = ffs_epfile_wait_ep(epfile, file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + +@@ -2214,7 +2247,7 @@ static void ffs_data_closed(struct ffs_data *ffs) + + if (epfiles) + ffs_epfiles_destroy(ffs->sb, epfiles, +- ffs->eps_count); ++ ffs->epfiles_count); + + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); +@@ -2273,7 +2306,7 @@ static void ffs_data_clear(struct ffs_data *ffs) + * copy of epfile will save us from use-after-free. + */ + if (epfiles) { +- ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); ++ ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); + ffs->epfiles = NULL; + } + +@@ -2371,11 +2404,16 @@ static void functionfs_unbind(struct ffs_data *ffs) + static int ffs_epfiles_create(struct ffs_data *ffs) + { + struct ffs_epfile *epfile, *epfiles; +- unsigned i, count; ++ unsigned int i, count, epfiles_count; + int err; + + count = ffs->eps_count; +- epfiles = kzalloc_objs(*epfiles, count); ++ epfiles_count = count; ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) ++ epfiles_count += count / 2; ++ ffs->epfiles_count = epfiles_count; ++ ++ epfiles = kzalloc_objs(*epfiles, epfiles_count); + if (!epfiles) + return -ENOMEM; + +@@ -2398,6 +2436,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs) + } + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ struct ffs_epfile *comp = epfiles + count; ++ ++ for (i = 0; i < count; i += 2, ++comp) { ++ struct ffs_epfile *ep1 = &epfiles[i]; ++ struct ffs_epfile *ep2 = &epfiles[i + 1]; ++ bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; ++ ++ comp->ffs = ffs; ++ comp->is_rw_proxy = true; ++ comp->epfile_in = ep1_in ? ep1 : ep2; ++ comp->epfile_out = ep1_in ? ep2 : ep1; ++ mutex_init(&comp->mutex); ++ mutex_init(&comp->dmabufs_mutex); ++ INIT_LIST_HEAD(&comp->dmabufs); ++ snprintf(comp->name, sizeof(comp->name), "%s_rw", ++ epfiles[i].name); ++ err = ffs_sb_create_file(ffs->sb, comp->name, ++ comp, &ffs_epfile_operations); ++ if (err) { ++ ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); ++ return err; ++ } ++ } ++ } ++ + ffs->epfiles = epfiles; + return 0; + } +@@ -2975,7 +3039,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + FUNCTIONFS_VIRTUAL_ADDR | + FUNCTIONFS_EVENTFD | + FUNCTIONFS_ALL_CTRL_RECIP | +- FUNCTIONFS_CONFIG0_SETUP)) { ++ FUNCTIONFS_CONFIG0_SETUP | ++ FUNCTIONFS_RW_PROXY_EPS)) { + ret = -ENOSYS; + goto error; + } +@@ -3063,6 +3128,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, + goto error; + } + ++ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { ++ if (ffs->eps_count % 2) { ++ ret = -EINVAL; ++ goto error; ++ } ++ ++ for (i = 1; i < ffs->eps_count; i += 2) { ++ if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == ++ (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { ++ ret = -EINVAL; ++ goto error; ++ } ++ } ++ } ++ + ffs->raw_descs_data = _data; + ffs->raw_descs = raw_descs; + ffs->raw_descs_length = data - raw_descs; +@@ -3358,7 +3438,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, + struct usb_request *req; + struct usb_ep *ep; + u8 bEndpointAddress; +- u16 wMaxPacketSize; ++ __le16 wMaxPacketSize; + + /* + * We back up bEndpointAddress because autoconfig overwrites +diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c +--- a/drivers/usb/gadget/function/f_mass_storage.c ++++ b/drivers/usb/gadget/function/f_mass_storage.c +@@ -203,7 +203,6 @@ + /*------------------------------------------------------------------------*/ + + #define FSG_DRIVER_DESC "Mass Storage Function" +-#define FSG_DRIVER_VERSION "2009/09/11" + + static const char fsg_string_interface[] = "Mass Storage"; + +@@ -3516,8 +3515,6 @@ static struct usb_function_instance *fsg_alloc_inst(void) + if (rc) + goto release_common; + +- pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); +- + memset(&config, 0, sizeof(config)); + config.removable = true; + rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0", +diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c +--- a/drivers/usb/gadget/function/u_audio.c ++++ b/drivers/usb/gadget/function/u_audio.c +@@ -641,15 +641,15 @@ int u_audio_start_capture(struct g_audio *audio_dev) + ret = config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + if (ret < 0) { + dev_err(dev, "config_ep_by_speed in_ep_fback failed (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } + +- prm->fb_ep_enabled = true; + ret = usb_ep_enable(ep_fback); + if (ret < 0) { + dev_err(dev, "usb_ep_enable failed for in_ep_fback (%d)\n", ret); +- return ret; // TODO: Clean up out_ep ++ goto err_out_ep; + } ++ prm->fb_ep_enabled = true; + req_len = ep_fback->maxpacket; + + req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); +@@ -680,6 +680,12 @@ int u_audio_start_capture(struct g_audio *audio_dev) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + + return 0; ++ ++err_out_ep: ++ set_active(prm, false); ++ free_ep(prm, ep); ++ ++ return ret; + } + EXPORT_SYMBOL_GPL(u_audio_start_capture); + +diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h +--- a/drivers/usb/gadget/function/u_fs.h ++++ b/drivers/usb/gadget/function/u_fs.h +@@ -252,8 +252,14 @@ struct ffs_data { + + unsigned short strings_count; + unsigned short interfaces_count; ++ ++ /* ++ * eps_count tracks the number of underlying hardware endpoints. ++ * epfiles_count tracks the total number of VFS endpoint files. ++ * When companion endpoints are active, epfiles_count > eps_count. ++ */ + unsigned short eps_count; +- unsigned short _pad1; ++ unsigned short epfiles_count; + + /* filled by __ffs_data_got_strings() */ + /* ids in stringtabs are set in functionfs_bind() */ +diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c +--- a/drivers/usb/gadget/legacy/inode.c ++++ b/drivers/usb/gadget/legacy/inode.c +@@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) + struct kiocb *iocb = priv->iocb; + size_t ret; + +- kthread_use_mm(mm); +- ret = copy_to_iter(priv->buf, priv->actual, &priv->to); +- kthread_unuse_mm(mm); +- if (!ret) ++ if (mmget_not_zero(mm)) { ++ kthread_use_mm(mm); ++ ret = copy_to_iter(priv->buf, priv->actual, &priv->to); ++ kthread_unuse_mm(mm); ++ mmput(mm); ++ if (!ret) ++ ret = -EFAULT; ++ } else { + ret = -EFAULT; ++ } ++ mmdrop(mm); + + /* completing the iocb can drop the ctx and mm, don't touch mm after */ + iocb->ki_complete(iocb, ret); +@@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) + * complete the aio request immediately. + */ + if (priv->to_free == NULL || unlikely(req->actual == 0)) { ++ mmdrop(priv->mm); + kfree(req->buf); + kfree(priv->to_free); + kfree(priv); +@@ -541,6 +548,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + priv->epdata = epdata; + priv->actual = 0; + priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ ++ mmgrab(priv->mm); + + /* each kiocb is coupled to one usb_request, but we can't + * allocate or submit those if the host disconnected. +@@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, + + fail: + spin_unlock_irq(&epdata->dev->lock); ++ mmdrop(priv->mm); + kfree(priv->to_free); + kfree(priv); + put_ep(epdata); +diff --git a/drivers/usb/gadget/legacy/nokia.c b/drivers/usb/gadget/legacy/nokia.c +--- a/drivers/usb/gadget/legacy/nokia.c ++++ b/drivers/usb/gadget/legacy/nokia.c +@@ -50,7 +50,7 @@ static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; + */ + #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS + +-#endif /* CONFIG_USB_DEBUG */ ++#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + + FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); + +diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig +--- a/drivers/usb/gadget/udc/Kconfig ++++ b/drivers/usb/gadget/udc/Kconfig +@@ -90,7 +90,7 @@ config USB_BCM63XX_UDC + + config USB_FSL_USB2 + tristate "Freescale Highspeed USB DR Peripheral Controller" +- depends on FSL_SOC ++ depends on FSL_SOC || COMPILE_TEST + help + Some of Freescale PowerPC and i.MX processors have a High Speed + Dual-Role(DR) USB controller, which supports device mode. +diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c +--- a/drivers/usb/gadget/udc/fsl_qe_udc.c ++++ b/drivers/usb/gadget/udc/fsl_qe_udc.c +@@ -1945,6 +1945,7 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + u16 index, u16 length) + { + u16 usb_status = 0; ++ struct usb_request *usb_req; + struct qe_req *req; + struct qe_ep *ep; + int status = 0; +@@ -1983,8 +1984,11 @@ static void ch9getstatus(struct qe_udc *udc, u8 request_type, u16 value, + } + } + +- req = container_of(qe_alloc_request(&ep->ep, GFP_KERNEL), +- struct qe_req, req); ++ usb_req = qe_alloc_request(&ep->ep, GFP_KERNEL); ++ if (!usb_req) ++ goto stall; ++ ++ req = container_of(usb_req, struct qe_req, req); + req->req.length = 2; + req->req.buf = udc->statusbuf; + *(u16 *)req->req.buf = cpu_to_le16(usb_status); +diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c +--- a/drivers/usb/gadget/udc/pch_udc.c ++++ b/drivers/usb/gadget/udc/pch_udc.c +@@ -320,7 +320,6 @@ struct pch_vbus_gpio_data { + * @lock: protects all state + * @stall: stall requested + * @prot_stall: protcol stall requested +- * @registered: driver registered with system + * @suspended: driver in suspended state + * @connected: gadget driver associated + * @vbus_session: required vbus_session state +diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c +--- a/drivers/usb/host/fsl-mph-dr-of.c ++++ b/drivers/usb/host/fsl-mph-dr-of.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + + struct fsl_usb2_dev_data { + char *dr_mode; /* controller mode */ +@@ -183,7 +184,7 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + const struct of_device_id *match; + const unsigned char *prop; + static unsigned int idx; +- int i; ++ int i, err; + + if (!of_device_is_available(np)) + return -ENODEV; +@@ -246,6 +247,10 @@ static int fsl_usb2_mph_dr_of_probe(struct platform_device *ofdev) + } + } + ++ err = devm_regulator_get_enable_optional(&ofdev->dev, "vbus"); ++ if (err) ++ return dev_err_probe(&ofdev->dev, err, "failed to get vbus regulator\n"); ++ + for (i = 0; i < ARRAY_SIZE(dev_data->drivers); i++) { + if (!dev_data->drivers[i]) + continue; +diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c +--- a/drivers/usb/host/ohci-dbg.c ++++ b/drivers/usb/host/ohci-dbg.c +@@ -683,7 +683,7 @@ static int fill_buffer(struct debug_buffer *buf) + int ret; + + if (!buf->page) +- buf->page = (char *)get_zeroed_page(GFP_KERNEL); ++ buf->page = kzalloc(PAGE_SIZE, GFP_KERNEL); + + if (!buf->page) { + ret = -ENOMEM; +@@ -729,11 +729,8 @@ static int debug_close(struct inode *inode, struct file *file) + { + struct debug_buffer *buf = file->private_data; + +- if (buf) { +- if (buf->page) +- free_page((unsigned long)buf->page); +- kfree(buf); +- } ++ kfree(buf->page); ++ kfree(buf); + + return 0; + } +diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h +--- a/drivers/usb/host/xhci-caps.h ++++ b/drivers/usb/host/xhci-caps.h +@@ -5,22 +5,23 @@ + */ + + #include ++#include + + /* hc_capbase - bitmasks */ + /* bits 7:0 - Capability Registers Length */ +-#define HC_LENGTH(p) ((p) & 0xff) ++#define HC_LENGTH GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - Host Controller Interface Version Number */ +-#define HC_VERSION(p) (((p) >> 16) & 0xffff) ++#define HC_VERSION GENMASK(31, 16) + + /* HCSPARAMS1 - hcs_params1 - bitmasks */ + /* bits 7:0 - Number of Device Slots */ +-#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +-#define HCS_SLOTS_MASK 0xff +-/* bits 18:8 - Number of Interrupters, max values is 1024 */ +-#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +-/* bits 31:24, Max Ports - max value is 255 */ +-#define HCS_MAX_PORTS(p) (((p) >> 24) & 0xff) ++#define HCS_SLOTS_MASK GENMASK(7, 0) ++/* bits 18:8 - Number of Interrupters, max values is 1024 */ ++#define HCS_MAX_INTRS GENMASK(18, 8) ++/* bits 23:19 - Rsvd */ ++/* bits 31:24 - Max Ports, max values is 255 */ ++#define HCS_MAX_PORTS GENMASK(31, 24) + + /* HCSPARAMS2 - hcs_params2 - bitmasks */ + /* +@@ -33,24 +34,25 @@ + * Note: 1 Frame = 8 Microframes + * xHCI specification section 5.3.4. + */ +-#define HCS_IST_VALUE(p) ((p) & 0x7) ++#define HCS_IST_VALUE GENMASK(2, 0) + #define HCS_IST_UNIT BIT(3) + /* bits 7:4 - Event Ring Segment Table Max, 2^(n) */ +-#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) ++#define HCS_ERST_MAX GENMASK(7, 4) + /* bits 20:8 - Rsvd */ + /* bits 25:21 - Max Scratchpad Buffers (Hi), 5 Most significant bits */ +-#define HCS_MAX_SP_HI(p) (((p) >> 21) & 0x1f) ++#define HCS_MAX_SP_HI GENMASK(25, 21) + /* bit 26 - Scratchpad restore, for save/restore HW state */ + /* bits 31:27 - Max Scratchpad Buffers (Lo), 5 Least significant bits */ +-#define HCS_MAX_SP_LO(p) (((p) >> 27) & 0x1f) +-#define HCS_MAX_SCRATCHPAD(p) (HCS_MAX_SP_HI(p) << 5 | HCS_MAX_SP_LO(p)) ++#define HCS_MAX_SP_LO GENMASK(31, 27) ++#define HCS_MAX_SCRATCHPAD(p) (FIELD_GET(HCS_MAX_SP_HI, (p)) << 5 | \ ++ FIELD_GET(HCS_MAX_SP_LO, (p))) + + /* HCSPARAMS3 - hcs_params3 - bitmasks */ + /* bits 7:0 - U1 Device Exit Latency, Max U1 to U0 latency for the roothub ports */ +-#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) ++#define HCS_U1_LATENCY GENMASK(7, 0) + /* bits 15:8 - Rsvd */ + /* bits 31:16 - U2 Device Exit Latency, Max U2 to U0 latency for the roothub ports */ +-#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) ++#define HCS_U2_LATENCY GENMASK(31, 16) + + /* HCCPARAMS1 - hcc_params - bitmasks */ + /* bit 0 - 64-bit Addressing Capability */ +@@ -77,19 +79,20 @@ + /* bit 11 - Contiguous Frame ID Capability */ + #define HCC_CFC BIT(11) + /* bits 15:12 - Max size for Primary Stream Arrays, 2^(n+1) */ +-#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) ++#define HCC_MAX_PSA GENMASK(15, 12) ++#define GET_MAX_PSA_SIZE(p) (1 << (FIELD_GET(HCC_MAX_PSA, (p)) + 1)) + /* bits 31:16 - xHCI Extended Capabilities Pointer, from PCI base: 2^(n) */ +-#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff) ++#define HCC_EXT_CAPS GENMASK(31, 16) + + /* DBOFF - db_off - bitmasks */ + /* bits 1:0 - Rsvd */ + /* bits 31:2 - Doorbell Array Offset */ +-#define DBOFF_MASK (0xfffffffc) ++#define DBOFF_MASK GENMASK(31, 2) + + /* RTSOFF - run_regs_off - bitmasks */ + /* bits 4:0 - Rsvd */ + /* bits 31:5 - Runtime Register Space Offse */ +-#define RTSOFF_MASK (~0x1f) ++#define RTSOFF_MASK GENMASK(31, 5) + + /* HCCPARAMS2 - hcc_params2 - bitmasks */ + /* bit 0 - U3 Entry Capability */ +diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c +--- a/drivers/usb/host/xhci-dbgtty.c ++++ b/drivers/usb/host/xhci-dbgtty.c +@@ -665,11 +665,11 @@ int dbc_tty_init(void) + + void dbc_tty_exit(void) + { +- if (dbc_tty_driver) { +- tty_unregister_driver(dbc_tty_driver); +- tty_driver_kref_put(dbc_tty_driver); +- dbc_tty_driver = NULL; +- } ++ if (IS_ERR_OR_NULL(dbc_tty_driver)) ++ return; + ++ tty_unregister_driver(dbc_tty_driver); ++ tty_driver_kref_put(dbc_tty_driver); + idr_destroy(&dbc_tty_minors); ++ dbc_tty_driver = NULL; + } +diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c +--- a/drivers/usb/host/xhci-debugfs.c ++++ b/drivers/usb/host/xhci-debugfs.c +@@ -9,6 +9,7 @@ + + #include + #include ++#include + + #include "xhci.h" + #include "xhci-debugfs.h" +@@ -791,7 +792,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) + xhci->debugfs_root, "reg-cap"); + + xhci_debugfs_regset(xhci, +- HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)), ++ FIELD_GET(HC_LENGTH, readl(&xhci->cap_regs->hc_capbase)), + xhci_op_regs, ARRAY_SIZE(xhci_op_regs), + xhci->debugfs_root, "reg-op"); + +diff --git a/drivers/usb/host/xhci-histb.c b/drivers/usb/host/xhci-histb.c +--- a/drivers/usb/host/xhci-histb.c ++++ b/drivers/usb/host/xhci-histb.c +@@ -276,7 +276,7 @@ static int xhci_histb_probe(struct platform_device *pdev) + if (ret) + goto put_usb3_hcd; + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c +--- a/drivers/usb/host/xhci-hub.c ++++ b/drivers/usb/host/xhci-hub.c +@@ -115,8 +115,8 @@ static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, + + if ((xhci->quirks & XHCI_LPM_SUPPORT)) { + reg = readl(&xhci->cap_regs->hcs_params3); +- ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg); +- ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg)); ++ ss_cap->bU1devExitLat = FIELD_GET(HCS_U1_LATENCY, reg); ++ ss_cap->bU2DevExitLat = cpu_to_le16(FIELD_GET(HCS_U2_LATENCY, reg)); + } + + if (wLength < le16_to_cpu(bos->wTotalLength)) +@@ -1295,7 +1295,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + } + /* In spec software should not attempt to suspend + * a port unless the port reports that it is in the +- * enabled (PED = ‘1’,PLS < ‘3’) state. ++ * enabled (PED = '1',PLS < '3') state. + */ + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || +diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1001,7 +1002,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, + for (i = 0; i < 31; i++) { + dev->eps[i].ep_index = i; + dev->eps[i].vdev = dev; +- dev->eps[i].xhci = xhci; + INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); + INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); + } +@@ -1493,6 +1493,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, + return -ENOMEM; + + virt_dev->eps[ep_index].skip = false; ++ virt_dev->eps[ep_index].next_uframe = -1; + ep_ring = virt_dev->eps[ep_index].new_ring; + xhci_ring_init(xhci, ep_ring); + +@@ -2083,7 +2084,7 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, + addr, port_offset, port_count, major_revision); + /* Port count includes the current port offset */ + if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) +- /* WTF? "Valid values are ‘1’ to MaxPorts" */ ++ /* WTF? "Valid values are '1' to MaxPorts" */ + return; + + port_cap = &xhci->port_caps[xhci->num_port_caps++]; +@@ -2300,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) + if (!segs) + segs = ERST_DEFAULT_SEGS; + +- max_segs = BIT(HCS_ERST_MAX(xhci->hcs_params2)); ++ max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c +--- a/drivers/usb/host/xhci-mtk.c ++++ b/drivers/usb/host/xhci-mtk.c +@@ -468,7 +468,7 @@ static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) + * MTK xHCI 0.96: PSA is 1 by default even if doesn't support stream, + * and it's 3 when support it. + */ +- if (xhci->hci_version < 0x100 && HCC_MAX_PSA(xhci->hcc_params) == 4) ++ if (xhci->hci_version < 0x100 && GET_MAX_PSA_SIZE(xhci->hcc_params) == 4) + xhci->quirks |= XHCI_BROKEN_STREAMS; + } + +@@ -650,7 +650,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c +--- a/drivers/usb/host/xhci-pci.c ++++ b/drivers/usb/host/xhci-pci.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -662,7 +663,8 @@ int xhci_pci_common_probe(struct pci_dev *dev, const struct pci_device_id *id) + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (usb3_hcd && !(xhci->quirks & XHCI_BROKEN_STREAMS) && ++ GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; + + /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ +diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c +--- a/drivers/usb/host/xhci-plat.c ++++ b/drivers/usb/host/xhci-plat.c +@@ -340,7 +340,7 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s + } + + usb3_hcd = xhci_get_usb3_hcd(xhci); +- if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4 && ++ if (usb3_hcd && GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4 && + !(xhci->quirks & XHCI_BROKEN_STREAMS)) + usb3_hcd->can_do_streams = 1; + +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -57,6 +57,8 @@ + #include + #include + #include ++#include ++ + #include "xhci.h" + #include "xhci-trace.h" + +@@ -561,8 +563,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + * pointer command pending because the device can choose to start any + * stream once the endpoint is on the HW schedule. + */ +- if ((ep_state & EP_STOP_CMD_PENDING) || (ep_state & SET_DEQ_PENDING) || +- (ep_state & EP_HALTED) || (ep_state & EP_CLEARING_TT)) ++ if (ep_state & (EP_STOP_CMD_PENDING | SET_DEQ_PENDING | EP_HALTED | ++ EP_CLEARING_TT | EP_DROP_PENDING)) + return; + + trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id)); +@@ -573,9 +575,8 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, + } + + /* Ring the doorbell for any rings with pending URBs */ +-static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) ++void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, unsigned int slot_id, ++ unsigned int ep_index) + { + unsigned int stream_id; + struct xhci_virt_ep *ep; +@@ -598,13 +599,6 @@ static void ring_doorbell_for_active_rings(struct xhci_hcd *xhci, + } + } + +-void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, +- unsigned int slot_id, +- unsigned int ep_index) +-{ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); +-} +- + static struct xhci_virt_ep *xhci_get_virt_ep(struct xhci_hcd *xhci, + unsigned int slot_id, + unsigned int ep_index) +@@ -686,112 +680,100 @@ static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, + return le64_to_cpu(ep_ctx->deq); + } + +-static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, +- unsigned int slot_id, unsigned int ep_index, +- unsigned int stream_id, struct xhci_td *td) ++/* ++ * Move the endpoint dequeue pointer to the next queued TD on ring->td_list or ++ * to enqueue if no TDs are queued (empty ring) ++ * All cancelled TDs on ring->td_list should be moved to ep->cancelled_td_list ++ * before calling this function ++ */ ++static int xhci_move_deq_to_next_td(struct xhci_hcd *xhci, ++ struct xhci_virt_ep *ep, ++ unsigned int stream_id) + { +- struct xhci_virt_device *dev = xhci->devs[slot_id]; +- struct xhci_virt_ep *ep = &dev->eps[ep_index]; +- struct xhci_ring *ep_ring; + struct xhci_command *cmd; +- struct xhci_segment *new_seg; +- union xhci_trb *new_deq; +- int new_cycle; ++ struct xhci_ring *ring; ++ struct xhci_td *td; + dma_addr_t addr; +- u64 hw_dequeue; +- bool hw_dequeue_found = false; +- bool td_last_trb_found = false; ++ int new_cycle; + u32 trb_sct = 0; +- int ret; ++ int ret = 0; + +- ep_ring = xhci_triad_to_transfer_ring(xhci, slot_id, +- ep_index, stream_id); +- if (!ep_ring) { ++ ring = xhci_virt_ep_to_ring(xhci, ep, stream_id); ++ if (!ring) { + xhci_warn(xhci, "WARN can't find new dequeue, invalid stream ID %u\n", + stream_id); + return -ENODEV; + } + +- hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id) & TR_DEQ_PTR_MASK; +- new_seg = ep_ring->deq_seg; +- new_deq = ep_ring->dequeue; +- new_cycle = le32_to_cpu(td->end_trb->generic.field[3]) & TRB_CYCLE; +- +- /* +- * Walk the ring until both the next TRB and hw_dequeue are found (don't +- * move hw_dequeue back if it went forward due to a HW bug). Cycle state +- * is loaded from a known good TRB, track later toggles to maintain it. +- */ +- do { +- if (!hw_dequeue_found && xhci_trb_virt_to_dma(new_seg, new_deq) +- == (dma_addr_t)hw_dequeue) { +- hw_dequeue_found = true; +- if (td_last_trb_found) +- break; +- } +- if (new_deq == td->end_trb) +- td_last_trb_found = true; +- +- if (td_last_trb_found && trb_is_link(new_deq) && +- link_trb_toggles_cycle(new_deq)) +- new_cycle ^= 0x1; +- +- next_trb(&new_seg, &new_deq); +- +- /* Search wrapped around, bail out */ +- if (new_deq == ep->ring->dequeue) { +- xhci_err(xhci, "Error: Failed finding new dequeue state\n"); +- return -EINVAL; +- } +- +- } while (!hw_dequeue_found || !td_last_trb_found); +- +- /* Don't update the ring cycle state for the producer (us). */ +- addr = xhci_trb_virt_to_dma(new_seg, new_deq); +- if (addr == 0) { +- xhci_warn(xhci, "Can't find dma of new dequeue ptr\n"); +- xhci_warn(xhci, "deq seg = %p, deq ptr = %p\n", new_seg, new_deq); +- return -EINVAL; +- } +- + if ((ep->ep_state & SET_DEQ_PENDING)) { +- xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n", +- &addr); ++ xhci_warn(xhci, "Set TR Deq already pending\n"); + return -EBUSY; + } + + /* This function gets called from contexts where it cannot sleep */ + cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); + if (!cmd) { +- xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr); ++ xhci_warn(xhci, "Can't alloc Set TR Deq cmd\n"); + return -ENOMEM; + } + ++ /* ++ * Move dequeue to the beginning of next td, or to enqueue if ring is ++ * empty. Avoid moving dequeue to a link trb (empty ring) as it causes ++ * issues on some hosts. In that case advance the enqueue to next segment ++ * before moving dequeue to it ++ */ ++ ++ if (list_empty(&ring->td_list)) { ++ if (trb_is_link(ring->enqueue)) ++ inc_enq_past_link(xhci, ring, 0); ++ ep->queued_deq_seg = ring->enq_seg; ++ ep->queued_deq_ptr = ring->enqueue; ++ new_cycle = ring->cycle_state; ++ } else { ++ td = list_first_entry(&ring->td_list, struct xhci_td, td_list); ++ ep->queued_deq_seg = td->start_seg; ++ ep->queued_deq_ptr = td->start_trb; ++ new_cycle = le32_to_cpu(td->start_trb->generic.field[3]) & TRB_CYCLE; ++ } ++ ++ addr = xhci_trb_virt_to_dma(ep->queued_deq_seg, ep->queued_deq_ptr); ++ if (addr == 0) { ++ xhci_warn(xhci, "Can't find new dequeue dma of seg %p, ptr %p\n", ++ ep->queued_deq_seg, ep->queued_deq_ptr); ++ ret = -EINVAL; ++ goto err_out; ++ } ++ + if (stream_id) + trb_sct = SCT_FOR_TRB(SCT_PRI_TR); + ret = queue_command(xhci, cmd, + lower_32_bits(addr) | trb_sct | new_cycle, + upper_32_bits(addr), +- STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(slot_id) | +- EP_INDEX_FOR_TRB(ep_index) | TRB_TYPE(TRB_SET_DEQ), false); +- if (ret < 0) { +- xhci_free_command(xhci, cmd); +- return ret; +- } +- ep->queued_deq_seg = new_seg; +- ep->queued_deq_ptr = new_deq; ++ STREAM_ID_FOR_TRB(stream_id), SLOT_ID_FOR_TRB(ep->vdev->slot_id) | ++ EP_INDEX_FOR_TRB(ep->ep_index) | TRB_TYPE(TRB_SET_DEQ), false); ++ if (ret < 0) ++ goto err_out; + + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle); + +- /* Stop the TD queueing code from ringing the doorbell until +- * this command completes. The HC won't set the dequeue pointer +- * if the ring is running, and ringing the doorbell starts the +- * ring running. ++ /* ++ * Stop the TD queueing code from ringing the doorbell until this ++ * command completes. The HC won't set the dequeue pointer if the ring ++ * is running, and ringing the doorbell starts the ring. + */ + ep->ep_state |= SET_DEQ_PENDING; + xhci_ring_cmd_db(xhci); ++ + return 0; ++ ++err_out: ++ xhci_free_command(xhci, cmd); ++ ep->queued_deq_seg = NULL; ++ ep->queued_deq_ptr = NULL; ++ ++ return ret; + } + + /* flip_cycle means flip the cycle bit of all but the first and last TRB. +@@ -950,7 +932,7 @@ static void xhci_dequeue_td(struct xhci_hcd *xhci, struct xhci_td *td, struct xh + } + + /* Complete the cancelled URBs we unlinked from td_list. */ +-static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) ++static void xhci_giveback_invalidated_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_ring *ring; + struct xhci_td *td, *tmp_td; +@@ -958,17 +940,17 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { + +- ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ring = xhci_urb_to_transfer_ring(xhci, td->urb); + + if (td->cancel_status == TD_CLEARED) { +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ring, td->status); ++ xhci_td_cleanup(xhci, td, ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } +- if (ep->xhci->xhc_state & XHCI_STATE_DYING) ++ if (xhci->xhc_state & XHCI_STATE_DYING) + return; + } + } +@@ -1002,15 +984,19 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + struct xhci_td *td, + enum xhci_ep_reset_type reset_type) + { ++ struct xhci_port *rhub_port = ep->vdev->rhub_port; + unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +- * Avoid resetting endpoint if link is inactive. Can cause host hang. +- * Device will be reset soon to recover the link so don't do anything ++ * Avoid resetting endpoint if link is inactive or device disonnected. ++ * Can cause host hang. ++ * Device will be reset to recover an inactive link, so don't do anything + */ +- if (ep->vdev->flags & VDEV_PORT_ERROR) ++ if (rhub_port->link_inactive || !rhub_port->connected) { ++ ep->ep_state |= EP_DROP_PENDING; + return -ENODEV; ++ } + + /* add td to cancelled list and let reset ep handler take care of it */ + if (reset_type == EP_HARD_RESET) { +@@ -1047,15 +1033,13 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, + * only call this when ring is not in a running state + */ + +-static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) ++static int xhci_invalidate_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- struct xhci_hcd *xhci; + struct xhci_td *td = NULL; + struct xhci_td *tmp_td = NULL; + struct xhci_td *cached_td = NULL; + struct xhci_ring *ring; + u64 hw_deq; +- unsigned int slot_id = ep->vdev->slot_id; + int err; + + /* +@@ -1065,8 +1049,6 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (ep->ep_state & SET_DEQ_PENDING) + return 0; + +- xhci = ep->xhci; +- + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, + "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", +@@ -1080,6 +1062,13 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + td->urb, td->urb->stream_id); + continue; + } ++ ++ /* device disconnected or link error, ep will be dropped */ ++ if (ep->ep_state & EP_DROP_PENDING) { ++ td->cancel_status = TD_CLEARED; ++ continue; ++ } ++ + /* + * If a ring stopped on the TD we need to cancel then we have to + * move the xHC endpoint ring dequeue pointer past this TD. +@@ -1131,9 +1120,8 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + if (!cached_td) + return 0; + +- err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, +- cached_td->urb->stream_id, +- cached_td); ++ err = xhci_move_deq_to_next_td(xhci, ep, cached_td->urb->stream_id); ++ + if (err) { + /* Failed to move past cached td, just set cached TDs to no-op */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { +@@ -1161,23 +1149,23 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) + * + * Call under xhci->lock on a stopped endpoint. + */ +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep) ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { +- xhci_invalidate_cancelled_tds(ep); +- xhci_giveback_invalidated_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } + + /* + * Returns the TD the endpoint ring halted on. + * Only call for non-running rings without streams. + */ +-static struct xhci_td *find_halted_td(struct xhci_virt_ep *ep) ++static struct xhci_td *find_halted_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep) + { + struct xhci_td *td; + u64 hw_deq; + + if (!list_empty(&ep->ring->td_list)) { /* Not streams compatible */ +- hw_deq = xhci_get_hw_deq(ep->xhci, ep->vdev, ep->ep_index, 0); ++ hw_deq = xhci_get_hw_deq(xhci, ep->vdev, ep->ep_index, 0); + hw_deq &= TR_DEQ_PTR_MASK; + td = list_first_entry(&ep->ring->td_list, struct xhci_td, td_list); + if (trb_in_td(td, hw_deq)) +@@ -1252,7 +1240,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + reset_type = EP_SOFT_RESET; + } else { + reset_type = EP_HARD_RESET; +- td = find_halted_td(ep); ++ td = find_halted_td(xhci, ep); + if (td) + td->status = -EPROTO; + } +@@ -1310,13 +1298,17 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, + } + } + ++ /* link is inactive or disconnected, ep is not running and shouldn't be restarted */ ++ if (ep->vdev->rhub_port->link_inactive || !ep->vdev->rhub_port->connected) ++ ep->ep_state |= EP_DROP_PENDING; ++ + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + ep->ep_state &= ~EP_STOP_CMD_PENDING; + + /* Otherwise ring the doorbell(s) to restart queued transfers */ +- xhci_giveback_invalidated_tds(ep); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_giveback_invalidated_tds(xhci, ep); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) +@@ -1537,14 +1529,14 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + /* HW cached TDs cleared from cache, give them back */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, + cancelled_td_list) { +- ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); ++ ep_ring = xhci_urb_to_transfer_ring(xhci, td->urb); + if (td->cancel_status == TD_CLEARING_CACHE) { + td->cancel_status = TD_CLEARED; +- xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", ++ xhci_dbg(xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); +- xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); ++ xhci_td_cleanup(xhci, td, ep_ring, td->status); + } else { +- xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", ++ xhci_dbg(xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } + } +@@ -1555,17 +1547,17 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, + + /* Check for deferred or newly cancelled TDs */ + if (!list_empty(&ep->cancelled_td_list)) { +- xhci_dbg(ep->xhci, "%s: Pending TDs to clear, continuing with invalidation\n", ++ xhci_dbg(xhci, "%s: Pending TDs to clear, continuing with invalidation\n", + __func__); +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + /* Try to restart the endpoint if all is done */ +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + /* Start giving back any TDs invalidated above */ +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + } else { + /* Restart any rings with pending URBs */ +- xhci_dbg(ep->xhci, "%s: All TDs cleared, ring doorbell\n", __func__); +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_dbg(xhci, "%s: All TDs cleared, ring doorbell\n", __func__); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + } + +@@ -1591,16 +1583,16 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, + "Ignoring reset ep completion code of %u", cmd_comp_code); + + /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ +- xhci_invalidate_cancelled_tds(ep); ++ xhci_invalidate_cancelled_tds(xhci, ep); + + /* Clear our internal halted state */ + ep->ep_state &= ~EP_HALTED; + +- xhci_giveback_invalidated_tds(ep); ++ xhci_giveback_invalidated_tds(xhci, ep); + + /* if this was a soft reset, then restart */ + if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) +- ring_doorbell_for_active_rings(xhci, slot_id, ep_index); ++ xhci_ring_doorbell_for_active_rings(xhci, slot_id, ep_index); + } + + static void xhci_handle_cmd_enable_slot(int slot_id, struct xhci_command *command, +@@ -2008,13 +2000,15 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) + static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + { + struct xhci_virt_device *vdev = NULL; +- struct usb_hcd *hcd; +- u32 port_id; +- u32 portsc, cmd_reg; +- unsigned int hcd_portnum; + struct xhci_bus_state *bus_state; +- bool bogus_port_status = false; + struct xhci_port *port; ++ struct usb_hcd *hcd; ++ bool bogus_port_status = false; ++ unsigned int hcd_portnum; ++ u32 cmd_reg; ++ u32 port_id; ++ u32 portsc; ++ u32 pls; + + /* Port status change events always have a successful completion code */ + if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS) +@@ -2051,6 +2045,7 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + bus_state = &port->rhub->bus_state; + hcd_portnum = port->hcd_portnum; + portsc = xhci_portsc_readl(port); ++ pls = portsc & PORT_PLS_MASK; + + xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n", + hcd->self.busnum, hcd_portnum + 1, port_id, portsc); +@@ -2062,11 +2057,13 @@ static void handle_port_status(struct xhci_hcd *xhci, union xhci_trb *event) + usb_hcd_resume_root_hub(hcd); + } + +- if (vdev && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) { +- if (!(portsc & PORT_RESET)) +- vdev->flags |= VDEV_PORT_ERROR; +- } else if (vdev && portsc & PORT_RC) { +- vdev->flags &= ~VDEV_PORT_ERROR; ++ /* ++ * Tag broken links to avoid retries while hub driver sorts it out. ++ * Link status is not relible while port is in reset. ++ */ ++ if (!(portsc & PORT_RESET)) { ++ port->link_inactive = (pls == XDEV_INACTIVE); ++ port->connected = !!(portsc & PORT_CONNECT); + } + + if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { +@@ -3978,88 +3975,102 @@ static unsigned int xhci_get_last_burst_packet_count(struct xhci_hcd *xhci, + /* Returns the Isochronous Scheduling Threshold in Microframes. 1 Frame is 8 Microframes. */ + static int xhci_ist_microframes(struct xhci_hcd *xhci) + { +- int ist = HCS_IST_VALUE(xhci->hcs_params2); ++ int ist = FIELD_GET(HCS_IST_VALUE, xhci->hcs_params2); + + if (xhci->hcs_params2 & HCS_IST_UNIT) + ist *= 8; + return ist; + } + +-/* +- * Calculates Frame ID field of the isochronous TRB identifies the +- * target frame that the Interval associated with this Isochronous +- * Transfer Descriptor will start on. Refer to 4.11.2.5 in 1.1 spec. +- * +- * Returns actual frame id on success, negative value on error. +- */ +-static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, +- struct urb *urb, int index) ++ ++static bool xhci_isoc_td_uses_frame_id(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep, int i) + { +- int start_frame, ist, ret = 0; +- int start_frame_id, end_frame_id, current_frame_id; ++ if (urb->transfer_flags & URB_ISO_ASAP) ++ return false; + +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- start_frame = urb->start_frame + index * urb->interval; +- else +- start_frame = (urb->start_frame + index * urb->interval) >> 3; ++ if (xhci->hcc_params & HCC_CFC) ++ return true; + ++ /* set frame id for first TD of first URB in stream */ ++ if (ep->next_uframe == -1 && i == 0) ++ return true; ++ ++ return false; ++} ++ ++/* ++ * Check if frame is in the valid frame window, including start and end. ++ * If start > end then assume window wrapped around at a limit the frame ++ * value won't exceed. ++ */ ++static bool xhci_frame_in_range(u32 frame, u32 start, u32 end) ++{ ++ /* frame window end wrapped around */ ++ if (start > end) ++ return frame >= start || frame <= end; ++ ++ return frame >= start && frame <= end; ++} ++ ++/* ++ * Set the urb->start_frame of the URB. ++ * ++ * Returns microframe index of first TD ++ */ ++static int xhci_get_isoc_start_frame(struct xhci_hcd *xhci, struct urb *urb, ++ struct xhci_virt_ep *ep) ++{ ++ u32 curr_frame, start_uframe; ++ u32 urb_start, urb_end; ++ u32 win_start, win_end; ++ bool frame_unit; ++ int uinterval; ++ u32 mfindex; ++ int ist; ++ ++ /* check if urb uses frame units instead of microframes */ ++ frame_unit = (urb->dev->speed == USB_SPEED_FULL || ++ urb->dev->speed == USB_SPEED_LOW); ++ ++ uinterval = urb->interval; ++ if (frame_unit) ++ uinterval *= 8; ++ ++ /* get current microframe index and isoc scheduling threshold */ ++ mfindex = readl(&xhci->run_regs->microframe_index); + ist = xhci_ist_microframes(xhci); + +- /* Software shall not schedule an Isoch TD with a Frame ID value that +- * is less than the Start Frame ID or greater than the End Frame ID, +- * where: +- * +- * End Frame ID = (Current MFINDEX register value + 895 ms.) MOD 2048 +- * Start Frame ID = (Current MFINDEX register value + IST + 1) MOD 2048 +- * +- * Both the End Frame ID and Start Frame ID values are calculated +- * in microframes. When software determines the valid Frame ID value; +- * The End Frame ID value should be rounded down to the nearest Frame +- * boundary, and the Start Frame ID value should be rounded up to the +- * nearest Frame boundary. +- */ +- current_frame_id = readl(&xhci->run_regs->microframe_index); +- start_frame_id = roundup(current_frame_id + ist + 1, 8); +- end_frame_id = rounddown(current_frame_id + 895 * 8, 8); ++ /* calculate valid frame window, in frame units, see xhci 4.11.2.5 */ ++ curr_frame = MFINDEX_TO_FRAME(mfindex); ++ win_start = (curr_frame + DIV_ROUND_UP_POW2(ist, 8) + 1) % MAX_FRAMES; ++ win_end = (curr_frame + 895) % MAX_FRAMES; + +- start_frame &= 0x7ff; +- start_frame_id = (start_frame_id >> 3) & 0x7ff; +- end_frame_id = (end_frame_id >> 3) & 0x7ff; +- +- if (start_frame_id < end_frame_id) { +- if (start_frame > end_frame_id || +- start_frame < start_frame_id) +- ret = -EINVAL; +- } else if (start_frame_id > end_frame_id) { +- if ((start_frame > end_frame_id && +- start_frame < start_frame_id)) +- ret = -EINVAL; ++ /* Is this the first URB starting the whole isoc data flow? */ ++ if (ep->next_uframe < 0) { ++ /* align first URB to next interval boundary, or at last to full frame */ ++ start_uframe = mfindex + ist + XHCI_CFC_DELAY; ++ start_uframe = roundup(start_uframe, 8); ++ start_uframe = roundup(start_uframe, uinterval) % MAX_UFRAMES; + } else { +- ret = -EINVAL; +- } ++ /* URB is mid stream and expected to handle the next frame */ ++ start_uframe = ep->next_uframe; ++ urb_start = start_uframe / 8; ++ urb_end = (start_uframe + urb->number_of_packets * uinterval) / 8; ++ urb_end %= MAX_FRAMES; + +- if (index == 0) { +- if (ret == -EINVAL || start_frame == start_frame_id) { +- start_frame = start_frame_id + 1; +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) +- urb->start_frame = start_frame; +- else +- urb->start_frame = start_frame << 3; +- ret = 0; +- } +- } ++ if (!xhci_frame_in_range(urb_start, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for start frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + +- if (ret) { +- xhci_warn(xhci, "Frame ID %d (reg %d, index %d) beyond range (%d, %d)\n", +- start_frame, current_frame_id, index, +- start_frame_id, end_frame_id); +- xhci_warn(xhci, "Ignore frame ID field, use SIA bit instead\n"); +- return ret; ++ if (!xhci_frame_in_range(urb_end, win_start, win_end)) ++ xhci_dbg(xhci, "Ill-timed isoc URB %p for end frame %d, range %d-%d\n", ++ urb, urb_start, win_start, win_end); + } ++ /* set urb->start_frame */ ++ urb->start_frame = frame_unit ? start_uframe / 8 : start_uframe; + +- return start_frame; ++ return start_uframe; + } + + /* Check if we should generate event interrupt for a TD in an isoc URB */ +@@ -4099,7 +4110,8 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + int i, j; + bool more_trbs_coming; + struct xhci_virt_ep *xep; +- int frame_id; ++ int uinterval = urb->interval; ++ int start_uframe; + + xep = &xhci->devs[slot_id]->eps[ep_index]; + ep_ring = xhci->devs[slot_id]->eps[ep_index].ring; +@@ -4115,6 +4127,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + start_cycle = ep_ring->cycle_state; + + urb_priv = urb->hcpriv; ++ ++ if (urb->dev->speed == USB_SPEED_FULL || urb->dev->speed == USB_SPEED_LOW) ++ uinterval = urb->interval * 8; ++ ++ start_uframe = xhci_get_isoc_start_frame(xhci, urb, xep); ++ + /* Queue the TRBs for each TD, even if they are zero-length */ + for (i = 0; i < num_tds; i++) { + unsigned int total_pkt_count, max_pkt; +@@ -4146,14 +4164,16 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + goto cleanup; + } + td = &urb_priv->td[i]; +- /* use SIA as default, if frame id is used overwrite it */ +- sia_frame_id = TRB_SIA; +- if (!(urb->transfer_flags & URB_ISO_ASAP) && +- (xhci->hcc_params & HCC_CFC)) { +- frame_id = xhci_get_isoc_frame_id(xhci, urb, i); +- if (frame_id >= 0) +- sia_frame_id = TRB_FRAME_ID(frame_id); ++ ++ ++ /* Choose SIA or frame ID based scheduling for this TD */ ++ if (xhci_isoc_td_uses_frame_id(xhci, urb, xep, i)) { ++ sia_frame_id = (start_uframe + i * uinterval) / 8; ++ sia_frame_id = TRB_FRAME_ID(sia_frame_id % MAX_FRAMES); ++ } else { ++ sia_frame_id = TRB_SIA; + } ++ + /* + * Set isoc specific data for the first TRB in a TD. + * Prevent HW from getting the TRBs by keeping the cycle state +@@ -4232,9 +4252,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, + } + } + +- /* store the next frame id */ +- if (xhci->hcc_params & HCC_CFC) +- xep->next_frame_id = urb->start_frame + num_tds * urb->interval; ++ xep->next_uframe = (start_uframe + num_tds * uinterval) % MAX_UFRAMES; + + if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs == 0) { + if (xhci->quirks & XHCI_AMD_PLL_FIX) +@@ -4281,11 +4299,9 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + struct xhci_virt_device *xdev; + struct xhci_ring *ep_ring; + struct xhci_ep_ctx *ep_ctx; +- int start_frame; ++ struct xhci_virt_ep *xep; + int num_tds, num_trbs, i; + int ret; +- struct xhci_virt_ep *xep; +- int ist; + + xdev = xhci->devs[slot_id]; + xep = &xhci->devs[slot_id]->eps[ep_index]; +@@ -4311,38 +4327,12 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + */ + check_interval(urb, ep_ctx); + +- /* Calculate the start frame and put it in urb->start_frame. */ +- if ((xhci->hcc_params & HCC_CFC) && !list_empty(&ep_ring->td_list)) { +- if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) { +- urb->start_frame = xep->next_frame_id; +- goto skip_start_over; +- } +- } +- +- start_frame = readl(&xhci->run_regs->microframe_index); +- start_frame &= 0x3fff; + /* +- * Round up to the next frame and consider the time before trb really +- * gets scheduled by hardare. ++ * Check if this starts the isoc data flow. Relies on hw setting ep ctx ++ * state after doorbell ring. Consider adding list_empty(td_list) check + */ +- ist = xhci_ist_microframes(xhci); +- start_frame += ist + XHCI_CFC_DELAY; +- start_frame = roundup(start_frame, 8); +- +- /* +- * Round up to the next ESIT (Endpoint Service Interval Time) if ESIT +- * is greate than 8 microframes. +- */ +- if (urb->dev->speed == USB_SPEED_LOW || +- urb->dev->speed == USB_SPEED_FULL) { +- start_frame = roundup(start_frame, urb->interval << 3); +- urb->start_frame = start_frame >> 3; +- } else { +- start_frame = roundup(start_frame, urb->interval); +- urb->start_frame = start_frame; +- } +- +-skip_start_over: ++ if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ xep->next_uframe = -1; + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } +diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c +--- a/drivers/usb/host/xhci-tegra.c ++++ b/drivers/usb/host/xhci-tegra.c +@@ -6,6 +6,7 @@ + * Copyright (C) 2014 Google, Inc. + */ + ++#include + #include + #include + #include +@@ -993,7 +994,7 @@ static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) + u32 value; + + cap_regs = tegra->regs; +- op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); ++ op_regs = tegra->regs + FIELD_GET(HC_LENGTH, readl(&cap_regs->hc_capbase)), + + ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); + +@@ -1895,7 +1896,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) + goto remove_usb2; + } + +- if (HCC_MAX_PSA(xhci->hcc_params) >= 4) ++ if (GET_MAX_PSA_SIZE(xhci->hcc_params) >= 4) + xhci->shared_hcd->can_do_streams = 1; + + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); +diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + + #include "xhci.h" + #include "xhci-trace.h" +@@ -1664,7 +1665,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag + goto free_priv; + } + +- if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) { ++ if (xhci->devs[slot_id]->rhub_port->link_inactive) { + xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); + ret = -ENODEV; + goto free_priv; +@@ -1852,11 +1853,11 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) + } + + /* In this case no commands are pending but the endpoint is stopped */ +- if (ep->ep_state & EP_CLEARING_TT) { ++ if (ep->ep_state & (EP_CLEARING_TT | EP_DROP_PENDING)) { + /* and cancelled TDs can be given back right away */ + xhci_dbg(xhci, "Invalidating TDs instantly on slot %d ep %d in state 0x%x\n", + urb->dev->slot_id, ep_index, ep->ep_state); +- xhci_process_cancelled_tds(ep); ++ xhci_process_cancelled_tds(xhci, ep); + } else { + /* Otherwise, queue a new Stop Endpoint command */ + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); +@@ -3507,7 +3508,7 @@ static void xhci_calculate_streams_entries(struct xhci_hcd *xhci, + * level page entries), but that's an optional feature for xHCI host + * controllers. xHCs must support at least 4 stream IDs. + */ +- max_streams = HCC_MAX_PSA(xhci->hcc_params); ++ max_streams = GET_MAX_PSA_SIZE(xhci->hcc_params); + if (*num_stream_ctxs > max_streams) { + xhci_dbg(xhci, "xHCI HW only supports %u stream ctx entries.\n", + max_streams); +@@ -3637,7 +3638,7 @@ static int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + + /* MaxPSASize value 0 (2 streams) means streams are not supported */ + if ((xhci->quirks & XHCI_BROKEN_STREAMS) || +- HCC_MAX_PSA(xhci->hcc_params) < 4) { ++ GET_MAX_PSA_SIZE(xhci->hcc_params) < 4) { + xhci_dbg(xhci, "xHCI controller does not support streams.\n"); + return -ENOSYS; + } +@@ -4035,7 +4036,6 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + xhci_get_slot_state(xhci, virt_dev->out_ctx)); + xhci_dbg(xhci, "Not freeing device rings.\n"); + /* Don't treat this as an error. May change my mind later. */ +- virt_dev->flags = 0; + ret = 0; + goto command_cleanup; + case COMP_SUCCESS: +@@ -4087,10 +4087,12 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, + } + /* If necessary, update the number of active TTs on this root port */ + xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); +- virt_dev->flags = 0; + ret = 0; + + command_cleanup: ++ for (i = 0; i < EP_CTX_PER_DEV; i++) ++ virt_dev->eps[i].ep_state &= ~EP_DROP_PENDING; ++ + xhci_free_command(xhci, reset_device_cmd); + return ret; + } +@@ -4607,7 +4609,7 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, + int besl_device = 0; + u32 field; + +- u2del = HCS_U2_LATENCY(xhci->hcs_params3); ++ u2del = FIELD_GET(HCS_U2_LATENCY, xhci->hcs_params3); + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + if (field & USB_BESL_SUPPORT) { +@@ -5459,26 +5461,28 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + xhci_warn(xhci, "Host controller not accessible, removed?\n"); + return -ENODEV; + } +- xhci->op_regs = hcd->regs + HC_LENGTH(hc_capbase); ++ xhci->op_regs = hcd->regs + FIELD_GET(HC_LENGTH, hc_capbase); ++ + xhci->run_regs = hcd->regs + + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + hcs_params1 = readl(&xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); +- xhci->hci_version = HC_VERSION(hc_capbase); ++ xhci->hci_version = FIELD_GET(HC_VERSION, hc_capbase); + xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); + if (xhci->hci_version > 0x100) + xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + + xhci->dma_mask_bits = 64; +- xhci->max_slots = min(HCS_MAX_SLOTS(hcs_params1), MAX_HC_SLOTS); +- xhci->max_ports = min(HCS_MAX_PORTS(hcs_params1), MAX_HC_PORTS); ++ xhci->max_slots = min(FIELD_GET(HCS_SLOTS_MASK, hcs_params1), MAX_HC_SLOTS); ++ xhci->max_ports = min(FIELD_GET(HCS_MAX_PORTS, hcs_params1), MAX_HC_PORTS); ++ + /* xhci-plat or xhci-pci might have set max_interrupters already */ + if (!xhci->max_interrupters) +- xhci->max_interrupters = min(HCS_MAX_INTRS(hcs_params1), MAX_HC_INTRS); +- else if (xhci->max_interrupters > HCS_MAX_INTRS(hcs_params1)) +- xhci->max_interrupters = HCS_MAX_INTRS(hcs_params1); ++ xhci->max_interrupters = min(FIELD_GET(HCS_MAX_INTRS, hcs_params1), MAX_HC_INTRS); ++ else if (xhci->max_interrupters > FIELD_GET(HCS_MAX_INTRS, hcs_params1)) ++ xhci->max_interrupters = FIELD_GET(HCS_MAX_INTRS, hcs_params1); + + xhci->quirks |= quirks; + +@@ -5519,7 +5523,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) + * DMA_BIT_MASK(32)) in this xhci_gen_setup(). + */ + if (xhci->quirks & XHCI_NO_64BIT_SUPPORT) +- xhci->hcc_params &= ~BIT(0); ++ xhci->hcc_params &= ~HCC_64BIT_ADDR; + + /* + * Set dma_mask and coherent_dma_mask to 64-bits if xHC supports +diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h +--- a/drivers/usb/host/xhci.h ++++ b/drivers/usb/host/xhci.h +@@ -290,6 +290,11 @@ struct xhci_run_regs { + struct xhci_intr_reg ir_set[1024]; + }; + ++/* Bits [13:3] of the microframe index equals the 1ms frame index */ ++#define MFINDEX_TO_FRAME(p) (((p) >> 3) & 0x7ff) ++#define MAX_FRAMES 2048 ++#define MAX_UFRAMES (MAX_FRAMES * 8) ++ + /** + * struct doorbell_array + * +@@ -677,9 +682,9 @@ struct xhci_virt_ep { + #define EP_SOFT_CLEAR_TOGGLE BIT(7) + /* usb_hub_clear_tt_buffer is in progress */ + #define EP_CLEARING_TT BIT(8) ++#define EP_DROP_PENDING BIT(9) /* port disconnect or link error, don't restart */ + /* ---- Related to URB cancellation ---- */ + struct list_head cancelled_td_list; +- struct xhci_hcd *xhci; + /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue + * command. We'll need to update the ring's dequeue segment and dequeue + * pointer after the command completes. +@@ -699,7 +704,7 @@ struct xhci_virt_ep { + struct list_head bw_endpoint_list; + unsigned long stop_time; + /* Isoch Frame ID checking storage */ +- int next_frame_id; ++ int next_uframe; + /* Use new Isoch TRB layout needed for extended TBC support */ + bool use_extended_tbc; + /* set if this endpoint is controlled via sideband access*/ +@@ -753,14 +758,6 @@ struct xhci_virt_device { + struct xhci_port *rhub_port; + struct xhci_interval_bw_table *bw_table; + struct xhci_tt_bw_info *tt_info; +- /* +- * flags for state tracking based on events and issued commands. +- * Software can not rely on states from output contexts because of +- * latency between events and xHC updating output context values. +- * See xhci 1.1 section 4.8.3 for more details +- */ +- unsigned long flags; +-#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */ + + /* The current max exit latency for the enabled USB3 link states. */ + u16 current_mel; +@@ -1480,6 +1477,8 @@ struct xhci_port { + int hcd_portnum; + struct xhci_hub *rhub; + struct xhci_port_cap *port_cap; ++ unsigned int link_inactive:1; ++ unsigned int connected:1; + unsigned int lpm_incapable:1; + unsigned long resume_timestamp; + bool rexit_active; +@@ -1961,7 +1960,7 @@ unsigned int count_trbs(u64 addr, u64 len); + unsigned int xhci_num_trbs_free(struct xhci_ring *ring); + int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, + int suspend, gfp_t gfp_flags); +-void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); ++void xhci_process_cancelled_tds(struct xhci_hcd *xhci, struct xhci_virt_ep *ep); + void xhci_update_erst_dequeue(struct xhci_hcd *xhci, + struct xhci_interrupter *ir, + bool clear_ehb); +diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c +--- a/drivers/usb/misc/cypress_cy7c63.c ++++ b/drivers/usb/misc/cypress_cy7c63.c +@@ -89,8 +89,11 @@ static int vendor_command(struct cypress *dev, unsigned char request, + address, data, iobuf, CYPRESS_MAX_REQSIZE, + USB_CTRL_GET_TIMEOUT); + /* we must not process garbage */ +- if (retval < 2) ++ if (retval < 2) { ++ if (retval >= 0) ++ retval = -EIO; + goto err_buf; ++ } + + /* store returned data (more READs to be added) */ + switch (request) { +@@ -175,8 +178,9 @@ static ssize_t read_port(struct device *dev, struct device_attribute *attr, + dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); + + result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); +- + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); ++ if (result < 0) ++ return result; + + return sprintf(buf, "%d", cyp->port[port_num]); + } +diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c +--- a/drivers/usb/misc/uss720.c ++++ b/drivers/usb/misc/uss720.c +@@ -223,6 +223,7 @@ static int get_1284_register(struct parport *pp, unsigned char reg, unsigned cha + } + printk(KERN_WARNING "get_1284_register timeout\n"); + kill_all_async_requests_priv(priv); ++ kref_put(&rq->ref_count, destroy_async); + return -EIO; + } + +diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c +--- a/drivers/usb/mtu3/mtu3_host.c ++++ b/drivers/usb/mtu3/mtu3_host.c +@@ -46,6 +46,14 @@ + #define WC1_IS_P_95 BIT(12) + #define WC1_IS_EN_P0_95 BIT(6) + ++/* mt8196 */ ++#define PERI_WK_CTRL0_8196 0x08 ++#define WC0_IS_EN_P0_96 BIT(0) ++#define WC0_IS_EN_P1_96 BIT(7) ++ ++#define PERI_WK_CTRL1_8196 0x10 ++#define WC1_IS_EN_P2_96 BIT(0) ++ + /* mt2712 etc */ + #define PERI_SSUSB_SPM_CTRL 0x0 + #define SSC_IP_SLEEP_EN BIT(4) +@@ -59,6 +67,9 @@ enum ssusb_uwk_vers { + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ ++ SSUSB_UWK_V1_7, /* mt8196 IP0 */ ++ SSUSB_UWK_V1_8, /* mt8196 IP1 */ ++ SSUSB_UWK_V1_9, /* mt8196 IP2 */ + }; + + /* +@@ -100,6 +111,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; ++ case SSUSB_UWK_V1_7: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P0_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_8: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8196; ++ msk = WC0_IS_EN_P1_96; ++ val = enable ? msk : 0; ++ break; ++ case SSUSB_UWK_V1_9: ++ reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8196; ++ msk = WC1_IS_EN_P2_96; ++ val = enable ? msk : 0; ++ break; + case SSUSB_UWK_V2: + reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; + msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; +diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c +--- a/drivers/usb/mtu3/mtu3_plat.c ++++ b/drivers/usb/mtu3/mtu3_plat.c +@@ -544,7 +544,8 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) + + ssusb_phy_power_off(ssusb); + clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); +- ssusb_wakeup_set(ssusb, true); ++ if (device_may_wakeup(dev) && (ssusb->is_host || PMSG_IS_AUTO(msg))) ++ ssusb_wakeup_set(ssusb, true); + return 0; + + sleep_err: +diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig +--- a/drivers/usb/musb/Kconfig ++++ b/drivers/usb/musb/Kconfig +@@ -116,7 +116,6 @@ config USB_MUSB_POLARFIRE_SOC + tristate "Microchip PolarFire SoC platforms" + depends on ARCH_MICROCHIP_POLARFIRE || COMPILE_TEST + depends on NOP_USB_XCEIV +- select USB_MUSB_DUAL_ROLE + help + Say Y here to enable support for USB on Microchip's PolarFire SoC. + +diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c +--- a/drivers/usb/musb/musb_host.c ++++ b/drivers/usb/musb/musb_host.c +@@ -1774,7 +1774,8 @@ void musb_host_rx(struct musb *musb, u8 epnum) + status = -EPIPE; + + } else if (rx_csr & MUSB_RXCSR_H_ERROR) { +- dev_err(musb->controller, "ep%d RX three-strikes error", epnum); ++ dev_err_ratelimited(musb->controller, ++ "ep%d RX three-strikes error\n", epnum); + + /* + * The three-strikes error could only happen when the USB +@@ -1788,6 +1789,17 @@ void musb_host_rx(struct musb *musb, u8 epnum) + rx_csr &= ~MUSB_RXCSR_H_ERROR; + musb_writew(epio, MUSB_RXCSR, rx_csr); + ++ /* ++ * Unplugging a USB-Ethernet adapter while it is busy can make ++ * the controller keep re-asserting the three-strikes error for ++ * this endpoint before the disconnect is processed. That floods ++ * the log and can wedge the host port until reboot. Drop the ++ * stale pending RX interrupt on platforms that support it (e.g. ++ * AM335x/DSPS) to break the storm; the transfer is still ++ * aborted below via the fault path. ++ */ ++ musb_platform_clear_ep_rxintr(musb, epnum); ++ + } else if (rx_csr & MUSB_RXCSR_DATAERROR) { + + if (USB_ENDPOINT_XFER_ISOC != qh->type) { +diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c +--- a/drivers/usb/serial/digi_acceleport.c ++++ b/drivers/usb/serial/digi_acceleport.c +@@ -32,10 +32,6 @@ + /* so we can be sure to send the full buffer in one urb */ + #define DIGI_OUT_BUF_SIZE 8 + +-/* port input buffer length -- must be >= transfer buffer length - 3 */ +-/* so we can be sure to hold at least one full buffer from one urb */ +-#define DIGI_IN_BUF_SIZE 64 +- + /* retry timeout while sleeping */ + #define DIGI_RETRY_TIMEOUT (HZ/10) + +@@ -176,10 +172,10 @@ + /* Structures */ + + struct digi_serial { +- spinlock_t ds_serial_lock; ++ struct mutex open_mutex; + struct usb_serial_port *ds_oob_port; /* out-of-band port */ + int ds_oob_port_num; /* index of out-of-band port */ +- int ds_device_started; ++ int open_count; + }; + + struct digi_port { +@@ -194,7 +190,6 @@ struct digi_port { + int dp_throttled; + int dp_throttle_restart; + wait_queue_head_t dp_flush_wait; +- wait_queue_head_t dp_close_wait; /* wait queue for close */ + wait_queue_head_t write_wait; + struct usb_serial_port *dp_port; + }; +@@ -227,9 +222,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty); + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); + static void digi_close(struct usb_serial_port *port); + static void digi_dtr_rts(struct usb_serial_port *port, int on); +-static int digi_startup_device(struct usb_serial *serial); + static int digi_startup(struct usb_serial *serial); +-static void digi_disconnect(struct usb_serial *serial); + static void digi_release(struct usb_serial *serial); + static int digi_port_probe(struct usb_serial_port *port); + static void digi_port_remove(struct usb_serial_port *port); +@@ -272,7 +265,7 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .dtr_rts = digi_dtr_rts, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -282,7 +275,6 @@ static struct usb_serial_driver digi_acceleport_2_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -301,7 +293,7 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .close = digi_close, + .write = digi_write, + .write_room = digi_write_room, +- .write_bulk_callback = digi_write_bulk_callback, ++ .write_bulk_callback = digi_write_bulk_callback, + .read_bulk_callback = digi_read_bulk_callback, + .chars_in_buffer = digi_chars_in_buffer, + .throttle = digi_rx_throttle, +@@ -311,7 +303,6 @@ static struct usb_serial_driver digi_acceleport_4_device = { + .tiocmget = digi_tiocmget, + .tiocmset = digi_tiocmset, + .attach = digi_startup, +- .disconnect = digi_disconnect, + .release = digi_release, + .port_probe = digi_port_probe, + .port_remove = digi_port_remove, +@@ -335,7 +326,6 @@ static struct usb_serial_driver * const serial_drivers[] = { + * interruptible_sleep_on_timeout is deprecated and has been replaced + * with the equivalent code. + */ +- + static long cond_wait_interruptible_timeout_irqrestore( + wait_queue_head_t *q, long timeout, + spinlock_t *lock, unsigned long flags) +@@ -351,6 +341,13 @@ __releases(lock) + return timeout; + } + ++static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ ++ return serial_priv->ds_oob_port; ++} ++ + /* + * Digi Write OOB Command + * +@@ -361,15 +358,14 @@ __releases(lock) + * the interruptible flag is true, or a negative error + * returned by usb_submit_urb. + */ +- + static int digi_write_oob_command(struct usb_serial_port *port, + unsigned char *buf, int count, int interruptible) + { +- int ret = 0; +- int len; +- struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, + "digi_write_oob_command: TOP: port=%d, count=%d\n", +@@ -406,10 +402,8 @@ static int digi_write_oob_command(struct usb_serial_port *port, + dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", + __func__, ret); + return ret; +- + } + +- + /* + * Digi Write In Band Command + * +@@ -421,16 +415,15 @@ static int digi_write_oob_command(struct usb_serial_port *port, + * timeout ticks. Returns 0 if successful, or a negative + * error returned by digi_write. + */ +- + static int digi_write_inb_command(struct usb_serial_port *port, + unsigned char *buf, int count, unsigned long timeout) + { +- int ret = 0; +- int len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; + unsigned long expire; + unsigned long flags; ++ int ret = 0; ++ int len; + + dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); +@@ -459,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, + /* len must be a multiple of 4 and small enough to */ + /* guarantee the write will send buffered data first, */ + /* so commands are in order with data and not split */ +- len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + if (len > 4) + len &= ~3; + +@@ -467,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port, + if (priv->dp_out_buf_len > 0) { + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; +- memcpy(data + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); + memcpy(data + 2 + priv->dp_out_buf_len, buf, len); +- port->write_urb->transfer_buffer_length +- = priv->dp_out_buf_len + 2 + len; ++ port->write_urb->transfer_buffer_length = ++ priv->dp_out_buf_len + 2 + len; + } else { + memcpy(data, buf, len); + port->write_urb->transfer_buffer_length = len; +@@ -484,7 +476,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + count -= len; + buf += len; + } +- + } + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + +@@ -495,7 +486,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, + return ret; + } + +- + /* + * Digi Set Modem Signals + * +@@ -505,17 +495,15 @@ static int digi_write_inb_command(struct usb_serial_port *port, + * -EINTR if interrupted while sleeping, or a non-zero error + * returned by usb_submit_urb. + */ +- + static int digi_set_modem_signals(struct usb_serial_port *port, + unsigned int modem_signals, int interruptible) + { +- +- int ret; + struct digi_port *port_priv = usb_get_serial_port_data(port); +- struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; ++ struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); + struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); + unsigned char *data = oob_port->write_urb->transfer_buffer; + unsigned long flags; ++ int ret; + + dev_dbg(&port->dev, + "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", +@@ -573,14 +561,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port, + * is only called from close, and only one process can be in close on a + * port at a time, so its ok. + */ +- + static int digi_transmit_idle(struct usb_serial_port *port, + unsigned long timeout) + { +- int ret; +- unsigned char buf[2]; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[2]; + unsigned long flags; ++ int ret; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_transmit_idle = 0; +@@ -607,16 +594,15 @@ static int digi_transmit_idle(struct usb_serial_port *port, + } + priv->dp_transmit_idle = 0; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return 0; +- + } + +- + static void digi_rx_throttle(struct tty_struct *tty) + { +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; + + /* stop receiving characters by not resubmitting the read urb */ + spin_lock_irqsave(&priv->dp_port_lock, flags); +@@ -625,13 +611,12 @@ static void digi_rx_throttle(struct tty_struct *tty) + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + } + +- + static void digi_rx_unthrottle(struct tty_struct *tty) + { +- int ret = 0; +- unsigned long flags; + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned long flags; ++ int ret = 0; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + +@@ -651,7 +636,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty) + __func__, ret, priv->dp_port_num); + } + +- + static void digi_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + const struct ktermios *old_termios) +@@ -762,7 +746,6 @@ static void digi_set_termios(struct tty_struct *tty, + + /* set stop bits */ + if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { +- + if ((cflag & CSTOPB)) + arg = DIGI_STOP_BITS_2; + else +@@ -772,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty, + buf[i++] = priv->dp_port_num; + buf[i++] = arg; + buf[i++] = 0; +- + } + + /* set input flow control */ +@@ -841,7 +823,6 @@ static void digi_set_termios(struct tty_struct *tty, + tty_encode_baud_rate(tty, baud, baud); + } + +- + static int digi_break_ctl(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; +@@ -855,50 +836,48 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state) + return digi_write_inb_command(port, buf, 4, 0); + } + +- + static int digi_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = priv->dp_modem_signals; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return val; + } + +- + static int digi_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; + struct digi_port *priv = usb_get_serial_port_data(port); +- unsigned int val; + unsigned long flags; ++ unsigned int val; + + spin_lock_irqsave(&priv->dp_port_lock, flags); + val = (priv->dp_modem_signals & ~clear) | set; + spin_unlock_irqrestore(&priv->dp_port_lock, flags); ++ + return digi_set_modem_signals(port, val, 1); + } + +- + static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) + { +- +- int ret, data_len, new_len; + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = port->write_urb->transfer_buffer; ++ int ret, data_len, new_len; + unsigned long flags; + + dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); + + /* copy user data (which can sleep) before getting spin lock */ +- count = min(count, port->bulk_out_size-2); ++ count = min(count, port->bulk_out_size - 2); + count = min(64, count); + + /* be sure only one write proceeds at a time */ +@@ -920,7 +899,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + + /* allow space for any buffered data and for new data, up to */ + /* transfer buffer size - 2 (for command and length bytes) */ +- new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); ++ new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); + data_len = new_len + priv->dp_out_buf_len; + + if (data_len == 0) { +@@ -928,7 +907,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + return 0; + } + +- port->write_urb->transfer_buffer_length = data_len+2; ++ port->write_urb->transfer_buffer_length = data_len + 2; + + *data++ = DIGI_CMD_SEND_DATA; + *data++ = data_len; +@@ -954,39 +933,22 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, + "%s: usb_submit_urb failed, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + dev_dbg(&port->dev, "digi_write: returning %d\n", ret); +- return ret; + ++ return ret; + } + + static void digi_write_bulk_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; +- struct usb_serial *serial; +- struct digi_port *priv; +- struct digi_serial *serial_priv; ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char *data = urb->transfer_buffer; + unsigned long flags; +- int ret = 0; +- int status = urb->status; + bool wakeup; +- +- /* port and serial sanity check */ +- if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { +- pr_err("%s: port or port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- serial = port->serial; +- if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { +- dev_err(&port->dev, +- "%s: serial or serial->private is NULL, status=%d\n", +- __func__, status); +- return; +- } ++ int ret = 0; + + /* handle oob callback */ + if (priv->dp_port_num == serial_priv->ds_oob_port_num) { +- dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + wake_up_interruptible(&priv->write_wait); +@@ -999,15 +961,13 @@ static void digi_write_bulk_callback(struct urb *urb) + spin_lock_irqsave(&priv->dp_port_lock, flags); + priv->dp_write_urb_in_use = 0; + if (priv->dp_out_buf_len > 0) { +- *((unsigned char *)(port->write_urb->transfer_buffer)) +- = (unsigned char)DIGI_CMD_SEND_DATA; +- *((unsigned char *)(port->write_urb->transfer_buffer) + 1) +- = (unsigned char)priv->dp_out_buf_len; +- port->write_urb->transfer_buffer_length = +- priv->dp_out_buf_len + 2; +- memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, +- priv->dp_out_buf_len); +- ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); ++ data[0] = DIGI_CMD_SEND_DATA; ++ data[1] = priv->dp_out_buf_len; ++ memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); ++ ++ urb->transfer_buffer_length = priv->dp_out_buf_len + 2; ++ ++ ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret == 0) { + priv->dp_write_urb_in_use = 1; + priv->dp_out_buf_len = 0; +@@ -1041,8 +1001,8 @@ static unsigned int digi_write_room(struct tty_struct *tty) + + spin_unlock_irqrestore(&priv->dp_port_lock, flags); + dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room); +- return room; + ++ return room; + } + + static unsigned int digi_chars_in_buffer(struct tty_struct *tty) +@@ -1070,17 +1030,53 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) + digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1); + } + ++static int digi_open_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ int ret = 0; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count++ == 0) { ++ ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ++ ret); ++ serial_priv->open_count--; ++ } ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++ ++ return ret; ++} ++ ++static void digi_close_oob_port(struct usb_serial *serial) ++{ ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct usb_serial_port *oob_port = serial_priv->ds_oob_port; ++ ++ mutex_lock(&serial_priv->open_mutex); ++ ++ if (serial_priv->open_count-- == 1) { ++ usb_kill_urb(oob_port->read_urb); ++ usb_kill_urb(oob_port->write_urb); ++ } ++ ++ mutex_unlock(&serial_priv->open_mutex); ++} ++ + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + { +- int ret; +- unsigned char buf[32]; + struct digi_port *priv = usb_get_serial_port_data(port); + struct ktermios not_termios; +- int throttled; ++ unsigned char buf[32]; ++ int ret; + +- /* be sure the device is started up */ +- if (digi_startup_device(port->serial) != 0) +- return -ENXIO; ++ ret = digi_open_oob_port(port->serial); ++ if (ret) ++ return ret; + + /* read modem signals automatically whenever they change */ + buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; +@@ -1106,32 +1102,34 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) + } + + spin_lock_irq(&priv->dp_port_lock); +- throttled = priv->dp_throttle_restart; + priv->dp_throttled = 0; + priv->dp_throttle_restart = 0; + spin_unlock_irq(&priv->dp_port_lock); + +- if (throttled) { +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret) { +- dev_err(&port->dev, "failed to submit read urb: %d\n", ret); +- return ret; +- } ++ ret = usb_submit_urb(port->read_urb, GFP_KERNEL); ++ if (ret) { ++ dev_err(&port->dev, "failed to submit read urb: %d\n", ret); ++ goto err_close_oob; + } + + return 0; +-} + ++err_close_oob: ++ digi_close_oob_port(port->serial); ++ ++ return ret; ++} + + static void digi_close(struct usb_serial_port *port) + { ++ struct digi_port *priv = usb_get_serial_port_data(port); ++ unsigned char buf[32]; + DEFINE_WAIT(wait); + int ret; +- unsigned char buf[32]; +- struct digi_port *priv = usb_get_serial_port_data(port); ++ ++ usb_kill_urb(port->read_urb); + + mutex_lock(&port->serial->disc_mutex); +- /* if disconnected, just clear flags */ + if (port->serial->disconnected) + goto exit; + +@@ -1177,57 +1175,18 @@ static void digi_close(struct usb_serial_port *port) + TASK_INTERRUPTIBLE); + schedule_timeout(DIGI_CLOSE_TIMEOUT); + finish_wait(&priv->dp_flush_wait, &wait); ++exit: ++ mutex_unlock(&port->serial->disc_mutex); + + /* shutdown any outstanding bulk writes */ + usb_kill_urb(port->write_urb); +-exit: +- spin_lock_irq(&priv->dp_port_lock); +- priv->dp_write_urb_in_use = 0; +- wake_up_interruptible(&priv->dp_close_wait); +- spin_unlock_irq(&priv->dp_port_lock); +- mutex_unlock(&port->serial->disc_mutex); +-} + +- +-/* +- * Digi Startup Device +- * +- * Starts reads on all ports. Must be called AFTER startup, with +- * urbs initialized. Returns 0 if successful, non-zero error otherwise. +- */ +- +-static int digi_startup_device(struct usb_serial *serial) +-{ +- int i, ret = 0; +- struct digi_serial *serial_priv = usb_get_serial_data(serial); +- struct usb_serial_port *port; +- +- /* be sure this happens exactly once */ +- spin_lock(&serial_priv->ds_serial_lock); +- if (serial_priv->ds_device_started) { +- spin_unlock(&serial_priv->ds_serial_lock); +- return 0; +- } +- serial_priv->ds_device_started = 1; +- spin_unlock(&serial_priv->ds_serial_lock); +- +- /* start reading from each bulk in endpoint for the device */ +- /* set USB_DISABLE_SPD flag for write bulk urbs */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- port = serial->port[i]; +- ret = usb_submit_urb(port->read_urb, GFP_KERNEL); +- if (ret != 0) { +- dev_err(&port->dev, +- "%s: usb_submit_urb failed, ret=%d, port=%d\n", +- __func__, ret, i); +- break; +- } +- } +- return ret; ++ digi_close_oob_port(port->serial); + } + + static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + { ++ struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv; + + priv = kzalloc_obj(*priv); +@@ -1235,10 +1194,13 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) + return -ENOMEM; + + spin_lock_init(&priv->dp_port_lock); ++ ++ if (port == serial_priv->ds_oob_port) ++ lockdep_set_subclass(&priv->dp_port_lock, SINGLE_DEPTH_NESTING); ++ + priv->dp_port_num = port_num; + init_waitqueue_head(&priv->dp_transmit_idle_wait); + init_waitqueue_head(&priv->dp_flush_wait); +- init_waitqueue_head(&priv->dp_close_wait); + init_waitqueue_head(&priv->write_wait); + priv->dp_port = port; + +@@ -1275,10 +1237,13 @@ static int digi_startup(struct usb_serial *serial) + if (!serial_priv) + return -ENOMEM; + +- spin_lock_init(&serial_priv->ds_serial_lock); ++ mutex_init(&serial_priv->open_mutex); ++ + serial_priv->ds_oob_port_num = oob_port_num; + serial_priv->ds_oob_port = serial->port[oob_port_num]; + ++ usb_set_serial_data(serial, serial_priv); ++ + ret = digi_port_init(serial_priv->ds_oob_port, + serial_priv->ds_oob_port_num); + if (ret) { +@@ -1286,24 +1251,9 @@ static int digi_startup(struct usb_serial *serial) + return ret; + } + +- usb_set_serial_data(serial, serial_priv); +- + return 0; + } + +- +-static void digi_disconnect(struct usb_serial *serial) +-{ +- int i; +- +- /* stop reads and writes on all ports */ +- for (i = 0; i < serial->type->num_ports + 1; i++) { +- usb_kill_urb(serial->port[i]->read_urb); +- usb_kill_urb(serial->port[i]->write_urb); +- } +-} +- +- + static void digi_release(struct usb_serial *serial) + { + struct digi_serial *serial_priv; +@@ -1333,30 +1283,25 @@ static void digi_port_remove(struct usb_serial_port *port) + static void digi_read_bulk_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +- struct digi_port *priv; +- struct digi_serial *serial_priv; +- int ret; ++ struct usb_serial *serial = port->serial; ++ struct digi_serial *serial_priv = usb_get_serial_data(serial); ++ struct digi_port *priv = usb_get_serial_port_data(port); + int status = urb->status; +- +- /* port sanity check, do not resubmit if port is not valid */ +- if (port == NULL) +- return; +- priv = usb_get_serial_port_data(port); +- if (priv == NULL) { +- dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", +- __func__, status); +- return; +- } +- if (port->serial == NULL || +- (serial_priv = usb_get_serial_data(port->serial)) == NULL) { +- dev_err(&port->dev, "%s: serial is bad or serial->private " +- "is NULL, status=%d\n", __func__, status); +- return; +- } ++ int ret; + + /* do not resubmit urb if it has any status error */ +- if (status) { +- dev_err(&port->dev, ++ switch (status) { ++ case 0: ++ break; ++ case -ENOENT: ++ case -ECONNRESET: ++ case -ESHUTDOWN: ++ dev_err(&serial->interface->dev, ++ "%s: nonzero read bulk status: status=%d, port=%d\n", ++ __func__, status, priv->dp_port_num); ++ return; ++ default: ++ dev_err(&serial->interface->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; +@@ -1374,11 +1319,10 @@ static void digi_read_bulk_callback(struct urb *urb) + /* continue read */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) { +- dev_err(&port->dev, ++ dev_err(&serial->interface->dev, + "%s: failed resubmitting urb, ret=%d, port=%d\n", + __func__, ret, priv->dp_port_num); + } +- + } + + /* +@@ -1390,7 +1334,6 @@ static void digi_read_bulk_callback(struct urb *urb) + * It returns 0 if successful, 1 if successful but the port is + * throttled, and -1 if the sanity checks failed. + */ +- + static int digi_read_inb_callback(struct urb *urb) + { + struct usb_serial_port *port = urb->context; +@@ -1468,10 +1411,8 @@ static int digi_read_inb_callback(struct urb *urb) + dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); + + return throttled ? 1 : 0; +- + } + +- + /* + * Digi Read OOB Callback + * +@@ -1480,10 +1421,8 @@ static int digi_read_inb_callback(struct urb *urb) + * the port->serial is valid. It returns 0 if successful, and + * -1 if the sanity checks failed. + */ +- + static int digi_read_oob_callback(struct urb *urb) + { +- + struct usb_serial_port *port = urb->context; + struct usb_serial *serial = port->serial; + struct tty_struct *tty; +@@ -1491,8 +1430,8 @@ static int digi_read_oob_callback(struct urb *urb) + unsigned char *buf = urb->transfer_buffer; + int opcode, line, status, val; + unsigned long flags; +- int i; + unsigned int rts; ++ int i; + + if (urb->actual_length < 4) + return -1; +@@ -1504,7 +1443,8 @@ static int digi_read_oob_callback(struct urb *urb) + status = buf[i + 2]; + val = buf[i + 3]; + +- dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", ++ dev_dbg(&serial->interface->dev, ++ "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + opcode, line, status, val); + + if (status != 0 || line >= serial->type->num_ports) +@@ -1562,8 +1502,8 @@ static int digi_read_oob_callback(struct urb *urb) + } + tty_kref_put(tty); + } +- return 0; + ++ return 0; + } + + module_usb_serial_driver(serial_drivers, id_table_combined); +diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c +--- a/drivers/usb/serial/keyspan_pda.c ++++ b/drivers/usb/serial/keyspan_pda.c +@@ -31,7 +31,6 @@ + struct keyspan_pda_private { + int tx_room; + struct work_struct unthrottle_work; +- struct usb_serial *serial; + struct usb_serial_port *port; + bool throttled; + bool throttle_req; +diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c +--- a/drivers/usb/serial/metro-usb.c ++++ b/drivers/usb/serial/metro-usb.c +@@ -36,6 +36,7 @@ + struct metrousb_private { + spinlock_t lock; + int throttled; ++ int throttle_req; + unsigned long control_state; + }; + +@@ -108,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb) + unsigned char *data = urb->transfer_buffer; + unsigned long flags; + int throttled = 0; +- int result = 0; ++ int result; + + dev_dbg(&port->dev, "%s\n", __func__); + +@@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb) + + /* Set any port variables. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- throttled = metro_priv->throttled; ++ if (metro_priv->throttle_req) { ++ metro_priv->throttled = 1; ++ throttled = 1; ++ } + spin_unlock_irqrestore(&metro_priv->lock, flags); + + if (throttled) +@@ -169,12 +173,13 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) + struct usb_serial *serial = port->serial; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int result; + + /* Set the private data information for the port. */ + spin_lock_irqsave(&metro_priv->lock, flags); + metro_priv->control_state = 0; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + + /* Clear the urb pipe. */ +@@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty) + + /* Set the private information for the port to stop reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); +- metro_priv->throttled = 1; ++ metro_priv->throttle_req = 1; + spin_unlock_irqrestore(&metro_priv->lock, flags); + } + +@@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty) + struct usb_serial_port *port = tty->driver_data; + struct metrousb_private *metro_priv = usb_get_serial_port_data(port); + unsigned long flags; +- int result = 0; ++ int throttled; ++ int result; + + /* Set the private information for the port to resume reading data. */ + spin_lock_irqsave(&metro_priv->lock, flags); ++ throttled = metro_priv->throttled; + metro_priv->throttled = 0; ++ metro_priv->throttle_req = 0; + spin_unlock_irqrestore(&metro_priv->lock, flags); + +- /* Submit the urb to read from the port. */ +- result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); +- if (result) +- dev_err(&port->dev, +- "failed submitting interrupt in urb error code=%d\n", +- result); ++ if (throttled) { ++ result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); ++ if (result) { ++ dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", ++ result); ++ } ++ } + } + + static struct usb_serial_driver metrousb_device = { +diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c +--- a/drivers/usb/serial/pl2303.c ++++ b/drivers/usb/serial/pl2303.c +@@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, ++ { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, + { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, + { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), +@@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define VENDOR_WRITE_REQUEST_TYPE 0x40 + #define VENDOR_WRITE_REQUEST 0x01 + #define VENDOR_WRITE_NREQUEST 0x80 ++#define VENDOR_WRITE_MPREQUEST 0x80 + + #define VENDOR_READ_REQUEST_TYPE 0xc0 + #define VENDOR_READ_REQUEST 0x01 + #define VENDOR_READ_NREQUEST 0x81 ++#define VENDOR_READ_MPREQUEST 0x80 ++ ++#define PL256X_RESET_REQUEST_TYPE 0x40 ++#define PL256X_RESET_REQUEST 0x96 + + #define UART_STATE_INDEX 8 + #define UART_STATE_MSR_MASK 0x8b +@@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table); + #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 + #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + ++#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 ++#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 ++#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 ++#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 ++ ++#define PL256X_FLOWCTRL_MASK 0x43 ++#define PL256X_FLOWCTRL_XON_XOFF 0x40 ++#define PL256X_FLOWCTRL_RTS_CTS 0x03 ++#define PL256X_FLOWCTRL_NONE 0x00 ++ + static int pl2303_set_break(struct usb_serial_port *port, bool enable); + + enum pl2303_type { +@@ -181,6 +197,7 @@ enum pl2303_type { + TYPE_TB, + TYPE_HXD, + TYPE_HXN, ++ TYPE_MP, + TYPE_COUNT + }; + +@@ -196,6 +213,8 @@ struct pl2303_type_data { + struct pl2303_serial_private { + const struct pl2303_type_data *type; + unsigned long quirks; ++ u16 interface_num; ++ u16 flowctrl_reg; + }; + + struct pl2303_private { +@@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { + .max_baud_rate = 12000000, + .no_divisors = true, + }, ++ [TYPE_MP] = { ++ .name = "MP", ++ .max_baud_rate = 24000000, ++ .no_divisors = true, ++ }, + }; + ++static int pl256x_uart_reset(struct usb_serial *serial) ++{ ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); ++ struct device *dev = &serial->interface->dev; ++ int res; ++ ++ res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), ++ PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, ++ 0, spriv->interface_num, NULL, 0, 100); ++ if (res) { ++ dev_err(dev, "failed to reset device: %d\n", res); ++ return res; ++ } ++ ++ return 0; ++} ++ + static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + unsigned char buf[1]) + { +@@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value, + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_READ_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_READ_MPREQUEST; + else + request = VENDOR_READ_REQUEST; + +@@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) + + if (spriv->type == &pl2303_type_data[TYPE_HXN]) + request = VENDOR_WRITE_NREQUEST; ++ else if (spriv->type == &pl2303_type_data[TYPE_MP]) ++ request = VENDOR_WRITE_MPREQUEST; + else + request = VENDOR_WRITE_REQUEST; + +@@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) + if (!buf) + return -ENOMEM; + +- if (spriv->type == &pl2303_type_data[TYPE_HXN]) ++ if (spriv->type == &pl2303_type_data[TYPE_HXN] || ++ spriv->type == &pl2303_type_data[TYPE_MP]) { + ret = pl2303_vendor_read(serial, reg, buf); +- else ++ } else { + ret = pl2303_vendor_read(serial, reg | 0x80, buf); ++ } + + if (ret) + goto out_free; +@@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial) + case 0x905: /* GT-2AB */ + case 0x1005: /* GC-Q20 */ + return TYPE_HXN; ++ case 0x3302: /* PL2533 VC 2 Port */ ++ case 0x3304: /* PL2533 VC 4 Port */ ++ case 0x4302: /* PL2543 VC 2 Port */ ++ case 0x4304: /* PL2543 VC 4 Port */ ++ case 0x6502: /* PL2565 VC 2 Port */ ++ case 0x6504: /* PL2565 VC 4 Port */ ++ case 0x6506: /* PL2565 VC 4 Port QFN64 package */ ++ return TYPE_MP; + } + break; + } +@@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial) + struct pl2303_serial_private *spriv; + enum pl2303_type type; + unsigned char *buf; ++ unsigned int ifnum; + int ret; + + ret = pl2303_detect_type(serial); +@@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial) + type = ret; + dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + ++ ifnum = serial->interface->altsetting->desc.bInterfaceNumber; ++ + spriv = kzalloc_obj(*spriv); + if (!spriv) + return -ENOMEM; + ++ if (type == TYPE_MP) { ++ switch (ifnum) { ++ case 0: ++ spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; ++ break; ++ case 1: ++ spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; ++ break; ++ case 2: ++ spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; ++ break; ++ case 3: ++ spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; ++ break; ++ default: ++ kfree(spriv); ++ return -ENODEV; ++ } ++ } ++ + spriv->type = &pl2303_type_data[type]; + spriv->quirks = (unsigned long)usb_get_serial_data(serial); + spriv->quirks |= spriv->type->quirks; ++ spriv->interface_num = ifnum; + + if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) + spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; + + usb_set_serial_data(serial, spriv); + +- if (type != TYPE_HXN) { ++ if (type != TYPE_HXN && type != TYPE_MP) { + buf = kmalloc(1, GFP_KERNEL); + if (!buf) { + kfree(spriv); +@@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port) + static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) + { + struct usb_device *dev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int retval; + + dev_dbg(&port->dev, "%s - %02x\n", __func__, value); + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, +- value, 0, NULL, 0, 100); ++ value, spriv->interface_num, NULL, 0, 100); + if (retval) + dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); + +@@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port, + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret != 7) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + +@@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port, + unsigned char buf[7]) + { + struct usb_device *udev = port->serial->dev; ++ struct usb_serial *serial = port->serial; ++ struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + int ret; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, +- 0, 0, buf, 7, 100); ++ 0, spriv->interface_num, buf, 7, 100); + if (ret < 0) { + dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); + return ret; +@@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_RTS_CTS); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_RTS_CTS); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); + } +@@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_XON_XOFF); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_XON_XOFF); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); + } +@@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty, + pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, + PL2303_HXN_FLOWCTRL_MASK, + PL2303_HXN_FLOWCTRL_NONE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl2303_vendor_write(serial, spriv->flowctrl_reg, ++ PL256X_FLOWCTRL_NONE); + } else { + pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); + } +@@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) + pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, + PL2303_HXN_RESET_UPSTREAM_PIPE | + PL2303_HXN_RESET_DOWNSTREAM_PIPE); ++ } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { ++ pl256x_uart_reset(serial); + } else { + pl2303_vendor_write(serial, 8, 0); + pl2303_vendor_write(serial, 9, 0); +@@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable) + + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + BREAK_REQUEST, BREAK_REQUEST_TYPE, state, +- 0, NULL, 0, 100); ++ spriv->interface_num, NULL, 0, 100); + if (result) { + dev_err(&port->dev, "error sending break = %d\n", result); + return result; +diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h +--- a/drivers/usb/serial/pl2303.h ++++ b/drivers/usb/serial/pl2303.h +@@ -26,7 +26,7 @@ + #define PL2303_PRODUCT_ID_HCR331 0x331a + #define PL2303_PRODUCT_ID_MOTOROLA 0x0307 + #define PL2303_PRODUCT_ID_ZTEK 0xe1f1 +- ++#define PL256X_PRODUCT_ID_4P 0x2533 + + #define ATEN_VENDOR_ID 0x0557 + #define ATEN_VENDOR_ID2 0x0547 +diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig +--- a/drivers/usb/typec/altmodes/Kconfig ++++ b/drivers/usb/typec/altmodes/Kconfig +@@ -5,6 +5,7 @@ menu "USB Type-C Alternate Mode drivers" + config TYPEC_DP_ALTMODE + tristate "DisplayPort Alternate Mode driver" + depends on DRM ++ select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF + help + DisplayPort USB Type-C Alternate Mode allows DisplayPort + displays and adapters to be attached to the USB Type-C +diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c +--- a/drivers/usb/typec/altmodes/displayport.c ++++ b/drivers/usb/typec/altmodes/displayport.c +@@ -764,6 +764,7 @@ int dp_altmode_probe(struct typec_altmode *alt) + struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); + struct fwnode_handle *fwnode; + struct dp_altmode *dp; ++ u32 cap = DP_CAP_CAPABILITY(alt->vdo); + + /* Port can only be DFP_U. */ + if (typec_altmode_get_data_role(alt) != TYPEC_HOST) +@@ -778,6 +779,18 @@ int dp_altmode_probe(struct typec_altmode *alt) + return -ENODEV; + } + ++ /* ++ * Make sure the DisplayPort VDO is valid (VESA DPAM v2.1a, Section ++ * 5.4.1, Table 5-6, DP Capabilities VDO). A device exposing DP on a ++ * USB-C receptacle must advertise at least one pin assignment for the ++ * capability it claims, otherwise Alt Mode can never be configured. ++ */ ++ if ((cap == DP_CAP_DFP_D && !DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo)) || ++ (cap == DP_CAP_UFP_D && !DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo))) { ++ typec_altmode_put_plug(plug); ++ return -ENODEV; ++ } ++ + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) { + typec_altmode_put_plug(plug); +@@ -790,7 +803,6 @@ int dp_altmode_probe(struct typec_altmode *alt) + dp->alt = alt; + + alt->desc = "DisplayPort"; +- typec_altmode_set_ops(alt, &dp_altmode_ops); + + if (plug) { + plug->desc = "Displayport"; +@@ -811,6 +823,10 @@ int dp_altmode_probe(struct typec_altmode *alt) + if (plug) + typec_altmode_set_drvdata(plug, dp); + ++ if ((alt->vdo & DP_CAP_RECEPTACLE) && typec_cable_altmode_unsupported(alt)) ++ return 0; ++ ++ typec_altmode_set_ops(alt, &dp_altmode_ops); + if (!alt->mode_selection) { + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; + schedule_work(&dp->work); +diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c +--- a/drivers/usb/typec/altmodes/thunderbolt.c ++++ b/drivers/usb/typec/altmodes/thunderbolt.c +@@ -284,6 +284,10 @@ static int tbt_altmode_probe(struct typec_altmode *alt) + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); ++ ++ if (typec_cable_altmode_unsupported(alt)) ++ return 0; ++ + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (!alt->mode_selection && tbt_ready(alt)) { +diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c +--- a/drivers/usb/typec/class.c ++++ b/drivers/usb/typec/class.c +@@ -1429,6 +1429,77 @@ int typec_cable_is_active(struct typec_cable *cable) + } + EXPORT_SYMBOL_GPL(typec_cable_is_active); + ++enum typec_cable_altmode_support { ++ CABLE_SUPPORT_UNKNOWN, ++ CABLE_SUPPORTED, ++ CABLE_NOT_SUPPORTED, ++}; ++ ++static enum typec_cable_altmode_support ++typec_cable_check_altmode_support(struct typec_cable *cable, ++ struct typec_altmode *alt) ++{ ++ struct typec_altmode *plug; ++ u32 speed; ++ ++ /* ++ * Check if the cable has an e-marker, supports modal operation, and the ++ * SOP' altmode nodes are created. ++ */ ++ plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); ++ if (plug) { ++ typec_altmode_put_plug(plug); ++ return CABLE_SUPPORTED; ++ } ++ ++ /* The identity is not specified */ ++ if (!cable->identity) ++ return CABLE_SUPPORT_UNKNOWN; ++ ++ /* Non-e-marked cable */ ++ if (!cable->identity->id_header) ++ return CABLE_NOT_SUPPORTED; ++ ++ switch (PD_IDH_PTYPE(cable->identity->id_header)) { ++ case IDH_PTYPE_PCABLE: ++ speed = VDO_TYPEC_CABLE_SPEED(cable->identity->vdo[0]); ++ if (speed == CABLE_USB2_ONLY) ++ return CABLE_NOT_SUPPORTED; ++ return CABLE_SUPPORTED; ++ case IDH_PTYPE_ACABLE: ++ /* ++ * Active cables must establish an SOP' communication ++ * node. Since that check failed at the beginning of ++ * this function, this active cable does not support ++ * this specific altmode. ++ */ ++ return CABLE_NOT_SUPPORTED; ++ } ++ ++ return CABLE_SUPPORT_UNKNOWN; ++} ++ ++/** ++ * typec_cable_altmode_unsupported - Check if a cable restricts altmode ++ * @alt: The Alternate Mode to evaluate ++ * ++ * Returns true if the connected cable is incapable of handling the altmode. ++ */ ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt) ++{ ++ enum typec_cable_altmode_support support = CABLE_SUPPORT_UNKNOWN; ++ struct typec_cable *cable; ++ ++ cable = typec_cable_get(typec_altmode2port(alt)); ++ if (cable) { ++ support = typec_cable_check_altmode_support(cable, alt); ++ typec_cable_put(cable); ++ } ++ ++ return support == CABLE_NOT_SUPPORTED; ++} ++EXPORT_SYMBOL_GPL(typec_cable_altmode_unsupported); ++ + /** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values +diff --git a/drivers/usb/typec/mux/tusb1046.c b/drivers/usb/typec/mux/tusb1046.c +--- a/drivers/usb/typec/mux/tusb1046.c ++++ b/drivers/usb/typec/mux/tusb1046.c +@@ -178,6 +178,7 @@ static const struct of_device_id tusb1046_match_table[] = { + {.compatible = "ti,tusb1046"}, + {}, + }; ++MODULE_DEVICE_TABLE(of, tusb1046_match_table); + + static struct i2c_driver tusb1046_driver = { + .driver = { +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -212,6 +213,24 @@ static const char * const tcpm_ams_str[] = { + FOREACH_AMS(GENERATE_STRING) + }; + ++#define FOREACH_VDM_DISCOVERY(S) \ ++ S(VDM_DISCOVERY_UNKNOWN), \ ++ S(VDM_DISCOVERY_PARTNER_IDENT), \ ++ S(VDM_DISCOVERY_CABLE_IDENT), \ ++ S(VDM_DISCOVERY_PARTNER_SVIDS), \ ++ S(VDM_DISCOVERY_PARTNER_MODES), \ ++ S(VDM_DISCOVERY_CABLE_SVIDS), \ ++ S(VDM_DISCOVERY_CABLE_MODES), \ ++ S(VDM_DISCOVERY_COMPLETE) ++ ++enum vdm_discovery_states { ++ FOREACH_VDM_DISCOVERY(GENERATE_ENUM) ++}; ++ ++static const char * const vdm_discovery_state_strings[] = { ++ FOREACH_VDM_DISCOVERY(GENERATE_STRING) ++}; ++ + enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, +@@ -232,7 +251,9 @@ enum pd_msg_request { + PD_MSG_DATA_SINK_CAP, + PD_MSG_DATA_SOURCE_CAP, + PD_MSG_DATA_REV, +- PD_MSG_EXT_SINK_CAP_EXT ++ PD_MSG_EXT_SINK_CAP_EXT, ++ PD_MSG_DATA_BATT_STATUS, ++ PD_MSG_EXT_BATT_CAP, + }; + + enum adev_actions { +@@ -274,7 +295,7 @@ enum frs_typec_current { + #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) + + #define GET_SINK_CAP_RETRY_MS 100 +-#define SEND_DISCOVER_RETRY_MS 100 ++#define SEND_DISCOVERY_VDM_RETRY_MS 100 + + struct pd_mode_data { + int svid_index; /* current SVID index */ +@@ -387,7 +408,15 @@ struct pd_timings { + }; + + /* Convert microwatt to watt */ +-#define UW_TO_W(pow) ((pow) / 1000000) ++#define UW_TO_W(pow) (div_u64((pow), 1000000)) ++ ++/* ++ * As per USB PD Spec Rev 3.18 (Sec. 6.5.13.11), the number of fixed batteries ++ * that a port can be queried is restricted to 4. ++ */ ++#define MAX_NUM_FIXED_BATT 4 ++ ++#define BATTERY_PROPERTY_UNKNOWN 0xffff + + /* + * struct pd_identifier - Contains info about PD identifiers +@@ -483,8 +512,6 @@ struct tcpm_port { + bool vbus_source; + bool vbus_charge; + +- /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */ +- bool send_discover; + bool op_vsafe5v; + + int try_role; +@@ -510,8 +537,8 @@ struct tcpm_port { + struct kthread_work vdm_state_machine; + struct hrtimer enable_frs_timer; + struct kthread_work enable_frs; +- struct hrtimer send_discover_timer; +- struct kthread_work send_discover_work; ++ struct hrtimer vdm_discovery_timer; ++ struct kthread_work vdm_discovery_work; + bool state_machine_running; + /* Set to true when VDM State Machine has following actions. */ + bool vdm_sm_running; +@@ -578,6 +605,9 @@ struct tcpm_port { + + u32 bist_request; + ++ /* VDM Discovery State to determine message sent */ ++ enum vdm_discovery_states vdm_discovery_state; ++ + /* PD state for Vendor Defined Messages */ + enum vdm_states vdm_state; + u32 vdm_retries; +@@ -641,12 +671,6 @@ struct tcpm_port { + bool potential_contaminant; + + /* SOP* Related Fields */ +- /* +- * Flag to determine if SOP' Discover Identity is available. The flag +- * is set if Discover Identity on SOP' does not immediately follow +- * Discover Identity on SOP. +- */ +- bool send_discover_prime; + /* + * tx_sop_type determines which SOP* a message is being sent on. + * For messages that are queued and not sent immediately such as in +@@ -683,6 +707,9 @@ struct tcpm_port { + + struct pd_identifier pd_ident; + struct sink_caps_ext_data sink_caps_ext; ++ struct power_supply **fixed_batt; ++ u32 fixed_batt_cnt; ++ u32 batt_request_id; + #ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + struct mutex logbuffer_lock; /* log buffer access lock */ +@@ -771,6 +798,9 @@ static const char * const pd_rev[] = { + #define tcpm_wait_for_discharge(port) \ + (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0) + ++#define tcpm_can_send_vdm(state) \ ++ ((state == SRC_READY || state == SNK_READY || state == SRC_VDM_IDENTITY_REQUEST)) ++ + static enum tcpm_state tcpm_default_state(struct tcpm_port *port) + { + if (port->port_type == TYPEC_PORT_DRP) { +@@ -1470,6 +1500,20 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + ++static void tcpm_get_fixed_batt(struct tcpm_port *port) ++{ ++ int ret; ++ ++ if (!port->self_powered || port->fixed_batt_cnt > 0) ++ return; ++ ++ ret = power_supply_get_system_batteries(port->dev, &port->fixed_batt); ++ if (ret < 0) ++ tcpm_log(port, "Failed to get battery array, ret=%d", ret); ++ else ++ port->fixed_batt_cnt = ret; ++} ++ + static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + { + u16 operating_snk_watt = port->operating_snk_mw / 1000; +@@ -1482,6 +1526,8 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + if (!port->self_powered) + data->spr_op_pdp = operating_snk_watt; + ++ tcpm_get_fixed_batt(port); ++ + /* + * SPR Sink Minimum PDP indicates the minimum power required to operate + * a sink device in its lowest level of functionality without requiring +@@ -1507,6 +1553,7 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + skedb.load_step = data->load_step; + skedb.load_char = cpu_to_le16(data->load_char); + skedb.compliance = data->compliance; ++ skedb.batt_info = min(port->fixed_batt_cnt, MAX_NUM_FIXED_BATT); + skedb.modes = data->modes; + skedb.spr_min_pdp = data->spr_min_pdp; + skedb.spr_op_pdp = data->spr_op_pdp; +@@ -1525,6 +1572,153 @@ static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) + port->message_id, + data_obj_cnt, + 1 /* Denotes if ext header */)); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static u16 tcpm_charge_to_energy(int charge, int voltage) ++{ ++ u64 energy = div_u64((u64)charge * voltage, 1000000); ++ ++ /* Battery telemetry is reported in increments of 0.1Wh */ ++ return (u16)UW_TO_W(energy * 10); ++} ++ ++static int tcpm_pd_send_batt_status(struct tcpm_port *port) ++{ ++ u16 present_charge = BATTERY_PROPERTY_UNKNOWN; ++ bool batt_present = false, invalid_ref = true; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct power_supply *batt; ++ u8 charging_status = 0; ++ struct pd_message msg; ++ int ret, charge_now; ++ u32 bsdo; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_status; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_PRESENT, &val); ++ if (ret) ++ tcpm_log(port, ++ "Failed to fetch power_supply_prop_present ret %d", ++ ret); ++ else ++ batt_present = val.intval > 0; ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_CHARGE_NOW, ++ &val); ++ if (!ret) { ++ charge_now = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) ++ present_charge = tcpm_charge_to_energy(charge_now, ++ val.intval); ++ } ++ ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_STATUS, &val); ++ if (!ret) { ++ switch (val.intval) { ++ case POWER_SUPPLY_STATUS_CHARGING: ++ charging_status = BSDO_BATTERY_INFO_CHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_DISCHARGING: ++ charging_status = BSDO_BATTERY_INFO_DISCHARGING; ++ break; ++ case POWER_SUPPLY_STATUS_NOT_CHARGING: ++ case POWER_SUPPLY_STATUS_FULL: ++ charging_status = BSDO_BATTERY_INFO_IDLE; ++ break; ++ default: ++ charging_status = BSDO_BATTERY_INFO_RSVD; ++ break; ++ } ++ } ++ ++send_status: ++ ++ bsdo = BSDO(present_charge, charging_status, batt_present, invalid_ref); ++ msg.payload[0] = cpu_to_le32(bsdo); ++ msg.header = PD_HEADER_LE(PD_DATA_BATT_STATUS, ++ port->pwr_role, ++ port->data_role, ++ port->negotiated_rev, ++ port->message_id, ++ 1); ++ ++ return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); ++} ++ ++static int tcpm_pd_send_batt_cap(struct tcpm_port *port) ++{ ++ u16 design_cap = BATTERY_PROPERTY_UNKNOWN; ++ u16 charge_cap = BATTERY_PROPERTY_UNKNOWN; ++ u32 batt_id = port->batt_request_id; ++ union power_supply_propval val; ++ struct batt_cap_ext_msg bcdb; ++ struct power_supply *batt; ++ bool invalid_ref = true; ++ struct pd_message msg; ++ u8 data_obj_cnt; ++ int ret, vol; ++ ++ tcpm_get_fixed_batt(port); ++ memset(&msg, 0, sizeof(msg)); ++ ++ if (batt_id >= port->fixed_batt_cnt || batt_id >= MAX_NUM_FIXED_BATT) ++ goto send_cap; ++ ++ invalid_ref = false; ++ batt = port->fixed_batt[batt_id]; ++ ret = power_supply_get_property(batt, POWER_SUPPLY_PROP_VOLTAGE_AVG, ++ &val); ++ if (!ret) { ++ vol = val.intval; ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, ++ &val); ++ if (!ret) ++ design_cap = tcpm_charge_to_energy(val.intval, vol); ++ ++ ret = power_supply_get_property(batt, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ &val); ++ if (!ret) ++ charge_cap = tcpm_charge_to_energy(val.intval, vol); ++ } ++ ++send_cap: ++ ++ /* ++ * As per the USB PD Rev3.1 v1.8 spec, if a battery VID (assigned by the ++ * USB-IF) does not exist or an invalid battery reference is made by the ++ * requestor, then set the VID field to 0xffff. If the VID field is ++ * 0xffff, set the PID field to 0. ++ */ ++ bcdb.vid = BATTERY_PROPERTY_UNKNOWN; ++ bcdb.pid = 0; ++ bcdb.batt_design_cap = cpu_to_le16(design_cap); ++ bcdb.batt_last_chg_cap = cpu_to_le16(charge_cap); ++ bcdb.batt_type = invalid_ref ? BATT_CAP_BATT_TYPE_INVALID_REF : 0; ++ memcpy(msg.ext_msg.data, &bcdb, sizeof(bcdb)); ++ msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(bcdb), ++ 0, /* Denotes if request chunk */ ++ 0, /* Chunk number */ ++ 1 /* Chunked */); ++ ++ data_obj_cnt = count_chunked_data_objs(sizeof(bcdb)); ++ msg.header = PD_HEADER_EXT_LE(PD_EXT_BATT_CAP, port->pwr_role, ++ port->data_role, port->negotiated_rev, ++ port->message_id, data_obj_cnt); ++ + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + } + +@@ -1559,13 +1753,19 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + } + } + +-static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms) ++static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) ++{ ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_cancel_work_sync(&port->vdm_discovery_work); ++} ++ ++static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) + { + if (delay_ms) { +- hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); ++ hrtimer_start(&port->vdm_discovery_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL); + } else { +- hrtimer_cancel(&port->send_discover_timer); +- kthread_queue_work(port->wq, &port->send_discover_work); ++ hrtimer_cancel(&port->vdm_discovery_timer); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + } + } + +@@ -1773,16 +1973,11 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, + WARN_ON(!mutex_is_locked(&port->lock)); + + /* If is sending discover_identity, handle received message first */ +- if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { +- if (tx_sop_type == TCPC_TX_SOP_PRIME) +- port->send_discover_prime = true; +- else +- port->send_discover = true; +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); +- } else { ++ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); ++ else + /* Make sure we are not still processing a previous VDM packet */ + WARN_ON(port->vdm_state > VDM_STATE_DONE); +- } + + port->vdo_count = cnt + 1; + port->vdo_data[0] = header; +@@ -1805,8 +2000,7 @@ static void tcpm_queue_vdm_work(struct kthread_work *work) + struct tcpm_port *port = event->port; + + mutex_lock(&port->lock); +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + tcpm_log_force(port, "dropping altmode_vdm_event"); + goto port_unlock; + } +@@ -2034,7 +2228,7 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) + if (!port->partner) + return; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_partner_register_altmode(port->partner, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2052,9 +2246,10 @@ static void tcpm_register_plug_altmodes(struct tcpm_port *port) + struct typec_altmode *altmode; + int i; + +- typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); ++ typec_plug_set_num_altmodes(port->plug_prime, ++ min(modep->altmodes, ALTMODE_DISCOVERY_MAX)); + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + altmode = typec_plug_register_altmode(port->plug_prime, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { +@@ -2154,6 +2349,19 @@ static bool tcpm_cable_vdm_supported(struct tcpm_port *port) + tcpm_can_communicate_sop_prime(port); + } + ++static void tcpm_update_vdm_discovery_state(struct tcpm_port *port, ++ enum vdm_discovery_states new_state) ++{ ++ enum vdm_discovery_states old_state = port->vdm_discovery_state; ++ ++ if (old_state != new_state) ++ tcpm_log_force(port, "vdm discovery state changed: %s -> %s", ++ vdm_discovery_state_strings[old_state], ++ vdm_discovery_state_strings[new_state]); ++ ++ port->vdm_discovery_state = new_state; ++} ++ + static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) +@@ -2171,6 +2379,7 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + +@@ -2179,10 +2388,12 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(USB_SID_PD, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_MODES); + return 1; + } + + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + modep = &port->mode_data_prime; + modep->svid_index++; +@@ -2193,11 +2404,13 @@ static int tcpm_handle_discover_mode(struct tcpm_port *port, u32 *response, + response[0] = VDO(svid, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_MODES); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_MODES); + return 1; + } + + tcpm_register_plug_altmodes(port); + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + return 0; +@@ -2376,18 +2589,18 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + typec_cable_set_svdm_version(port->cable, + svdm_version); + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_PARTNER_IDENT); ++ + /* 6.4.4.3.1 */ + svdm_consume_identity(port, p, cnt); + /* Attempt Vconn swap, delay SOP' discovery if necessary */ + if (tcpm_attempt_vconn_swap_discovery(port)) { +- port->send_discover_prime = true; + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (!ret) + return 0; + /* Cannot perform Vconn swap */ + port->upcoming_state = INVALID_STATE; +- port->send_discover_prime = false; + } + + /* +@@ -2398,7 +2611,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + if (IS_ERR_OR_NULL(port->cable) && + tcpm_can_communicate_sop_prime(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; +- port->send_discover_prime = true; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_IDENT); +@@ -2426,6 +2638,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + return 0; + } ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, +@@ -2445,16 +2658,27 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + rlen = 1; + } else { + if (rx_sop_type == TCPC_TX_SOP) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_PARTNER_SVIDS); + if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_CABLE_SVIDS); + if (modep_prime->nsvids) { + response[0] = VDO(modep_prime->svids[0], 1, + svdm_version, CMD_DISCOVER_MODES); + rlen = 1; ++ } else { ++ tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, ++ VDM_DISCOVERY_COMPLETE); + } + } + } +@@ -2505,8 +2729,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, + case CMDT_RSP_NAK: + tcpm_ams_finish(port); + switch (cmd) { ++ /* ++ * The cable is not allowed to respond with NAK so this must've happened over SOP ++ */ + case CMD_DISCOVER_IDENT: + case CMD_DISCOVER_SVID: ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ break; + case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): + break; + case CMD_DISCOVER_MODES: +@@ -2686,44 +2915,6 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, + port->vdm_sm_running = false; + } + +-static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, +- const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) +-{ +- int svdm_version; +- u32 header; +- +- switch (tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- /* +- * If the port partner is discovered, then the port partner's +- * SVDM Version will be returned +- */ +- svdm_version = typec_get_cable_svdm_version(port->typec_port); +- if (svdm_version < 0) +- svdm_version = SVDM_VER_MAX; +- break; +- case TCPC_TX_SOP: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- default: +- svdm_version = typec_get_negotiated_svdm_version(port->typec_port); +- if (svdm_version < 0) +- return; +- break; +- } +- +- if (WARN_ON(count > VDO_MAX_SIZE - 1)) +- count = VDO_MAX_SIZE - 1; +- +- /* set VDM header with VID & CMD */ +- header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? +- 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), +- svdm_version, cmd); +- tcpm_queue_vdm(port, header, data, count, tx_sop_type); +-} +- + static unsigned int vdm_ready_timeout(u32 vdm_hdr) + { + unsigned int timeout; +@@ -2769,8 +2960,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ +- if (port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) { ++ if (!tcpm_can_send_vdm(port->state)) { + port->vdm_sm_running = false; + break; + } +@@ -2780,22 +2970,10 @@ static void vdm_run_state_machine(struct tcpm_port *port) + switch (PD_VDO_CMD(vdo_hdr)) { + case CMD_DISCOVER_IDENT: + res = tcpm_ams_start(port, DISCOVER_IDENTITY); +- if (res == 0) { +- switch (port->tx_sop_type) { +- case TCPC_TX_SOP_PRIME: +- port->send_discover_prime = false; +- break; +- case TCPC_TX_SOP: +- port->send_discover = false; +- break; +- default: +- port->send_discover = false; +- break; +- } +- } else if (res == -EAGAIN) { ++ if (res == -EAGAIN) { + port->vdo_data[0] = 0; +- mod_send_discover_delayed_work(port, +- SEND_DISCOVER_RETRY_MS); ++ mod_vdm_discovery_delayed_work(port, ++ SEND_DISCOVERY_VDM_RETRY_MS); + } + break; + case CMD_DISCOVER_SVID: +@@ -2853,6 +3031,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + port->vdm_state = VDM_STATE_DONE; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* +@@ -2869,6 +3048,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) + tcpm_ams_finish(port); + } else { + tcpm_ams_finish(port); ++ port->vdo_data[0] = 0; + if (port->tx_sop_type == TCPC_TX_SOP) + break; + /* Handle SOP' Transmission Errors */ +@@ -2878,11 +3058,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + * discovery process on SOP only. + */ + case CMD_DISCOVER_IDENT: +- port->vdo_data[0] = 0; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version( + port->typec_port), + CMD_DISCOVER_SVID); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); + tcpm_queue_vdm(port, response[0], &response[1], + 0, TCPC_TX_SOP); + break; +@@ -2892,9 +3072,11 @@ static void vdm_run_state_machine(struct tcpm_port *port) + */ + case CMD_DISCOVER_SVID: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + case CMD_DISCOVER_MODES: + tcpm_register_partner_altmodes(port); ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + break; + default: + break; +@@ -3807,7 +3989,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3823,7 +4006,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + } else { +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3832,7 +4016,8 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, + } + break; + case PD_CTRL_VCONN_SWAP: +- if (port->send_discover && port->negotiated_rev < PD_REV30) { ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN && ++ port->negotiated_rev < PD_REV30) { + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } +@@ -3891,6 +4076,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + { + enum pd_ext_msg_type type = pd_header_type_le(msg->header); + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); ++ const struct pd_chunked_ext_message_data *ext_msg = &msg->ext_msg; + + /* stopping VDM state machine if interrupted by other Messages */ + if (tcpm_vdm_ams(port)) { +@@ -3899,7 +4085,7 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + mod_vdm_delayed_work(port, 0); + } + +- if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) { ++ if (!(le16_to_cpu(ext_msg->header) & PD_EXT_HDR_CHUNKED)) { + tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); + tcpm_log(port, "Unchunked extended messages unsupported"); + return; +@@ -3924,9 +4110,25 @@ static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + NONE_AMS, 0); + } + break; +- case PD_EXT_SOURCE_CAP_EXT: +- case PD_EXT_GET_BATT_CAP: + case PD_EXT_GET_BATT_STATUS: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_DATA_BATT_STATUS, ++ GETTING_BATTERY_STATUS); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_GET_BATT_CAP: ++ if (data_size >= 1) { ++ port->batt_request_id = ext_msg->data[0]; ++ tcpm_pd_handle_msg(port, PD_MSG_EXT_BATT_CAP, ++ GETTING_BATTERY_CAPABILITIES); ++ } else { ++ tcpm_set_state(port, SOFT_RESET_SEND, 0); ++ } ++ break; ++ case PD_EXT_SOURCE_CAP_EXT: + case PD_EXT_BATT_CAP: + case PD_EXT_GET_MANUFACTURER_INFO: + case PD_EXT_MANUFACTURER_INFO: +@@ -4137,6 +4339,22 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) + ret); + tcpm_ams_finish(port); + break; ++ case PD_MSG_DATA_BATT_STATUS: ++ ret = tcpm_pd_send_batt_status(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery status ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; ++ case PD_MSG_EXT_BATT_CAP: ++ ret = tcpm_pd_send_batt_cap(port); ++ if (ret) ++ tcpm_log(port, ++ "Failed to send battery cap ret=%d", ++ ret); ++ tcpm_ams_finish(port); ++ break; + default: + break; + } +@@ -4853,8 +5071,7 @@ static int tcpm_src_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + +@@ -4896,11 +5113,11 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) + struct pd_mode_data *modep_prime = &port->mode_data_prime; + int i; + +- for (i = 0; i < modep->altmodes; i++) { ++ for (i = 0; i < modep->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->partner_altmode[i]); + port->partner_altmode[i] = NULL; + } +- for (i = 0; i < modep_prime->altmodes; i++) { ++ for (i = 0; i < modep_prime->altmodes && i < ALTMODE_DISCOVERY_MAX; i++) { + typec_unregister_altmode(port->plug_prime_altmode[i]); + port->plug_prime_altmode[i] = NULL; + } +@@ -4931,6 +5148,8 @@ static void tcpm_reset_port(struct tcpm_port *port) + port->in_ams = false; + port->ams = NONE_AMS; + port->vdm_sm_running = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + tcpm_unregister_altmodes(port); + tcpm_typec_disconnect(port); + port->attached = false; +@@ -5014,8 +5233,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) + port->partner = NULL; + + port->attached = true; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + + return 0; + } +@@ -5422,16 +5640,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + /* +@@ -5814,16 +6027,11 @@ static void run_state_machine(struct tcpm_port *port) + * as well. + */ + if (port->explicit_contract) { +- if (port->send_discover_prime) { +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- } else { +- port->tx_sop_type = TCPC_TX_SOP; ++ if (port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + tcpm_set_initial_svdm_version(port); +- } +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + } else { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); + } + + power_supply_changed(port->psy); +@@ -5869,8 +6077,8 @@ static void run_state_machine(struct tcpm_port *port) + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_unregister_altmodes(port); + port->nr_sink_caps = 0; +- port->send_discover = true; +- port->send_discover_prime = false; ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); ++ mod_vdm_discovery_cancel_delayed_work(port); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, + PD_T_PS_HARD_RESET); +@@ -6017,25 +6225,15 @@ static void run_state_machine(struct tcpm_port *port) + /* DR_Swap states */ + case DR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case DR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); +- if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { +- port->send_discover = true; +- port->send_discover_prime = false; +- } + tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); + break; + case DR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); +- port->send_discover = false; +- port->send_discover_prime = false; + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6047,6 +6245,8 @@ static void run_state_machine(struct tcpm_port *port) + else + tcpm_set_roles(port, true, TYPEC_STATE_USB, port->pwr_role, + TYPEC_HOST); ++ if (port->data_role == TYPEC_HOST || port->negotiated_rev > PD_REV20) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_UNKNOWN); + tcpm_ams_finish(port); + tcpm_set_state(port, ready_state(port), 0); + break; +@@ -6330,9 +6530,7 @@ static void run_state_machine(struct tcpm_port *port) + + /* Cable states */ + case SRC_VDM_IDENTITY_REQUEST: +- port->send_discover_prime = true; +- port->tx_sop_type = TCPC_TX_SOP_PRIME; +- mod_send_discover_delayed_work(port, 0); ++ mod_vdm_discovery_delayed_work(port, 0); + port->upcoming_state = SRC_SEND_CAPABILITIES; + break; + +@@ -7052,8 +7250,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + goto unlock; + + /* Send when the state machine is idle */ +- if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || +- port->send_discover_prime) ++ if (port->state != SNK_READY || port->vdm_sm_running || ++ port->vdm_discovery_state == VDM_DISCOVERY_UNKNOWN) + goto resched; + + port->upcoming_state = GET_SINK_CAP; +@@ -7070,29 +7268,160 @@ static void tcpm_enable_frs_work(struct kthread_work *work) + mutex_unlock(&port->lock); + } + +-static void tcpm_send_discover_work(struct kthread_work *work) ++static void tcpm_vdm_discovery_work(struct kthread_work *work) + { +- struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work); ++ struct tcpm_port *port = container_of(work, struct tcpm_port, vdm_discovery_work); ++ enum tcpm_transmit_type tx_sop_type = TCPC_TX_SOP; ++ struct typec_port *typec = port->typec_port; ++ struct pd_mode_data *modep, *modep_prime; ++ u32 msg[2] = { }; ++ int svdm_version; + + mutex_lock(&port->lock); +- /* No need to send DISCOVER_IDENTITY anymore */ +- if (!port->send_discover && !port->send_discover_prime) +- goto unlock; + +- if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { +- port->send_discover = false; +- port->send_discover_prime = false; ++ tcpm_log_force(port, "%s state [%s]", __func__, ++ vdm_discovery_state_strings[port->vdm_discovery_state]); ++ ++ /* No need to perform work if Discovery process is complete */ ++ if (port->vdm_discovery_state == VDM_DISCOVERY_COMPLETE) + goto unlock; +- } + + /* Retry if the port is not idle */ +- if ((port->state != SRC_READY && port->state != SNK_READY && +- port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { +- mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); ++ if (!tcpm_can_send_vdm(port->state) || port->vdm_sm_running) { ++ mod_vdm_discovery_delayed_work(port, SEND_DISCOVERY_VDM_RETRY_MS); + goto unlock; + } + +- tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); ++ modep = &port->mode_data; ++ modep_prime = &port->mode_data_prime; ++ ++ svdm_version = typec_get_negotiated_svdm_version(typec); ++ ++ switch (port->vdm_discovery_state) { ++ /* ++ * The port has not received a Discover Identity response from the port partner. ++ * ++ * 1. The port will send Discover Identity to the partner over SOP in the SRC_READY and ++ * SNK_READY states if there is an explicit contract ++ * 2. The port will send Discover Identity to the cable over SOP' in the ++ * SRC_VDM_IDENTITY_REQUEST state if capable of doing so. ++ */ ++ case VDM_DISCOVERY_UNKNOWN: ++ /* Can't send Discover Identity, VDM discovery is complete */ ++ if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ ++ if (port->state == SRC_VDM_IDENTITY_REQUEST) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = SVDM_VER_MAX; ++ } ++ ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the port partner. ++ * ++ * 1. The port will send Discover Identity to the cable over SOP' in the SRC_READY and ++ * SNK_READY states if it did not previously discover the cable but is capable of doing ++ * so. ++ * 2. The port will send Discover SVIDs to the partner over SOP in the SRC_READY and ++ * SNK_READY states otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_IDENT: ++ if (tcpm_can_communicate_sop_prime(port) && !port->cable) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_IDENT); ++ } else { ++ if (tcpm_can_communicate_sop_prime(port)) ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_CABLE_IDENT); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } ++ break; ++ /* ++ * The port has received a Discover Identity ACK from the cable. ++ * ++ * 1. The port will send Discover SVIDs to the partner over SOP. ++ */ ++ case VDM_DISCOVERY_CABLE_IDENT: ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the partner or the last SVIDs supported ++ * by the partner. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP if the partner supports ++ * modal operation and valid SVIDs were registered. ++ * 2. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_SVIDS: ++ if (modep->nsvids && supports_modal(port)) { ++ msg[0] = VDO(modep->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the partner for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the port partner over SOP. ++ * 2. The port will send Discover SVIDs over SOP' if the port can communicate over SOP' ++ * and the cable supports VDMs. ++ * 3. The vdm_discovery_state will move to VDM_DISCOVERY_COMPLETE otherwise. ++ */ ++ case VDM_DISCOVERY_PARTNER_MODES: ++ /* Not all modes have been discovered yet */ ++ if (modep->svid_index < modep->nsvids) { ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else if (tcpm_can_communicate_sop_prime(port) && tcpm_cable_vdm_supported(port)) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); ++ } else { ++ tcpm_update_vdm_discovery_state(port, VDM_DISCOVERY_COMPLETE); ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover SVIDs ACK from the cable over SOP'. ++ * ++ * 1. The port will send Discover Modes for the first SVID over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_SVIDS: ++ if (modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[0], 1, svdm_version, CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ /* ++ * The port has received a Discover Modes ACK from the cable for any mode. ++ * ++ * 1. The port will send Discover Modes for the next SVID that has not been discovered to ++ * the cable over SOP'. ++ */ ++ case VDM_DISCOVERY_CABLE_MODES: ++ if (modep_prime->svid_index < modep_prime->nsvids) { ++ tx_sop_type = TCPC_TX_SOP_PRIME; ++ svdm_version = typec_get_cable_svdm_version(typec); ++ msg[0] = VDO(modep_prime->svids[modep->svid_index], 1, svdm_version, ++ CMD_DISCOVER_MODES); ++ } else { ++ goto unlock; ++ } ++ break; ++ default: ++ goto unlock; ++ } ++ ++ if (svdm_version >= 0) ++ tcpm_queue_vdm(port, msg[0], &msg[1], 0, tx_sop_type); + + unlock: + mutex_unlock(&port->lock); +@@ -8508,12 +8837,12 @@ static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer) + return HRTIMER_NORESTART; + } + +-static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer) ++static enum hrtimer_restart vdm_discovery_timer_handler(struct hrtimer *timer) + { +- struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer); ++ struct tcpm_port *port = container_of(timer, struct tcpm_port, vdm_discovery_timer); + + if (port->registered) +- kthread_queue_work(port->wq, &port->send_discover_work); ++ kthread_queue_work(port->wq, &port->vdm_discovery_work); + return HRTIMER_NORESTART; + } + +@@ -8547,14 +8876,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) + kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work); + kthread_init_work(&port->event_work, tcpm_pd_event_handler); + kthread_init_work(&port->enable_frs, tcpm_enable_frs_work); +- kthread_init_work(&port->send_discover_work, tcpm_send_discover_work); ++ kthread_init_work(&port->vdm_discovery_work, tcpm_vdm_discovery_work); + hrtimer_setup(&port->state_machine_timer, state_machine_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + hrtimer_setup(&port->vdm_state_machine_timer, vdm_state_machine_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_setup(&port->enable_frs_timer, enable_frs_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); +- hrtimer_setup(&port->send_discover_timer, send_discover_timer_handler, CLOCK_MONOTONIC, ++ hrtimer_setup(&port->vdm_discovery_timer, vdm_discovery_timer_handler, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + spin_lock_init(&port->pd_event_lock); +@@ -8649,11 +8978,12 @@ void tcpm_unregister_port(struct tcpm_port *port) + port->registered = false; + kthread_destroy_worker(port->wq); + +- hrtimer_cancel(&port->send_discover_timer); ++ hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); + hrtimer_cancel(&port->vdm_state_machine_timer); + hrtimer_cancel(&port->state_machine_timer); + ++ power_supply_put_system_batteries(port->fixed_batt, port->fixed_batt_cnt); + tcpm_reset_port(port); + + tcpm_port_unregister_pd(port); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -129,6 +129,7 @@ enum { + TPS_MODE_BIST, + TPS_MODE_DISC, + TPS_MODE_PTCH, ++ TPS_MODE_APP1, + }; + + static const char *const modes[] = { +@@ -137,6 +138,7 @@ static const char *const modes[] = { + [TPS_MODE_BIST] = "BIST", + [TPS_MODE_DISC] = "DISC", + [TPS_MODE_PTCH] = "PTCH", ++ [TPS_MODE_APP1] = "APP1", + }; + + /* Unrecognized commands will be replaced with "!CMD" */ +@@ -158,6 +160,7 @@ struct tipd_data { + int (*init)(struct tps6598x *tps); + int (*switch_power_state)(struct tps6598x *tps, u8 target_state); + bool (*read_data_status)(struct tps6598x *tps); ++ bool (*read_power_status)(struct tps6598x *tps); + int (*reset)(struct tps6598x *tps); + int (*connect)(struct tps6598x *tps, u32 status); + }; +@@ -629,6 +632,34 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) + return true; + } + ++/* ++ * TPS66993 deprecated Power_Status register (0x3F). BC1.2 is not supported ++ * and the remaining bits are redundant with STATUS register (0x1A). ++ * Synthesize pwr_status from the already-read STATUS register. ++ */ ++static bool tps66993_read_power_status(struct tps6598x *tps) ++{ ++ u16 pwr_status = 0; ++ ++ /* Same masks as TPS_POWER_STATUS_CONNECTION() / SOURCESINK() / PWROPMODE() in tps6598x.h */ ++ if (tps->status & TPS_STATUS_PLUG_PRESENT) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_CONNECTION_MASK, 1); ++ ++ /* SOURCESINK: 1=sink; STATUS.PortRole 1=source, opposite convention */ ++ if (!TPS_STATUS_TO_TYPEC_PORTROLE(tps->status)) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_SOURCESINK_MASK, 1); ++ ++ if (TPS_STATUS_VBUS_STATUS(tps->status) == TPS_STATUS_VBUS_STATUS_PD) ++ pwr_status |= FIELD_PREP(TPS_POWER_STATUS_TYPEC_CURRENT_MASK, ++ TPS_POWER_STATUS_TYPEC_CURRENT_PD); ++ ++ tps->pwr_status = pwr_status; ++ ++ tps->data->trace_power_status(pwr_status); ++ ++ return true; ++} ++ + static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) + { + int ret; +@@ -906,7 +937,7 @@ static irqreturn_t cd321x_interrupt(int irq, void *data) + goto err_unlock; + + if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE) { +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) { + if (tps6598x_read_partner_identity(tps)) { +@@ -961,7 +992,7 @@ static irqreturn_t tps25750_interrupt(int irq, void *data) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_clear_ints; + + if (event[0] & TPS_REG_INT_DATA_STATUS_UPDATE) +@@ -1034,17 +1065,25 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data) + if (!tps6598x_read_status(tps, &status)) + goto err_unlock; + ++ tps->status = status; ++ + if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE) +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unlock; + + if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE) + if (!tps->data->read_data_status(tps)) + goto err_unlock; + +- /* Handle plug insert or removal */ +- if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) ++ /* ++ * Refresh power status before connect - needed for TPS66993 which ++ * synthesizes pwr_status from STATUS and never gets POWER_STATUS_UPDATE. ++ */ ++ if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT) { ++ if (!tps->data->read_power_status(tps)) ++ goto err_unlock; + tps6598x_handle_plug_event(tps, status); ++ } + + err_unlock: + mutex_unlock(&tps->lock); +@@ -1080,6 +1119,7 @@ static int tps6598x_check_mode(struct tps6598x *tps) + + switch (ret) { + case TPS_MODE_APP: ++ case TPS_MODE_APP1: + case TPS_MODE_PTCH: + return ret; + case TPS_MODE_BOOT: +@@ -1753,7 +1793,7 @@ static int tps6598x_probe(struct i2c_client *client) + struct tps6598x *tps; + struct fwnode_handle *fwnode; + u32 status; +- u32 vid; ++ u32 vid = 0; + int ret; + + data = i2c_get_match_data(client); +@@ -1781,8 +1821,11 @@ static int tps6598x_probe(struct i2c_client *client) + + if (!device_is_compatible(tps->dev, "ti,tps25750")) { + ret = tps6598x_read32(tps, TPS_REG_VID, &vid); +- if (ret < 0 || !vid) ++ if (ret < 0 || !vid) { ++ dev_err(tps->dev, "failed to read vendor ID: %d, vid: %#x\n", ++ ret, vid); + return -ENODEV; ++ } + } + + /* +@@ -1818,6 +1861,8 @@ static int tps6598x_probe(struct i2c_client *client) + goto err_clear_mask; + } + ++ tps->status = status; ++ + /* + * This fwnode has a "compatible" property, but is never populated as a + * struct device. Instead we simply parse it to read the properties. +@@ -1845,7 +1890,7 @@ static int tps6598x_probe(struct i2c_client *client) + + if (status & TPS_STATUS_PLUG_PRESENT) { + ret = -EINVAL; +- if (!tps6598x_read_power_status(tps)) ++ if (!tps->data->read_power_status(tps)) + goto err_unregister_port; + if (!tps->data->read_data_status(tps)) + goto err_unregister_port; +@@ -1860,7 +1905,7 @@ static int tps6598x_probe(struct i2c_client *client) + IRQF_SHARED | IRQF_ONESHOT, + dev_name(&client->dev), tps); + } else { +- dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n"); ++ dev_dbg(tps->dev, "no IRQ specified, using polling mode\n"); + INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); + queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, + msecs_to_jiffies(POLL_INTERVAL)); +@@ -1987,6 +2032,7 @@ static const struct tipd_data cd321x_data = { + .trace_status = trace_tps6598x_status, + .init = cd321x_init, + .read_data_status = cd321x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, +@@ -2006,6 +2052,25 @@ static const struct tipd_data tps6598x_data = { + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, ++ .reset = tps6598x_reset, ++ .connect = tps6598x_connect, ++}; ++ ++static const struct tipd_data tps66993_data = { ++ .irq_handler = tps6598x_interrupt, ++ .irq_mask1 = TPS_REG_INT_DATA_STATUS_UPDATE | ++ TPS_REG_INT_PLUG_EVENT, ++ .tps_struct_size = sizeof(struct tps6598x), ++ .register_port = tps6598x_register_port, ++ .unregister_port = tps6598x_unregister_port, ++ .trace_data_status = trace_tps6598x_data_status, ++ .trace_power_status = trace_tps6598x_power_status, ++ .trace_status = trace_tps6598x_status, ++ .apply_patch = tps6598x_apply_patch, ++ .init = tps6598x_init, ++ .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps66993_read_power_status, + .reset = tps6598x_reset, + .connect = tps6598x_connect, + }; +@@ -2024,12 +2089,14 @@ static const struct tipd_data tps25750_data = { + .apply_patch = tps25750_apply_patch, + .init = tps25750_init, + .read_data_status = tps6598x_read_data_status, ++ .read_power_status = tps6598x_read_power_status, + .reset = tps25750_reset, + .connect = tps6598x_connect, + }; + + static const struct of_device_id tps6598x_of_match[] = { + { .compatible = "ti,tps6598x", &tps6598x_data}, ++ { .compatible = "ti,tps66993", &tps66993_data}, + { .compatible = "apple,cd321x", &cd321x_data}, + { .compatible = "ti,tps25750", &tps25750_data}, + {} +diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h +--- a/drivers/usb/typec/tipd/tps6598x.h ++++ b/drivers/usb/typec/tipd/tps6598x.h +@@ -142,9 +142,13 @@ + #define TPS_SYSTEM_POWER_STATE_S4 0x04 + #define TPS_SYSTEM_POWER_STATE_S5 0x05 + +-/* TPS_REG_POWER_STATUS bits */ +-#define TPS_POWER_STATUS_CONNECTION(x) TPS_FIELD_GET(BIT(0), (x)) +-#define TPS_POWER_STATUS_SOURCESINK(x) TPS_FIELD_GET(BIT(1), (x)) ++/* TPS_REG_POWER_STATUS bits (masks shared by TPS_FIELD_GET accessors and FIELD_PREP) */ ++#define TPS_POWER_STATUS_CONNECTION_MASK BIT(0) ++#define TPS_POWER_STATUS_SOURCESINK_MASK BIT(1) ++#define TPS_POWER_STATUS_CONNECTION(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_CONNECTION_MASK, (x)) ++#define TPS_POWER_STATUS_SOURCESINK(x) \ ++ TPS_FIELD_GET(TPS_POWER_STATUS_SOURCESINK_MASK, (x)) + #define TPS_POWER_STATUS_BC12_DET(x) TPS_FIELD_GET(BIT(2), (x)) + + #define TPS_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2) +diff --git a/drivers/usb/typec/ucsi/debugfs.c b/drivers/usb/typec/ucsi/debugfs.c +--- a/drivers/usb/typec/ucsi/debugfs.c ++++ b/drivers/usb/typec/ucsi/debugfs.c +@@ -82,8 +82,8 @@ static int ucsi_resp_show(struct seq_file *s, void *not_used) + if (ucsi->debugfs->status) + return ucsi->debugfs->status; + +- seq_printf(s, "0x%016llx%016llx\n", ucsi->debugfs->response.high, +- ucsi->debugfs->response.low); ++ seq_printf(s, "0x%016llx%016llx%016llx\n", ucsi->debugfs->response.ext, ++ ucsi->debugfs->response.high, ucsi->debugfs->response.low); + return 0; + } + DEFINE_SHOW_ATTRIBUTE(ucsi_resp); +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1102,8 +1102,8 @@ static int ucsi_register_plug(struct ucsi_connector *con) + plug = typec_register_plug(con->cable, &desc); + if (IS_ERR(plug)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register plug (%ld)\n", con->num, +- PTR_ERR(plug)); ++ "con%d: failed to register plug (%pe)\n", con->num, ++ plug); + return PTR_ERR(plug); + } + +@@ -1161,8 +1161,8 @@ static int ucsi_register_cable(struct ucsi_connector *con) + cable = typec_register_cable(con->port, &desc); + if (IS_ERR(cable)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register cable (%ld)\n", con->num, +- PTR_ERR(cable)); ++ "con%d: failed to register cable (%pe)\n", con->num, ++ cable); + return PTR_ERR(cable); + } + +@@ -1291,8 +1291,8 @@ static int ucsi_register_partner(struct ucsi_connector *con) + partner = typec_register_partner(con->port, &desc); + if (IS_ERR(partner)) { + dev_err(con->ucsi->dev, +- "con%d: failed to register partner (%ld)\n", con->num, +- PTR_ERR(partner)); ++ "con%d: failed to register partner (%pe)\n", con->num, ++ partner); + return PTR_ERR(partner); + } + +@@ -2221,7 +2221,7 @@ static void ucsi_init_work(struct work_struct *work) + return; + } + +- queue_delayed_work(system_long_wq, &ucsi->work, ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); + } + } +@@ -2345,7 +2345,7 @@ int ucsi_register(struct ucsi *ucsi) + UCSI_BCD_GET_MINOR(ucsi->version), + UCSI_BCD_GET_SUBMINOR(ucsi->version)); + +- queue_delayed_work(system_long_wq, &ucsi->work, 0); ++ queue_delayed_work(system_dfl_long_wq, &ucsi->work, 0); + + ucsi_debugfs_register(ucsi); + return 0; +diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h +--- a/drivers/usb/typec/ucsi/ucsi.h ++++ b/drivers/usb/typec/ucsi/ucsi.h +@@ -466,6 +466,7 @@ struct ucsi_debugfs_entry { + struct ucsi_data { + u64 low; + u64 high; ++ u64 ext; + } response; + int status; + u8 message_out[MESSAGE_OUT_MAX_LEN]; +diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h +--- a/include/linux/power_supply.h ++++ b/include/linux/power_supply.h +@@ -806,11 +806,26 @@ extern int power_supply_reg_notifier(struct notifier_block *nb); + extern void power_supply_unreg_notifier(struct notifier_block *nb); + #if IS_ENABLED(CONFIG_POWER_SUPPLY) + extern struct power_supply *power_supply_get_by_name(const char *name); ++extern int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys); ++extern void power_supply_put_system_batteries(struct power_supply **psys, int count); + extern void power_supply_put(struct power_supply *psy); + #else + static inline void power_supply_put(struct power_supply *psy) {} + static inline struct power_supply *power_supply_get_by_name(const char *name) + { return NULL; } ++static inline int __must_check power_supply_get_system_batteries(struct device *dev, ++ struct power_supply ***psys) ++{ ++ if (psys) ++ *psys = NULL; ++ return 0; ++} ++ ++static inline void power_supply_put_system_batteries(struct power_supply **psys, ++ int count) ++{ ++} + #endif + extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, + const char *property); +diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h +--- a/include/linux/usb/pd.h ++++ b/include/linux/usb/pd.h +@@ -106,6 +106,9 @@ enum pd_ext_msg_type { + #define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) + ++#define PD_HEADER_EXT_LE(type, pwr, data, rev, id, cnt) \ ++ cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (1))) ++ + static inline unsigned int pd_header_cnt(u16 header) + { + return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; +@@ -219,6 +222,25 @@ static inline u8 count_chunked_data_objs(u32 size) + return ((size / 4) + (size % 4 ? 1 : 0)); + } + ++/** ++ * batt_cap_ext_msg - Battery capability extended PD message ++ * @vid: Battery Vendor ID (assigned by USB-IF) ++ * @pid: Battery Product ID (assigned by battery or device vendor) ++ * @batt_design_cap: Battery design capacity in 0.1Wh ++ * @batt_last_chg_cap: Battery last full charge capacity in 0.1Wh ++ * @batt_type: Battery Type. bit0 when set indicates invalid battery reference. ++ * Rest of the bits are reserved. ++ */ ++struct batt_cap_ext_msg { ++ __le16 vid; ++ __le16 pid; ++ __le16 batt_design_cap; ++ __le16 batt_last_chg_cap; ++ u8 batt_type; ++} __packed; ++ ++#define BATT_CAP_BATT_TYPE_INVALID_REF BIT(0) ++ + /* Sink Caps Extended Data Block Version */ + #define SKEDB_VER_1_0 1 + +@@ -724,4 +746,33 @@ void usb_power_delivery_unlink_device(struct usb_power_delivery *pd, struct devi + + #endif /* CONFIG_TYPEC */ + ++/* Battery Status Data Object */ ++#define BSDO_PRESENT_CAPACITY GENMASK(31, 16) ++#define BSDO_CHG_STATUS GENMASK(11, 10) ++#define BSDO_BATTERY_PRESENT BIT(9) ++#define BSDO_INVALID_BATTERY_REFERENCE BIT(8) ++ ++/* ++ * Battery Charge Status: Battery Charging Status Values as defined in ++ * "USB PD Spec Rev3.1 Ver1.8", "Table 6-46 Battery Status Data Object (BSDO)". ++ */ ++#define BSDO_BATTERY_INFO_CHARGING 0x0 ++#define BSDO_BATTERY_INFO_DISCHARGING 0x1 ++#define BSDO_BATTERY_INFO_IDLE 0x2 ++#define BSDO_BATTERY_INFO_RSVD 0x3 ++ ++/** ++ * BSDO() - Pack data into Battery Status Data Object format. ++ * @batt_charge: Battery's present state of charge in 0.1WH increment. ++ * @chg_status: Battery charge status. ++ * @batt_present: Indicates that battery is present/attached when set else absent when unset. ++ * @invalid_ref: Indicates that an invalid battery reference was made in the Get_Battery_Status ++ * request. ++ */ ++#define BSDO(batt_charge, chg_status, batt_present, invalid_ref) \ ++ ((FIELD_PREP(BSDO_PRESENT_CAPACITY, batt_charge)) | \ ++ (FIELD_PREP(BSDO_CHG_STATUS, chg_status)) | \ ++ ((batt_present) ? BSDO_BATTERY_PRESENT : 0) | \ ++ ((invalid_ref) ? BSDO_INVALID_BATTERY_REFERENCE : 0)) ++ + #endif /* __LINUX_USB_PD_H */ +diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h +--- a/include/linux/usb/typec.h ++++ b/include/linux/usb/typec.h +@@ -337,6 +337,7 @@ void typec_unregister_cable(struct typec_cable *cable); + struct typec_cable *typec_cable_get(struct typec_port *port); + void typec_cable_put(struct typec_cable *cable); + int typec_cable_is_active(struct typec_cable *cable); ++bool typec_cable_altmode_unsupported(struct typec_altmode *alt); + + struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc); +diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h +--- a/include/uapi/linux/usb/functionfs.h ++++ b/include/uapi/linux/usb/functionfs.h +@@ -25,6 +25,7 @@ enum functionfs_flags { + FUNCTIONFS_EVENTFD = 32, + FUNCTIONFS_ALL_CTRL_RECIP = 64, + FUNCTIONFS_CONFIG0_SETUP = 128, ++ FUNCTIONFS_RW_PROXY_EPS = 256, + }; + + /* Descriptor of an non-audio endpoint */ +@@ -414,4 +415,27 @@ struct usb_functionfs_event { + #define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \ + struct usb_ffs_dmabuf_transfer_req) + ++/* ++ * Enable or disable automatic zero-length packet (ZLP) appending for the ++ * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to ++ * enable. ++ * ++ * When enabled, the kernel will automatically append a ZLP at the end of ++ * a transfer if the payload length is an exact multiple of the endpoint's ++ * max packet size. ++ * ++ * This is useful for compatibility with legacy protocols which require ++ * automatic ZLP appending to data written from userspace. ++ * ++ * This ioctl can only be used on IN endpoints. It can be called at any time ++ * after the FunctionFS instance is active, even before the host has connected ++ * or enabled the endpoint. ++ * ++ * Returns zero on success, or a negative errno value on error: ++ * -ENODEV: The FunctionFS instance is not active. ++ * -EINVAL: The endpoint is not an IN endpoint. ++ * -EFAULT: Invalid user space pointer for the argument. ++ */ ++#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32) ++ + #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ +diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs +--- a/rust/kernel/usb.rs ++++ b/rust/kernel/usb.rs +@@ -24,11 +24,8 @@ + }; + use core::{ + marker::PhantomData, +- mem::{ +- offset_of, +- MaybeUninit, // +- }, +- ptr::NonNull, ++ mem::offset_of, ++ ptr::NonNull, // + }; + + /// An adapter for the registration of USB drivers. +@@ -130,8 +127,7 @@ pub const fn from_id(vendor: u16, product: u16) -> Self { + match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16, + idVendor: vendor, + idProduct: product, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -143,8 +139,7 @@ pub const fn from_device_ver(vendor: u16, product: u16, bcd_lo: u16, bcd_hi: u16 + idProduct: product, + bcdDevice_lo: bcd_lo, + bcdDevice_hi: bcd_hi, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -155,8 +150,7 @@ pub const fn from_device_info(class: u8, subclass: u8, protocol: u8) -> Self { + bDeviceClass: class, + bDeviceSubClass: subclass, + bDeviceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -167,8 +161,7 @@ pub const fn from_interface_info(class: u8, subclass: u8, protocol: u8) -> Self + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -180,8 +173,7 @@ pub const fn from_device_interface_class(vendor: u16, product: u16, class: u8) - + idVendor: vendor, + idProduct: product, + bInterfaceClass: class, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -193,8 +185,7 @@ pub const fn from_device_interface_protocol(vendor: u16, product: u16, protocol: + idVendor: vendor, + idProduct: product, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -206,8 +197,7 @@ pub const fn from_device_interface_number(vendor: u16, product: u16, number: u8) + idVendor: vendor, + idProduct: product, + bInterfaceNumber: number, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + +@@ -227,8 +217,7 @@ pub const fn from_device_and_interface_info( + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, +- // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. +- ..unsafe { MaybeUninit::zeroed().assume_init() } ++ ..pin_init::zeroed() + }) + } + } +@@ -393,6 +382,7 @@ fn as_ref(&self) -> &Device { + + // SAFETY: Instances of `Interface` are always reference-counted. + unsafe impl AlwaysRefCounted for Interface { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Interface` guarantee that `self.as_raw()` + // returns a valid `struct usb_interface` pointer, for which we will +@@ -400,6 +390,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_intf(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) } +@@ -444,6 +435,7 @@ fn as_raw(&self) -> *mut bindings::usb_device { + + // SAFETY: Instances of `Device` are always reference-counted. + unsafe impl AlwaysRefCounted for Device { ++ #[inline] + fn inc_ref(&self) { + // SAFETY: The invariants of `Device` guarantee that `self.as_raw()` + // returns a valid `struct usb_device` pointer, for which we will +@@ -451,6 +443,7 @@ fn inc_ref(&self) { + unsafe { bindings::usb_get_dev(self.as_raw()) }; + } + ++ #[inline] + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) } diff --git a/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch.sig new file mode 100644 index 0000000..d35c194 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0603-usb-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch new file mode 100644 index 0000000..8545f24 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch @@ -0,0 +1,35 @@ +diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c +--- a/drivers/usb/host/xhci-mem.c ++++ b/drivers/usb/host/xhci-mem.c +@@ -2301,7 +2301,7 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int segs, gfp_t flags) + if (!segs) + segs = ERST_DEFAULT_SEGS; + +- max_segs = FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2) << 2; ++ max_segs = BIT(FIELD_GET(HCS_ERST_MAX, xhci->hcs_params2)); + segs = min(segs, max_segs); + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); +diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c +--- a/drivers/usb/host/xhci-ring.c ++++ b/drivers/usb/host/xhci-ring.c +@@ -4328,11 +4328,16 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags, + check_interval(urb, ep_ctx); + + /* +- * Check if this starts the isoc data flow. Relies on hw setting ep ctx +- * state after doorbell ring. Consider adding list_empty(td_list) check ++ * Schedule the URB discontiguously if all previous URBs have completed. ++ * XXX core can't tell if completions are pending but not running yet. + */ +- if (GET_EP_CTX_STATE(ep_ctx) != EP_STATE_RUNNING) ++ if (list_empty(&ep_ring->td_list) && ++ !hcd_periodic_completion_in_progress(xhci_to_hcd(xhci), urb->ep)) { ++ if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_RUNNING) ++ xhci_dbg(xhci, "Unexpected running ring at isoc stream start, uframe: %d\n", ++ xep->next_uframe); + xep->next_uframe = -1; ++ } + + return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch.sig new file mode 100644 index 0000000..d93226e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0604-xhci-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch b/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch new file mode 100644 index 0000000..a0b7740 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch @@ -0,0 +1,186 @@ +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -909,6 +909,10 @@ int tb_domain_init(void) + { + int ret; + ++ ret = tb_pci_rescan_init(); ++ if (ret) ++ return ret; ++ + tb_configfs_init(); + tb_debugfs_init(); + tb_acpi_init(); +@@ -927,6 +931,7 @@ err_xdomain: + err_acpi: + tb_acpi_exit(); + tb_debugfs_exit(); ++ tb_pci_rescan_exit(); + + return ret; + } +@@ -940,4 +945,5 @@ void tb_domain_exit(void) + tb_acpi_exit(); + tb_debugfs_exit(); + tb_configfs_exit(); ++ tb_pci_rescan_exit(); + } +diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c +--- a/drivers/thunderbolt/tb.c ++++ b/drivers/thunderbolt/tb.c +@@ -9,7 +9,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + + #include "tb.h" +@@ -18,6 +20,7 @@ + + #define TB_TIMEOUT 100 /* ms */ + #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ ++#define TB_PCI_RESCAN_DELAY 300 /* ms */ + + /* + * How many time bandwidth allocation request from graphics driver is +@@ -49,6 +52,13 @@ MODULE_PARM_DESC(asym_threshold, + "threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: " + __MODULE_STRING(TB_ASYM_THRESHOLD) ")"); + ++struct tb_pci_rescan { ++ struct delayed_work work; ++ struct kref kref; ++ struct pci_bus *bus; ++ bool stopped; ++}; ++ + /** + * struct tb_cm - Simple Thunderbolt connection manager + * @tunnel_list: List of active tunnels +@@ -67,8 +77,11 @@ struct tb_cm { + bool hotplug_active; + struct delayed_work remove_work; + struct tb_bandwidth_group groups[MAX_GROUPS]; ++ struct tb_pci_rescan *pci_rescan; + }; + ++static struct workqueue_struct *tb_pci_rescan_wq; ++ + static inline struct tb *tcm_to_tb(struct tb_cm *tcm) + { + return ((void *)tcm - sizeof(struct tb)); +@@ -2282,6 +2295,82 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw) + return 0; + } + ++static void tb_pci_rescan_release(struct kref *kref) ++{ ++ struct tb_pci_rescan *rescan = container_of(kref, typeof(*rescan), kref); ++ ++ put_device(&rescan->bus->dev); ++ kfree(rescan); ++} ++ ++static void tb_pci_rescan_work_fn(struct work_struct *work) ++{ ++ struct tb_pci_rescan *rescan = ++ container_of(to_delayed_work(work), typeof(*rescan), work); ++ ++ pci_lock_rescan_remove(); ++ if (!READ_ONCE(rescan->stopped)) ++ pci_rescan_bus(rescan->bus); ++ pci_unlock_rescan_remove(); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_schedule(struct tb *tb) ++{ ++ struct tb_cm *tcm = tb_priv(tb); ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) { ++ struct pci_bus *bus; ++ ++ if (!dev_is_pci(tb->nhi->dev)) ++ return; ++ bus = to_pci_dev(tb->nhi->dev)->bus; ++ if (bus->parent) ++ bus = bus->parent; ++ ++ rescan = kzalloc_obj(*rescan); ++ if (!rescan) ++ return; ++ INIT_DELAYED_WORK(&rescan->work, tb_pci_rescan_work_fn); ++ kref_init(&rescan->kref); ++ get_device(&bus->dev); ++ rescan->bus = bus; ++ tcm->pci_rescan = rescan; ++ } ++ ++ kref_get(&rescan->kref); ++ if (mod_delayed_work(tb_pci_rescan_wq, &rescan->work, ++ msecs_to_jiffies(TB_PCI_RESCAN_DELAY))) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++} ++ ++static void tb_pci_rescan_stop(struct tb_cm *tcm) ++{ ++ struct tb_pci_rescan *rescan = tcm->pci_rescan; ++ ++ if (!rescan) ++ return; ++ ++ WRITE_ONCE(rescan->stopped, true); ++ if (cancel_delayed_work(&rescan->work)) ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ kref_put(&rescan->kref, tb_pci_rescan_release); ++ tcm->pci_rescan = NULL; ++} ++ ++int tb_pci_rescan_init(void) ++{ ++ tb_pci_rescan_wq = alloc_workqueue("thunderbolt_pci_rescan", ++ WQ_FREEZABLE | WQ_UNBOUND, 0); ++ return tb_pci_rescan_wq ? 0 : -ENOMEM; ++} ++ ++void tb_pci_rescan_exit(void) ++{ ++ destroy_workqueue(tb_pci_rescan_wq); ++} ++ + static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + { + struct tb_port *up, *down, *port; +@@ -2323,6 +2412,7 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) + tb_sw_warn(sw, "failed to connect xHCI\n"); + + list_add_tail(&tunnel->list, &tcm->tunnel_list); ++ tb_pci_rescan_schedule(tb); + return 0; + } + +@@ -2957,6 +3047,7 @@ static void tb_stop(struct tb *tb) + struct tb_tunnel *n; + + cancel_delayed_work(&tcm->remove_work); ++ tb_pci_rescan_stop(tcm); + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { + /* +diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h +--- a/drivers/thunderbolt/tb.h ++++ b/drivers/thunderbolt/tb.h +@@ -759,6 +759,8 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, + + struct tb *icm_probe(struct tb_nhi *nhi); + struct tb *tb_probe(struct tb_nhi *nhi); ++int tb_pci_rescan_init(void); ++void tb_pci_rescan_exit(void); + + extern const struct device_type tb_domain_type; + extern const struct device_type tb_retimer_type; diff --git a/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig b/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig new file mode 100644 index 0000000..63eb3c6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0605-thunderbolt-pcie-rescan-after-tunnel.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch new file mode 100644 index 0000000..0d85afa --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch @@ -0,0 +1,239 @@ +diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c +--- a/drivers/thunderbolt/domain.c ++++ b/drivers/thunderbolt/domain.c +@@ -788,21 +788,6 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + transmit_ring, receive_path, receive_ring); + } + +-static void tb_domain_reset_interface(struct tb *tb) +-{ +- struct tb_nhi *nhi = tb->nhi; +- +- if (!nhi->ops->reset_interface) +- return; +- +- guard(mutex)(&tb->lock); +- +- /* The reset clears the ring state so stop the control channel */ +- tb_ctl_stop(tb->ctl); +- nhi->ops->reset_interface(nhi); +- tb_ctl_start(tb->ctl); +-} +- + /** + * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain + * @tb: Domain disabling the DMA paths +@@ -825,20 +810,11 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, + int transmit_path, int transmit_ring, + int receive_path, int receive_ring) + { +- int ret; +- + if (!tb->cm_ops->disconnect_xdomain_paths) + return -ENOTSUPP; + +- ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, ++ return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + transmit_ring, receive_path, receive_ring); +- if (ret) +- return ret; +- +- if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) +- tb_domain_reset_interface(tb); +- +- return 0; + } + + static int disconnect_xdomain(struct device *dev, void *data) +diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c +--- a/drivers/thunderbolt/nhi.c ++++ b/drivers/thunderbolt/nhi.c +@@ -1180,32 +1180,6 @@ static void nhi_reset(struct tb_nhi *nhi) + dev_warn(nhi->dev, "timeout resetting host router\n"); + } + +-/** +- * nhi_reset_interface() - Reset the host interface +- * @nhi: Host interface to reset +- * +- * Brings the registers in the memory BAR back to their default state and +- * clears the End-to-End Flow Control state. The caller is responsible for +- * stopping the control channel over the reset because it clears the ring +- * state as well. +- */ +-void nhi_reset_interface(struct tb_nhi *nhi) +-{ +- u32 val; +- +- val = ioread32(nhi->iobase + REG_CAPS); +- /* Only v1 host interfaces implement the reset */ +- if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) +- return; +- +- dev_dbg(nhi->dev, "issuing host interface reset\n"); +- +- iowrite32(REG_HOST_INTERFACE_RESET_RST, +- nhi->iobase + REG_HOST_INTERFACE_RESET); +- /* Wait for tHIReset (10 ms) to complete */ +- usleep_range(10000, 20000); +-} +- + static struct tb *nhi_select_cm(struct tb_nhi *nhi) + { + struct tb *tb; +diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h +--- a/drivers/thunderbolt/nhi.h ++++ b/drivers/thunderbolt/nhi.h +@@ -36,8 +36,6 @@ irqreturn_t nhi_msi(int irq, void *data); + irqreturn_t ring_msix(int irq, void *data); + int nhi_probe(struct tb_nhi *nhi); + void nhi_shutdown(struct tb_nhi *nhi); +-void nhi_reset_interface(struct tb_nhi *nhi); +- + extern const struct dev_pm_ops nhi_pm_ops; + + /** +@@ -54,7 +52,6 @@ extern const struct dev_pm_ops nhi_pm_ops; + * @release_ring_irq: NHI specific interrupt release hook + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook +- * @reset_interface: Resets the host interface + */ + struct tb_nhi_ops { + int (*init)(struct tb_nhi *nhi); +@@ -69,7 +66,6 @@ struct tb_nhi_ops { + void (*release_ring_irq)(struct tb_ring *ring); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); +- void (*reset_interface)(struct tb_nhi *nhi); + }; + + /* +@@ -120,24 +116,11 @@ struct tb_nhi_ops { + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 + #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 + +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 +-#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b +-#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 +-#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c +-#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d +-#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e +- + #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 + + /* Host interface quirks */ +-#define QUIRK_AUTO_CLEAR_INT BIT(0) +-#define QUIRK_E2E BIT(1) +-#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) ++#define QUIRK_AUTO_CLEAR_INT BIT(0) ++#define QUIRK_E2E BIT(1) + + /* + * Minimal number of vectors when we use MSI-X. Two for control channel +diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h +--- a/drivers/thunderbolt/nhi_regs.h ++++ b/drivers/thunderbolt/nhi_regs.h +@@ -115,10 +115,6 @@ struct ring_desc { + #define REG_CAPS_VERSION_MASK GENMASK(23, 16) + #define REG_CAPS_VERSION_2 0x40 + +-/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ +-#define REG_HOST_INTERFACE_RESET 0x39858 +-#define REG_HOST_INTERFACE_RESET_RST BIT(0) +- + #define REG_DMA_MISC 0x39864 + #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) + #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) +diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c +--- a/drivers/thunderbolt/pci.c ++++ b/drivers/thunderbolt/pci.c +@@ -62,27 +62,6 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) + nhi->quirks |= QUIRK_E2E; + break; + } +- } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { +- switch (pdev->device) { +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: +- case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: +- /* +- * These AMD hosts may hang the Tx ring when the +- * DMA paths are torn down so they need the host +- * interface reset after each teardown. +- */ +- nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; +- break; +- } + } + } + +@@ -279,7 +258,6 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { + .shutdown = nhi_pci_release_irq, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + /* Ice Lake specific NHI operations */ +@@ -463,7 +441,6 @@ static const struct tb_nhi_ops icl_nhi_ops = { + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +- .reset_interface = nhi_reset_interface, + }; + + static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c +--- a/drivers/thunderbolt/quirks.c ++++ b/drivers/thunderbolt/quirks.c +@@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = { + quirk_usb3_maximum_bandwidth }, + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, + quirk_usb3_maximum_bandwidth }, ++ /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */ ++ { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5, ++ quirk_clx_disable }, + /* + * Block Runtime PM in DP redrive mode for Intel Barlow Ridge host + * controllers. +diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c +--- a/drivers/thunderbolt/stream.c ++++ b/drivers/thunderbolt/stream.c +@@ -980,6 +980,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) + } + + mutex_unlock(&sdev->lock); ++ ++ /* Stream handles IOCB_NOWAIT just fine */ ++ file->f_mode |= FMODE_NOWAIT; + return 0; + + err_unlock: +diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c +--- a/drivers/thunderbolt/xdomain.c ++++ b/drivers/thunderbolt/xdomain.c +@@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, + + len += sizeof(res->hdr.xd_hdr) / 4; + len -= sizeof(*res) / 4; ++ if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) { ++ ret = -EINVAL; ++ goto err; ++ } + + if (res->offset != req.offset) { + ret = -EINVAL; diff --git a/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch.sig new file mode 100644 index 0000000..8e90b6c Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0606-thunderbolt-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch new file mode 100644 index 0000000..2e3ea13 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch @@ -0,0 +1,1673 @@ +diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c +--- a/drivers/usb/cdns3/cdns3-gadget.c ++++ b/drivers/usb/cdns3/cdns3-gadget.c +@@ -3269,6 +3269,10 @@ static void cdns3_gadget_exit(struct cdns *cdns) + usb_del_gadget(&priv_dev->gadget); + devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + ++ /* The works can still be queued until the IRQ is freed. */ ++ cancel_work_sync(&priv_dev->pending_status_wq); ++ cancel_work_sync(&priv_dev->aligned_buf_wq); ++ + cdns3_free_all_eps(priv_dev); + + while (!list_empty(&priv_dev->aligned_buf_list)) { +diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c +--- a/drivers/usb/cdns3/cdns3-pci-wrap.c ++++ b/drivers/usb/cdns3/cdns3-pci-wrap.c +@@ -96,6 +96,11 @@ static int cdns3_pci_probe(struct pci_dev *pdev, + + if (pci_is_enabled(func)) { + wrap = pci_get_drvdata(func); ++ if (!wrap) { ++ dev_err(&pdev->dev, ++ "second function not initialized, retrying\n"); ++ return -EPROBE_DEFER; ++ } + } else { + wrap = kzalloc_obj(*wrap); + if (!wrap) +diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c +--- a/drivers/usb/chipidea/ci_hdrc_tegra.c ++++ b/drivers/usb/chipidea/ci_hdrc_tegra.c +@@ -306,8 +306,10 @@ static int tegra_usb_probe(struct platform_device *pdev) + + pm_runtime_enable(&pdev->dev); + err = pm_runtime_resume_and_get(&pdev->dev); +- if (err) ++ if (err) { ++ pm_runtime_disable(&pdev->dev); + return err; ++ } + + if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) + usb->needs_double_reset = true; +diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c +--- a/drivers/usb/class/cdc-acm.c ++++ b/drivers/usb/class/cdc-acm.c +@@ -331,7 +331,6 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) + + spin_lock_irqsave(&acm->read_lock, flags); + acm->ctrlin = newctrl; +- acm->oldcount = acm->iocount; + + if (difference & USB_CDC_SERIAL_STATE_DSR) + acm->iocount.dsr++; +@@ -788,6 +787,9 @@ static void acm_port_shutdown(struct tty_port *port) + usb_autopm_put_interface_async(acm->control); + } + ++ if (acm->disconnected) ++ return; ++ + acm_unpoison_urbs(acm); + + if (acm->quirks & ALWAYS_POLL_CTRL) { +@@ -1027,11 +1029,16 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + +- do { ++ spin_lock_irq(&acm->read_lock); ++ old = acm->iocount; ++ spin_unlock_irq(&acm->read_lock); ++ ++ add_wait_queue(&acm->wioctl, &wait); ++ for (;;) { ++ set_current_state(TASK_INTERRUPTIBLE); ++ + spin_lock_irq(&acm->read_lock); +- old = acm->oldcount; + new = acm->iocount; +- acm->oldcount = new; + spin_unlock_irq(&acm->read_lock); + + if ((arg & TIOCM_DSR) && +@@ -1044,22 +1051,20 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) + old.rng != new.rng) + break; + +- add_wait_queue(&acm->wioctl, &wait); +- set_current_state(TASK_INTERRUPTIBLE); +- schedule(); +- remove_wait_queue(&acm->wioctl, &wait); + if (acm->disconnected) { +- if (arg & TIOCM_CD) +- break; +- else +- rv = -ENODEV; +- } else { +- if (signal_pending(current)) +- rv = -ERESTARTSYS; ++ rv = -ENODEV; ++ break; + } +- } while (!rv); + +- ++ schedule(); ++ ++ if (signal_pending(current)) { ++ rv = -ERESTARTSYS; ++ break; ++ } ++ } ++ __set_current_state(TASK_RUNNING); ++ remove_wait_queue(&acm->wioctl, &wait); + + return rv; + } +diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h +--- a/drivers/usb/class/cdc-acm.h ++++ b/drivers/usb/class/cdc-acm.h +@@ -90,7 +90,6 @@ struct acm { + unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ + unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ +- struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ + unsigned int writesize; /* max packet size for the output bulk endpoint */ + unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ +diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c +--- a/drivers/usb/core/hub.c ++++ b/drivers/usb/core/hub.c +@@ -73,8 +73,11 @@ + /* + * Give SS hubs 200ms time after wake to train downstream links before + * assuming no port activity and allowing hub to runtime suspend back. ++ * Root hubs have no upstream hub whose wake propagation needs to be ++ * accounted for, so they need less time, use 120ms for them. + */ + #define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ ++#define USB_SS_RH_PORT_U0_WAKE_TIME 120 /* ms */ + + /* Protect struct usb_device->state and ->children members + * Note: Both are also protected by ->dev.sem, except that ->state can +@@ -1358,7 +1361,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) + + queue_delayed_work(system_power_efficient_wq, + &hub->post_resume_work, +- msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); ++ msecs_to_jiffies(hdev->parent ? ++ USB_SS_PORT_U0_WAKE_TIME : ++ USB_SS_RH_PORT_U0_WAKE_TIME)); + return; + } + +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1337,7 +1337,8 @@ static void remove_intf_ep_devs(struct usb_interface *intf) + * + * Disables the endpoint for URB submission and nukes all pending URBs. + * If @reset_hardware is set then also deallocates hcd/hardware state +- * for the endpoint. ++ * for the endpoint (clearing both ep_in and ep_out pointers for ++ * bidirectional non-ep0 control endpoints). + */ + void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + bool reset_hardware) +@@ -1358,6 +1359,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, + dev->ep_in[epnum] = NULL; + } + if (ep) { ++ if (reset_hardware && epnum != 0 && ++ usb_endpoint_xfer_control(&ep->desc)) { ++ dev->ep_out[epnum] = NULL; ++ dev->ep_in[epnum] = NULL; ++ } + ep->enabled = 0; + usb_hcd_flush_endpoint(dev, ep); + if (reset_hardware) +diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c +--- a/drivers/usb/dwc2/hcd.c ++++ b/drivers/usb/dwc2/hcd.c +@@ -5082,14 +5082,13 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) + } + + cancel_work_sync(&hsotg->phy_reset_work); +- +- timer_delete(&hsotg->wkp_timer); + } + + static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) + { + /* Turn off all host-specific interrupts */ + dwc2_disable_host_interrupts(hsotg); ++ timer_shutdown_sync(&hsotg->wkp_timer); + + dwc2_hcd_free(hsotg); + } +diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h +--- a/drivers/usb/dwc3/core.h ++++ b/drivers/usb/dwc3/core.h +@@ -49,6 +49,7 @@ + #define DWC3_ENDPOINTS_NUM 32 + #define DWC3_XHCI_RESOURCES_NUM 2 + #define DWC3_ISOC_MAX_RETRIES 5 ++#define DWC3_ERR_RECOVERY_MAX 3 + + #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ + #define DWC3_EVENT_BUFFERS_SIZE 4096 +@@ -841,6 +842,12 @@ enum dwc3_link_state { + DWC3_LINK_STATE_MASK = 0x0f, + }; + ++enum dwc3_err_state { ++ DWC3_ERR_NONE = 0, ++ DWC3_ERR_RECOVERY, ++ DWC3_ERR_UNRECOVERABLE, ++}; ++ + /* TRB Length, PCM and Status */ + #define DWC3_TRB_SIZE_MASK (0x00ffffff) + #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) +@@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { + /** + * struct dwc3 - representation of our controller + * @drd_work: workqueue used for role swapping ++ * @err_recovery_work: workqueue used for controller error recovery + * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @setup_buf: used while precessing STD USB requests +@@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { + * @ep0_in_setup: one control transfer is completed and enter setup phase + * @lock: for synchronizing + * @mutex: for mode switching ++ * @connect_mutex: for the pull-up and err_recovery_work + * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device + * @xhci: pointer to our xHCI child +@@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { + * @ep0_next_event: hold the next expected event + * @ep0state: state of endpoint zero + * @link_state: link state ++ * @err_state: current error recovery state. ++ * @err_recovery_count: number of consecutive error recovery attempts until ++ * confirmed healthy and reset to 0 on reset event. + * @speed: device speed (super, high, full, low) + * @hwparams: copy of hwparams registers + * @regset: debugfs pointer to regdump file +@@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { + */ + struct dwc3 { + struct work_struct drd_work; ++ struct work_struct err_recovery_work; + struct dwc3_trb *ep0_trb; + void *bounce; + u8 *setup_buf; +@@ -1203,6 +1216,9 @@ struct dwc3 { + /* mode switching lock */ + struct mutex mutex; + ++ /* serializes pull-up run/stop vs error recovery */ ++ struct mutex connect_mutex; ++ + struct device *dev; + struct device *sysdev; + +@@ -1332,6 +1348,9 @@ struct dwc3 { + enum dwc3_ep0_next ep0_next_event; + enum dwc3_ep0_state ep0state; + enum dwc3_link_state link_state; ++ enum dwc3_err_state err_state; ++ ++ u32 err_recovery_count; + + u16 u2sel; + u16 u2pel; +diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c +--- a/drivers/usb/dwc3/dwc3-am62.c ++++ b/drivers/usb/dwc3/dwc3-am62.c +@@ -299,6 +299,7 @@ static int dwc3_ti_probe(struct platform_device *pdev) + + err_pm_disable: + clk_disable_unprepare(am62->usb2_refclk); ++ pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + return ret; +diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c +--- a/drivers/usb/dwc3/ep0.c ++++ b/drivers/usb/dwc3/ep0.c +@@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ ret = -ESHUTDOWN; ++ goto out; ++ } ++ + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { + dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", + dep->name); +@@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep0_set_halt(ep, value); + spin_unlock_irqrestore(&dwc->lock, flags); + +diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c +--- a/drivers/usb/dwc3/gadget.c ++++ b/drivers/usb/dwc3/gadget.c +@@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, + return 0; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_queue(dep, req); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + ret = __dwc3_gadget_ep_set_halt(dep, value, false); + spin_unlock_irqrestore(&dwc->lock, flags); + +@@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) + int ret; + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + dep->flags |= DWC3_EP_WEDGE; + + if (dep->number == 0 || dep->number == 1) +@@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + if (!dwc->gadget->wakeup_armed) { + dev_err(dwc->dev, "not armed for remote wakeup\n"); + spin_unlock_irqrestore(&dwc->lock, flags); +@@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) + } + + spin_lock_irqsave(&dwc->lock, flags); ++ if (dwc->err_state != DWC3_ERR_NONE) { ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return -ESHUTDOWN; ++ } + /* + * If the link is in U3, signal for remote wakeup and wait for the + * link to transition to U0 before sending device notification. +@@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) + + synchronize_irq(dwc->irq_gadget); + ++ /* Serialize against dwc3_err_recovery_work() */ ++ mutex_lock(&dwc->connect_mutex); + if (!is_on) + ret = dwc3_gadget_soft_disconnect(dwc); + else + ret = dwc3_gadget_soft_connect(dwc); ++ mutex_unlock(&dwc->connect_mutex); + + pm_runtime_put(dwc->dev); + +@@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) + + dwc->suspended = false; + ++ /* The controller is recovered. */ ++ if (dwc->err_state == DWC3_ERR_NONE) ++ dwc->err_recovery_count = 0; ++ + /* + * Ideally, dwc3_reset_gadget() would trigger the function + * drivers to stop any active transfers through ep disable. +@@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) + return ret; + } + ++static void dwc3_schedule_err_recovery(struct dwc3 *dwc) ++{ ++ dwc->err_state = DWC3_ERR_RECOVERY; ++ schedule_work(&dwc->err_recovery_work); ++} ++ + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + { + struct dwc3 *dwc = evt->dwc; +@@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) + return IRQ_NONE; + + if (count > evt->length) { +- dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", ++ dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + count, evt->length); +- return IRQ_NONE; ++ /* ++ * This is a fatal error - the driver and controller are out of ++ * sync on which event has been consumed. Reinitializing the ++ * controller is required to recover. Write the bogus count back ++ * to GEVNTCOUNT to clear the IRQ source, consistent with the ++ * stale event clearing in dwc3_event_buffers_setup(), then ++ * schedule error recovery. ++ */ ++ spin_lock(&dwc->lock); ++ dwc3_schedule_err_recovery(dwc); ++ spin_unlock(&dwc->lock); ++ dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); ++ return IRQ_HANDLED; + } + + evt->count = count; +@@ -4735,6 +4784,57 @@ static void dwc_gadget_release(struct device *dev) + kfree(gadget); + } + ++static void dwc3_err_recovery_work(struct work_struct *work) ++{ ++ struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); ++ unsigned long flags; ++ int ret; ++ ++ /* serializes against dwc3_gadget_pullup() */ ++ mutex_lock(&dwc->connect_mutex); ++ ++ ret = dwc3_gadget_soft_disconnect(dwc); ++ ++ dwc3_disconnect_gadget_sleepable(dwc); ++ ++ if (ret) ++ goto err_unrecoverable; ++ ++ if (dwc->softconnect) { ++ u32 count; ++ /* ++ * Wait irq to finish before soft_connect resets evt->lpos and ++ * the event buffer registers to avoid racing with ++ * dwc3_process_event_buf(). ++ */ ++ synchronize_irq(dwc->irq_gadget); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ count = ++dwc->err_recovery_count; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ ++ if (count > DWC3_ERR_RECOVERY_MAX) ++ goto err_unrecoverable; ++ ++ ret = dwc3_gadget_soft_connect(dwc); ++ if (ret) ++ goto err_unrecoverable; ++ } ++ mutex_unlock(&dwc->connect_mutex); ++ ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_NONE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++ return; ++ ++err_unrecoverable: ++ dev_err(dwc->dev, "Unable to recover the controller\n"); ++ mutex_unlock(&dwc->connect_mutex); ++ spin_lock_irqsave(&dwc->lock, flags); ++ dwc->err_state = DWC3_ERR_UNRECOVERABLE; ++ spin_unlock_irqrestore(&dwc->lock, flags); ++} ++ + /** + * dwc3_gadget_init - initializes gadget related registers + * @dwc: pointer to our controller context structure +@@ -4778,6 +4878,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) + } + + init_completion(&dwc->ep0_in_setup); ++ INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); ++ mutex_init(&dwc->connect_mutex); + dwc->gadget = kzalloc_obj(struct usb_gadget); + if (!dwc->gadget) { + ret = -ENOMEM; +@@ -4874,6 +4976,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) + if (!dwc->gadget) + return; + ++ cancel_work_sync(&dwc->err_recovery_work); ++ mutex_destroy(&dwc->connect_mutex); + dwc3_enable_susphy(dwc, true); + usb_del_gadget(dwc->gadget); + dwc3_gadget_free_endpoints(dwc); +diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c +--- a/drivers/usb/gadget/function/f_fs.c ++++ b/drivers/usb/gadget/function/f_fs.c +@@ -1877,8 +1877,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + break; + case FUNCTIONFS_ENDPOINT_DESC: + { ++ const struct usb_endpoint_descriptor *desc; ++ struct usb_endpoint_descriptor desc1; + int desc_idx; +- struct usb_endpoint_descriptor desc1, *desc; + + switch (epfile->ffs->gadget->speed) { + case USB_SPEED_SUPER: +@@ -1892,7 +1893,17 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, + desc_idx = 0; + } + +- desc = epfile->ep->descs[desc_idx]; ++ do { ++ desc = epfile->ep->descs[desc_idx]; ++ } while (!desc && --desc_idx >= 0); ++ ++ if (!desc) ++ desc = epfile->ep->ep->desc; ++ if (!desc) { ++ ret = -EINVAL; ++ break; ++ } ++ + memcpy(&desc1, desc, desc->bLength); + + spin_unlock_irq(&epfile->ffs->eps_lock); +diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c +--- a/drivers/usb/gadget/function/f_midi2.c ++++ b/drivers/usb/gadget/function/f_midi2.c +@@ -1633,8 +1633,8 @@ struct f_midi2_usb_config { + + /* MIDI 1.0 jacks */ + unsigned char jack_in, jack_out, jack_id; +- struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; +- struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; ++ struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES * 2]; ++ struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES * 2]; + }; + + static int append_config(struct f_midi2_usb_config *config, void *d) +diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c +--- a/drivers/usb/gadget/function/f_uac1_legacy.c ++++ b/drivers/usb/gadget/function/f_uac1_legacy.c +@@ -797,6 +797,9 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) + + static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + con->data[cmd] = value; + + return 0; +@@ -804,6 +807,9 @@ static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) + + static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) + { ++ if (cmd >= ARRAY_SIZE(con->data)) ++ return -EINVAL; ++ + return con->data[cmd]; + } + +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/core.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c +@@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev) + for (i = 0; i < vhub->max_ports; i++) + ast_vhub_del_dev(&vhub->ports[i].dev); + ++ /* Final drain; the worker takes vhub->lock, so stay outside of it */ ++ cancel_work_sync(&vhub->wake_work); ++ + spin_lock_irqsave(&vhub->lock, flags); + + /* Mask & ack all interrupts */ +@@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev) + return -ENOMEM; + + spin_lock_init(&vhub->lock); ++ INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + vhub->pdev = pdev; + vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, + VHUB_IRQ_DEV1_BIT); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c +@@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) + int rc = -EINVAL; + + spin_lock_irqsave(&d->vhub->lock, flags); +- if (!d->wakeup_en) ++ if (!d->wakeup_en || !d->registered) + goto err; + + DDBG(d, "Device initiated wakeup\n"); +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +--- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c ++++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c +@@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub, + ast_vhub_send_host_wakeup(vhub); + } + +-static void ast_vhub_wake_work(struct work_struct *work) ++void ast_vhub_wake_work(struct work_struct *work) + { + struct ast_vhub *vhub = container_of(work, + struct ast_vhub, +@@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work) + + void ast_vhub_hub_wake_all(struct ast_vhub *vhub) + { ++ lockdep_assert_held(&vhub->lock); ++ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain +@@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) + int ast_vhub_init_hub(struct ast_vhub *vhub) + { + vhub->speed = USB_SPEED_UNKNOWN; +- INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); + + return ast_vhub_init_desc(vhub); + } +diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +--- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h ++++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +@@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub); + void ast_vhub_hub_resume(struct ast_vhub *vhub); + void ast_vhub_hub_reset(struct ast_vhub *vhub); + void ast_vhub_hub_wake_all(struct ast_vhub *vhub); ++void ast_vhub_wake_work(struct work_struct *work); + + /* dev.c */ + int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); +diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c +--- a/drivers/usb/gadget/udc/dummy_hcd.c ++++ b/drivers/usb/gadget/udc/dummy_hcd.c +@@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, + { + bool fifo = req == &dum->fifo_req; + ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + usb_gadget_giveback_request(_ep, &req->req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + if (fifo) + dum->fifo_req_busy = 0; + } +@@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, + req->req.complete = fifo_complete; + + list_add_tail(&req->queue, &ep->queue); ++ ++dum->callback_usage; + spin_unlock(&dum->lock); + _req->actual = _req->length; + _req->status = 0; + usb_gadget_giveback_request(_ep, _req); + spin_lock(&dum->lock); ++ --dum->callback_usage; + } else + list_add_tail(&req->queue, &ep->queue); + spin_unlock_irqrestore(&dum->lock, flags); +diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c +--- a/drivers/usb/host/octeon-hcd.c ++++ b/drivers/usb/host/octeon-hcd.c +@@ -378,8 +378,22 @@ struct octeon_hcd { + struct cvmx_usb_transaction *active_split; + struct cvmx_usb_tx_fifo periodic; + struct cvmx_usb_tx_fifo nonperiodic; ++ struct hrtimer sof_timer; + }; + ++static int cvmx_usb_poll(struct octeon_hcd *usb); ++ ++static enum hrtimer_restart octeon_usb_sof_timer(struct hrtimer *t) ++{ ++ struct octeon_hcd *usb = container_of(t, struct octeon_hcd, sof_timer); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&usb->lock, flags); ++ cvmx_usb_poll(usb); ++ spin_unlock_irqrestore(&usb->lock, flags); ++ return HRTIMER_NORESTART; ++} ++ + /* + * This macro logically sets a single field in a CSR. It does the sequence + * read, modify, and write +@@ -578,7 +592,7 @@ static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) + int result; + u64 address = CVMX_USBCX_GRSTCTL(usb->index); + u64 done = cvmx_get_cycle() + 100 * +- (u64)octeon_get_clock_rate / 1000000; ++ (u64)octeon_get_clock_rate() / 1000000; + union cvmx_usbcx_grstctl c; + + while (1) { +@@ -853,6 +867,14 @@ static int cvmx_usb_initialize(struct device *dev, + * USBC_GAHBCFG[PTXFEMPLVL] + * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 + */ ++ usbcx_gahbcfg.u32 = cvmx_usb_read_csr32(usb, ++ CVMX_USBCX_GHWCFG3(usb->index)); ++ if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { ++ dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", ++ usbcx_gahbcfg.u32); ++ return -ENODEV; ++ } ++ + usbcx_gahbcfg.u32 = 0; + usbcx_gahbcfg.s.dmaen = !(usb->init_flags & + CVMX_USB_INITIALIZE_FLAGS_NO_DMA); +@@ -1900,6 +1922,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + int channel; + struct cvmx_usb_pipe *pipe; + int need_sof; ++ u64 min_due; + enum cvmx_usb_transfer ttype; + + if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { +@@ -1940,15 +1963,33 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) + * future that might need to be scheduled + */ + need_sof = 0; ++ min_due = ~0ull; + for (ttype = CVMX_USB_TRANSFER_CONTROL; + ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { + list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { +- if (pipe->next_tx_frame > usb->frame_number) { +- need_sof = 1; +- break; +- } ++ if (pipe->next_tx_frame > usb->frame_number && ++ pipe->next_tx_frame < min_due) ++ min_due = pipe->next_tx_frame; + } + } ++ if (min_due != ~0ull) { ++ u64 delta = min_due - usb->frame_number; ++ ++ /* ++ * frame_number is resynced from HFNUM on every poll, so a ++ * deadline that is many frames away does not need an ++ * interrupt on every SOF to count them down - sleep on the ++ * timer instead and keep SOF interrupts for deadlines within ++ * a few frames. Stay well below the 16383-frame wrap of ++ * HFNUM. One (micro)frame is 125us in high-speed mode. ++ */ ++ if (delta <= 4 || delta > 8000) ++ need_sof = 1; ++ else ++ hrtimer_start(&usb->sof_timer, ++ ns_to_ktime((delta - 2) * 125000), ++ HRTIMER_MODE_REL); ++ } + USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), + cvmx_usbcx_gintmsk, sofmsk, need_sof); + } +@@ -3638,6 +3679,8 @@ static int octeon_usb_probe(struct platform_device *pdev) + usb = (struct octeon_hcd *)hcd->hcd_priv; + + spin_lock_init(&usb->lock); ++ hrtimer_setup(&usb->sof_timer, octeon_usb_sof_timer, CLOCK_MONOTONIC, ++ HRTIMER_MODE_REL); + + usb->init_flags = initialize_flags; + +@@ -3688,6 +3731,7 @@ static void octeon_usb_remove(struct platform_device *pdev) + unsigned long flags; + + usb_remove_hcd(hcd); ++ hrtimer_cancel(&usb->sof_timer); + spin_lock_irqsave(&usb->lock, flags); + status = cvmx_usb_shutdown(usb); + spin_unlock_irqrestore(&usb->lock, flags); +diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c +--- a/drivers/usb/host/ohci-da8xx.c ++++ b/drivers/usb/host/ohci-da8xx.c +@@ -447,6 +447,7 @@ static int ohci_da8xx_probe(struct platform_device *pdev) + + err_remove_hcd: + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + err: + usb_put_hcd(hcd); + return error; +@@ -457,6 +458,7 @@ static void ohci_da8xx_remove(struct platform_device *pdev) + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + usb_put_hcd(hcd); + } + +diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c +--- a/drivers/usb/host/ohci-s3c2410.c ++++ b/drivers/usb/host/ohci-s3c2410.c +@@ -335,6 +335,7 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) + struct usb_hcd *hcd = platform_get_drvdata(dev); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + s3c2410_stop_hc(dev); + usb_put_hcd(hcd); + } +diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c +--- a/drivers/usb/host/ohci-spear.c ++++ b/drivers/usb/host/ohci-spear.c +@@ -103,6 +103,7 @@ static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) + struct spear_ohci *sohci_p = to_spear_ohci(hcd); + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + clk_disable_unprepare(sohci_p->clk); + + usb_put_hcd(hcd); +diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c +--- a/drivers/usb/host/ohci-st.c ++++ b/drivers/usb/host/ohci-st.c +@@ -235,6 +235,7 @@ static void st_ohci_platform_remove(struct platform_device *dev) + int clk; + + usb_remove_hcd(hcd); ++ device_wakeup_disable(hcd->self.controller); + + if (pdata->power_off) + pdata->power_off(dev); +diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c +--- a/drivers/usb/serial/bus.c ++++ b/drivers/usb/serial/bus.c +@@ -165,7 +165,11 @@ int usb_serial_bus_register(struct usb_serial_driver *driver) + + void usb_serial_bus_deregister(struct usb_serial_driver *driver) + { +- free_dynids(driver); + driver_unregister(&driver->driver); ++ free_dynids(driver); + } + ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver) ++{ ++ driver_remove_file(&driver->driver, &driver_attr_new_id); ++} +diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c +--- a/drivers/usb/serial/cp210x.c ++++ b/drivers/usb/serial/cp210x.c +@@ -223,6 +223,7 @@ static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ + { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ + { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ ++ { USB_DEVICE(0x1B1C, 0x1C02) }, /* Corsair AX1500i Power Supply */ + { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ + { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ + { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ +diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c +--- a/drivers/usb/serial/generic.c ++++ b/drivers/usb/serial/generic.c +@@ -266,6 +266,7 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); + void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + unsigned int bps; + unsigned long period; + unsigned long expire; +@@ -285,7 +286,14 @@ void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) + __func__, jiffies_to_msecs(timeout), + jiffies_to_msecs(period)); + expire = jiffies + timeout; +- while (!port->serial->type->tx_empty(port)) { ++ for (;;) { ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty) || port->serial->type->tx_empty(port)) { ++ mutex_unlock(&tport->mutex); ++ break; ++ } ++ mutex_unlock(&tport->mutex); ++ + schedule_timeout_interruptible(period); + if (signal_pending(current)) + break; +diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c +--- a/drivers/usb/serial/option.c ++++ b/drivers/usb/serial/option.c +@@ -260,6 +260,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EM060K_12a 0x012a + #define QUECTEL_PRODUCT_EM060K_12b 0x012b + #define QUECTEL_PRODUCT_EM060K_12c 0x012c ++#define QUECTEL_PRODUCT_RG660QB 0x013d + #define QUECTEL_PRODUCT_EG91 0x0191 + #define QUECTEL_PRODUCT_EG95 0x0195 + #define QUECTEL_PRODUCT_BG96 0x0296 +@@ -280,6 +281,7 @@ static void option_instat_callback(struct urb *urb); + #define QUECTEL_PRODUCT_EC200U 0x0901 + #define QUECTEL_PRODUCT_EG912Y 0x6001 + #define QUECTEL_PRODUCT_EC200S_CN 0x6002 ++#define QUECTEL_PRODUCT_EG060W 0x6004 + #define QUECTEL_PRODUCT_EC200A 0x6005 + #define QUECTEL_PRODUCT_EG916Q 0x6007 + #define QUECTEL_PRODUCT_EM061K_LWW 0x6008 +@@ -1268,12 +1270,15 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG060W, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0xff, 0x30) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, +@@ -2168,6 +2173,8 @@ static const struct usb_device_id option_ids[] = { + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ + .driver_info = RSVD(7) }, ++ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x902b, 0xff), ++ .driver_info = RSVD(4) }, + { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ + .driver_info = RSVD(3) | RSVD(4) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ +@@ -2429,6 +2436,13 @@ static const struct usb_device_id option_ids[] = { + .driver_info = RSVD(5) }, + { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ + .driver_info = RSVD(3) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x01) }, /* Compal EXC-T1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x02) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x03) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x04) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, + { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ + .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, + { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ +diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c +--- a/drivers/usb/serial/quatech2.c ++++ b/drivers/usb/serial/quatech2.c +@@ -52,6 +52,7 @@ + #define QT2_XMIT_FLUSH 0x05 /* no following info */ + #define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 921600 + #define DEFAULT_BAUD_RATE 9600 + +@@ -156,7 +157,7 @@ static inline int calc_baud_divisor(int baudrate) + + static inline int qt2_set_port_config(struct usb_device *dev, + unsigned char port_number, +- u16 baudrate, u16 lcr) ++ speed_t baudrate, u16 lcr) + { + int divisor = calc_baud_divisor(baudrate); + u16 index = ((u16) (lcr << 8) | (u16) (port_number)); +@@ -257,9 +258,9 @@ static void qt2_set_termios(struct tty_struct *tty, + struct usb_device *dev = port->serial->dev; + struct qt2_port_private *port_priv; + struct ktermios *termios = &tty->termios; +- u16 baud; + unsigned int cflag = termios->c_cflag; + u16 new_lcr = 0; ++ speed_t baud; + int status; + + port_priv = usb_get_serial_port_data(port); +@@ -277,6 +278,18 @@ static void qt2_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + status = qt2_set_port_config(dev, port_priv->device_port, baud, + new_lcr); + if (status < 0) +@@ -373,7 +386,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) + port_priv->device_port = (u8) device_port; + + if (tty) +- qt2_set_termios(tty, port, &tty->termios); ++ qt2_set_termios(tty, port, NULL); + + return 0; + +diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c +--- a/drivers/usb/serial/ssu100.c ++++ b/drivers/usb/serial/ssu100.c +@@ -32,6 +32,7 @@ + + #define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) + ++#define MIN_BAUD_RATE 50 + #define MAX_BAUD_RATE 460800 + + #define ATC_DISABLED 0x00 +@@ -217,9 +218,10 @@ static void ssu100_set_termios(struct tty_struct *tty, + { + struct usb_device *dev = port->serial->dev; + struct ktermios *termios = &tty->termios; +- u16 baud, divisor, remainder; ++ speed_t baud, remainder; + unsigned int cflag = termios->c_cflag; + u16 urb_value = 0; /* will hold the new flags */ ++ u16 divisor; + int result; + + if (cflag & PARENB) { +@@ -235,6 +237,18 @@ static void ssu100_set_termios(struct tty_struct *tty, + if (!baud) + baud = 9600; + ++ if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { ++ if (old_termios) ++ baud = tty_termios_baud_rate(old_termios); ++ else ++ baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); ++ ++ tty_encode_baud_rate(tty, baud, baud); ++ ++ if (!baud) ++ baud = 9600; ++ } ++ + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); + + +@@ -310,7 +324,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) + dev_dbg(&port->dev, "%s - set uart failed\n", __func__); + + if (tty) +- ssu100_set_termios(tty, port, &tty->termios); ++ ssu100_set_termios(tty, port, NULL); + + return usb_serial_generic_open(tty, port); + } +diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c +--- a/drivers/usb/serial/usb-serial.c ++++ b/drivers/usb/serial/usb-serial.c +@@ -400,17 +400,13 @@ static unsigned int serial_chars_in_buffer(struct tty_struct *tty) + static void serial_wait_until_sent(struct tty_struct *tty, int timeout) + { + struct usb_serial_port *port = tty->driver_data; +- struct usb_serial *serial = port->serial; + + dev_dbg(&port->dev, "%s\n", __func__); + + if (!port->serial->type->wait_until_sent) + return; + +- mutex_lock(&serial->disc_mutex); +- if (!serial->disconnected) +- port->serial->type->wait_until_sent(tty, timeout); +- mutex_unlock(&serial->disc_mutex); ++ port->serial->type->wait_until_sent(tty, timeout); + } + + static void serial_throttle(struct tty_struct *tty) +@@ -438,8 +434,13 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + unsigned int close_delay, closing_wait; ++ int ret = 0; + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + close_delay = jiffies_to_msecs(tport->close_delay) / 10; + closing_wait = tport->closing_wait; +@@ -452,10 +453,10 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) + + if (port->serial->type->get_serial) + port->serial->type->get_serial(tty, ss); +- ++out_unlock: + mutex_unlock(&tport->mutex); + +- return 0; ++ return ret; + } + + static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) +@@ -471,6 +472,10 @@ static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) + closing_wait = msecs_to_jiffies(closing_wait * 10); + + mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) { ++ ret = -EIO; ++ goto out_unlock; ++ } + + if (!capable(CAP_SYS_ADMIN)) { + if (close_delay != tport->close_delay || +@@ -498,6 +503,7 @@ static int serial_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + int retval = -ENOIOCTLCMD; + + dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd); +@@ -508,8 +514,21 @@ static int serial_ioctl(struct tty_struct *tty, + retval = port->serial->type->tiocmiwait(tty, arg); + break; + default: +- if (port->serial->type->ioctl) ++ if (!port->serial->type->ioctl) ++ break; ++ ++ if (cmd == TIOCSRS485) ++ down_write(&tty->termios_rwsem); ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ retval = -EIO; ++ else + retval = port->serial->type->ioctl(tty, cmd, arg); ++ mutex_unlock(&tport->mutex); ++ ++ if (cmd == TIOCSRS485) ++ up_write(&tty->termios_rwsem); + } + + return retval; +@@ -519,25 +538,40 @@ static void serial_set_termios(struct tty_struct *tty, + const struct ktermios *old) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->set_termios) +- port->serial->type->set_termios(tty, port, old); +- else ++ if (!port->serial->type->set_termios) { + tty_termios_copy_hw(&tty->termios, old); ++ return; ++ } ++ ++ mutex_lock(&tport->mutex); ++ if (!tty_io_error(tty)) ++ port->serial->type->set_termios(tty, port, old); ++ mutex_unlock(&tport->mutex); + } + + static int serial_break(struct tty_struct *tty, int break_state) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->break_ctl) +- return port->serial->type->break_ctl(tty, break_state); ++ if (!port->serial->type->break_ctl) ++ return -ENOTTY; + +- return -ENOTTY; ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->break_ctl(tty, break_state); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_proc_show(struct seq_file *m, void *v) +@@ -578,24 +612,44 @@ static int serial_proc_show(struct seq_file *m, void *v) + static int serial_tiocmget(struct tty_struct *tty) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmget) +- return port->serial->type->tiocmget(tty); +- return -ENOTTY; ++ if (!port->serial->type->tiocmget) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmget(tty); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) + { + struct usb_serial_port *port = tty->driver_data; ++ struct tty_port *tport = &port->port; ++ int ret; + + dev_dbg(&port->dev, "%s\n", __func__); + +- if (port->serial->type->tiocmset) +- return port->serial->type->tiocmset(tty, set, clear); +- return -ENOTTY; ++ if (!port->serial->type->tiocmset) ++ return -ENOTTY; ++ ++ mutex_lock(&tport->mutex); ++ if (tty_io_error(tty)) ++ ret = -EIO; ++ else ++ ret = port->serial->type->tiocmset(tty, set, clear); ++ mutex_unlock(&tport->mutex); ++ ++ return ret; + } + + static int serial_get_icount(struct tty_struct *tty, +@@ -1191,11 +1245,16 @@ static void usb_serial_disconnect(struct usb_interface *interface) + usb_serial_port_poison_urbs(port); + wake_up_interruptible(&port->port.delta_msr_wait); + cancel_work_sync(&port->work); ++ } ++ ++ if (serial->type->disconnect) ++ serial->type->disconnect(serial); ++ ++ for (i = 0; i < serial->num_ports; ++i) { ++ port = serial->port[i]; + if (device_is_registered(&port->dev)) + device_del(&port->dev); + } +- if (serial->type->disconnect) +- serial->type->disconnect(serial); + + release_sibling(serial, interface); + +@@ -1464,7 +1523,7 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + { + int rc; + struct usb_driver *udriver; +- struct usb_serial_driver * const *sd; ++ struct usb_serial_driver * const *sd, * const *s; + + /* + * udriver must be registered before any of the serial drivers, +@@ -1517,9 +1576,11 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers + return 0; + + err_deregister_drivers: ++ for (s = serial_drivers; s < sd; ++s) ++ usb_serial_bus_remove_new_id(*s); ++ usb_deregister(udriver); + while (sd-- > serial_drivers) + usb_serial_deregister(*sd); +- usb_deregister(udriver); + err_free_driver: + kfree(udriver); + return rc; +@@ -1537,10 +1598,23 @@ EXPORT_SYMBOL_GPL(__usb_serial_register_drivers); + void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) + { + struct usb_driver *udriver = (*serial_drivers)->usb_driver; ++ struct usb_serial_driver * const *sd; ++ ++ /* ++ * udriver must be deregistered before the serial drivers so that ++ * I/O is stopped before unbinding the ports. ++ * ++ * Remove the new_id attributes to prevent ids from being added and ++ * triggering a probe of udriver after it has been deregistered. ++ */ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_bus_remove_new_id(*sd); + +- for (; *serial_drivers; ++serial_drivers) +- usb_serial_deregister(*serial_drivers); + usb_deregister(udriver); ++ ++ for (sd = serial_drivers; *sd; ++sd) ++ usb_serial_deregister(*sd); ++ + kfree(udriver); + } + EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); +diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c +--- a/drivers/usb/serial/xr_serial.c ++++ b/drivers/usb/serial/xr_serial.c +@@ -850,12 +850,9 @@ static int xr_get_rs485_config(struct tty_struct *tty, + struct usb_serial_port *port = tty->driver_data; + struct xr_data *data = usb_get_serial_port_data(port); + +- down_read(&tty->termios_rwsem); +- if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { +- up_read(&tty->termios_rwsem); ++ /* core holds port mutex */ ++ if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) + return -EFAULT; +- } +- up_read(&tty->termios_rwsem); + + return 0; + } +@@ -871,10 +868,9 @@ static int xr_set_rs485_config(struct tty_struct *tty, + return -EFAULT; + xr_sanitize_serial_rs485(&rs485); + +- down_write(&tty->termios_rwsem); ++ /* core holds termios rwsem and port mutex */ + data->rs485 = rs485; + xr_set_flow_mode(tty, port, NULL); +- up_write(&tty->termios_rwsem); + + if (copy_to_user(argp, &rs485, sizeof(rs485))) + return -EFAULT; +diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c +--- a/drivers/usb/storage/sierra_ms.c ++++ b/drivers/usb/storage/sierra_ms.c +@@ -77,6 +77,13 @@ static int sierra_get_swoc_info(struct usb_device *udev, + sizeof(struct swoc_info), /* __u16 size */ + USB_CTRL_SET_TIMEOUT); /* int timeout */ + ++ /* ++ * A short IN transfer leaves the tail of swocInfo uninitialized; ++ * only a full transfer is valid. ++ */ ++ if (result != sizeof(struct swoc_info)) ++ return -EIO; ++ + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); + swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); + return result; +diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c +--- a/drivers/usb/typec/anx7411.c ++++ b/drivers/usb/typec/anx7411.c +@@ -1566,8 +1566,11 @@ static void anx7411_i2c_remove(struct i2c_client *client) + anx7411_partner_unregister_altmode(plat); + anx7411_unregister_partner(plat); + +- if (plat->workqueue) ++ if (plat->workqueue) { ++ disable_irq(plat->intp_irq); ++ cancel_work_sync(&plat->work); + destroy_workqueue(plat->workqueue); ++ } + + i2c_unregister_device(plat->spi_client); + +diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c +--- a/drivers/usb/typec/port-mapper.c ++++ b/drivers/usb/typec/port-mapper.c +@@ -42,6 +42,23 @@ static int usb4_port_compare(struct device *dev, void *fwnode) + return usb4_usb3_port_match(dev, fwnode); + } + ++static bool typec_has_usb4_host_interface(const struct fwnode_handle *fwnode) ++{ ++ if (!IS_REACHABLE(CONFIG_USB4)) ++ return false; ++ ++ struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = ++ fwnode_find_reference(fwnode, "usb4-host-interface", 0); ++ ++ /* ++ * The USB4 port can only appear if the host interface is enabled in ++ * the firmware and has been enumerated as a device. The latter is ++ * the same check as in usb_acpi_add_usb4_devlink(). ++ */ ++ return !IS_ERR(nhi_fwnode) && fwnode_device_is_available(nhi_fwnode) && ++ nhi_fwnode->dev; ++} ++ + static int typec_port_compare(struct device *dev, void *fwnode) + { + return device_match_fwnode(dev, fwnode); +@@ -64,11 +81,11 @@ static int typec_port_match(struct device *dev, void *data) + adev_fwnode); + + /* +- * If dev is USB 3.x port, it may have reference to the ++ * If dev is USB 3.x port, it may have reference to an available + * USB4 host interface in which case we can also link the + * Type-C port with the USB4 port. + */ +- if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) ++ if (typec_has_usb4_host_interface(adev_fwnode)) + component_match_add(&arg->port->dev, &arg->match, + usb4_port_compare, adev_fwnode); + } +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -1756,7 +1756,8 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del + static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) + { + hrtimer_cancel(&port->vdm_discovery_timer); +- kthread_cancel_work_sync(&port->vdm_discovery_work); ++ if (port->wq) ++ kthread_cancel_work_sync(&port->vdm_discovery_work); + } + + static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) +@@ -7113,7 +7114,7 @@ static void tcpm_pd_event_handler(struct kthread_work *work) + port->upcoming_state = FR_SWAP_SEND; + ret = tcpm_ams_start(port, FAST_ROLE_SWAP); + if (ret == -EAGAIN) +- port->upcoming_state = INVALID_STATE; ++ tcpm_set_state(port, ERROR_RECOVERY, 0); + } else { + tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); + } +@@ -8977,6 +8978,7 @@ void tcpm_unregister_port(struct tcpm_port *port) + + port->registered = false; + kthread_destroy_worker(port->wq); ++ port->wq = NULL; + + hrtimer_cancel(&port->vdm_discovery_timer); + hrtimer_cancel(&port->enable_frs_timer); +diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c +--- a/drivers/usb/typec/tipd/core.c ++++ b/drivers/usb/typec/tipd/core.c +@@ -343,7 +343,7 @@ static void tps6598x_set_data_role(struct tps6598x *tps, + + static int tps6598x_connect(struct tps6598x *tps, u32 status) + { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + enum typec_pwr_opmode mode; + int ret; + +@@ -354,7 +354,6 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) + + desc.usb_pd = mode == TYPEC_PWR_MODE_PD; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) { + ret = tps6598x_read_partner_identity(tps); +@@ -850,11 +849,10 @@ static void cd321x_update_work(struct work_struct *work) + + /* Set up partner if we were previously disconnected (or changed). */ + if (!tps->partner) { +- struct typec_partner_desc desc; ++ struct typec_partner_desc desc = { }; + + desc.usb_pd = is_pd; + desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ +- desc.identity = NULL; + + if (desc.usb_pd) + desc.identity = &st.partner_identity; +@@ -1792,6 +1790,7 @@ static int tps6598x_probe(struct i2c_client *client) + const struct tipd_data *data; + struct tps6598x *tps; + struct fwnode_handle *fwnode; ++ bool patch_loaded = false; + u32 status; + u32 vid = 0; + int ret; +@@ -1847,6 +1846,7 @@ static int tps6598x_probe(struct i2c_client *client) + return ret; + + if (ret == TPS_MODE_PTCH) { ++ patch_loaded = true; + ret = tps->data->init(tps); + if (ret) + return ret; +@@ -1937,7 +1937,8 @@ static int tps6598x_probe(struct i2c_client *client) + tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); + err_reset_controller: + /* Reset PD controller to remove any applied patch */ +- tps->data->reset(tps); ++ if (patch_loaded) ++ tps->data->reset(tps); + + return ret; + } +diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c +--- a/drivers/usb/typec/ucsi/displayport.c ++++ b/drivers/usb/typec/ucsi/displayport.c +@@ -71,11 +71,14 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) + if (ret < 0) { + if (ucsi->version > 0x0100) + goto err_unlock; +- cur = 0xff; + } + +- if (cur < UCSI_MAX_ALTMODES) { +- ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ if (cur != 0xff) { ++ if (cur < UCSI_MAX_ALTMODES) ++ ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; ++ else ++ ret = -EINVAL; ++ + goto err_unlock; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c +--- a/drivers/usb/typec/ucsi/ucsi.c ++++ b/drivers/usb/typec/ucsi/ucsi.c +@@ -1801,10 +1801,21 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) + { + struct fwnode_handle *fwnode; + int i = 1; ++ int ret; ++ u32 port; + +- device_for_each_child_node(con->ucsi->dev, fwnode) +- if (i++ == con->num) +- return fwnode; ++ device_for_each_child_node(con->ucsi->dev, fwnode) { ++ ret = fwnode_property_read_u32(fwnode, "reg", &port); ++ if (ret < 0) { ++ if (i == con->num) ++ return fwnode; ++ } else { ++ if (port == con->num - 1) ++ return fwnode; ++ } ++ ++ i++; ++ } + return NULL; + } + +diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c +--- a/drivers/usb/typec/ucsi/ucsi_acpi.c ++++ b/drivers/usb/typec/ucsi/ucsi_acpi.c +@@ -121,6 +121,33 @@ static const struct ucsi_operations ucsi_acpi_ops = { + .async_control = ucsi_acpi_async_control + }; + ++static int ucsi_acer_read_version(struct ucsi *ucsi, u16 *version) ++{ ++ struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); ++ int ret; ++ ++ ret = ucsi_acpi_read_version(ucsi, version); ++ if (ret) ++ return ret; ++ ++ if (!*version) { ++ dev_warn(ua->dev, "UCSI version is zero, assuming 1.2\n"); ++ *version = UCSI_VERSION_1_2; ++ } ++ ++ return 0; ++} ++ ++static const struct ucsi_operations ucsi_acer_ops = { ++ .read_version = ucsi_acer_read_version, ++ .read_cci = ucsi_acpi_read_cci, ++ .poll_cci = ucsi_acpi_poll_cci, ++ .read_message_in = ucsi_acpi_read_message_in, ++ .write_message_out = ucsi_acpi_write_message_out, ++ .sync_control = ucsi_sync_control_common, ++ .async_control = ucsi_acpi_async_control ++}; ++ + static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, + void *val, size_t len, void *msg_out, + size_t msg_out_size) +@@ -162,6 +189,13 @@ static const struct ucsi_operations ucsi_gram_ops = { + }; + + static const struct dmi_system_id ucsi_acpi_quirks[] = { ++ { ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), ++ }, ++ .driver_data = (void *)&ucsi_acer_ops, ++ }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), +diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c +--- a/drivers/usb/typec/ucsi/ucsi_glink.c ++++ b/drivers/usb/typec/ucsi/ucsi_glink.c +@@ -362,6 +362,10 @@ static void pmic_glink_ucsi_destroy(void *data) + { + struct pmic_glink_ucsi *ucsi = data; + ++ /* Drain the work items before ucsi_destroy() frees the ucsi instance */ ++ cancel_work_sync(&ucsi->notify_work); ++ cancel_work_sync(&ucsi->register_work); ++ + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ + mutex_lock(&ucsi->lock); + ucsi_destroy(ucsi->ucsi); +@@ -467,8 +471,15 @@ static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) + { + struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); + +- /* Unregister first to stop having read & writes */ +- ucsi_unregister(ucsi->ucsi); ++ /* Callbacks can queue work until devres releases the client */ ++ disable_work_sync(&ucsi->notify_work); ++ disable_work_sync(&ucsi->register_work); ++ ++ /* register_work may have unregistered the instance already */ ++ if (ucsi->ucsi_registered) { ++ ucsi->ucsi_registered = false; ++ ucsi_unregister(ucsi->ucsi); ++ } + } + + static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { +diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h +--- a/include/linux/usb/serial.h ++++ b/include/linux/usb/serial.h +@@ -381,6 +381,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, + + int usb_serial_bus_register(struct usb_serial_driver *device); + void usb_serial_bus_deregister(struct usb_serial_driver *device); ++void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver); + + extern const struct bus_type usb_serial_bus_type; + extern struct tty_driver *usb_serial_tty_driver; diff --git a/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch.sig new file mode 100644 index 0000000..864678b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0607-usb-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch b/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch new file mode 100644 index 0000000..d2ac981 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch @@ -0,0 +1,29 @@ +diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c +--- a/drivers/usb/core/message.c ++++ b/drivers/usb/core/message.c +@@ -1052,6 +1052,25 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) + UTF16_LITTLE_ENDIAN, buf, size); + buf[err] = 0; + ++ /* ++ * Some devices report string descriptors with a declared length ++ * greater than the actual serial, leaving uninitialized firmware ++ * memory (often including C0 control characters) appended to the ++ * returned string. Truncate at the first control character so ++ * callers get a clean, well-formed string. ++ */ ++ { ++ int i; ++ for (i = 0; i < err; i++) { ++ unsigned char c = buf[i]; ++ if (c < 0x20 || c == 0x7f) { ++ buf[i] = 0; ++ err = i; ++ break; ++ } ++ } ++ } ++ + if (tbuf[1] != USB_DT_STRING) + dev_dbg(&dev->dev, + "wrong descriptor type %02x for string %d (\"%s\")\n", diff --git a/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch.sig b/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch.sig new file mode 100644 index 0000000..030b9fa Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0620-usb-string-sanitize.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch b/pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch new file mode 100644 index 0000000..87743c9 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/0650-wireguard-tstamp-type.patch.sig b/pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch.sig new file mode 100644 index 0000000..4dfd43e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0650-wireguard-tstamp-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch b/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch new file mode 100644 index 0000000..3b51649 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -819,6 +819,8 @@ static const struct usb_device_id quirks_table[] = { + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3613), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, ++ { USB_DEVICE(0x13d3, 0x3625), .driver_info = BTUSB_MEDIATEK | ++ BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3627), .driver_info = BTUSB_MEDIATEK | + BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x13d3, 0x3628), .driver_info = BTUSB_MEDIATEK | diff --git a/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig b/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig new file mode 100644 index 0000000..f497ab2 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch b/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch new file mode 100644 index 0000000..d3b514c --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch @@ -0,0 +1,27 @@ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +@@ -516,6 +516,10 @@ iwl_mld_mac80211_tx(struct ieee80211_hw *hw, + u32 link_id = u32_get_bits(info->control.flags, + IEEE80211_TX_CTRL_MLO_LINK); + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) { ++ ieee80211_free_txskb(hw, skb); ++ return; ++ } + /* In AP mode, mgmt frames are sent on the bcast station, + * so the FW can't translate the MLD addr to the link addr. Do it here + */ +diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +--- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c ++++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c +@@ -987,6 +987,9 @@ void iwl_mld_tx_from_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) + struct sk_buff *skb = NULL; + u8 zero_addr[ETH_ALEN] = {}; + ++ if (unlikely(test_bit(STATUS_FW_ERROR, &mld->trans->status))) ++ return; ++ + /* + * Don't transmit during firmware restart. The firmware is dead, + * so iwl_trans_tx() would return -EIO for each frame. Avoid the diff --git a/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig b/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig new file mode 100644 index 0000000..f2bc1c6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch b/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch new file mode 100644 index 0000000..2464f58 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch @@ -0,0 +1,145 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -2499,8 +2499,6 @@ static void btintel_pcie_inc_recovery_count(struct pci_dev *pdev, + } + } + +-static void btintel_pcie_reset(struct hci_dev *hdev); +- + static int btintel_pcie_acpi_reset_method(struct btintel_pcie_data *data) + { + union acpi_object *obj, argv4; +@@ -2693,56 +2691,86 @@ static void btintel_pcie_reset_work(struct work_struct *wk) + pci_unlock_rescan_remove(); + } + +-static void btintel_pcie_reset(struct hci_dev *hdev) ++/* Schedule a device reset of the requested type. ++ * ++ * BTINTEL_PCIE_RECOVERY_IN_PROGRESS serializes all reset requesters ++ * (sysfs reset attribute, hci_cmd_timeout(), hw_error, resume error ++ * path, etc.) so that: ++ * ++ * - dev_data->reset_type is written by exactly one caller (the ++ * thread that wins test_and_set_bit), eliminating the race where ++ * a second hw_error could clobber an already-scheduled reset's ++ * type; ++ * - the write happens AFTER the bit is set, so reset_work observes ++ * it through schedule_work()'s memory ordering; ++ * - losers return without touching reset_type or scheduling the ++ * work, so concurrent triggers are silently coalesced into the ++ * in-flight one (whose recovery will reinitialize the device ++ * regardless of the dropped trigger's variant). ++ * ++ * The bit is cleared only by .remove() / re-probe via fresh devm ++ * allocation, which is the intended one-shot semantics: a reset ++ * tears down and re-probes 'data', so there is no "in-flight" ++ * reset to follow up after device_reprobe() succeeds. ++ */ ++static void btintel_pcie_request_reset(struct btintel_pcie_data *data, ++ enum btintel_pcie_reset_type type) + { +- struct btintel_pcie_data *data; +- +- data = hci_get_drvdata(hdev); +- + if (!test_bit(BTINTEL_PCIE_SETUP_DONE, &data->flags)) + return; + + if (test_and_set_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return; + ++ data->reset_type = type; ++ + pci_dev_get(data->pdev); + schedule_work(&data->reset_work); + } + ++static void btintel_pcie_hci_reset(struct hci_dev *hdev) ++{ ++ struct btintel_pcie_data *data = hci_get_drvdata(hdev); ++ ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); ++} ++ + static void btintel_pcie_hw_error(struct hci_dev *hdev, u8 code) + { +- struct btintel_pcie_dev_recovery *data; ++ struct btintel_pcie_dev_recovery *rec; + struct btintel_pcie_data *dev_data = hci_get_drvdata(hdev); + struct pci_dev *pdev = dev_data->pdev; ++ enum btintel_pcie_reset_type type; + time64_t retry_window; + ++ if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &dev_data->flags)) ++ return; ++ + btintel_pcie_dump_debug_registers(hdev); + +- data = btintel_pcie_get_recovery(pdev, &hdev->dev); +- if (!data) ++ rec = btintel_pcie_get_recovery(pdev, &hdev->dev); ++ if (!rec) + return; + +- if (code == 0x13) +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_PLDR; +- else +- dev_data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; ++ type = (code == 0x13) ? BTINTEL_PCIE_IOSF_PRR_PLDR ++ : BTINTEL_PCIE_IOSF_PRR_FLR; + + bt_dev_err(hdev, "Encountered exception err:0x%x triggering: %s", code, +- dev_data->reset_type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); +- retry_window = ktime_get_boottime_seconds() - data->last_error; ++ type == BTINTEL_PCIE_IOSF_PRR_PLDR ? "PLDR" : "FLR"); ++ retry_window = ktime_get_boottime_seconds() - rec->last_error; + + if (retry_window < BTINTEL_PCIE_RESET_WINDOW_SECS && +- data->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { ++ rec->count >= BTINTEL_PCIE_FLR_MAX_RETRY) { + bt_dev_err(hdev, "Exhausted maximum: %d recovery attempts: %d", +- BTINTEL_PCIE_FLR_MAX_RETRY, data->count); ++ BTINTEL_PCIE_FLR_MAX_RETRY, rec->count); + bt_dev_dbg(hdev, "Boot time: %lld seconds", + ktime_get_boottime_seconds()); + bt_dev_dbg(hdev, "last error at: %lld seconds", +- data->last_error); ++ rec->last_error); + return; + } + btintel_pcie_inc_recovery_count(pdev, &hdev->dev); +- btintel_pcie_reset(hdev); ++ btintel_pcie_request_reset(dev_data, type); + } + + static bool btintel_pcie_wakeup(struct hci_dev *hdev) +@@ -2832,7 +2860,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data) + hdev->hw_error = btintel_pcie_hw_error; + hdev->set_diag = btintel_set_diag; + hdev->set_bdaddr = btintel_set_bdaddr; +- hdev->reset = btintel_pcie_reset; ++ hdev->reset = btintel_pcie_hci_reset; + hdev->wakeup = btintel_pcie_wakeup; + hdev->hci_drv = &btintel_pcie_hci_drv; + +@@ -3124,8 +3152,7 @@ static int btintel_pcie_resume(struct device *dev) + if (data->pm_sx_event == PM_EVENT_FREEZE || + data->pm_sx_event == PM_EVENT_HIBERNATE) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- data->reset_type = BTINTEL_PCIE_IOSF_PRR_FLR; +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; + } + +@@ -3156,7 +3183,7 @@ static int btintel_pcie_resume(struct device *dev) + queue_work(data->coredump_workqueue, &data->coredump_work); + } + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); +- btintel_pcie_reset(data->hdev); ++ btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + } + return err; + } diff --git a/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch.sig b/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch.sig new file mode 100644 index 0000000..590f6a4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0663-btintel-pcie-serialize-reset-type.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch b/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch new file mode 100644 index 0000000..54219c0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch @@ -0,0 +1,30 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, + BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + +- /* A hardware bug may cause the alive interrupt to be missed. +- * Check if the controller reached the expected state and retry +- * the operation only if it hasn't. ++ /* A hardware bug may cause the alive interrupt to be missed. Refresh ++ * boot_stage_cache from hardware, since only the interrupt handler ++ * updates it. Finally retry only if the state check still fails. + */ ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) + return 0; +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -50,6 +50,7 @@ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_DEVICE_HALTED (BIT(14)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_MAC_ACCESS_ON (BIT(16)) + #define BTINTEL_PCIE_CSR_BOOT_STAGE_ALIVE (BIT(23)) ++/* Reflects live D-state. Updated by hardware on every D-state transition. */ + #define BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY (BIT(24)) + + /* Registers for MSI-X */ diff --git a/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig b/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig new file mode 100644 index 0000000..c9f68b6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch b/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch new file mode 100644 index 0000000..2ec0e80 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch @@ -0,0 +1,145 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -3046,7 +3047,8 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0, status; ++ int retry = 0; ++ long status; + u32 dx_intr_timeout_ms = 200; + + do { +@@ -3098,18 +3100,23 @@ static int btintel_pcie_suspend_late(struct device *dev, pm_message_t mesg) + + data = pci_get_drvdata(pdev); + +- dxstate = (mesg.event == PM_EVENT_SUSPEND ? +- BTINTEL_PCIE_STATE_D3_HOT : BTINTEL_PCIE_STATE_D3_COLD); +- +- data->pm_sx_event = mesg.event; ++ /* S0ix (s2idle) uses D3_HOT; S3, freeze and hibernate use D3_COLD. */ ++ if (mesg.event == PM_EVENT_SUSPEND && ++ pm_suspend_target_state == PM_SUSPEND_TO_IDLE) ++ dxstate = BTINTEL_PCIE_STATE_D3_HOT; ++ else ++ dxstate = BTINTEL_PCIE_STATE_D3_COLD; + + start = ktime_get(); + + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, dxstate); + +- if (err) ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set dxstate:%u (%d)", ++ dxstate, err); + return err; ++ } + + bt_dev_dbg(data->hdev, + "device entered into d3 state from d0 in %lld us", +@@ -3132,7 +3139,7 @@ static int btintel_pcie_freeze(struct device *dev) + return btintel_pcie_suspend_late(dev, PMSG_FREEZE); + } + +-static int btintel_pcie_resume(struct device *dev) ++static int btintel_pcie_resume_event(struct device *dev, pm_message_t mesg) + { + struct pci_dev *pdev = to_pci_dev(dev); + struct btintel_pcie_data *data; +@@ -3140,19 +3147,15 @@ static int btintel_pcie_resume(struct device *dev) + int err; + + data = pci_get_drvdata(pdev); +- data->gp0_received = false; + + start = ktime_get(); + +- /* When the system enters S4 (hibernate) mode, bluetooth device loses +- * power, which results in the erasure of its loaded firmware. +- * Consequently, function level reset (flr) is required on system +- * resume to bring the controller back into an operational state by +- * initiating a new firmware download. ++ /* S3 and S4 may cut power, erasing the firmware. Force FLR to recover ++ * instead of a normal D0 transition. + */ +- +- if (data->pm_sx_event == PM_EVENT_FREEZE || +- data->pm_sx_event == PM_EVENT_HIBERNATE) { ++ if (mesg.event == PM_EVENT_RESTORE || ++ (mesg.event == PM_EVENT_RESUME && ++ pm_suspend_target_state == PM_SUSPEND_MEM)) { + set_bit(BTINTEL_PCIE_CORE_HALTED, &data->flags); + btintel_pcie_request_reset(data, BTINTEL_PCIE_IOSF_PRR_FLR); + return 0; +@@ -3161,7 +3164,9 @@ static int btintel_pcie_resume(struct device *dev) + /* Refer: 6.4.11.7 -> Platform power management */ + err = btintel_pcie_set_dxstate(data, BTINTEL_PCIE_STATE_D0); + +- if (err == 0) { ++ if (err) { ++ bt_dev_err(data->hdev, "Failed to set D0 state (%d)", err); ++ } else { + bt_dev_dbg(data->hdev, + "device entered into d0 state from d3 in %lld us", + ktime_to_us(ktime_get() - start)); +@@ -3190,13 +3195,28 @@ static int btintel_pcie_resume(struct device *dev) + return err; + } + ++static int btintel_pcie_resume(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESUME); ++} ++ ++static int btintel_pcie_restore(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_RESTORE); ++} ++ ++static int btintel_pcie_thaw(struct device *dev) ++{ ++ return btintel_pcie_resume_event(dev, PMSG_THAW); ++} ++ + static const struct dev_pm_ops btintel_pcie_pm_ops = { + .suspend = btintel_pcie_suspend, + .resume = btintel_pcie_resume, + .freeze = btintel_pcie_freeze, +- .thaw = btintel_pcie_resume, ++ .thaw = btintel_pcie_thaw, + .poweroff = btintel_pcie_hibernate, +- .restore = btintel_pcie_resume, ++ .restore = btintel_pcie_restore, + }; + + static struct pci_driver btintel_pcie_driver = { +diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h +--- a/drivers/bluetooth/btintel_pcie.h ++++ b/drivers/bluetooth/btintel_pcie.h +@@ -478,7 +478,6 @@ struct btintel_pcie_dump_header { + * @txq: TX Queue struct + * @rxq: RX Queue struct + * @alive_intr_ctxt: Alive interrupt context +- * @pm_sx_event: PM event on which system got suspended + */ + struct btintel_pcie_data { + struct pci_dev *pdev; +@@ -533,7 +532,6 @@ struct btintel_pcie_data { + enum btintel_pcie_reset_type reset_type; + struct btintel_pcie_dbgc dbgc; + struct btintel_pcie_dump_header dmp_hdr; +- u8 pm_sx_event; + u32 debug_evt_addr; + u32 debug_evt_size; + }; diff --git a/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig b/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig new file mode 100644 index 0000000..89af410 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch b/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch new file mode 100644 index 0000000..b1305d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch @@ -0,0 +1,113 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1012,6 +1012,10 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + submit_rx = false; + signal_waitq = false; + ++ /* Serialize the alive_intr_ctxt claim against set_dxstate()'s ++ * register-based fallback, which can run concurrently. ++ */ ++ spin_lock(&data->irq_lock); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1067,6 +1071,7 @@ static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data) + data->alive_intr_ctxt); + break; + } ++ spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); +@@ -3047,7 +3052,7 @@ static void btintel_pcie_coredump(struct device *dev) + + static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + { +- int retry = 0; ++ int retry = 0, err; + long status; + u32 dx_intr_timeout_ms = 200; + +@@ -3059,30 +3064,66 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + status = wait_event_timeout(data->gp0_wait_q, data->gp0_received, + msecs_to_jiffies(dx_intr_timeout_ms)); + +- if (status) +- return 0; ++ if (!status) { ++ bt_dev_warn(data->hdev, ++ "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", ++ dx_intr_timeout_ms, dxstate, retry); + +- bt_dev_warn(data->hdev, +- "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", +- dx_intr_timeout_ms, dxstate, retry); ++ /* clear gp0 cause */ ++ btintel_pcie_clr_reg_bits(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ } + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); +- +- /* A hardware bug may cause the alive interrupt to be missed. Refresh +- * boot_stage_cache from hardware, since only the interrupt handler +- * updates it. Finally retry only if the state check still fails. ++ /* gp0_received is set at the top of the handler, before the switch on ++ * alive_intr_ctxt. Error and lockdown are filtered out above it, but a ++ * matched case can still complete without doing anything - D3 breaks ++ * unchanged while the controller has not reached D0 - and a hardware ++ * bug may drop the interrupt outright. Either way the flag says a gp0 ++ * was handled, not that the transition completed, and only the register ++ * knows. Refresh the cache here and retry only if the state check still ++ * fails. + */ + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { +- if (btintel_pcie_in_d0(data)) ++ if (btintel_pcie_in_d0(data)) { ++ bool armed; ++ ++ /* Claim the transition under irq_lock so a ++ * concurrent gp0 handler run cannot also arm RX. ++ */ ++ spin_lock(&data->irq_lock); ++ armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; ++ if (!armed) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D0; ++ spin_unlock(&data->irq_lock); ++ ++ if (armed) ++ return 0; ++ ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ if (err) ++ return err; ++ + return 0; ++ } + } else { +- if (btintel_pcie_in_d3(data)) ++ if (btintel_pcie_in_d3(data)) { ++ /* Do what the handler's D0 -> D3 branch ++ * would have done, unless it already has. ++ */ ++ spin_lock(&data->irq_lock); ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) ++ data->alive_intr_ctxt = BTINTEL_PCIE_D3; ++ spin_unlock(&data->irq_lock); ++ + return 0; ++ } + } + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); diff --git a/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch.sig b/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch.sig new file mode 100644 index 0000000..8fbfd35 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0666-btintel-pcie-verify-serialize-dstate.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch b/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch new file mode 100644 index 0000000..9927412 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch @@ -0,0 +1,20 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -3069,10 +3069,12 @@ static int btintel_pcie_set_dxstate(struct btintel_pcie_data *data, u32 dxstate) + "Timeout (%u ms) on alive interrupt for D%d entry, retry count %d", + dx_intr_timeout_ms, dxstate, retry); + +- /* clear gp0 cause */ +- btintel_pcie_clr_reg_bits(data, +- BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, +- BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); ++ /* MSIX_HW_INT_CAUSES is W1C. Write only GP0 so other ++ * pending causes are not acknowledged here. ++ */ ++ btintel_pcie_wr_reg32(data, ++ BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES, ++ BTINTEL_PCIE_MSIX_HW_INT_CAUSES_GP0); + } + + /* gp0_received is set at the top of the handler, before the switch on diff --git a/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig b/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig new file mode 100644 index 0000000..817ba81 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0667-btintel-pcie-gp0-cause-w1c-write.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch b/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch new file mode 100644 index 0000000..d25c602 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch @@ -0,0 +1,86 @@ +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1016,6 +1016,8 @@ static void btintel_pcie_msix_gp0_handle + * register-based fallback, which can run concurrently. + */ + spin_lock(&data->irq_lock); ++ data->boot_stage_cache = btintel_pcie_rd_reg32(data, ++ BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + switch (data->alive_intr_ctxt) { + case BTINTEL_PCIE_ROM: + data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL; +@@ -1071,12 +1073,12 @@ static void btintel_pcie_msix_gp0_handle + data->alive_intr_ctxt); + break; + } +- spin_unlock(&data->irq_lock); + + if (submit_rx) { + btintel_pcie_reset_ia(data); + btintel_pcie_start_rx(data); + } ++ spin_unlock(&data->irq_lock); + + if (signal_waitq) { + bt_dev_dbg(data->hdev, "wake up gp0 wait_q"); +@@ -3086,40 +3088,34 @@ static int btintel_pcie_set_dxstate(stru + * knows. Refresh the cache here and retry only if the state check still + * fails. + */ ++ spin_lock(&data->irq_lock); + data->boot_stage_cache = btintel_pcie_rd_reg32(data, + BTINTEL_PCIE_CSR_BOOT_STAGE_REG); + if (dxstate == BTINTEL_PCIE_STATE_D0) { + if (btintel_pcie_in_d0(data)) { +- bool armed; ++ err = 0; + + /* Claim the transition under irq_lock so a + * concurrent gp0 handler run cannot also arm RX. + */ +- spin_lock(&data->irq_lock); +- armed = data->alive_intr_ctxt == BTINTEL_PCIE_D0; +- if (!armed) ++ if (data->alive_intr_ctxt != BTINTEL_PCIE_D0) { + data->alive_intr_ctxt = BTINTEL_PCIE_D0; +- spin_unlock(&data->irq_lock); + +- if (armed) +- return 0; +- +- /* Do what the handler's D3 -> D0 branch +- * would have done, unless it already has. +- */ +- btintel_pcie_reset_ia(data); +- err = btintel_pcie_start_rx(data); +- if (err) +- return err; ++ /* Do what the handler's D3 -> D0 branch ++ * would have done, unless it already has. ++ */ ++ btintel_pcie_reset_ia(data); ++ err = btintel_pcie_start_rx(data); ++ } ++ spin_unlock(&data->irq_lock); + +- return 0; ++ return err; + } + } else { + if (btintel_pcie_in_d3(data)) { + /* Do what the handler's D0 -> D3 branch + * would have done, unless it already has. + */ +- spin_lock(&data->irq_lock); + if (data->alive_intr_ctxt != BTINTEL_PCIE_D3) + data->alive_intr_ctxt = BTINTEL_PCIE_D3; + spin_unlock(&data->irq_lock); +@@ -3127,6 +3123,7 @@ static int btintel_pcie_set_dxstate(stru + return 0; + } + } ++ spin_unlock(&data->irq_lock); + + } while (++retry < BTINTEL_PCIE_DX_TRANSITION_MAX_RETRIES); + diff --git a/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch.sig b/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch.sig new file mode 100644 index 0000000..93a7c8a Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0668-btintel-pcie-dstate-races.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch b/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch new file mode 100644 index 0000000..164dbb6 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch @@ -0,0 +1,24 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -720,6 +720,7 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + { + struct sk_buff *skb; + const u8 param[1] = { 0xFF }; ++ int err; + + if (!version) + return -EINVAL; +@@ -738,10 +739,10 @@ static int btintel_read_version_tlv(struct hci_dev *hdev, + return -EIO; + } + +- btintel_parse_version_tlv(hdev, version, skb); ++ err = btintel_parse_version_tlv(hdev, version, skb); + + kfree_skb(skb); +- return 0; ++ return err; + } + + /* ------- REGMAP IBT SUPPORT ------- */ diff --git a/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch.sig b/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch.sig new file mode 100644 index 0000000..caff3f4 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0669-btintel-version-tlv-parse-errors.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch new file mode 100644 index 0000000..14623cd --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch @@ -0,0 +1,485 @@ +diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c +--- a/drivers/bluetooth/btintel.c ++++ b/drivers/bluetooth/btintel.c +@@ -288,7 +288,8 @@ void btintel_hw_error(struct hci_dev *hdev, u8 code) + goto unlock; + } + +- bt_dev_err(hdev, "Exception info %s", (char *)(skb->data + 1)); ++ bt_dev_err(hdev, "Exception info %.*s", (int)(skb->len - 1), ++ (char *)(skb->data + 1)); + + kfree_skb(skb); + +@@ -407,8 +408,16 @@ int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name) + /* DDC file contains one or more DDC structure which has + * Length (1 byte), DDC ID (2 bytes), and DDC value (Length - 2). + */ +- while (fw->size > fw_ptr - fw->data) { +- u8 cmd_plen = fw_ptr[0] + sizeof(u8); ++ while (fw->size > (size_t)(fw_ptr - fw->data)) { ++ size_t remaining = fw->size - (fw_ptr - fw->data); ++ unsigned int cmd_plen = fw_ptr[0] + 1U; ++ ++ if (cmd_plen < 3 || cmd_plen > U8_MAX || cmd_plen > remaining) { ++ bt_dev_err(hdev, "Malformed DDC record (plen=%u, remaining=%zu)", ++ cmd_plen, remaining); ++ release_firmware(fw); ++ return -EINVAL; ++ } + + skb = __hci_cmd_sync(hdev, 0xfc8b, cmd_plen, fw_ptr, + HCI_INIT_TIMEOUT); +diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c +--- a/drivers/bluetooth/btintel_pcie.c ++++ b/drivers/bluetooth/btintel_pcie.c +@@ -1200,7 +1200,7 @@ static int btintel_pcie_recv_frame(struct btintel_pcie_data *data, + { + int ret; + u8 pkt_type; +- u16 plen; ++ u32 plen; + u32 pcie_pkt_type; + void *pdata; + struct hci_dev *hdev = data->hdev; +@@ -2149,6 +2149,16 @@ static int btintel_pcie_send_frame(struct hci_dev *hdev, + if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) + return -ENODEV; + ++ /* Account for the 4-byte PCIe type header prepended before the ++ * DMA copy. Written as a subtraction to avoid wrap-around on ++ * attacker-controlled skb->len. ++ */ ++ if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { ++ bt_dev_err(hdev, "Packet too large: %u > %u", skb->len, ++ BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); ++ return -EMSGSIZE; ++ } ++ + /* Due to the fw limitation, the type header of the packet should be + * 4 bytes unlike 1 byte for UART. In UART, the firmware can read + * the first byte to get the packet type and redirect the rest of data +diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h +--- a/include/net/bluetooth/hci_core.h ++++ b/include/net/bluetooth/hci_core.h +@@ -62,7 +62,7 @@ struct inquiry_entry { + NAME_PENDING, + NAME_KNOWN, + } name_state; +- __u32 timestamp; ++ unsigned long timestamp; + struct inquiry_data data; + }; + +@@ -78,7 +78,7 @@ struct discovery_state { + struct list_head all; /* All devices found during inquiry */ + struct list_head unknown; /* Name state not known */ + struct list_head resolve; /* Name needs to be resolved */ +- __u32 timestamp; ++ unsigned long timestamp; + bdaddr_t last_adv_addr; + u8 last_adv_addr_type; + s8 last_adv_rssi; +@@ -561,6 +561,7 @@ struct hci_dev { + struct list_head link_keys; + struct list_head long_term_keys; + struct list_head identity_resolving_keys; ++ struct mutex remote_oob_lock; + struct list_head remote_oob_data; + struct list_head le_accept_list; + struct list_head le_resolv_list; +diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c +--- a/net/bluetooth/hci_conn.c ++++ b/net/bluetooth/hci_conn.c +@@ -302,11 +302,13 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.tx_bandwidth = cpu_to_le32(0x00001f40); + cp.rx_bandwidth = cpu_to_le32(0x00001f40); + ++ hci_dev_lock(hdev); ++ + switch (conn->codec.id) { + case BT_CODEC_MSBC: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x05; +@@ -332,7 +334,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + case BT_CODEC_TRANSPARENT: + if (!find_next_esco_param(conn, esco_param_msbc, + ARRAY_SIZE(esco_param_msbc))) +- return -EINVAL; ++ goto unlock; + + param = &esco_param_msbc[conn->attempt - 1]; + cp.tx_coding_format.id = 0x03; +@@ -359,11 +361,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + if (conn->parent && lmp_esco_capable(conn->parent)) { + if (!find_next_esco_param(conn, esco_param_cvsd, + ARRAY_SIZE(esco_param_cvsd))) +- return -EINVAL; ++ goto unlock; + param = &esco_param_cvsd[conn->attempt - 1]; + } else { + if (conn->attempt > ARRAY_SIZE(sco_param_cvsd)) +- return -EINVAL; ++ goto unlock; + param = &sco_param_cvsd[conn->attempt - 1]; + } + cp.tx_coding_format.id = 2; +@@ -386,9 +388,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + cp.out_transport_unit_size = 16; + break; + default: +- return -EINVAL; ++ goto unlock; + } + ++ hci_dev_unlock(hdev); ++ + cp.retrans_effort = param->retrans_effort; + cp.pkt_type = __cpu_to_le16(param->pkt_type); + cp.max_latency = __cpu_to_le16(param->max_latency); +@@ -397,6 +401,10 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) + return -EIO; + + return 0; ++ ++unlock: ++ hci_dev_unlock(hdev); ++ return -EINVAL; + } + + static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) +diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c +--- a/net/bluetooth/hci_core.c ++++ b/net/bluetooth/hci_core.c +@@ -1486,8 +1486,10 @@ int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s removing %pMR (%u)", hdev->name, bdaddr, bdaddr_type); + ++ mutex_lock(&hdev->remote_oob_lock); + list_del(&data->list); + kfree(data); ++ mutex_unlock(&hdev->remote_oob_lock); + + return 0; + } +@@ -1496,10 +1498,12 @@ void hci_remote_oob_data_clear(struct hci_dev *hdev) + { + struct oob_data *data, *n; + ++ mutex_lock(&hdev->remote_oob_lock); + list_for_each_entry_safe(data, n, &hdev->remote_oob_data, list) { + list_del(&data->list); + kfree(data); + } ++ mutex_unlock(&hdev->remote_oob_lock); + } + + int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, +@@ -1508,11 +1512,14 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + { + struct oob_data *data; + ++ mutex_lock(&hdev->remote_oob_lock); + data = hci_find_remote_oob_data(hdev, bdaddr, bdaddr_type); + if (!data) { + data = kmalloc_obj(*data); +- if (!data) ++ if (!data) { ++ mutex_unlock(&hdev->remote_oob_lock); + return -ENOMEM; ++ } + + bacpy(&data->bdaddr, bdaddr); + data->bdaddr_type = bdaddr_type; +@@ -1545,6 +1552,8 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, + + BT_DBG("%s for %pMR", hdev->name, bdaddr); + ++ mutex_unlock(&hdev->remote_oob_lock); ++ + return 0; + } + +@@ -2482,6 +2491,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) + mutex_init(&hdev->lock); + mutex_init(&hdev->req_lock); + mutex_init(&hdev->mgmt_pending_lock); ++ mutex_init(&hdev->remote_oob_lock); + + ida_init(&hdev->unset_handle_ida); + +@@ -2554,8 +2564,10 @@ int hci_register_dev(struct hci_dev *hdev) + return id; + + error = dev_set_name(&hdev->dev, "hci%u", id); +- if (error) ++ if (error) { ++ ida_free(&hci_index_ida, id); + return error; ++ } + + hdev->name = dev_name(&hdev->dev); + hdev->id = id; +@@ -3323,6 +3335,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + skb_shinfo(skb)->frag_list = NULL; + ++ spin_lock_bh(&queue->lock); ++ + __skb_queue_tail(queue, skb); + + do { +@@ -3337,6 +3351,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, + + __skb_queue_tail(queue, skb); + } while (list); ++ ++ spin_unlock_bh(&queue->lock); + } + + bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(queue)); +@@ -3400,9 +3416,6 @@ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, + struct hci_conn *conn = NULL, *c; + unsigned int num = 0, min = ~0; + +- /* We don't have to lock device here. Connections are always +- * added and removed with TX task disabled. */ +- + rcu_read_lock(); + + list_for_each_entry_rcu(c, &h->list, list) { +@@ -3607,13 +3620,15 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt, u8 type) + } + + /* Schedule SCO */ +-static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++static void __hci_sched_sco(struct hci_dev *hdev, __u8 type) + { + struct hci_conn *conn; + struct sk_buff *skb; + int quote, *cnt; + unsigned int pkts = hdev->sco_pkts; + ++ lockdep_assert_held(&hdev->lock); ++ + bt_dev_dbg(hdev, "type %u", type); + + if (!hci_conn_num(hdev, type) || !pkts) +@@ -3648,6 +3663,13 @@ static void hci_sched_sco(struct hci_dev *hdev, __u8 type) + queue_work(hdev->workqueue, &hdev->tx_work); + } + ++static void hci_sched_sco(struct hci_dev *hdev, __u8 type) ++{ ++ hci_dev_lock(hdev); ++ __hci_sched_sco(hdev, type); ++ hci_dev_unlock(hdev); ++} ++ + static void hci_sched_acl_pkt(struct hci_dev *hdev) + { + unsigned int cnt = hdev->acl_cnt; +@@ -3657,6 +3679,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + + __check_timeout(hdev, cnt, ACL_LINK); + ++ hci_dev_lock(hdev); ++ + while (hdev->acl_cnt && + (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3681,13 +3705,15 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (cnt != hdev->acl_cnt) + hci_prio_recalculate(hdev, ACL_LINK); ++ ++ hci_dev_unlock(hdev); + } + + static void hci_sched_acl(struct hci_dev *hdev) +@@ -3716,6 +3742,8 @@ static void hci_sched_le(struct hci_dev *hdev) + + __check_timeout(hdev, *cnt, LE_LINK); + ++ hci_dev_lock(hdev); ++ + tmp = *cnt; + while (*cnt && (chan = hci_chan_sent(hdev, LE_LINK, "e))) { + u32 priority = (skb_peek(&chan->data_q))->priority; +@@ -3737,13 +3765,15 @@ static void hci_sched_le(struct hci_dev *hdev) + chan->conn->sent++; + + /* Send pending SCO packets right away */ +- hci_sched_sco(hdev, SCO_LINK); +- hci_sched_sco(hdev, ESCO_LINK); ++ __hci_sched_sco(hdev, SCO_LINK); ++ __hci_sched_sco(hdev, ESCO_LINK); + } + } + + if (*cnt != tmp) + hci_prio_recalculate(hdev, LE_LINK); ++ ++ hci_dev_unlock(hdev); + } + + /* Schedule iso */ +@@ -3762,6 +3792,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + + __check_timeout(hdev, *cnt, type); + ++ hci_dev_lock(hdev); ++ + while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); +@@ -3775,6 +3807,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) + (*cnt)--; + } + } ++ ++ hci_dev_unlock(hdev); + } + + static void hci_tx_work(struct work_struct *work) +diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c +--- a/net/bluetooth/hci_debugfs.c ++++ b/net/bluetooth/hci_debugfs.c +@@ -364,7 +364,7 @@ static int inquiry_cache_show(struct seq_file *f, void *p) + + list_for_each_entry(e, &cache->all, all) { + struct inquiry_data *data = &e->data; +- seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n", ++ seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %lu\n", + &data->bdaddr, + data->pscan_rep_mode, data->pscan_period_mode, + data->pscan_mode, data->dev_class[2], +diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c +--- a/net/bluetooth/hci_sync.c ++++ b/net/bluetooth/hci_sync.c +@@ -312,6 +312,10 @@ static void hci_cmd_sync_work(struct work_struct *work) + while (1) { + struct hci_cmd_sync_work_entry *entry; + ++ /* Leave the backlog to hci_cmd_sync_clear() */ ++ if (hci_dev_test_flag(hdev, HCI_UNREGISTER)) ++ break; ++ + mutex_lock(&hdev->cmd_sync_work_lock); + entry = list_first_entry_or_null(&hdev->cmd_sync_work_list, + struct hci_cmd_sync_work_entry, +@@ -658,6 +662,8 @@ void hci_cmd_sync_clear(struct hci_dev *hdev) + { + struct hci_cmd_sync_work_entry *entry, *tmp; + ++ /* cmd_work is disabled, the pending request can only time out */ ++ hci_cmd_sync_cancel_sync(hdev, ENODEV); + cancel_work_sync(&hdev->cmd_sync_work); + cancel_work_sync(&hdev->reenable_adv_work); + +@@ -5678,6 +5684,7 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + { + struct discovery_state *d = &hdev->discovery; + struct inquiry_entry *e; ++ bdaddr_t addr; + int err; + + bt_dev_dbg(hdev, "state %u", hdev->discovery.state); +@@ -5713,15 +5720,21 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) + return 0; + + if (d->state == DISCOVERY_RESOLVING || d->state == DISCOVERY_STOPPING) { ++ hci_dev_lock(hdev); + e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, + NAME_PENDING); +- if (!e) ++ if (!e) { ++ hci_dev_unlock(hdev); + return 0; ++ } ++ ++ bacpy(&addr, &e->data.bdaddr); ++ hci_dev_unlock(hdev); + + /* Ignore cancel errors since it should interfere with stopping + * of the discovery. + */ +- hci_remote_name_cancel_sync(hdev, &e->data.bdaddr); ++ hci_remote_name_cancel_sync(hdev, &addr); + } + + return 0; +@@ -7151,6 +7164,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + bacpy(&cp.bdaddr, &conn->dst); + cp.pscan_rep_mode = 0x02; + ++ hci_dev_lock(hdev); + ie = hci_inquiry_cache_lookup(hdev, &conn->dst); + if (ie) { + if (inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { +@@ -7162,6 +7176,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) + + memcpy(conn->dev_class, ie->data.dev_class, 3); + } ++ hci_dev_unlock(hdev); + + cp.pkt_type = cpu_to_le16(conn->pkt_type); + if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) +diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c +--- a/net/bluetooth/rfcomm/tty.c ++++ b/net/bluetooth/rfcomm/tty.c +@@ -462,7 +462,7 @@ static int __rfcomm_release_dev(void __user *arg) + /* Shut down TTY synchronously before freeing rfcomm_dev */ + tty_port_tty_vhangup(&dev->port); + +- if (!test_bit(RFCOMM_TTY_OWNED, &dev->status)) ++ if (!test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); + + tty_port_put(&dev->port); +@@ -724,10 +724,9 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty) + * when the last process closes the tty. The behaviour is expected by + * userspace. + */ +- if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { +- set_bit(RFCOMM_TTY_OWNED, &dev->status); ++ if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags) && ++ !test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) + tty_port_put(&dev->port); +- } + + return 0; + } +diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c +--- a/net/bluetooth/smp.c ++++ b/net/bluetooth/smp.c +@@ -661,6 +661,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + else + bdaddr_type = BDADDR_LE_RANDOM; + ++ mutex_lock(&hdev->remote_oob_lock); + oob_data = hci_find_remote_oob_data(hdev, &hcon->dst, + bdaddr_type); + if (oob_data && oob_data->present) { +@@ -671,6 +672,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, + SMP_DBG("OOB Remote Confirmation: %16phN", smp->pcnf); + SMP_DBG("OOB Remote Random: %16phN", smp->rr); + } ++ mutex_unlock(&hdev->remote_oob_lock); + + } else { + authreq &= ~SMP_AUTH_SC; diff --git a/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch.sig new file mode 100644 index 0000000..cadba43 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0670-bluetooth-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch b/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch new file mode 100644 index 0000000..1102b3f --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch @@ -0,0 +1,16 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -108,7 +108,11 @@ int pcie_failed_link_retrain(struct pci_dev *dev) + + pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); + pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); +- if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) { ++ if (lnksta & PCI_EXP_LNKSTA_DLLLA) { ++ ; ++ } else if (PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2) == PCIE_SPEED_2_5GT) { ++ return ret; ++ } else if (pcie_lbms_seen(dev, lnksta)) { + pci_info(dev, "broken device, retraining non-functional downstream link at 2.5GT/s\n"); + ret = pcie_set_target_speed(dev, PCIE_SPEED_2_5GT, false); + if (ret) diff --git a/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch.sig new file mode 100644 index 0000000..2b1e9cc Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0700-pci-target-speed-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch b/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch new file mode 100644 index 0000000..c76cb96 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch @@ -0,0 +1,109 @@ +diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c +--- a/arch/x86/pci/fixup.c ++++ b/arch/x86/pci/fixup.c +@@ -886,6 +886,105 @@ static void quirk_clear_strap_no_soft_reset_dev2_f0(struct pci_dev *dev) + } + } + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15b8, quirk_clear_strap_no_soft_reset_dev2_f0); ++ ++/* ++ * Enhanced atomic operations can cause corruption with 64-bit IOVA ++ * on these devices. ++ */ ++#define RX_ENH_ATOMIC_EN BIT(8) ++ ++static const u32 nbio_7_7_pcie_smn_addrs[] = { ++ 0x111401d0, ++ 0x111411d0, ++ 0x111421d0, ++ 0x111431d0, ++ 0x111441d0, ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x114401d0, ++}; ++ ++static const u32 nbio_7_11_pcie_smn_addrs[] = { ++ 0x112401d0, ++ 0x112411d0, ++ 0x112421d0, ++ 0x112431d0, ++ 0x112441d0, ++ 0x112451d0, ++ 0x113401d0, ++ 0x113411d0, ++ 0x113421d0, ++ 0x113431d0, ++ 0x113441d0, ++ 0x113451d0, ++}; ++ ++static void quirk_amd_nbio_enhanced_atomic(struct pci_dev *host_bridge, ++ const u32 *smn_addrs, ++ size_t nr_smn_addrs) ++{ ++ bool changed = false; ++ size_t i; ++ u32 data; ++ int ret; ++ ++ for (i = 0; i < nr_smn_addrs; i++) { ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (!(data & RX_ENH_ATOMIC_EN)) ++ continue; ++ data = data & ~RX_ENH_ATOMIC_EN; ++ ret = amd_smn_write(0, smn_addrs[i], data); ++ if (ret) ++ continue; ++ if (changed) ++ continue; ++ ret = amd_smn_read(0, smn_addrs[i], &data); ++ if (ret) ++ continue; ++ if (data & RX_ENH_ATOMIC_EN) ++ continue; ++ changed = true; ++ } ++ ++ if (changed) ++ pci_info(host_bridge, "enhanced atomics disabled\n"); ++} ++ ++static void quirk_amd_nbio_7_7_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_7_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_7_pcie_smn_addrs)); ++} ++ ++static void quirk_amd_nbio_7_11_disable_enhanced_atomic(struct pci_dev *dev) ++{ ++ quirk_amd_nbio_enhanced_atomic(dev, nbio_7_11_pcie_smn_addrs, ++ ARRAY_SIZE(nbio_7_11_pcie_smn_addrs)); ++} ++ ++/* Phoenix, Hawk Point (NBIO 7.7) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x14E8, ++ quirk_amd_nbio_7_7_disable_enhanced_atomic); ++ ++/* Strix, Krackan, Strix Halo (NBIO 7.11) */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1507, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1122, ++ quirk_amd_nbio_7_11_disable_enhanced_atomic); ++ + #endif + + /* diff --git a/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch.sig b/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch.sig new file mode 100644 index 0000000..7732219 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0701-pci-amd-enhanced-atomics.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch b/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch new file mode 100644 index 0000000..9794028 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch @@ -0,0 +1,42 @@ +diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c +--- a/drivers/ata/ahci.c ++++ b/drivers/ata/ahci.c +@@ -68,7 +68,6 @@ enum board_ids { + /* board IDs for specific chipsets in alphabetical order */ + board_ahci_al, + board_ahci_avn, +- board_ahci_jmb585, + board_ahci_mcp65, + board_ahci_mcp77, + board_ahci_mcp89, +@@ -213,15 +212,6 @@ static const struct ata_port_info ahci_port_info[] = { + .udma_mask = ATA_UDMA6, + .port_ops = &ahci_avn_ops, + }, +- /* JMicron JMB582/585: 64-bit DMA is broken, force 32-bit */ +- [board_ahci_jmb585] = { +- AHCI_HFLAGS (AHCI_HFLAG_IGN_IRQ_IF_ERR | +- AHCI_HFLAG_32BIT_ONLY), +- .flags = AHCI_FLAG_COMMON, +- .pio_mask = ATA_PIO4, +- .udma_mask = ATA_UDMA6, +- .port_ops = &ahci_ops, +- }, + [board_ahci_mcp65] = { + AHCI_HFLAGS (AHCI_HFLAG_NO_FPDMA_AA | AHCI_HFLAG_NO_PMP | + AHCI_HFLAG_YES_NCQ), +@@ -954,14 +944,6 @@ static const struct pci_device_id ahci_pci_tbl[] = { + /* Elkhart Lake AHCI */ + PCI_VDEVICE(INTEL, 0x4b63), + .driver_data = board_ahci_pcs_quirk, +- }, { +- /* JMicron JMB582/585: force 32-bit DMA (broken 64-bit implementation) */ +- PCI_VDEVICE(JMICRON, 0x0582), +- .driver_data = board_ahci_jmb585, +- +- }, { +- PCI_VDEVICE(JMICRON, 0x0585), +- .driver_data = board_ahci_jmb585, + }, { + /* JMicron 360/1/3/5/6, match class to avoid IDE function */ + PCI_DEVICE(PCI_VENDOR_ID_JMICRON, PCI_ANY_ID), diff --git a/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch.sig b/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch.sig new file mode 100644 index 0000000..58c4c7c Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0702-ahci-jmb585-32bit-dma-revert.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch b/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch new file mode 100644 index 0000000..0fe6c04 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch @@ -0,0 +1,297 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -37,6 +37,12 @@ + #define MB2_SHADOW_OFFSET 0x2000 + #define MB2_SHADOW_SIZE 16 + ++/* DMR BAR4 register offsets */ ++#define SHADOW_MEMBAR1_28C1 0x2818 /* MEMBAR1 physical address */ ++#define SHADOW_MEMBAR2_28C1 0x2820 /* MEMBAR2 physical address */ ++#define BASE_ID_REG_28C1 0x2840 ++#define MEMBAR2_OFFSET_28C1 0x30d0 ++ + enum vmd_features { + /* + * Device may contain registers which hint the physical location of the +@@ -77,6 +83,15 @@ enum vmd_features { + * proper power management of the SoC. + */ + VMD_FEAT_BIOS_PM_QUIRK = (1 << 5), ++ ++ /* ++ * Newer VMD with device ID 0x28C1 has unique settings compared to its ++ * predecessor where BIOS enumerates the entire VMD device tree and ++ * stores respective configurations including bus start range and ++ * shadow registers in VMD MMIO space in VMD BAR4/BAR5, otherwise ++ * referred to as MEMBAR2 or MSI-X BAR. ++ */ ++ VMD_FEAT_USE_BIOS_INFO = (1 << 6), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -142,6 +157,7 @@ struct vmd_dev { + u8 first_vec; + char *name; + int instance; ++ unsigned long features; + }; + + static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) +@@ -366,6 +382,9 @@ static void vmd_set_msi_remapping(struct vmd_dev *vmd, bool enable) + { + u16 reg; + ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return; ++ + pci_read_config_word(vmd->dev, PCI_REG_VMCONFIG, ®); + reg = enable ? (reg & ~VMCONFIG_MSI_REMAP) : + (reg | VMCONFIG_MSI_REMAP); +@@ -389,11 +408,22 @@ static void vmd_remove_irq_domain(struct vmd_dev *vmd) + } + } + ++static unsigned int vmd_bus_to_ecam(struct vmd_dev *vmd, unsigned int busnr) ++{ ++ if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO)) ++ return busnr; ++ ++ return busnr - vmd->busn_start; ++} ++ + static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, + unsigned int devfn, int reg, int len) + { +- unsigned int busnr_ecam = bus->number - vmd->busn_start; +- u32 offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); ++ unsigned int busnr_ecam; ++ u32 offset; ++ ++ busnr_ecam = vmd_bus_to_ecam(vmd, bus->number); ++ offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); + + if (offset + len >= resource_size(&vmd->dev->resource[VMD_CFGBAR])) + return NULL; +@@ -518,22 +548,37 @@ static inline void vmd_acpi_begin(void) { } + static inline void vmd_acpi_end(void) { } + #endif /* CONFIG_ACPI */ + ++static resource_size_t vmd_cfgbar_ecam_space(struct vmd_dev *vmd) ++{ ++ resource_size_t cfgbar_buses; ++ unsigned int ecam_start; ++ ++ cfgbar_buses = resource_size(&vmd->dev->resource[VMD_CFGBAR]) >> 20; ++ ecam_start = vmd_bus_to_ecam(vmd, vmd->resources[0].start); ++ if (ecam_start >= cfgbar_buses) ++ return 0; ++ ++ return cfgbar_buses - ecam_start; ++} + static void vmd_domain_reset(struct vmd_dev *vmd) + { + u16 bus, max_buses = resource_size(&vmd->resources[0]); + u8 dev, functions, fn, hdr_type; ++ unsigned int ecam_bus; + char __iomem *base; + ++ max_buses = min_t(u16, max_buses, vmd_cfgbar_ecam_space(vmd)); + for (bus = 0; bus < max_buses; bus++) { ++ ecam_bus = vmd_bus_to_ecam(vmd, vmd->resources[0].start + bus); + for (dev = 0; dev < 32; dev++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, 0), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE); + + functions = (hdr_type & PCI_HEADER_TYPE_MFD) ? 8 : 1; + for (fn = 0; fn < functions; fn++) { +- base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, ++ base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus, + PCI_DEVFN(dev, fn), 0); + + hdr_type = readb(base + PCI_HEADER_TYPE) & +@@ -661,6 +706,46 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_get_bus_info_from_bar4(struct vmd_dev *vmd, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ u64 phys1, phys2, bar4_2840; ++ void __iomem *bar4; ++ u32 base_id; ++ u8 base_bus; ++ ++ bar4 = pci_ioremap_bar(vmd->dev, 4); ++ if (!bar4) ++ return -ENOMEM; ++ ++ /* Read shadow registers for MEMBAR1 and MEMBAR2 physical addresses */ ++ phys1 = readq(bar4 + SHADOW_MEMBAR1_28C1); ++ phys2 = readq(bar4 + SHADOW_MEMBAR2_28C1); ++ ++ /* ++ * Read and set bus start number from Base ID register. 24-bit Base ID ++ * register is part of 64-bit shadowed reqid hide range register and ++ * holds segment, bus, device and function. ++ */ ++ bar4_2840 = readq(bar4 + BASE_ID_REG_28C1); ++ base_id = bar4_2840 & 0xFFFFFF; ++ base_bus = base_id >> 8; ++ vmd->busn_start = base_bus; ++ ++ /* Calculate offsets like vmd_get_phys_offsets() does */ ++ if (phys1) ++ *offset1 = vmd->dev->resource[VMD_MEMBAR1].start - ++ (phys1 & PCI_BASE_ADDRESS_MEM_MASK); ++ if (phys2) ++ *offset2 = vmd->dev->resource[VMD_MEMBAR2].start - ++ (phys2 & PCI_BASE_ADDRESS_MEM_MASK); ++ ++ pci_iounmap(vmd->dev, bar4); ++ ++ return 0; ++} ++ + static irqreturn_t vmd_irq(int irq, void *data) + { + struct vmd_irq_list *irqs = data; +@@ -711,6 +796,52 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return 0; + } + ++static int vmd_prepare_offsets_and_bus(struct vmd_dev *vmd, ++ unsigned long features, ++ resource_size_t *membar2_offset, ++ resource_size_t *offset1, ++ resource_size_t *offset2) ++{ ++ int ret; ++ ++ /* ++ * Shadow registers may exist in certain VMD device IDs which allow ++ * guests to correctly assign host physical addresses to the root ports ++ * and child devices. These registers will either return the host value ++ * or 0, depending on an enable bit in the VMD device. ++ * ++ * For certain VMD devices (i.e. 0x28C1), BIOS places device info ++ * in BAR4 shadow registers to determine the base bus number and memory ++ * offsets. ++ */ ++ if (features & VMD_FEAT_USE_BIOS_INFO) { ++ *membar2_offset = MEMBAR2_OFFSET_28C1; ++ ret = vmd_get_bus_info_from_bar4(vmd, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { ++ *membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; ++ ret = vmd_get_phys_offsets(vmd, true, offset1, offset2); ++ if (ret) ++ return ret; ++ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { ++ ret = vmd_get_phys_offsets(vmd, false, offset1, offset2); ++ if (ret) ++ return ret; ++ } ++ ++ /* ++ * Certain VMD devices may have a root port configuration option which ++ * limits the bus range to between 0-127, 128-255, or 224-255. ++ */ ++ if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { ++ ret = vmd_get_bus_number_start(vmd); ++ if (ret) ++ return ret; ++ } ++ return 0; ++} ++ + /* + * Since VMD is an aperture to regular PCIe root ports, only allow it to + * control features that the OS is allowed to control on the physical PCI bus. +@@ -780,42 +911,24 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) + LIST_HEAD(resources); + resource_size_t offset[2] = {0}; + resource_size_t membar2_offset = 0x2000; ++ resource_size_t busn_end; + struct pci_bus *child; + struct pci_dev *dev; + int ret; + +- /* +- * Shadow registers may exist in certain VMD device ids which allow +- * guests to correctly assign host physical addresses to the root ports +- * and child devices. These registers will either return the host value +- * or 0, depending on an enable bit in the VMD device. +- */ +- if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) { +- membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE; +- ret = vmd_get_phys_offsets(vmd, true, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) { +- ret = vmd_get_phys_offsets(vmd, false, &offset[0], &offset[1]); +- if (ret) +- return ret; +- } +- +- /* +- * Certain VMD devices may have a root port configuration option which +- * limits the bus range to between 0-127, 128-255, or 224-255 +- */ +- if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) { +- ret = vmd_get_bus_number_start(vmd); +- if (ret) +- return ret; +- } ++ ret = vmd_prepare_offsets_and_bus(vmd, features, &membar2_offset, ++ &offset[0], &offset[1]); ++ if (ret) ++ return ret; + ++ /* Do not let resource[0] end go out of bounds */ + res = &vmd->dev->resource[VMD_CFGBAR]; ++ busn_end = vmd->busn_start + (resource_size(res) >> 20) - 1; ++ busn_end = min_t(resource_size_t, busn_end, 0xff); + vmd->resources[0] = (struct resource) { + .name = "VMD CFGBAR", + .start = vmd->busn_start, +- .end = vmd->busn_start + (resource_size(res) >> 20) - 1, ++ .end = busn_end, + .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, + }; + +@@ -998,6 +1111,7 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) + + vmd->dev = dev; + vmd->sysdata.domain = PCI_DOMAIN_NR_NOT_SET; ++ vmd->features = features; + vmd->instance = ida_alloc(&vmd_instance_ida, GFP_KERNEL); + if (vmd->instance < 0) + return vmd->instance; +@@ -1114,6 +1228,10 @@ static const struct pci_device_id vmd_ids[] = { + .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | + VMD_FEAT_HAS_BUS_RESTRICTIONS | + VMD_FEAT_CAN_BYPASS_MSI_REMAP,}, ++ {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_28C1), ++ .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | ++ VMD_FEAT_CAN_BYPASS_MSI_REMAP | ++ VMD_FEAT_USE_BIOS_INFO,}, + {PCI_VDEVICE(INTEL, 0x467f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x4c3d), +diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h +--- a/include/linux/pci_ids.h ++++ b/include/linux/pci_ids.h +@@ -2892,6 +2892,7 @@ + #define PCI_DEVICE_ID_INTEL_HDA_ICH8 0x284b + #define PCI_DEVICE_ID_INTEL_ICH8_6 0x2850 + #define PCI_DEVICE_ID_INTEL_VMD_28C0 0x28c0 ++#define PCI_DEVICE_ID_INTEL_VMD_28C1 0x28c1 + #define PCI_DEVICE_ID_INTEL_ICH9_0 0x2910 + #define PCI_DEVICE_ID_INTEL_ICH9_2 0x2912 + #define PCI_DEVICE_ID_INTEL_ICH9_3 0x2913 diff --git a/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig b/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig new file mode 100644 index 0000000..7e9f3d1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0703-pci-vmd-scan-bios-enumerated-devices.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch b/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch new file mode 100644 index 0000000..22be8d3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch @@ -0,0 +1,120 @@ +diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -92,6 +92,22 @@ enum vmd_features { + * referred to as MEMBAR2 or MSI-X BAR. + */ + VMD_FEAT_USE_BIOS_INFO = (1 << 6), ++ ++ /* ++ * Meteor Lake VMD (device ID 0x7d0b) is affected by erratum MTL016: ++ * the VMD may signal its MSI before the posted writes that carry the ++ * child device's DMA data have landed in memory. The demuxed handler ++ * then runs against a not-yet-coherent completion queue and misses the ++ * completion entirely, so the I/O is only recovered when the block ++ * layer timeout fires and polls the queue ("timeout, completion ++ * polled"). Intel's documented workaround is to issue a dummy read ++ * to the MSI initiator (the child device) before handling the ++ * interrupt: the read completion cannot pass the device's earlier ++ * posted writes, so it pulls them into memory per PCIe ordering ++ * rules. A read that terminates at the VMD itself does not order ++ * against the child's writes and is not sufficient (measured). ++ */ ++ VMD_FEAT_INTERRUPT_QUIRK = (1 << 7), + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -114,6 +130,9 @@ static DEFINE_RAW_SPINLOCK(list_lock); + * @irq: back pointer to parent. + * @enabled: true if driver enabled IRQ + * @virq: the virtual IRQ value provided to the requesting driver. ++ * @flush_addr: config space address of the initiating device, read before ++ * demuxing to flush its posted writes (MTL016); NULL if the ++ * VMD is not affected. + * + * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to + * a VMD IRQ using this structure. +@@ -123,8 +142,14 @@ struct vmd_irq { + struct vmd_irq_list *irq; + bool enabled; + unsigned int virq; ++ void __iomem *flush_addr; + }; + ++struct vmd_dev; ++ ++static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, ++ unsigned int devfn, int reg, int len); ++ + /** + * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector + * @irq_list: the list of irq's the VMD one demuxes to. +@@ -132,12 +157,15 @@ struct vmd_irq { + * @count: number of child IRQs assigned to this vector; used to track + * sharing. + * @virq: The underlying VMD Linux interrupt number ++ * @vmd: back pointer to the owning VMD device; serializes the MTL016 ++ * flush read against other config space access. + */ + struct vmd_irq_list { + struct list_head irq_list; + struct srcu_struct srcu; + unsigned int count; + unsigned int virq; ++ struct vmd_dev *vmd; + }; + + struct vmd_dev { +@@ -295,6 +323,13 @@ static int vmd_msi_alloc(struct irq_domain *domain, unsigned int virq, + INIT_LIST_HEAD(&vmdirq->node); + vmdirq->irq = vmd_next_irq(vmd, desc); + vmdirq->virq = virq + i; ++ if (vmd->features & VMD_FEAT_INTERRUPT_QUIRK) { ++ struct pci_dev *pdev = msi_desc_to_pci_dev(desc); ++ ++ vmdirq->flush_addr = vmd_cfg_addr(vmd, pdev->bus, ++ pdev->devfn, ++ PCI_VENDOR_ID, 2); ++ } + + irq_domain_set_info(domain, virq + i, vmdirq->irq->virq, + &vmd_msi_controller, vmdirq, +@@ -753,8 +788,20 @@ static irqreturn_t vmd_irq(int irq, void *data) + int idx; + + idx = srcu_read_lock(&irqs->srcu); +- list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) ++ list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) { ++ /* ++ * MTL016: the MSI may have outrun the initiating device's ++ * posted writes (e.g. its NVMe completion entry). A read ++ * that completes at the initiator flushes them, so the ++ * demuxed handler observes a coherent completion queue. ++ * The value is discarded; only the ordering matters. ++ */ ++ if (vmdirq->flush_addr) { ++ guard(raw_spinlock)(&irqs->vmd->cfg_lock); ++ readw(vmdirq->flush_addr); ++ } + generic_handle_irq(vmdirq->virq); ++ } + srcu_read_unlock(&irqs->srcu, idx); + + return IRQ_HANDLED; +@@ -785,6 +832,7 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd) + return err; + + INIT_LIST_HEAD(&vmd->irqs[i].irq_list); ++ vmd->irqs[i].vmd = vmd; + vmd->irqs[i].virq = pci_irq_vector(dev, i); + err = devm_request_irq(&dev->dev, vmd->irqs[i].virq, + vmd_irq, IRQF_NO_THREAD, +@@ -1239,7 +1287,7 @@ static const struct pci_device_id vmd_ids[] = { + {PCI_VDEVICE(INTEL, 0xa77f), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, 0x7d0b), +- .driver_data = VMD_FEATS_CLIENT,}, ++ .driver_data = VMD_FEATS_CLIENT | VMD_FEAT_INTERRUPT_QUIRK,}, + {PCI_VDEVICE(INTEL, 0xad0b), + .driver_data = VMD_FEATS_CLIENT,}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), diff --git a/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig b/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig new file mode 100644 index 0000000..69bd67e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0704-pci-vmd-meteor-lake-mtl016-flush.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch b/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch new file mode 100644 index 0000000..49806cf --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -4370,6 +4370,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_JMICRON, + DECLARE_PCI_FIXUP_HEADER(0x1c28, /* Lite-On */ + 0x0122, /* Plextor M6E (Marvell 88SS9183)*/ + quirk_dma_func1_alias); ++/* Toshiba AHCI SSD controller in MacBookAir6,1 and MacBookAir7,1 */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA, 0x010b, ++ quirk_dma_func1_alias); + + /* + * Some devices DMA with the wrong devfn, not just the wrong function. diff --git a/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig b/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig new file mode 100644 index 0000000..ab588ee Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch b/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch new file mode 100644 index 0000000..4b13fed --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch @@ -0,0 +1,413 @@ +diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h +--- a/drivers/iommu/amd/amd_iommu.h ++++ b/drivers/iommu/amd/amd_iommu.h +@@ -48,6 +48,9 @@ extern u8 amd_iommu_hpt_vasize; + extern unsigned long amd_iommu_pgsize_bitmap; + extern bool amd_iommu_hatdis; + ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu); ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu); ++ + /* Protection domain ops */ + void amd_iommu_init_identity_domain(void); + struct protection_domain *protection_domain_alloc(void); +diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h +--- a/drivers/iommu/amd/amd_iommu_types.h ++++ b/drivers/iommu/amd/amd_iommu_types.h +@@ -65,6 +65,7 @@ + #define MMIO_MSI_ADDR_LO_OFFSET 0x015C + #define MMIO_MSI_ADDR_HI_OFFSET 0x0160 + #define MMIO_MSI_DATA_OFFSET 0x0164 ++#define MMIO_PERF_OPT_OFFSET 0x016C + #define MMIO_INTCAPXT_EVT_OFFSET 0x0170 + #define MMIO_INTCAPXT_PPR_OFFSET 0x0178 + #define MMIO_INTCAPXT_GALOG_OFFSET 0x0180 +@@ -99,6 +100,8 @@ + #define FEATURE_GLX GENMASK_ULL(15, 14) + #define FEATURE_GAM_VAPIC BIT_ULL(21) + #define FEATURE_PASMAX GENMASK_ULL(36, 32) ++#define FEATURE_PERF_OPT BIT_ULL(45) ++#define PERF_OPT_EN BIT(13) + #define FEATURE_GIOSUP BIT_ULL(48) + #define FEATURE_HASUP BIT_ULL(49) + #define FEATURE_EPHSUP BIT_ULL(50) +@@ -663,6 +666,9 @@ struct amd_iommu { + /* Extended features 2 */ + u64 features2; + ++ /* Devices requesting PerfOpt; the shared PERF_OPT_EN bit is on while >0. Protected by @lock. */ ++ int perfopt_refcount; ++ + /* PCI device id of the IOMMU device */ + u16 devid; + +@@ -824,6 +830,7 @@ struct iommu_dev_data { + u8 ppr :1; /* Enable device PPR support */ + bool use_vapic; /* Enable device to use vapic mode */ + bool defer_attach; ++ bool perfopt; + + struct ratelimit_state rs; /* Ratelimit IOPF messages */ + }; +diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c +--- a/drivers/iommu/amd/init.c ++++ b/drivers/iommu/amd/init.c +@@ -1945,6 +1945,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h, + if (!iommu->mmio_base) + return -ENOMEM; + ++ if (amd_iommu_perfopt_clear(iommu)) ++ pr_err("IOMMU%d: failed to clear PerfOpt\n", iommu->index); ++ + return init_iommu_from_acpi(iommu, h); + } + +@@ -3035,10 +3038,46 @@ static void enable_iommus_vapic(void) + #endif + } + ++static int clear_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_clear(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ ++static int restore_perfopt_all(void) ++{ ++ struct amd_iommu *iommu; ++ int err, ret = 0; ++ ++ for_each_iommu(iommu) { ++ err = amd_iommu_perfopt_restore(iommu); ++ if (err) ++ ret = err; ++ } ++ ++ return ret; ++} ++ + static void disable_iommus(void) + { + struct amd_iommu *iommu; + ++ /* ++ * PerfOpt is an optional performance bit, so a failure to clear it must ++ * not skip the mandatory disable below. This also runs from the void ++ * amd_iommu_disable() shutdown/kexec path, which cannot report an error. ++ */ ++ if (clear_perfopt_all()) ++ pr_err("Failed to clear PerfOpt while disabling IOMMUs\n"); ++ + for_each_iommu(iommu) + iommu_disable(iommu); + +@@ -3064,6 +3103,10 @@ static void amd_iommu_resume(void *data) + for_each_iommu(iommu) + early_enable_iommu(iommu); + ++ /* early_enable_iommu() cleared PERF_OPT_EN; re-assert it from the refcount. */ ++ if (restore_perfopt_all()) ++ pr_err("Failed to restore PerfOpt after IOMMU resume\n"); ++ + iommu_enable_event_buffer(); + amd_iommu_enable_interrupts(); + } +diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c +--- a/drivers/iommu/amd/iommu.c ++++ b/drivers/iommu/amd/iommu.c +@@ -2352,6 +2352,9 @@ static int attach_device(struct device *dev, + if (ret) + goto out; + ++ if (dev_data->perfopt) ++ goto skip_caps; ++ + /* Setup GCR3 table */ + if (pdom_is_sva_capable(domain)) { + ret = init_gcr3_table(dev_data, domain); +@@ -2376,6 +2379,7 @@ static int attach_device(struct device *dev, + pdev_enable_cap_ats(pdev); + } + ++skip_caps: + /* Update data structures */ + dev_data->domain = domain; + spin_lock_irqsave(&domain->lock, flags); +@@ -2444,6 +2448,192 @@ static void detach_device(struct device *dev) + mutex_unlock(&dev_data->mutex); + } + ++/* Program the per-IOMMU PerfOpt enable bit. Caller must hold iommu->lock. */ ++static int __perfopt_write(struct amd_iommu *iommu, bool enable) ++{ ++ u32 old, val, readback; ++ ++ if (!(readq(iommu->mmio_base + MMIO_EXT_FEATURES) & FEATURE_PERF_OPT)) ++ return enable ? -ENODEV : 0; ++ ++ old = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (old == U32_MAX) ++ return -EIO; ++ ++ val = enable ? old | PERF_OPT_EN : old & ~PERF_OPT_EN; ++ if (val != old) ++ writel(val, iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ readback = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET); ++ if (readback == U32_MAX || ++ (readback & PERF_OPT_EN) != (val & PERF_OPT_EN)) ++ return -EIO; ++ return 0; ++} ++ ++/* ++ * PERF_OPT_EN is a single bit shared by every device behind @iommu, so it is ++ * reference counted: armed on the first requesting device, cleared on the last. ++ */ ++static int perfopt_get(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount == 0) { ++ ret = __perfopt_write(iommu, true); ++ if (ret) ++ goto out; ++ } ++ iommu->perfopt_refcount++; ++out: ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++static int perfopt_put(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ if (iommu->perfopt_refcount > 0 && --iommu->perfopt_refcount == 0) ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Force PERF_OPT_EN off without touching the refcount (used on init, shutdown, ++ * and suspend). The count is preserved so amd_iommu_perfopt_restore() can ++ * re-arm on resume. ++ */ ++int amd_iommu_perfopt_clear(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, false); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++/* ++ * Re-assert PERF_OPT_EN from the refcount after the hardware was reprogrammed on ++ * resume, so devices armed before suspend keep the optimization without each ++ * consumer driver re-arming. ++ */ ++int amd_iommu_perfopt_restore(struct amd_iommu *iommu) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!iommu->mmio_base) ++ return 0; ++ ++ raw_spin_lock_irqsave(&iommu->lock, flags); ++ ret = __perfopt_write(iommu, iommu->perfopt_refcount > 0); ++ raw_spin_unlock_irqrestore(&iommu->lock, flags); ++ return ret; ++} ++ ++int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ int ret; ++ ++ if (!iommu || !dev_data) ++ return -ENODEV; ++ ++ if (!(iommu->features & FEATURE_PERF_OPT)) ++ return -ENODEV; ++ ++ domain = dev_data->domain; ++ if (!domain) ++ return -ENODEV; ++ ++ /* Already armed for this device (e.g. re-entry on resume). */ ++ if (dev_data->perfopt) ++ return 0; ++ ++ /* ++ * The bit is only architecturally valid while the device is untranslated: ++ * identity domain with ATS/PRI/PASID off. The identity domain is ++ * SVA-capable so attach_device() enabled ATS/PRI/PASID and built a GCR3 ++ * table. Re-home the device onto the same identity domain with ++ * perfopt set, so the attach_device() skip_caps path leaves ++ * ATS/PRI/PASID off and no GCR3 table. This follows the detach/attach ++ * pattern used by amd_iommu_attach_device(). ++ * ++ * Locking: this and amd_iommu_disable_perfopt() run only from the ++ * consumer driver's bind/unbind path. group->mutex is not exposed to ++ * drivers, but a device bound to its native driver cannot have its domain ++ * changed concurrently by the core (VFIO ownership is mutually exclusive; ++ * sysfs domain changes require an unused group), so the detach/attach pair ++ * is serialized without it. ++ */ ++ dev_data->perfopt = true; ++ detach_device(&pdev->dev); ++ ret = attach_device(&pdev->dev, domain); ++ if (ret) ++ goto err_restore; ++ ++ ret = perfopt_get(iommu); ++ if (ret) ++ goto err_rearm; ++ ++ dev_info_once(&pdev->dev, "PerfOpt armed on IOMMU%d\n", iommu->index); ++ return 0; ++ ++err_rearm: ++ detach_device(&pdev->dev); ++err_restore: ++ dev_data->perfopt = false; ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore state after PerfOpt setup; device left detached\n"); ++ dev_err_once(&pdev->dev, "PerfOpt failed to arm on IOMMU%d (%d)\n", ++ iommu->index, ret); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(amd_iommu_enable_perfopt); ++ ++void amd_iommu_disable_perfopt(struct pci_dev *pdev) ++{ ++ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev); ++ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev); ++ struct protection_domain *domain; ++ ++ if (!iommu || !dev_data || !dev_data->perfopt || !dev_data->domain) ++ return; ++ ++ if (WARN_ON(perfopt_put(iommu))) ++ pci_err(pdev, "failed to clear PerfOpt\n"); ++ ++ /* ++ * Restore ATS/PRI/PASID (and thus SVA) by re-homing the device onto its ++ * identity domain with the flag cleared, so a later bind without PerfOpt ++ * sees a normally-capable device. See the locking note in ++ * amd_iommu_enable_perfopt(). ++ */ ++ domain = dev_data->domain; ++ dev_data->perfopt = false; ++ detach_device(&pdev->dev); ++ if (attach_device(&pdev->dev, domain)) ++ pci_err(pdev, "failed to restore caps after PerfOpt disable\n"); ++} ++EXPORT_SYMBOL_GPL(amd_iommu_disable_perfopt); + static struct iommu_device *amd_iommu_probe_device(struct device *dev) + { + struct iommu_device *iommu_dev; +@@ -2511,6 +2701,14 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev) + static void amd_iommu_release_device(struct device *dev) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt on release\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + WARN_ON(dev_data->domain); + +@@ -2885,6 +3083,19 @@ static int blocked_domain_attach_device(struct iommu_domain *domain, + struct iommu_domain *old) + { + struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev); ++ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data); ++ ++ /* ++ * blocked_domain is also the .release_domain, so this is the normal ++ * teardown path: drop the reference and clear the flag here too, and ++ * don't fail teardown if the WARN-guarded write doesn't stick. ++ */ ++ if (dev_data->perfopt) { ++ if (WARN_ON(perfopt_put(iommu))) ++ dev_err(dev, "IOMMU%d: failed to clear PerfOpt for blocked domain\n", ++ iommu->index); ++ dev_data->perfopt = false; ++ } + + if (dev_data->domain) + detach_device(dev); +@@ -2953,6 +3164,9 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev, + struct amd_iommu *iommu = get_amd_iommu_from_dev(dev); + int ret; + ++ if (dev_data->perfopt && !pdom_is_in_pt_mode(domain)) ++ return -EBUSY; ++ + /* + * Skip attach device to domain if new domain is same as + * devices current domain +diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h +--- a/include/linux/amd-iommu.h ++++ b/include/linux/amd-iommu.h +@@ -11,11 +11,11 @@ + #include + + struct amd_iommu; ++struct pci_dev; + + #ifdef CONFIG_AMD_IOMMU + + struct task_struct; +-struct pci_dev; + + extern void amd_iommu_detect(void); + +@@ -76,4 +76,15 @@ static inline int amd_iommu_snp_disable(void) { return 0; } + static inline bool amd_iommu_sev_tio_supported(void) { return false; } + #endif + ++#ifdef CONFIG_AMD_IOMMU ++int amd_iommu_enable_perfopt(struct pci_dev *pdev); ++void amd_iommu_disable_perfopt(struct pci_dev *pdev); ++#else ++static inline int amd_iommu_enable_perfopt(struct pci_dev *pdev) ++{ ++ return 0; ++} ++static inline void amd_iommu_disable_perfopt(struct pci_dev *pdev) { } ++#endif ++ + #endif /* _ASM_X86_AMD_IOMMU_H */ diff --git a/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch.sig b/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch.sig new file mode 100644 index 0000000..cd3768f Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0711-iommu-amd-perfopt-support.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch b/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch new file mode 100644 index 0000000..5c74198 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch @@ -0,0 +1,128 @@ +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h +@@ -160,6 +160,7 @@ struct amdgpu_watchdog_timer { + * Modules parameters. + */ + extern int amdgpu_modeset; ++extern int amdgpu_iommu_perfopt; + extern unsigned int amdgpu_vram_limit; + extern int amdgpu_vis_vram_limit; + extern int amdgpu_gart_size; +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -3842,6 +3843,28 @@ amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px) + apple_gmux_detect(NULL, NULL))); + } + ++static inline bool amdgpu_device_identity(struct amdgpu_device *adev) ++{ ++ struct pci_dev *pdev = adev->pdev; ++ struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev); ++ ++ if (!domain) ++ return false; ++ ++ return domain->type == IOMMU_DOMAIN_IDENTITY; ++} ++ ++static bool amdgpu_device_use_perfopt(struct amdgpu_device *adev) ++{ ++ if (amdgpu_iommu_perfopt == 0) ++ return false; ++ ++ if (!(adev->flags & AMD_IS_APU)) ++ return false; ++ ++ return amdgpu_device_identity(adev); ++} ++ + /** + * amdgpu_device_init - initialize the driver + * +@@ -4048,6 +4071,16 @@ int amdgpu_device_init(struct amdgpu_device *adev, + if (r) + return r; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(pdev); ++ ++ /* Optional optimization; a failure to arm it must not abort probe. */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + /* + * No need to remove conflicting FBs for non-display class devices. + * This prevents the sysfb from being freed accidently. +@@ -4416,6 +4449,9 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev) + + amdgpu_gart_dummy_page_fini(adev); + ++ if (amdgpu_device_use_perfopt(adev)) ++ amd_iommu_disable_perfopt(adev->pdev); ++ + if (pci_dev_is_disconnected(adev->pdev)) + amdgpu_device_unmap_mmio(adev); + +@@ -4779,6 +4815,20 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients) + if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + ++ if (amdgpu_device_use_perfopt(adev)) { ++ int perfopt_ret = amd_iommu_enable_perfopt(adev->pdev); ++ ++ /* ++ * Must not return on failure: a bare return would leak the ++ * SR-IOV VF exclusive-mode acquisition taken above (released ++ * via the exit: path). ++ */ ++ if (perfopt_ret) ++ dev_warn(adev->dev, ++ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n", ++ perfopt_ret); ++ } ++ + if (adev->in_s0ix) + amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D0Entry); + +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -185,6 +185,7 @@ char *amdgpu_disable_cu; + char *amdgpu_virtual_display; + int amdgpu_enforce_isolation = -1; + int amdgpu_modeset = -1; ++int amdgpu_iommu_perfopt = -1; + + /* Specifies the default granularity for SVM, used in buffer + * migration and restoration of backing memory when handling +@@ -393,6 +394,17 @@ module_param_named(fw_load_type, amdgpu_fw_load_type, int, 0444); + MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)"); + module_param_named(aspm, amdgpu_aspm, int, 0444); + ++/** ++ * DOC: iommu_perfopt (int) ++ * Control the AMD IOMMU PerfOpt DMA-latency optimization ++ * (0 = disable; -1 = enable on supported devices). ++ * This arms the IOMMU PerfOpt control (IOMMU spec, MMIO Offset 016Ch, EFR PerfOptSup / PerfOptEn) ++ * Arming it disables ATS, PRI, PASID and SVA for the GPU and removes IOMMU DMA containment for it, ++ * trading isolation for lower DMA latency. ++ */ ++MODULE_PARM_DESC(iommu_perfopt, "Control IOMMU PerfOpt DMA-latency optimization (-1 = enable on supported devices, 0 = disable)"); ++module_param_named(iommu_perfopt, amdgpu_iommu_perfopt, int, 0444); ++ + /** + * DOC: runpm (int) + * Override for runtime power management control for dGPUs. The amdgpu driver can dynamically power down diff --git a/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig b/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig new file mode 100644 index 0000000..59f21e5 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0712-drm-amdgpu-perfopt-in-identity-domain.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch b/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch new file mode 100644 index 0000000..a66a783 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch @@ -0,0 +1,7419 @@ +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -224,6 +224,7 @@ config SENSORS_ADT7462 + config SENSORS_ADT7470 + tristate "Analog Devices ADT7470" + depends on I2C ++ depends on PWM || PWM=n + select REGMAP_I2C + help + If you say yes here you get support for the Analog Devices +@@ -292,11 +293,11 @@ config SENSORS_ASC7621 + will be called asc7621. + + config SENSORS_ASUS_ROG_RYUJIN +- tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver" ++ tristate "ASUS ROG RYUJIN hardware monitoring driver" + depends on HID + help + If you say yes here you get support for the fans and sensors of +- the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler. ++ supported ASUS ROG RYUJIN II and III AIO CPU liquid coolers. + + This driver can also be built as a module. If so, the module + will be called asus_rog_ryujin. +@@ -335,6 +336,17 @@ config SENSORS_K10TEMP + This driver can also be built as a module. If so, the module + will be called k10temp. + ++config SENSORS_KB9002 ++ tristate "Kandou KB9002 PCIe retimer" ++ depends on I2C ++ help ++ If you say yes here you get support for the integrated ++ temperature sensor and firmware version readout on Kandou ++ KB9002 PCIe 5.0 retimers, accessed over SMBus. ++ ++ This driver can also be built as a module. If so, the module ++ will be called kb9002. ++ + config SENSORS_KBATT + tristate "KEBA battery controller support" + depends on KEBA_CP500 +@@ -403,7 +415,6 @@ config SENSORS_ARCTIC_FAN_CONTROLLER + config SENSORS_ARM_SCMI + tristate "ARM SCMI Sensors" + depends on ARM_SCMI_PROTOCOL +- depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on SCMI based platforms. The actual +@@ -415,7 +426,6 @@ config SENSORS_ARM_SCMI + config SENSORS_ARM_SCPI + tristate "ARM SCPI Sensors" + depends on ARM_SCPI_PROTOCOL +- depends on THERMAL || !THERMAL_OF + help + This driver provides support for temperature, voltage, current + and power sensors available on ARM Ltd's SCP based platforms. The +@@ -435,7 +445,6 @@ config SENSORS_ASB100 + config SENSORS_ASPEED + tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver" + depends on ARCH_ASPEED || COMPILE_TEST +- depends on THERMAL || THERMAL=n + select REGMAP + help + This driver provides support for ASPEED AST2400/AST2500 PWM +@@ -506,8 +515,8 @@ config SENSORS_CORSAIR_PSU + If you say yes here you get support for Corsair PSUs with a HID + interface. + Currently this driver supports the (RM/HX)550i, (RM/HX)650i, +- (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies +- by Corsair. ++ (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i, HX1200i and HX1500i power ++ supplies by Corsair. + + This driver can also be built as a module. If so, the module + will be called corsair-psu. +@@ -788,7 +797,6 @@ config SENSORS_G762 + config SENSORS_GPIO_FAN + tristate "GPIO fan" + depends on GPIOLIB || COMPILE_TEST +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to GPIO lines. + +@@ -802,7 +810,7 @@ config SENSORS_GXP_FAN_CTRL + If you say yes here you get support for GXP fan control functionality. + + The GXP controls fan function via the CPLD through the use of PWM +- registers. This driver reports status and pwm setting of the fans. ++ registers. This driver enables pwm setting of the fans. + + config SENSORS_HIH6130 + tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor" +@@ -942,6 +950,19 @@ config SENSORS_JC42 + This driver can also be built as a module. If so, the module + will be called jc42. + ++config SENSORS_POLARFIRE_SOC_TVS ++ tristate "PolarFire SoC (MPFS) temperature and voltage sensor" ++ depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST ++ depends on MFD_SYSCON ++ help ++ This driver adds support for the PolarFire SoC (MPFS) Temperature and ++ Voltage Sensor. ++ ++ To compile this driver as a module, choose M here. the ++ module will be called tvs-mpfs. ++ ++ If unsure, say N. ++ + config SENSORS_POWERZ + tristate "ChargerLAB POWER-Z USB-C tester" + depends on USB +@@ -1357,7 +1378,6 @@ config SENSORS_MAX6639 + config SENSORS_MAX6650 + tristate "Maxim MAX6650 sensor chip" + depends on I2C +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for the MAX6650 / MAX6651 + sensor chips. +@@ -1732,7 +1752,6 @@ config SENSORS_PC87427 + config SENSORS_NTC_THERMISTOR + tristate "NTC thermistor support" + depends on IIO +- depends on THERMAL || !THERMAL_OF + help + This driver supports NTC thermistors sensor reading and its + interpretation. The driver can also monitor the temperature and +@@ -1933,7 +1952,6 @@ config SENSORS_PT5161L + config SENSORS_PWM_FAN + tristate "PWM fan" + depends on PWM || COMPILE_TEST +- depends on THERMAL || THERMAL=n + help + If you say yes here you get support for fans connected to PWM lines. + The driver uses the generic PWM interface, thus it will work on a +@@ -1945,7 +1963,6 @@ config SENSORS_PWM_FAN + config SENSORS_QNAP_MCU_HWMON + tristate "QNAP MCU hardware monitoring" + depends on MFD_QNAP_MCU +- depends on THERMAL || THERMAL=n + help + Say yes here to enable support for fan and temperature sensor + connected to a QNAP MCU, as found in a number of QNAP network +@@ -2010,8 +2027,9 @@ config SENSORS_SHT3x + depends on I2C + select CRC8 + help +- If you say yes here you get support for the Sensiron SHT30, SHT31 and +- SHT85 humidity and temperature sensors. ++ If you say yes here you get support for the Sensiron SHT30, SHT31, ++ SHT85 and the compatible GXCAS GXHT30 humidity and temperature ++ sensors. + + This driver can also be built as a module. If so, the module + will be called sht3x. +@@ -2070,6 +2088,17 @@ config SENSORS_DME1737 + This driver can also be built as a module. If so, the module + will be called dme1737. + ++config SENSORS_EIC7700_PVT ++ tristate "Eswin EIC7700 Voltage, Temperature sensor driver" ++ depends on ARCH_ESWIN || COMPILE_TEST ++ select POLYNOMIAL ++ help ++ If you say yes here you get support for Eswin EIC7700 PVT sensor ++ embedded into the SoC. ++ ++ This driver can also be built as a module. If so, the module will be ++ called eic7700-pvt. ++ + config SENSORS_EMC1403 + tristate "SMSC EMC1403/23 thermal sensor" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737) += dme1737.o + obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o + obj-$(CONFIG_SENSORS_DS620) += ds620.o + obj-$(CONFIG_SENSORS_DS1621) += ds1621.o ++obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o + obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o + obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o + obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o +@@ -112,6 +113,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o + obj-$(CONFIG_SENSORS_JC42) += jc42.o + obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o + obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o ++obj-$(CONFIG_SENSORS_KB9002) += kb9002.o + obj-$(CONFIG_SENSORS_KBATT) += kbatt.o + obj-$(CONFIG_SENSORS_KFAN) += kfan.o + obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o +@@ -197,6 +199,7 @@ obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o + obj-$(CONFIG_SENSORS_PC87360) += pc87360.o + obj-$(CONFIG_SENSORS_PC87427) += pc87427.o + obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o ++obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o + obj-$(CONFIG_SENSORS_POWERZ) += powerz.o + obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o + obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o +diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c +--- a/drivers/hwmon/acpi_power_meter.c ++++ b/drivers/hwmon/acpi_power_meter.c +@@ -21,8 +21,6 @@ + #include + + #define ACPI_POWER_METER_NAME "power_meter" +-#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" +-#define ACPI_POWER_METER_CLASS "pwr_meter_resource" + + #define NUM_SENSORS 17 + +@@ -877,7 +875,7 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, ++ acpi_bus_generate_netlink_event("pwr_meter_resource", + dev_name(&resource->acpi_dev->dev), + event, 0); + } +@@ -899,8 +897,6 @@ static int acpi_power_meter_probe(struct platform_device *pdev) + resource->sensors_valid = 0; + resource->acpi_dev = device; + mutex_init(&resource->lock); +- strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); + + platform_set_drvdata(pdev, resource); + +diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c +--- a/drivers/hwmon/adt7470.c ++++ b/drivers/hwmon/adt7470.c +@@ -22,6 +22,9 @@ + #include + #include + #include ++#include ++#include ++#include + + /* Addresses to scan */ + static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; +@@ -100,6 +103,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; + #define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2)) + + #define ADT7470_PWM_COUNT 4 ++#define ADT7470_PWM_MAX 255 + #define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x)) + #define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x)) +@@ -163,6 +167,7 @@ struct adt7470_data { + char limits_valid; + unsigned long sensors_last_updated; /* In jiffies */ + unsigned long limits_last_updated; /* In jiffies */ ++ bool use_pwm_framework; + + int num_temp_sensors; /* -1 = probe */ + int temperatures_probed; +@@ -850,7 +855,7 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + + switch (attr) { + case hwmon_pwm_input: +- val = clamp_val(val, 0, 255); ++ val = clamp_val(val, 0, ADT7470_PWM_MAX); + mutex_lock(&data->lock); + err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), + val); +@@ -886,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val + return err; + } + ++struct adt7470_pwm_wfhw { ++ u8 val; ++}; ++ ++static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const struct pwm_waveform *wf, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ u64 actual_period; ++ ++ if (wf->duty_length_ns == 0) { ++ wfhw->val = 0; ++ return 0; ++ } ++ ++ /* ++ * The PWM frequency (period) is a single chip-wide setting shared by ++ * all 4 channels, so it cannot be changed on a per-pwm_device basis ++ * through this API. The duty cycle is rounded against the currently ++ * configured hardware period rather than the period requested in ++ * @wf; round_waveform_fromhw() reports the actual resulting ++ * waveform back so the core/consumer can detect a mismatch. ++ */ ++ actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ ++ if (actual_period > wf->period_length_ns) ++ /* period too short */ ++ return 1; ++ ++ if (wf->duty_length_ns >= actual_period) { ++ wfhw->val = ADT7470_PWM_MAX; ++ } else { ++ wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns, ++ ADT7470_PWM_MAX, ++ actual_period); ++ } ++ ++ return 0; ++} ++ ++static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw, ++ struct pwm_waveform *wf) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); ++ wf->duty_offset_ns = 0; ++ wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns, ++ ADT7470_PWM_MAX); ++ return 0; ++} ++ ++static int adt7470_pwm_read_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ struct device *dev = regmap_get_device(data->regmap); ++ struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ ++ data = adt7470_update_device(dev); ++ if (IS_ERR(data)) ++ return PTR_ERR(data); ++ ++ /* ++ * No lock needed: like the other hwmon_ops read callbacks in this ++ * driver (e.g. adt7470_pwm_read()), this only does a single byte ++ * read from the cache populated by adt7470_update_device(). ++ */ ++ wfhw->val = data->pwm[pwm->hwpwm]; ++ ++ return 0; ++} ++ ++static int adt7470_pwm_write_waveform(struct pwm_chip *chip, ++ struct pwm_device *pwm, ++ const void *_wfhw) ++{ ++ struct adt7470_data *data = pwmchip_get_drvdata(chip); ++ const struct adt7470_pwm_wfhw *wfhw = _wfhw; ++ unsigned int pwm_auto_reg_mask; ++ int err; ++ ++ if (pwm->hwpwm % 2) ++ pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; ++ else ++ pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; ++ ++ guard(mutex)(&data->lock); ++ ++ if (data->pwm[pwm->hwpwm] == wfhw->val && ++ data->pwm_automatic[pwm->hwpwm] == 0) ++ return 0; ++ ++ /* Put the PWM channel in manual mode before updating it. */ ++ err = regmap_update_bits(data->regmap, ++ ADT7470_REG_PWM_CFG(pwm->hwpwm), ++ pwm_auto_reg_mask, 0); ++ if (err < 0) ++ return err; ++ ++ data->pwm_automatic[pwm->hwpwm] = 0; ++ ++ err = regmap_write(data->regmap, ++ ADT7470_REG_PWM(pwm->hwpwm), wfhw->val); ++ if (err < 0) ++ return err; ++ ++ data->pwm[pwm->hwpwm] = wfhw->val; ++ ++ return 0; ++} ++ ++static const struct pwm_ops adt7470_pwm_ops = { ++ .sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw), ++ .round_waveform_tohw = adt7470_pwm_round_waveform_tohw, ++ .round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw, ++ .read_waveform = adt7470_pwm_read_waveform, ++ .write_waveform = adt7470_pwm_write_waveform, ++}; ++ + static ssize_t pwm_max_show(struct device *dev, + struct device_attribute *devattr, char *buf) + { +@@ -910,7 +1042,7 @@ static ssize_t pwm_max_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_max[attr->index] = temp; +@@ -945,7 +1077,7 @@ static ssize_t pwm_min_store(struct device *dev, + if (kstrtol(buf, 10, &temp)) + return -EINVAL; + +- temp = clamp_val(temp, 0, 255); ++ temp = clamp_val(temp, 0, ADT7470_PWM_MAX); + + mutex_lock(&data->lock); + data->pwm_min[attr->index] = temp; +@@ -1106,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = { + &dev_attr_alarm_mask.attr, + &dev_attr_num_temp_sensors.attr, + &dev_attr_auto_update_interval.attr, ++ NULL ++}; ++ ++static struct attribute *adt7470_pwm_attrs[] = { + &sensor_dev_attr_force_pwm_max.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, +@@ -1130,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = { + NULL + }; + +-ATTRIBUTE_GROUPS(adt7470); ++static const struct attribute_group adt7470_group = { ++ .attrs = adt7470_attrs, ++}; ++ ++static umode_t adt7470_pwm_is_visible(struct kobject *kobj, ++ struct attribute *attr, int index) ++{ ++ struct device *dev = kobj_to_dev(kobj); ++ struct adt7470_data *data = dev_get_drvdata(dev); ++ ++ if (data->use_pwm_framework) ++ return 0; ++ ++ return attr->mode; ++} ++ ++static const struct attribute_group adt7470_pwm_group = { ++ .attrs = adt7470_pwm_attrs, ++ .is_visible = adt7470_pwm_is_visible, ++}; ++ ++static const struct attribute_group *adt7470_groups[] = { ++ &adt7470_group, ++ &adt7470_pwm_group, ++ NULL, ++}; + + static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +@@ -1165,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a + static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) + { ++ const struct adt7470_data *data = _data; + umode_t mode = 0; + + switch (type) { +@@ -1197,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ + } + break; + case hwmon_pwm: ++ /* Hide all pwm attributes if this device is exposed to the PWM ++ * framework ++ */ ++ if (data->use_pwm_framework) { ++ mode = 0; ++ break; ++ } ++ + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: +@@ -1226,6 +1396,8 @@ static const struct hwmon_ops adt7470_hwmon_ops = { + }; + + static const struct hwmon_channel_info * const adt7470_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, +@@ -1327,6 +1499,29 @@ static int adt7470_probe(struct i2c_client *client) + + data->pwm_freq = (u32)freq_val; + ++ data->use_pwm_framework = false; ++ ++ if (device_property_present(dev, "#pwm-cells")) { ++ if (IS_REACHABLE(CONFIG_PWM)) { ++ struct pwm_chip *chip; ++ ++ chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0); ++ if (IS_ERR(chip)) ++ return PTR_ERR(chip); ++ ++ chip->ops = &adt7470_pwm_ops; ++ pwmchip_set_drvdata(chip, data); ++ ++ err = devm_pwmchip_add(dev, chip); ++ if (err) ++ return dev_err_probe(dev, err, "failed to register PWM chip\n"); ++ ++ data->use_pwm_framework = true; ++ } else { ++ dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n"); ++ } ++ } ++ + /* Register sysfs hooks */ + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &adt7470_chip_info, +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + /* data port used by Apple SMC */ + #define APPLESMC_DATA_PORT 0x300 +@@ -133,6 +134,7 @@ static struct applesmc_registers { + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ ++ char fan_positions[10][17]; /* cached fan position labels */ + } smcreg = { + .mutex = __MUTEX_INITIALIZER(smcreg.mutex), + }; +@@ -143,8 +145,8 @@ static s16 rest_x; + static s16 rest_y; + static u8 backlight_state[2]; + +-static struct device *hwmon_dev; + static struct input_dev *applesmc_idev; ++static struct device *hwmon_dev; + + /* + * Last index written to key_at_index sysfs file, and value to use for all other +@@ -372,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + __be32 be; + int ret = 0; + +- if (cache->valid) ++ /* Pairs with smp_store_release() to ensure cache contents are visible */ ++ if (smp_load_acquire(&cache->valid)) + return cache; + + mutex_lock(&smcreg.mutex); +@@ -391,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) + cache->len = info[0]; + memcpy(cache->type, &info[1], 4); + cache->flags = info[5]; +- cache->valid = true; ++ /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ ++ smp_store_release(&cache->valid, true); + + out: + mutex_unlock(&smcreg.mutex); +@@ -566,7 +570,7 @@ static int applesmc_init_smcreg_try(void) + { + struct applesmc_registers *s = &smcreg; + bool left_light_sensor = false, right_light_sensor = false; +- unsigned int count; ++ unsigned int count, i; + u8 tmp[1]; + int ret; + +@@ -597,6 +601,16 @@ static int applesmc_init_smcreg_try(void) + if (s->fan_count > 10) + s->fan_count = 10; + ++ for (i = 0; i < s->fan_count; i++) { ++ char newkey[5]; ++ ++ scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); ++ ret = applesmc_read_key(newkey, s->fan_positions[i], 16); ++ s->fan_positions[i][16] = 0; ++ if (ret) ++ scnprintf(s->fan_positions[i], 17, " Fan %d", i); ++ } ++ + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); + if (ret) + return ret; +@@ -808,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, + return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); + } + +-/* Displays sensor key as label */ +-static ssize_t applesmc_show_sensor_label(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- +- return sysfs_emit(sysfsbuf, "%s\n", key); +-} +- +-/* Displays degree Celsius * 1000 */ +-static ssize_t applesmc_show_temperature(struct device *dev, +- struct device_attribute *devattr, char *sysfsbuf) +-{ +- const char *key = smcreg.index[to_index(devattr)]; +- int ret; +- s16 value; +- int temp; +- +- ret = applesmc_read_s16(key, &value); +- if (ret) +- return ret; +- +- temp = 250 * (value >> 6); +- +- return sysfs_emit(sysfsbuf, "%d\n", temp); +-} +- + static ssize_t applesmc_show_fan_speed(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -854,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, + return sysfs_emit(sysfsbuf, "%u\n", speed); + } + +-static ssize_t applesmc_store_fan_speed(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- unsigned long speed; +- char newkey[5]; +- u8 buffer[2]; +- +- if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) +- return -EINVAL; /* Bigger than a 14-bit value */ +- +- scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], +- to_index(attr)); +- +- buffer[0] = (speed >> 6) & 0xff; +- buffer[1] = (speed << 2) & 0xff; +- ret = applesmc_write_key(newkey, buffer, 2); +- +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_manual(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- u16 manual = 0; +- u8 buffer[2]; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- return ret; +- +- manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; +- return sysfs_emit(sysfsbuf, "%d\n", manual); +-} +- +-static ssize_t applesmc_store_fan_manual(struct device *dev, +- struct device_attribute *attr, +- const char *sysfsbuf, size_t count) +-{ +- int ret; +- u8 buffer[2]; +- unsigned long input; +- u16 val; +- +- if (kstrtoul(sysfsbuf, 10, &input) < 0) +- return -EINVAL; +- +- ret = applesmc_read_key(FANS_MANUAL, buffer, 2); +- if (ret) +- goto out; +- +- val = (buffer[0] << 8 | buffer[1]); +- +- if (input) +- val = val | (0x01 << to_index(attr)); +- else +- val = val & ~(0x01 << to_index(attr)); +- +- buffer[0] = (val >> 8) & 0xFF; +- buffer[1] = val & 0xFF; +- +- ret = applesmc_write_key(FANS_MANUAL, buffer, 2); +- +-out: +- if (ret) +- return ret; +- else +- return count; +-} +- +-static ssize_t applesmc_show_fan_position(struct device *dev, +- struct device_attribute *attr, char *sysfsbuf) +-{ +- int ret; +- char newkey[5]; +- u8 buffer[17]; +- +- scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); +- +- ret = applesmc_read_key(newkey, buffer, 16); +- buffer[16] = 0; +- +- if (ret) +- return ret; +- +- return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); +-} +- + static ssize_t applesmc_calibrate_show(struct device *dev, + struct device_attribute *attr, char *sysfsbuf) + { +@@ -1094,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { + { } + }; + +-static struct applesmc_node_group fan_group[] = { +- { "fan%d_label", applesmc_show_fan_position }, +- { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, +- { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, +- { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, +- { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, +- { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, +- { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, +- { } +-}; + +-static struct applesmc_node_group temp_group[] = { +- { "temp%d_label", applesmc_show_sensor_label }, +- { "temp%d_input", applesmc_show_temperature }, +- { } +-}; + + /* Module stuff */ + +@@ -1312,8 +1191,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { + }; + MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); + ++static struct applesmc_dev_attr *fan_safe_attrs; ++static struct attribute **fan_safe_attr_list; ++static struct attribute_group fan_safe_group; ++static const struct attribute_group *applesmc_extra_groups[2]; ++ ++static u32 *applesmc_temp_config; ++static u32 *applesmc_fan_config; ++static u32 *applesmc_pwm_config; ++static struct hwmon_channel_info *applesmc_info_temp; ++static struct hwmon_channel_info *applesmc_info_fan; ++static struct hwmon_channel_info *applesmc_info_pwm; ++static const struct hwmon_channel_info **applesmc_info_arr; ++static struct hwmon_chip_info *applesmc_chip; ++ ++static void applesmc_free_hwmon(void) ++{ ++ kfree(applesmc_temp_config); ++ kfree(applesmc_fan_config); ++ kfree(applesmc_pwm_config); ++ kfree(applesmc_info_temp); ++ kfree(applesmc_info_fan); ++ kfree(applesmc_info_pwm); ++ kfree(applesmc_info_arr); ++ kfree(applesmc_chip); ++ kfree(fan_safe_attrs); ++ kfree(fan_safe_attr_list); ++} ++ ++static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input || attr == hwmon_temp_label) ++ return 0444; ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: ++ case hwmon_fan_label: ++ case hwmon_fan_max: ++ return 0444; ++ case hwmon_fan_min: ++ case hwmon_fan_target: ++ return 0644; ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) ++ return 0644; ++ break; ++ default: ++ break; ++ } ++ return 0; ++} ++ ++static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_input) { ++ const char *key = smcreg.index[channel]; ++ s16 value; ++ ++ ret = applesmc_read_s16(key, &value); ++ if (ret) ++ return ret; ++ *val = 250 * (value >> 6); ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dAc", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_min: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMn", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_max: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dMx", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ case hwmon_fan_target: { ++ char key[5]; ++ u8 buffer[2]; ++ ++ scnprintf(key, sizeof(key), "F%dTg", channel); ++ ret = applesmc_read_key(key, buffer, 2); ++ if (ret) ++ return ret; ++ *val = ((buffer[0] << 8 | buffer[1]) >> 2); ++ return 0; ++ } ++ default: ++ break; ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ ++ ret = applesmc_read_key(FANS_MANUAL, buffer, 2); ++ if (ret) ++ return ret; ++ *val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ int ret; ++ ++ switch (type) { ++ case hwmon_fan: ++ if (attr == hwmon_fan_min || attr == hwmon_fan_target) { ++ char key[5]; ++ u8 buffer[2]; ++ const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; ++ ++ if (val < 0 || val >= 0x4000) ++ return -EINVAL; ++ scnprintf(key, sizeof(key), fmt, channel); ++ buffer[0] = (val >> 6) & 0xff; ++ buffer[1] = (val << 2) & 0xff; ++ return applesmc_write_key(key, buffer, 2); ++ } ++ break; ++ case hwmon_pwm: ++ if (attr == hwmon_pwm_enable) { ++ u8 buffer[2]; ++ u16 manual_val; ++ const struct applesmc_entry *entry; ++ ++ if (val != 1 && val != 2) ++ return -EINVAL; ++ ++ entry = applesmc_get_entry_by_key(FANS_MANUAL); ++ if (IS_ERR(entry)) ++ return PTR_ERR(entry); ++ ++ mutex_lock(&smcreg.mutex); ++ ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); ++ if (ret) ++ goto out_unlock; ++ manual_val = (buffer[0] << 8 | buffer[1]); ++ if (val == 1) ++ manual_val |= (0x01 << channel); ++ else ++ manual_val &= ~(0x01 << channel); ++ buffer[0] = (manual_val >> 8) & 0xff; ++ buffer[1] = manual_val & 0xff; ++ ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); ++out_unlock: ++ mutex_unlock(&smcreg.mutex); ++ return ret; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = smcreg.index[channel]; ++ return 0; ++ } ++ break; ++ case hwmon_fan: ++ if (attr == hwmon_fan_label) { ++ *str = smcreg.fan_positions[channel] + 4; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops applesmc_hwmon_ops = { ++ .is_visible = applesmc_hwmon_is_visible, ++ .read = applesmc_hwmon_read, ++ .write = applesmc_hwmon_write, ++ .read_string = applesmc_hwmon_read_string, ++}; ++ + static int __init applesmc_init(void) + { ++ int i; + int ret; + + if (!dmi_check_system(applesmc_whitelist)) { +@@ -1348,17 +1457,97 @@ static int __init applesmc_init(void) + if (ret) + goto out_smcreg; + +- ret = applesmc_create_nodes(fan_group, smcreg.fan_count); +- if (ret) ++ /* allocate hwmon channel configs */ ++ applesmc_temp_config = kcalloc(smcreg.index_count + 1, ++ sizeof(*applesmc_temp_config), GFP_KERNEL); ++ applesmc_fan_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_fan_config), GFP_KERNEL); ++ applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, ++ sizeof(*applesmc_pwm_config), GFP_KERNEL); ++ if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { ++ ret = -ENOMEM; + goto out_info; ++ } + +- ret = applesmc_create_nodes(temp_group, smcreg.index_count); +- if (ret) +- goto out_fans; ++ for (i = 0; i < smcreg.index_count; i++) ++ applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; ++ applesmc_temp_config[smcreg.index_count] = 0; ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | ++ HWMON_F_MAX | HWMON_F_TARGET; ++ applesmc_pwm_config[i] = HWMON_PWM_ENABLE; ++ } ++ applesmc_fan_config[smcreg.fan_count] = 0; ++ applesmc_pwm_config[smcreg.fan_count] = 0; ++ ++ applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); ++ applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); ++ applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); ++ if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_temp->type = hwmon_temp; ++ applesmc_info_temp->config = applesmc_temp_config; ++ ++ applesmc_info_fan->type = hwmon_fan; ++ applesmc_info_fan->config = applesmc_fan_config; ++ ++ applesmc_info_pwm->type = hwmon_pwm; ++ applesmc_info_pwm->config = applesmc_pwm_config; ++ ++ applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ if (!applesmc_info_arr) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_info_arr[0] = applesmc_info_temp; ++ applesmc_info_arr[1] = applesmc_info_fan; ++ applesmc_info_arr[2] = applesmc_info_pwm; ++ applesmc_info_arr[3] = NULL; ++ ++ applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ if (!applesmc_chip) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ applesmc_chip->ops = &applesmc_hwmon_ops; ++ applesmc_chip->info = applesmc_info_arr; ++ ++ /* Create non-standard fanX_safe attributes group */ ++ fan_safe_attrs = kcalloc(smcreg.fan_count, ++ sizeof(*fan_safe_attrs), GFP_KERNEL); ++ fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, ++ sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ if (!fan_safe_attrs || !fan_safe_attr_list) { ++ ret = -ENOMEM; ++ goto out_info; ++ } ++ ++ for (i = 0; i < smcreg.fan_count; i++) { ++ struct applesmc_dev_attr *node = &fan_safe_attrs[i]; ++ ++ scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); ++ node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ ++ node->sda.dev_attr.show = applesmc_show_fan_speed; ++ node->sda.dev_attr.store = NULL; ++ sysfs_attr_init(&node->sda.dev_attr.attr); ++ node->sda.dev_attr.attr.name = node->name; ++ node->sda.dev_attr.attr.mode = 0444; ++ fan_safe_attr_list[i] = &node->sda.dev_attr.attr; ++ } ++ fan_safe_attr_list[smcreg.fan_count] = NULL; ++ fan_safe_group.attrs = fan_safe_attr_list; ++ applesmc_extra_groups[0] = &fan_safe_group; ++ applesmc_extra_groups[1] = NULL; + + ret = applesmc_create_accelerometer(); + if (ret) +- goto out_temperature; ++ goto out_info; + + ret = applesmc_create_light_sensor(); + if (ret) +@@ -1368,7 +1557,8 @@ static int __init applesmc_init(void) + if (ret) + goto out_light_sysfs; + +- hwmon_dev = hwmon_device_register(&pdev->dev); ++ hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, ++ applesmc_chip, applesmc_extra_groups); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + goto out_light_ledclass; +@@ -1382,11 +1572,8 @@ static int __init applesmc_init(void) + applesmc_release_light_sensor(); + out_accelerometer: + applesmc_release_accelerometer(); +-out_temperature: +- applesmc_destroy_nodes(temp_group); +-out_fans: +- applesmc_destroy_nodes(fan_group); + out_info: ++ applesmc_free_hwmon(); + applesmc_destroy_nodes(info_group); + out_smcreg: + applesmc_destroy_smcreg(); +@@ -1407,13 +1594,12 @@ static void __exit applesmc_exit(void) + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); + applesmc_release_accelerometer(); +- applesmc_destroy_nodes(temp_group); +- applesmc_destroy_nodes(fan_group); + applesmc_destroy_nodes(info_group); + applesmc_destroy_smcreg(); + platform_device_unregister(pdev); + platform_driver_unregister(&applesmc_driver); + release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); ++ applesmc_free_hwmon(); + } + + module_init(applesmc_init); +diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c +--- a/drivers/hwmon/asus-ec-sensors.c ++++ b/drivers/hwmon/asus-ec-sensors.c +@@ -185,6 +185,20 @@ enum ec_sensors { + #define SENSOR_TEMP_SENSOR_EXTRA_2 BIT(ec_sensor_temp_sensor_extra_2) + #define SENSOR_TEMP_SENSOR_EXTRA_3 BIT(ec_sensor_temp_sensor_extra_3) + ++/* ++ * The values for temperature sensor readings without physical sensors connected. ++ * The value varies across generations and is seemingly defined by the EC chip ++ * used in the given board. ++ */ ++static const s32 temperature_blank_values[] = {-62, -60, -40}; ++ ++static const s32 environment_temp_sensors = ++ SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_ALT1 | ++ SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | ++ SENSOR_TEMP_WATER_BLOCK_IN | SENSOR_TEMP_WATER_BLOCK_OUT | ++ SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_SENSOR_EXTRA_1 | ++ SENSOR_TEMP_SENSOR_EXTRA_2 | SENSOR_TEMP_SENSOR_EXTRA_3; ++ + enum board_family { + family_unknown, + family_amd_400_series, +@@ -386,6 +400,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), ++ [ec_sensor_fan_cpu_opt] = ++ EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe), + [ec_sensor_temp_water_in] = +@@ -487,6 +503,14 @@ static const struct ec_board_info board_info_crosshair_x670e_hero = { + .family = family_amd_600_series, + }; + ++static const struct ec_board_info board_info_crosshair_x870e_hero = { ++ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, ++ .family = family_amd_800_series, ++}; ++ + static const struct ec_board_info board_info_maximus_vi_hero = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | +@@ -527,6 +551,14 @@ static const struct ec_board_info board_info_maximus_z790_extreme = { + .family = family_intel_700_series, + }; + ++static const struct ec_board_info board_info_maximus_z790_hero = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, ++ .family = family_intel_700_series, ++}; ++ + static const struct ec_board_info board_info_prime_x470_pro = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | +@@ -590,6 +622,13 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { + .family = family_amd_800_series, + }; + ++static const struct ec_board_info board_info_pro_art_z690_creator_wifi = { ++ .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | ++ SENSOR_FAN_CPU_OPT, ++ .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, ++ .family = family_intel_600_series, ++}; ++ + static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = { + /* Board also has a nct6798 */ + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME | +@@ -743,7 +782,7 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { + + static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | +- SENSOR_TEMP_MB | SENSOR_TEMP_VRM | ++ SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +@@ -859,6 +898,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_pro_art_x670E_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI", + &board_info_pro_art_x870E_creator_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt Z690-CREATOR WIFI", ++ &board_info_pro_art_z690_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI", + &board_info_pro_ws_trx50_sage_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI A", +@@ -883,6 +924,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_crosshair_x670e_gene), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", + &board_info_crosshair_x670e_hero), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X870E HERO", ++ &board_info_crosshair_x870e_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO", + &board_info_maximus_xi_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", +@@ -893,6 +936,8 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_maximus_z690_formula), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME", + &board_info_maximus_z790_extreme), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 HERO", ++ &board_info_maximus_z790_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", + &board_info_strix_b550_e_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", +@@ -927,8 +972,12 @@ static const struct dmi_system_id dmi_table[] = { + &board_info_strix_x870_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI", + &board_info_strix_x870e_e_gaming_wifi), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI7 R2", ++ &board_info_strix_x870e_e_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7", + &board_info_strix_x870e_h_gaming_wifi7), ++ DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING", ++ &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", + &board_info_strix_z390_f_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", +@@ -955,6 +1004,7 @@ static const struct dmi_system_id dmi_table[] = { + }; + + struct ec_sensor { ++ /* this is ec_sensors enum value */ + unsigned int info_index; + s32 cached_value; + }; +@@ -1047,6 +1097,12 @@ get_sensor_info(const struct ec_sensors_data *state, int index) + return state->sensors_info + state->sensors[index].info_index; + } + ++static enum ec_sensors ++get_ec_sensor_type(const struct ec_sensors_data *state, int index) ++{ ++ return state->sensors[index].info_index; ++} ++ + static int find_ec_sensor_index(const struct ec_sensors_data *ec, + enum hwmon_sensor_types type, int channel) + { +@@ -1290,6 +1346,17 @@ static int get_cached_value_or_update(const struct device *dev, + return 0; + } + ++static bool is_blank_temperature_value(s32 value) ++{ ++ size_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(temperature_blank_values); ++i) { ++ if (value == temperature_blank_values[i]) ++ return true; ++ } ++ return false; ++} ++ + /* + * Now follow the functions that implement the hwmon interface + */ +@@ -1297,6 +1364,8 @@ static int get_cached_value_or_update(const struct device *dev, + static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { ++ const struct ec_sensor_info *sensor_info; ++ enum ec_sensors ec_sensor; + int ret; + s32 value = 0; + +@@ -1308,12 +1377,19 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + } + + ret = get_cached_value_or_update(dev, sidx, state, &value); +- if (!ret) { +- *val = scale_sensor_value(value, +- get_sensor_info(state, sidx)->type); +- } ++ if (ret) ++ return ret; + +- return ret; ++ sensor_info = get_sensor_info(state, sidx); ++ if (sensor_info->type == hwmon_temp) { ++ ec_sensor = get_ec_sensor_type(state, sidx); ++ if ((environment_temp_sensors & BIT(ec_sensor)) && ++ is_blank_temperature_value(value)) ++ return -ENODATA; ++ } ++ *val = scale_sensor_value(value, sensor_info->type); ++ ++ return 0; + } + + static int asus_ec_hwmon_read_string(struct device *dev, +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. ++ * hwmon driver for Asus ROG Ryujin AIO coolers. + * + * Copyright 2024 Aleksa Savic + */ +@@ -17,6 +17,9 @@ + + #define USB_VENDOR_ID_ASUS_ROG 0x0b05 + #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ ++#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb ++#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade ++#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada + + struct rog_ryujin_device_info { + u8 temp_offset; +@@ -34,6 +37,14 @@ static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = { + .has_controller = true, + }; + ++static const struct rog_ryujin_device_info rog_ryujin_iii_info = { ++ .temp_offset = 5, ++ .pump_speed_offset = 7, ++ .fan_speed_offset = 10, ++ .duty_channel = 1, ++ .has_controller = false, ++}; ++ + #define STATUS_VALIDITY 1500 /* ms */ + #define MAX_REPORT_LENGTH 65 + +@@ -173,7 +184,6 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { +- unsigned long flags; + int ret; + + /* +@@ -181,9 +191,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ spin_lock_bh(&priv->status_report_request_lock); + reinit_completion(status_completion); +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); ++ spin_unlock_bh(&priv->status_report_request_lock); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -411,18 +421,15 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); +- unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + +- spin_lock_irqsave(&priv->status_report_request_lock, flags); +- + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- goto unlock; ++ return 0; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -436,7 +443,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- goto unlock; ++ return 0; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -448,7 +455,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- goto unlock; ++ return 0; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -467,7 +474,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- goto unlock; ++ return 0; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -477,7 +484,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- goto unlock; ++ return 0; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -496,7 +503,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- goto unlock; ++ return 0; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -505,8 +512,6 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + +-unlock: +- spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +@@ -596,6 +601,12 @@ static void rog_ryujin_remove(struct hid_device *hdev) + static const struct hid_device_id rog_ryujin_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO), + .driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EXTREME), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE), ++ .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { } + }; + +@@ -625,4 +636,4 @@ module_exit(rog_ryujin_exit); + + MODULE_LICENSE("GPL"); + MODULE_AUTHOR("Aleksa Savic "); +-MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); ++MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin AIO coolers"); +diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c +--- a/drivers/hwmon/axi-fan-control.c ++++ b/drivers/hwmon/axi-fan-control.c +@@ -508,8 +508,7 @@ static int axi_fan_control_probe(struct platform_device *pdev) + IRQF_ONESHOT | IRQF_TRIGGER_HIGH, + NULL, ctl); + if (ret) +- return dev_err_probe(&pdev->dev, ret, +- "failed to request an irq\n"); ++ return ret; + + return 0; + } +diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c +--- a/drivers/hwmon/chipcap2.c ++++ b/drivers/hwmon/chipcap2.c +@@ -22,6 +22,8 @@ + #include + #include + #include ++#include ++#include + + #define CC2_START_CM 0xA0 + #define CC2_START_NOM 0x80 +@@ -83,6 +85,7 @@ struct cc2_data { + struct i2c_client *client; + struct regulator *regulator; + const char *name; ++ const char *label; + int irq_ready; + int irq_low; + int irq_high; +@@ -444,6 +447,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_humidity_input: + return 0444; ++ case hwmon_humidity_label: ++ return cc2->label ? 0444 : 0; + case hwmon_humidity_min_alarm: + return cc2->rh_alarm.low_alarm_visible ? 0444 : 0; + case hwmon_humidity_max_alarm: +@@ -461,6 +466,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, + switch (attr) { + case hwmon_temp_input: + return 0444; ++ case hwmon_temp_label: ++ return cc2->label ? 0444 : 0; + default: + return 0; + } +@@ -547,6 +554,16 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) + return 0; + } + ++static int cc2_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ struct cc2_data *data = dev_get_drvdata(dev); ++ ++ *str = data->label; ++ ++ return 0; ++} ++ + static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) + { +@@ -665,8 +682,9 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) + } + + static const struct hwmon_channel_info *cc2_info[] = { +- HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), +- HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX | ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_LABEL | ++ HWMON_H_MIN | HWMON_H_MAX | + HWMON_H_MIN_HYST | HWMON_H_MAX_HYST | + HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM), + NULL +@@ -675,6 +693,7 @@ static const struct hwmon_channel_info *cc2_info[] = { + static const struct hwmon_ops cc2_hwmon_ops = { + .is_visible = cc2_is_visible, + .read = cc2_read, ++ .read_string = cc2_read_string, + .write = cc2_write, + }; + +@@ -705,6 +724,8 @@ static int cc2_probe(struct i2c_client *client) + return dev_err_probe(dev, PTR_ERR(data->regulator), + "Failed to get regulator\n"); + ++ device_property_read_string(dev, "label", &data->label); ++ + ret = cc2_request_ready_irq(data, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request ready irq\n"); +diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c +--- a/drivers/hwmon/coretemp.c ++++ b/drivers/hwmon/coretemp.c +@@ -68,7 +68,7 @@ enum coretemp_attr_index { + * This value is passed as "id" field to rdmsr/wrmsr functions. + * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, + * from where the temperature values should be read. +- * @attr_size: Total number of pre-core attrs displayed in the sysfs. ++ * @attr_size: Number of sysfs attributes in this temperature data group. + */ + struct temp_data { + int temp; +diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c +--- a/drivers/hwmon/corsair-psu.c ++++ b/drivers/hwmon/corsair-psu.c +@@ -884,7 +884,7 @@ static const struct hid_device_id corsairpsu_idtable[] = { + { HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */ + { HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */ + { HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */ +- { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */ ++ { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy, Series 2023 and 2025 */ + { HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */ + { HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */ + { }, +diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c +--- a/drivers/hwmon/cros_ec_hwmon.c ++++ b/drivers/hwmon/cros_ec_hwmon.c +@@ -148,9 +148,17 @@ static bool cros_ec_hwmon_is_error_temp(u8 temp) + temp == EC_TEMP_SENSOR_NOT_CALIBRATED; + } + ++/* This differs slightly from the variant in units.h to avoid rounding inconsistencies. */ ++#define CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS (-273000) ++ ++static long cros_ec_hwmon_kelvin_to_millicelsius(long t) ++{ ++ return t * MILLIDEGREE_PER_DEGREE + CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS; ++} ++ + static long cros_ec_hwmon_temp_to_millicelsius(u8 temp) + { +- return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); ++ return cros_ec_hwmon_kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); + } + + static bool cros_ec_hwmon_attr_is_temp_threshold(u32 attr) +@@ -228,8 +236,13 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + ret = cros_ec_hwmon_read_temp_threshold(priv->cros_ec, channel, + cros_ec_hwmon_attr_to_thres(attr), + &threshold); +- if (ret == 0) +- *val = kelvin_to_millicelsius(threshold); ++ if (ret == 0) { ++ /* Limit to sensible, non-overflowing values. */ ++ if (threshold > 255 + 273) ++ *val = 255000; ++ else ++ *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); ++ } + } + } + +diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c +--- a/drivers/hwmon/da9055-hwmon.c ++++ b/drivers/hwmon/da9055-hwmon.c +@@ -256,11 +256,8 @@ static int da9055_hwmon_probe(struct platform_device *pdev) + NULL, da9055_auxadc_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "adc-irq", hwmon); +- if (ret != 0) { +- dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", +- ret); ++ if (ret != 0) + return ret; +- } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", + hwmon, +diff --git a/drivers/hwmon/eic7700-pvt.c b/drivers/hwmon/eic7700-pvt.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.c +@@ -0,0 +1,509 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ * ++ * Authors: ++ * Yulin Lu ++ * Huan He ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "eic7700-pvt.h" ++ ++static const struct pvt_sensor_info pvt_info[] = { ++ PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP), ++ PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT), ++}; ++ ++static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"}; ++ ++/* ++ * The original translation formulae of the temperature (in degrees of Celsius) ++ * to PVT data and vice-versa are following: ++ * N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) + ++ * 1.6198e2, ++ * T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) + ++ * 3.2843e-1*(N^1) - 4.8690e1, ++ * where T = [-40, 125]C and N = [27, 771]. ++ * They must be accordingly altered to be suitable for the integer arithmetics. ++ * The technique is called 'factor redistribution', which just makes sure the ++ * multiplications and divisions are made so to have a result of the operations ++ * within the integer numbers limit. In addition we need to translate the ++ * formulae to accept millidegrees of Celsius. Here what they look like after ++ * the alterations: ++ * N = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T + ++ * 16198e2) / 1e4, ++ * T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N - ++ * 48690, ++ * where T = [-40000, 125000] mC and N = [27, 771]. ++ */ ++static const struct polynomial poly_N_to_temp = { ++ .total_divider = 1, ++ .terms = { ++ {4, -18439, 1000, 1}, ++ {3, 80705, 1000, 1}, ++ {2, -185010, 1000, 1}, ++ {1, 328430, 1000, 1}, ++ {0, -48690, 1, 1} ++ } ++}; ++ ++/* ++ * Similar alterations are performed for the voltage conversion equations. ++ * The original formulae are: ++ * N = 1.3905e3*V - 5.7685e2, ++ * V = (N + 5.7685e2) / 1.3905e3, ++ * where V = [0.72, 0.88] V and N = [424, 646]. ++ * After the optimization they looks as follows: ++ * N = (13905e-3*V - 5768.5) / 10, ++ * V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10. ++ * where V = [720, 880] mV and N = [424, 646]. ++ */ ++static const struct polynomial poly_N_to_volt = { ++ .total_divider = 10, ++ .terms = { ++ {1, 100000, 13905, 1}, ++ {0, 57685000, 1, 13905} ++ } ++}; ++ ++static inline u32 eic7700_pvt_update(void __iomem *reg, u32 mask, u32 data) ++{ ++ u32 old; ++ ++ old = readl_relaxed(reg); ++ writel((old & ~mask) | (data & mask), reg); ++ ++ return old & mask; ++} ++ ++static inline void eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) ++{ ++ u32 old; ++ ++ mode = FIELD_PREP(PVT_MODE_MASK, mode); ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) ++{ ++ u32 old; ++ ++ old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ writel(val, pvt->regs + PVT_TRIM); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); ++} ++ ++static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ u32 stat, val; ++ int active; ++ ++ if (IS_ENABLED(CONFIG_PM)) { ++ active = pm_runtime_get_if_active(pvt->dev); ++ if (active <= 0) ++ return IRQ_NONE; ++ } ++ ++ stat = readl(pvt->regs + PVT_INT); ++ if (!(stat & PVT_INT_STAT)) { ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ return IRQ_NONE; ++ } ++ ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ /* ++ * Read the data, update the cache and notify a waiter of this event. ++ */ ++ val = readl(pvt->regs + PVT_DATA); ++ WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val)); ++ complete(&pvt->conversion); ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ pm_runtime_put(pvt->dev); ++ ++ return IRQ_HANDLED; ++} ++ ++static int eic7700_pvt_read_data(struct pvt_hwmon *pvt, ++ enum pvt_sensor_type type, long *val) ++{ ++ unsigned long timeout; ++ u32 data; ++ int ret; ++ ++ /* ++ * Wait for PVT conversion to complete and update the data cache. The ++ * data read procedure is following: set the requested PVT sensor mode, ++ * enable conversion, wait until conversion is finished, then disable ++ * conversion and IRQ, and read the cached data. ++ */ ++ reinit_completion(&pvt->conversion); ++ ++ eic7700_pvt_set_mode(pvt, pvt_info[type].mode); ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN); ++ ++ /* ++ * Wait with timeout since in case if the sensor is suddenly powered ++ * down the request won't be completed and the caller will hang up on ++ * this procedure until the power is back up again. Multiply the ++ * timeout by the factor of two to prevent a false timeout. ++ */ ++ timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); ++ ret = wait_for_completion_timeout(&pvt->conversion, timeout); ++ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ ++ if (!ret) ++ synchronize_irq(pvt->irq); ++ ++ data = READ_ONCE(pvt->data_cache); ++ ++ if (!ret) ++ return -ETIMEDOUT; ++ ++ if (type == PVT_TEMP) ++ *val = polynomial_calc(&poly_N_to_temp, data); ++ else ++ *val = polynomial_calc(&poly_N_to_volt, data); ++ ++ return 0; ++} ++ ++static const struct hwmon_channel_info *pvt_channel_info[] = { ++ HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), ++ NULL ++}; ++ ++static umode_t eic7700_pvt_hwmon_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int ch) ++{ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ } ++ break; ++ case hwmon_in: ++ switch (attr) { ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_hwmon_read(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, long *val) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = pm_runtime_get_sync(pvt->dev); ++ if (ret < 0) { ++ dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret); ++ pm_runtime_put_noidle(pvt->dev); ++ return ret; ++ } ++ ++ switch (type) { ++ case hwmon_temp: ++ switch (attr) { ++ case hwmon_temp_input: ++ ret = eic7700_pvt_read_data(pvt, ch, val); ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_input) ++ ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val); ++ else ++ ret = -EOPNOTSUPP; ++ break; ++ default: ++ ret = -EOPNOTSUPP; ++ } ++ ++ pm_runtime_mark_last_busy(pvt->dev); ++ pm_runtime_put_autosuspend(pvt->dev); ++ return ret; ++} ++ ++static int eic7700_pvt_hwmon_read_string(struct device *dev, ++ enum hwmon_sensor_types type, u32 attr, ++ int ch, const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ if (attr == hwmon_temp_label) { ++ *str = pvt_info[ch].label; ++ return 0; ++ } ++ break; ++ case hwmon_in: ++ if (attr == hwmon_in_label) { ++ *str = pvt_info[PVT_VOLT + ch].label; ++ return 0; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops pvt_hwmon_ops = { ++ .is_visible = eic7700_pvt_hwmon_is_visible, ++ .read = eic7700_pvt_hwmon_read, ++ .read_string = eic7700_pvt_hwmon_read_string ++}; ++ ++static const struct hwmon_chip_info pvt_hwmon_info = { ++ .ops = &pvt_hwmon_ops, ++ .info = pvt_channel_info ++}; ++ ++static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct pvt_hwmon *pvt; ++ ++ pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); ++ if (!pvt) ++ return ERR_PTR(-ENOMEM); ++ ++ pvt->dev = dev; ++ init_completion(&pvt->conversion); ++ ++ return pvt; ++} ++ ++static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt) ++{ ++ /* ++ * Make sure controller are disabled so not to accidentally have ISR ++ * executed before the driver data is fully initialized. Clear the IRQ ++ * status as well. ++ */ ++ eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); ++ eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); ++ readl(pvt->regs + PVT_INT); ++ readl(pvt->regs + PVT_DATA); ++ ++ /* Setup default sensor mode and temperature trim. */ ++ eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode); ++ ++ /* ++ * Max conversion latency (~333 µs) derived from PVT spec: ++ * maximum sampling rate = 3000 samples/sec. ++ */ ++ pvt->timeout = ns_to_ktime(PVT_TOUT_MIN); ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int eic7700_pvt_request_irq(struct pvt_hwmon *pvt) ++{ ++ struct platform_device *pdev = to_platform_device(pvt->dev); ++ int ret; ++ ++ pvt->irq = platform_get_irq(pdev, 0); ++ if (pvt->irq < 0) ++ return pvt->irq; ++ ++ ret = devm_request_threaded_irq(pvt->dev, pvt->irq, ++ eic7700_pvt_hard_isr, NULL, ++ IRQF_TRIGGER_HIGH, "pvt", pvt); ++ if (ret) { ++ dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int eic7700_pvt_create_hwmon(struct pvt_hwmon *pvt) ++{ ++ pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", ++ pvt, &pvt_hwmon_info, ++ NULL); ++ if (IS_ERR(pvt->hwmon)) { ++ dev_err(pvt->dev, "Couldn't create hwmon device\n"); ++ return PTR_ERR(pvt->hwmon); ++ } ++ ++ return 0; ++} ++ ++static void eic7700_pvt_disable_pm_runtime(void *data) ++{ ++ struct pvt_hwmon *pvt = data; ++ ++ pm_runtime_dont_use_autosuspend(pvt->dev); ++ pm_runtime_disable(pvt->dev); ++ ++ if (!pm_runtime_status_suspended(pvt->dev)) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ pm_runtime_set_suspended(pvt->dev); ++ } ++} ++ ++static int eic7700_pvt_probe(struct platform_device *pdev) ++{ ++ struct reset_control *rst; ++ struct pvt_hwmon *pvt; ++ int i, ret; ++ ++ pvt = eic7700_pvt_create_data(pdev); ++ if (IS_ERR(pvt)) ++ return PTR_ERR(pvt); ++ ++ platform_set_drvdata(pdev, pvt); ++ ++ pvt->regs = devm_platform_ioremap_resource(pdev, 0); ++ if (IS_ERR(pvt->regs)) ++ return PTR_ERR(pvt->regs); ++ ++ for (i = 0; i < PVT_CLK_NUM; i++) ++ pvt->clks[i].id = pvt_clk_names[i]; ++ ++ ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(&pdev->dev, ret, ++ "Couldn't get clock descriptors\n"); ++ ++ rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); ++ if (IS_ERR(rst)) ++ return dev_err_probe(pvt->dev, PTR_ERR(rst), ++ "Couldn't get reset control\n"); ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) ++ return dev_err_probe(pvt->dev, ret, ++ "Failed to enable clocks\n"); ++ ++ ret = eic7700_pvt_init_iface(pvt); ++ if (ret) { ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ return ret; ++ } ++ ++ if (IS_ENABLED(CONFIG_PM)) ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ pm_runtime_enable(&pdev->dev); ++ pm_runtime_set_autosuspend_delay(&pdev->dev, 3000); ++ pm_runtime_use_autosuspend(&pdev->dev); ++ pm_runtime_get_noresume(&pdev->dev); ++ ++ ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime, ++ pvt); ++ if (ret) { ++ pm_runtime_put_noidle(&pdev->dev); ++ return dev_err_probe(&pdev->dev, ret, ++ "Can't register PM cleanup\n"); ++ } ++ ++ ret = eic7700_pvt_request_irq(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ ret = eic7700_pvt_create_hwmon(pvt); ++ if (ret) ++ goto err_put_pm_runtime; ++ ++ pm_runtime_put_autosuspend(&pdev->dev); ++ ++ return 0; ++ ++err_put_pm_runtime: ++ pm_runtime_put_noidle(&pdev->dev); ++ return ret; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); ++ if (ret) { ++ dev_err(dev, "Failed to enable clocks: %d\n", ret); ++ return ret; ++ } ++ ++ eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); ++ ++ return 0; ++} ++ ++static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev) ++{ ++ struct pvt_hwmon *pvt = dev_get_drvdata(dev); ++ ++ clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops eic7700_pvt_pm_ops = { ++ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) ++ RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume, ++ NULL) ++}; ++ ++static const struct of_device_id pvt_of_match[] = { ++ { .compatible = "eswin,eic7700-pvt"}, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, pvt_of_match); ++ ++static struct platform_driver pvt_driver = { ++ .probe = eic7700_pvt_probe, ++ .driver = { ++ .name = "eic7700-pvt", ++ .of_match_table = pvt_of_match, ++ .pm = pm_ptr(&eic7700_pvt_pm_ops), ++ }, ++}; ++module_platform_driver(pvt_driver); ++ ++MODULE_AUTHOR("Yulin Lu "); ++MODULE_AUTHOR("Huan He "); ++MODULE_DESCRIPTION("Eswin eic7700 PVT driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/eic7700-pvt.h b/drivers/hwmon/eic7700-pvt.h +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/eic7700-pvt.h +@@ -0,0 +1,99 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * ESWIN EIC7700 Voltage, Temperature sensor driver ++ * ++ * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. ++ */ ++#ifndef __HWMON_EIC7700_PVT_H__ ++#define __HWMON_EIC7700_PVT_H__ ++ ++#include ++#include ++#include ++#include ++ ++/* ESWIN EIC7700 PVT registers and their bitfields */ ++#define PVT_TRIM 0x04 ++#define PVT_MODE 0x08 ++#define PVT_MODE_MASK GENMASK(2, 0) ++#define PVT_CTRL_MODE_TEMP 0x0 ++#define PVT_CTRL_MODE_VOLT 0x4 ++#define PVT_ENA 0x0c ++#define PVT_ENA_EN BIT(0) ++#define PVT_INT 0x10 ++#define PVT_INT_STAT BIT(0) ++#define PVT_INT_CLR BIT(1) ++#define PVT_DATA 0x14 ++#define PVT_DATA_OUT GENMASK(9, 0) ++ ++/* ++ * PVT sensors-related limits and default values ++ * @PVT_TEMP_CHS: Number of temperature hwmon channels. ++ * @PVT_VOLT_CHS: Number of voltage hwmon channels. ++ * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value ++ * when one is determined for ESWIN EIC7700 SoC). ++ * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. ++ */ ++#define PVT_TEMP_CHS 1 ++#define PVT_VOLT_CHS 1 ++#define PVT_TRIM_DEF 0 ++#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) ++ ++/* ++ * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT ++ * sampling mode) ++ * @PVT_TEMP: PVT Temperature sensor. ++ * @PVT_VOLT: PVT Voltage sensor. ++ */ ++enum pvt_sensor_type { ++ PVT_TEMP = 0, ++ PVT_VOLT ++}; ++ ++#define PVT_CLK_NUM 2 ++ ++/* ++ * struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure ++ * @channel: Sensor channel ID. ++ * @label: hwmon sensor label. ++ * @mode: PVT mode corresponding to the channel. ++ * @type: Sensor type. ++ */ ++struct pvt_sensor_info { ++ int channel; ++ const char *label; ++ u32 mode; ++ enum hwmon_sensor_types type; ++}; ++ ++#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \ ++ { \ ++ .channel = _ch, \ ++ .label = _label, \ ++ .mode = PVT_CTRL_MODE_ ##_mode, \ ++ .type = _type, \ ++ } ++ ++/* ++ * struct pvt_hwmon - Eswin EIC7700 PVT private data ++ * @dev: device structure of the PVT platform device. ++ * @hwmon: hwmon device structure. ++ * @regs: pointer to the Eswin EIC7700 PVT registers region. ++ * @irq: PVT events IRQ number. ++ * @clks: PVT clock descriptors. ++ * @data_cache: data cache in raw format. ++ * @conversion: data conversion completion. ++ * @timeout: conversion timeout. ++ */ ++struct pvt_hwmon { ++ struct device *dev; ++ struct device *hwmon; ++ void __iomem *regs; ++ int irq; ++ struct clk_bulk_data clks[PVT_CLK_NUM]; ++ u32 data_cache; ++ struct completion conversion; ++ ktime_t timeout; ++}; ++ ++#endif /* __HWMON_EIC7700_PVT_H__ */ +diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c +--- a/drivers/hwmon/emc1403.c ++++ b/drivers/hwmon/emc1403.c +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + + #define THERMAL_PID_REG 0xfd +@@ -654,14 +655,20 @@ static int emc1403_probe(struct i2c_client *client) + { + struct thermal_data *data; + struct device *hwmon_dev; +- const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client); ++ int ret; ++ ++ ret = devm_regulator_get_enable(&client->dev, "vdd"); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to enable regulator\n"); + + data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + +- data->chip = id->driver_data; ++ data->chip = (uintptr_t)i2c_get_match_data(client); ++ + data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); +@@ -677,10 +684,20 @@ static const unsigned short emc1403_address_list[] = { + 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END + }; + ++static const struct of_device_id emc1403_of_match[] = { ++ { .compatible = "smsc,emc1402", .data = (void *)emc1402 }, ++ { .compatible = "smsc,emc1403", .data = (void *)emc1403 }, ++ { .compatible = "smsc,emc1404", .data = (void *)emc1404 }, ++ { .compatible = "smsc,emc1428", .data = (void *)emc1428 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, emc1403_of_match); ++ + static struct i2c_driver sensor_emc1403 = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc1403", ++ .of_match_table = emc1403_of_match, + }, + .detect = emc1403_detect, + .probe = emc1403_probe, +diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c +--- a/drivers/hwmon/gxp-fan-ctrl.c ++++ b/drivers/hwmon/gxp-fan-ctrl.c +@@ -1,59 +1,16 @@ + // SPDX-License-Identifier: GPL-2.0-only +-/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ ++/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */ + +-#include + #include + #include + #include + #include + #include + +-#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ +-#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */ +-#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */ +-#define POWER_BIT 24 +- + struct gxp_fan_ctrl_drvdata { + void __iomem *base; +- void __iomem *plreg; +- void __iomem *fn2; + }; + +-static bool fan_installed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_INST); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_failed(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u8 val; +- +- val = readb(drvdata->plreg + OFS_FAN_FAIL); +- +- return !!(val & BIT(fan)); +-} +- +-static long fan_enabled(struct device *dev, int fan) +-{ +- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 val; +- +- /* +- * Check the power status as if the platform is off the value +- * reported for the PWM will be incorrect. Report fan as +- * disabled. +- */ +- val = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); +-} +- + static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +@@ -80,37 +37,11 @@ static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, + } + } + +-static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) +-{ +- switch (attr) { +- case hwmon_fan_enable: +- *val = fan_enabled(dev, channel); +- return 0; +- case hwmon_fan_fault: +- *val = fan_failed(dev, channel); +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- + static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) + { + struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); +- u32 reg; + +- /* +- * Check the power status of the platform. If the platform is off +- * the value reported for the PWM will be incorrect. In this case +- * report a PWM of zero. +- */ +- +- reg = readl(drvdata->fn2 + OFS_SEVSTAT); +- +- if (reg & BIT(POWER_BIT)) +- *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; +- else +- *val = 0; ++ *val = readb(drvdata->base + channel); + + return 0; + } +@@ -119,8 +50,6 @@ static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) + { + switch (type) { +- case hwmon_fan: +- return gxp_fan_read(dev, attr, channel, val); + case hwmon_pwm: + return gxp_pwm_read(dev, attr, channel, val); + default: +@@ -135,16 +64,6 @@ static umode_t gxp_fan_ctrl_is_visible(const void *_data, + umode_t mode = 0; + + switch (type) { +- case hwmon_fan: +- switch (attr) { +- case hwmon_fan_enable: +- case hwmon_fan_fault: +- mode = 0444; +- break; +- default: +- break; +- } +- break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: +@@ -168,15 +87,6 @@ static const struct hwmon_ops gxp_fan_ctrl_ops = { + }; + + static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { +- HWMON_CHANNEL_INFO(fan, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE, +- HWMON_F_FAULT | HWMON_F_ENABLE), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, +@@ -211,18 +121,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev) + return dev_err_probe(dev, PTR_ERR(drvdata->base), + "failed to map base\n"); + +- drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, +- "pl"); +- if (IS_ERR(drvdata->plreg)) +- return dev_err_probe(dev, PTR_ERR(drvdata->plreg), +- "failed to map plreg\n"); +- +- drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, +- "fn2"); +- if (IS_ERR(drvdata->fn2)) +- return dev_err_probe(dev, PTR_ERR(drvdata->fn2), +- "failed to map fn2\n"); +- + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "hpe_gxp_fan_ctrl", + drvdata, +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -57,8 +57,8 @@ struct hwmon_device_attribute { + }; + + #define to_hwmon_attr(d) \ +- container_of(d, struct hwmon_device_attribute, dev_attr) +-#define to_dev_attr(a) container_of(a, struct device_attribute, attr) ++ container_of_const(d, struct hwmon_device_attribute, dev_attr) ++#define to_dev_attr(a) container_of_const(a, struct device_attribute, attr) + + /* + * Thermal zone information +@@ -71,27 +71,27 @@ struct hwmon_thermal_data { + }; + + static ssize_t +-name_show(struct device *dev, struct device_attribute *attr, char *buf) ++name_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->name); + } +-static DEVICE_ATTR_RO(name); ++static const DEVICE_ATTR_RO(name); + + static ssize_t +-label_show(struct device *dev, struct device_attribute *attr, char *buf) ++label_show(struct device *dev, const struct device_attribute *attr, char *buf) + { + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); + } +-static DEVICE_ATTR_RO(label); ++static const DEVICE_ATTR_RO(label); + +-static struct attribute *hwmon_dev_attrs[] = { ++static const struct attribute *const hwmon_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_label.attr, + NULL + }; + + static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, +- struct attribute *attr, int n) ++ const struct attribute *attr, int n) + { + struct device *dev = kobj_to_dev(kobj); + struct hwmon_device *hdev = to_hwmon_device(dev); +@@ -106,8 +106,8 @@ static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, + } + + static const struct attribute_group hwmon_dev_attr_group = { +- .attrs = hwmon_dev_attrs, +- .is_visible = hwmon_dev_attr_is_visible, ++ .attrs_const = hwmon_dev_attrs, ++ .is_visible_const = hwmon_dev_attr_is_visible, + }; + + static const struct attribute_group *hwmon_dev_attr_groups[] = { +@@ -115,13 +115,13 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = { + NULL + }; + +-static void hwmon_free_attrs(struct attribute **attrs) ++static void hwmon_free_attrs(const struct attribute *const *attrs) + { + int i; + + for (i = 0; attrs[i]; i++) { +- struct device_attribute *dattr = to_dev_attr(attrs[i]); +- struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); ++ const struct device_attribute *dattr = to_dev_attr(attrs[i]); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); + + kfree(hattr); + } +@@ -132,8 +132,8 @@ static void hwmon_dev_release(struct device *dev) + { + struct hwmon_device *hwdev = to_hwmon_device(dev); + +- if (hwdev->group.attrs) +- hwmon_free_attrs(hwdev->group.attrs); ++ if (hwdev->group.attrs_const) ++ hwmon_free_attrs(hwdev->group.attrs_const); + kfree(hwdev->groups); + kfree(hwdev->label); + kfree(hwdev); +@@ -341,7 +341,7 @@ static int hwmon_match_device(struct device *dev, const void *data) + return dev->class == &hwmon_class; + } + +-static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, ++static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, + char *buf) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -349,7 +349,7 @@ static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, + return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); + } + +-static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, ++static ssize_t pec_store(struct device *dev, const struct device_attribute *devattr, + const char *buf, size_t count) + { + struct i2c_client *client = to_i2c_client(dev); +@@ -389,7 +389,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, + return err; + } + +-static DEVICE_ATTR_RW(pec); ++static const DEVICE_ATTR_RW(pec); + + static void hwmon_remove_pec(void *dev) + { +@@ -424,9 +424,9 @@ static int hwmon_pec_register(struct device *hdev) + /* sysfs attribute management */ + + static ssize_t hwmon_attr_show(struct device *dev, +- struct device_attribute *devattr, char *buf) ++ const struct device_attribute *devattr, char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + s64 val64; + long val; +@@ -449,10 +449,10 @@ static ssize_t hwmon_attr_show(struct device *dev, + } + + static ssize_t hwmon_attr_show_string(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + char *buf) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + enum hwmon_sensor_types type = hattr->type; + const char *s; +@@ -472,10 +472,10 @@ static ssize_t hwmon_attr_show_string(struct device *dev, + } + + static ssize_t hwmon_attr_store(struct device *dev, +- struct device_attribute *devattr, ++ const struct device_attribute *devattr, + const char *buf, size_t count) + { +- struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); ++ const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); + long val; + int ret; +@@ -509,12 +509,12 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) + (type == hwmon_fan && attr == hwmon_fan_label); + } + +-static struct attribute *hwmon_genattr(const void *drvdata, +- enum hwmon_sensor_types type, +- u32 attr, +- int index, +- const char *template, +- const struct hwmon_ops *ops) ++static const struct attribute *hwmon_genattr(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, ++ int index, ++ const char *template, ++ const struct hwmon_ops *ops) + { + struct hwmon_device_attribute *hattr; + struct device_attribute *dattr; +@@ -551,8 +551,8 @@ static struct attribute *hwmon_genattr(const void *drvdata, + hattr->ops = ops; + + dattr = &hattr->dev_attr; +- dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; +- dattr->store = hwmon_attr_store; ++ dattr->show_const = is_string ? hwmon_attr_show_string : hwmon_attr_show; ++ dattr->store_const = hwmon_attr_store; + + a = &dattr->attr; + sysfs_attr_init(a); +@@ -830,7 +830,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) + } + + static int hwmon_genattrs(const void *drvdata, +- struct attribute **attrs, ++ const struct attribute **attrs, + const struct hwmon_ops *ops, + const struct hwmon_channel_info *info) + { +@@ -849,7 +849,7 @@ static int hwmon_genattrs(const void *drvdata, + u32 attr; + + while (attr_mask) { +- struct attribute *a; ++ const struct attribute *a; + + attr = __ffs(attr_mask); + attr_mask &= ~BIT(attr); +@@ -868,11 +868,11 @@ static int hwmon_genattrs(const void *drvdata, + return aindex; + } + +-static struct attribute ** ++static const struct attribute ** + __hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip) + { + int ret, i, aindex = 0, nattrs = 0; +- struct attribute **attrs; ++ const struct attribute **attrs; + + for (i = 0; chip->info[i]; i++) + nattrs += hwmon_num_channel_attrs(chip->info[i]); +@@ -927,7 +927,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + hdev = &hwdev->dev; + + if (chip) { +- struct attribute **attrs; ++ const struct attribute **attrs; + int ngroups = 2; /* terminating NULL plus &hwdev->groups */ + + if (groups) +@@ -946,7 +946,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, + goto free_hwmon; + } + +- hwdev->group.attrs = attrs; ++ hwdev->group.attrs_const = attrs; + ngroups = 0; + hwdev->groups[ngroups++] = &hwdev->group; + +diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c +--- a/drivers/hwmon/ina2xx.c ++++ b/drivers/hwmon/ina2xx.c +@@ -124,6 +124,7 @@ static const struct regmap_config ina2xx_regmap_config = { + enum ina2xx_ids { + ina219, + ina226, ++ ina232, + ina234, + ina260, + sy24655 +@@ -210,6 +211,20 @@ static const struct ina2xx_config ina2xx_config[] = { + .current_shift = 4, + .has_update_interval = true, + }, ++ [ina232] = { ++ .config_default = INA226_CONFIG_DEFAULT, ++ .calibration_value = 2048, ++ .shunt_div = 400, ++ .shunt_voltage_shift = 0, ++ .bus_voltage_shift = 0, ++ .bus_voltage_lsb = 1600, ++ .power_lsb_factor = 32, ++ .has_alerts = true, ++ .has_ishunt = false, ++ .has_power_average = false, ++ .current_shift = 0, ++ .has_update_interval = true, ++ }, + [ina260] = { + .config_default = INA260_CONFIG_DEFAULT, + .shunt_div = 400, +@@ -1123,6 +1138,7 @@ static const struct i2c_device_id ina2xx_id[] = { + { .name = "ina226", .driver_data = ina226 }, + { .name = "ina230", .driver_data = ina226 }, + { .name = "ina231", .driver_data = ina226 }, ++ { .name = "ina232", .driver_data = ina232 }, + { .name = "ina234", .driver_data = ina234 }, + { .name = "ina260", .driver_data = ina260 }, + { .name = "sy24655", .driver_data = sy24655 }, +@@ -1155,6 +1171,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { + .compatible = "ti,ina231", + .data = (void *)ina226 + }, ++ { ++ .compatible = "ti,ina232", ++ .data = (void *)ina232 ++ }, + { + .compatible = "ti,ina234", + .data = (void *)ina234 +diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c +--- a/drivers/hwmon/k10temp.c ++++ b/drivers/hwmon/k10temp.c +@@ -207,6 +207,10 @@ static const char *k10temp_temp_label[] = { + "Tccd10", + "Tccd11", + "Tccd12", ++ "Tccd13", ++ "Tccd14", ++ "Tccd15", ++ "Tccd16", + }; + + static int k10temp_read_labels(struct device *dev, +@@ -243,7 +247,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, + if (*val < 0 && !data->disp_negative) + *val = 0; + break; +- case 2 ... 13: /* Tccd{1-12} */ ++ case 2 ... 17: /* Tccd{1-16} */ + ret = read_ccd_temp_reg(data, channel - 2, ®val); + + if (ret) +@@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const k10temp_info[] = { + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, ++ HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + NULL + }; +@@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) + } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { ++ case 0x00 ... 0x2f: /* Zen5 Turin */ ++ data->ccd_offset = 0x1F0; ++ k10temp_get_ccd_support(data, 16); ++ break; + case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ + data->ccd_offset = 0x308; + k10temp_get_ccd_support(data, 8); +diff --git a/drivers/hwmon/kb9002.c b/drivers/hwmon/kb9002.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/kb9002.c +@@ -0,0 +1,460 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Kandou KB9002 PCIe 5.0 retimer hwmon driver. ++ * ++ * The retimer exposes a system management bus (SMBus 3.0 with PEC) ++ * target for firmware-managed status registers. This driver assumes ++ * the chip is strapped to SMBus mode and exports the aggregated ++ * maximum die temperature as hwmon temp1_input (millidegrees Celsius) ++ * plus the firmware version and boot status under debugfs. ++ * ++ * The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch ++ * the host interface) carries the 32-bit register address and data ++ * big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read) ++ * carries the register address and returned data little-endian. ++ * ++ * Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD). ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define KB9002_DEV_NAME "kb9002" ++ ++/* ++ * SMBus read command codes. Each read is a two-phase PEC-protected ++ * transaction: prime writes the target address, data reads it back with ++ * the register contents. FW reads use a 16-bit address, HW reads 32-bit. ++ */ ++#define KB9002_CC_FW_READ_PRIME 0x82 ++#define KB9002_CC_FW_READ_DATA 0x81 ++#define KB9002_CC_HW_READ_PRIME 0x8a ++#define KB9002_CC_HW_READ_DATA 0x89 ++ ++/* Firmware register offsets (16-bit). */ ++#define KB9002_FW_REG_VID 0x0004 ++#define KB9002_FW_REG_FW_VERSION 0x0500 ++#define KB9002_FW_REG_TEMP_MAXIMUM 0x0550 ++ ++#define KB9002_VID_MASK GENMASK(31, 16) ++#define KB9002_VID_KANDOU 0x1e6f ++ ++/* Firmware boot status: 0xe8 in the top byte means init completed OK. */ ++#define KB9002_HW_REG_FW_BOOT_STATUS 0xe0090008 ++#define KB9002_FW_BOOT_STATUS_OK_MSB 0xe8 ++ ++/* ++ * Hardware registers reached over raw I2C (32-bit addressing). The ++ * host-interface bit selects SMBus (set) vs raw-I2C target; parts ++ * strapped to raw I2C need it set before SMBus access works. ++ */ ++#define KB9002_HW_REG_REVID 0x00480004 ++#define KB9002_HW_REG_HOST_IF 0x00480008 ++#define KB9002_HOST_IF_SMBUS BIT(1) ++ ++#define KB9002_REVID_MASK GENMASK(7, 0) ++#define KB9002_REVID_B0 0x10 ++#define KB9002_REVID_B1 0x11 ++ ++/* Retries to drain a stray leading 0xff from the raw-I2C FIFO. */ ++#define KB9002_REVID_READ_RETRIES 16 ++ ++/* Temperature: 32-bit Q16.16 absolute Kelvin. */ ++#define KB9002_TEMP_FRAC_BITS 16 ++#define KB9002_ABS_ZERO_MILLI_C (-273150) ++ ++/* Firmware takes up to ~2s to respond after a host-interface change. */ ++#define KB9002_FW_READY_POLL_US (25 * USEC_PER_MSEC) ++#define KB9002_FW_READY_TIMEOUT_US (2 * USEC_PER_SEC) ++ ++struct kb9002_data { ++ struct i2c_client *client; ++ struct device *hwmon_dev; ++}; ++ ++/* Raw-I2C read: write the 32-bit BE address, then read 4 BE data bytes. */ ++static int kb9002_i2c_read(struct i2c_client *client, u32 reg, u32 *val) ++{ ++ u8 addr[4]; ++ u8 rbuf[4]; ++ struct i2c_msg msgs[2] = { ++ { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(addr), ++ .buf = addr, ++ }, ++ { ++ .addr = client->addr, ++ .flags = I2C_M_RD, ++ .len = sizeof(rbuf), ++ .buf = rbuf, ++ }, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, addr); ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) ++ return ret; ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *val = get_unaligned_be32(rbuf); ++ return 0; ++} ++ ++/* Raw-I2C write: 4 BE address bytes followed by 4 BE data bytes. */ ++static int kb9002_i2c_write(struct i2c_client *client, u32 reg, u32 val) ++{ ++ u8 buf[8]; ++ struct i2c_msg msg = { ++ .addr = client->addr, ++ .flags = 0, ++ .len = sizeof(buf), ++ .buf = buf, ++ }; ++ int ret; ++ ++ put_unaligned_be32(reg, &buf[0]); ++ put_unaligned_be32(val, &buf[4]); ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ if (ret < 0) ++ return ret; ++ if (ret != 1) ++ return -EIO; ++ ++ return 0; ++} ++ ++/* ++ * Read the silicon revision ID. A fresh FIFO may start with a stray ++ * 0xff that shifts the result, so drain one byte between retries until ++ * the top byte is no longer 0xff. ++ */ ++static int kb9002_read_revid(struct i2c_client *client, u32 *revid) ++{ ++ u8 dummy; ++ int ret; ++ int i; ++ ++ for (i = 0; i < KB9002_REVID_READ_RETRIES; i++) { ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_REVID, revid); ++ if (ret) ++ return ret; ++ if ((*revid >> 24) != 0xff) ++ return 0; ++ /* Drain one byte from the chip to re-align the I2C FIFO. */ ++ i2c_master_recv(client, &dummy, 1); ++ } ++ ++ return -EIO; ++} ++ ++/* ++ * Read a 32-bit firmware register over SMBus: block-write the 16-bit LE ++ * address, then block-read the echoed address plus 4 LE data bytes. ++ */ ++static int kb9002_fw_read(struct kb9002_data *data, u16 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[2]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le16(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_FW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_FW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* Like kb9002_fw_read but for a hardware register (32-bit LE address). */ ++static int kb9002_smbus_hw_read(struct kb9002_data *data, u32 reg, u32 *val) ++{ ++ struct i2c_client *client = data->client; ++ u8 addr[4]; ++ u8 rbuf[I2C_SMBUS_BLOCK_MAX]; ++ int ret; ++ ++ put_unaligned_le32(reg, addr); ++ ++ ret = i2c_smbus_write_block_data(client, KB9002_CC_HW_READ_PRIME, ++ sizeof(addr), addr); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_block_data(client, KB9002_CC_HW_READ_DATA, rbuf); ++ if (ret < 0) ++ return ret; ++ if (ret < (int)(sizeof(addr) + sizeof(*val))) ++ return -EIO; ++ ++ *val = get_unaligned_le32(&rbuf[sizeof(addr)]); ++ return 0; ++} ++ ++/* ++ * Switch the host interface from raw-I2C to SMBus and wait for firmware ++ * to come back up. Called only when SMBus access failed in probe, i.e. ++ * the chip is strapped to raw-I2C mode. Confirms the revision, sets the ++ * SMBus-mode bit, then polls until firmware responds again. ++ */ ++static int kb9002_enable_smbus_target(struct kb9002_data *data) ++{ ++ struct i2c_client *client = data->client; ++ u32 revid; ++ u32 val; ++ int op_ret; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "raw I2C required to switch to SMBus mode\n"); ++ ++ ret = kb9002_read_revid(client, &revid); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "revision ID read failed\n"); ++ ++ switch (FIELD_GET(KB9002_REVID_MASK, revid)) { ++ case KB9002_REVID_B0: ++ case KB9002_REVID_B1: ++ break; ++ default: ++ return dev_err_probe(&client->dev, -ENODEV, ++ "unsupported revision ID 0x%08x\n", revid); ++ } ++ ++ ret = kb9002_i2c_read(client, KB9002_HW_REG_HOST_IF, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface read failed\n"); ++ ++ val |= KB9002_HOST_IF_SMBUS; ++ ++ ret = kb9002_i2c_write(client, KB9002_HW_REG_HOST_IF, val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "host interface write failed\n"); ++ ++ /* Wait until firmware re-initialisation completes. */ ++ ret = read_poll_timeout(kb9002_fw_read, op_ret, op_ret == 0, ++ KB9002_FW_READY_POLL_US, ++ KB9002_FW_READY_TIMEOUT_US, true, ++ data, KB9002_FW_REG_VID, &val); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "firmware not responding over SMBus\n"); ++ ++ return 0; ++} ++ ++/* Convert Q16.16 absolute Kelvin to millidegrees Celsius. */ ++static long kb9002_temp_to_milli_c(u32 raw) ++{ ++ s64 milli_k = ((s64)raw * 1000) >> KB9002_TEMP_FRAC_BITS; ++ ++ return (long)milli_k + KB9002_ABS_ZERO_MILLI_C; ++} ++ ++static int kb9002_read_temp(struct kb9002_data *data, long *val) ++{ ++ u32 raw; ++ int ret; ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_TEMP_MAXIMUM, &raw); ++ if (ret) ++ return ret; ++ ++ *val = kb9002_temp_to_milli_c(raw); ++ return 0; ++} ++ ++static umode_t kb9002_is_visible(const void *drvdata, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ return 0444; ++} ++ ++static int kb9002_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct kb9002_data *data = dev_get_drvdata(dev); ++ ++ if (type == hwmon_temp && attr == hwmon_temp_input) ++ return kb9002_read_temp(data, val); ++ ++ return -EOPNOTSUPP; ++} ++ ++static const struct hwmon_ops kb9002_hwmon_ops = { ++ .is_visible = kb9002_is_visible, ++ .read = kb9002_read, ++}; ++ ++static const struct hwmon_channel_info * const kb9002_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), ++ NULL, ++}; ++ ++static const struct hwmon_chip_info kb9002_chip_info = { ++ .ops = &kb9002_hwmon_ops, ++ .info = kb9002_hwmon_info, ++}; ++ ++static int kb9002_fw_version_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 ver; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_FW_VERSION, &ver); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%u.%02u.%02u.%u\n", ++ (ver >> 24) & 0xff, (ver >> 16) & 0xff, ++ (ver >> 8) & 0xff, (ver >> 0) & 0xff); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_version); ++ ++static int kb9002_fw_load_status_show(struct seq_file *s, void *unused) ++{ ++ struct kb9002_data *data = s->private; ++ u32 status; ++ int ret; ++ ++ guard(hwmon_lock)(data->hwmon_dev); ++ ++ ret = kb9002_smbus_hw_read(data, KB9002_HW_REG_FW_BOOT_STATUS, &status); ++ if (ret) ++ return ret; ++ ++ seq_printf(s, "%s\n", ++ (status >> 24) == KB9002_FW_BOOT_STATUS_OK_MSB ? ++ "normal" : "abnormal"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(kb9002_fw_load_status); ++ ++static void kb9002_debugfs_init(struct kb9002_data *data) ++{ ++ struct dentry *dir = data->client->debugfs; ++ ++ debugfs_create_file("fw_ver", 0444, dir, data, ++ &kb9002_fw_version_fops); ++ debugfs_create_file("fw_load_status", 0444, dir, data, ++ &kb9002_fw_load_status_fops); ++} ++ ++static int kb9002_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct kb9002_data *data; ++ struct device *hwmon_dev; ++ u32 vid; ++ int ret; ++ ++ if (!i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_BLOCK_DATA | ++ I2C_FUNC_SMBUS_PEC)) ++ return -ENODEV; ++ ++ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->client = client; ++ ++ /* All firmware register accesses are PEC-protected. */ ++ client->flags |= I2C_CLIENT_PEC; ++ ++ i2c_set_clientdata(client, data); ++ ++ /* ++ * Try SMBus first. If the chip is strapped to raw-I2C mode it ++ * will not respond to SMBus framing, so fall back to switching ++ * the host interface over raw I2C and retry. ++ */ ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) { ++ dev_dbg(dev, "SMBus probe failed (%d), trying raw-I2C host-interface switch\n", ++ ret); ++ ret = kb9002_enable_smbus_target(data); ++ if (ret) ++ return ret; ++ ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "VID read failed after host-interface switch\n"); ++ } ++ if (FIELD_GET(KB9002_VID_MASK, vid) != KB9002_VID_KANDOU) ++ return dev_err_probe(dev, -ENODEV, ++ "unexpected VID 0x%08x\n", vid); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(dev, KB9002_DEV_NAME, ++ data, ++ &kb9002_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++ data->hwmon_dev = hwmon_dev; ++ kb9002_debugfs_init(data); ++ return 0; ++} ++ ++static const struct i2c_device_id kb9002_id[] = { ++ { .name = KB9002_DEV_NAME }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, kb9002_id); ++ ++static const struct of_device_id kb9002_of_match[] = { ++ { .compatible = "kandou,kb9002" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, kb9002_of_match); ++ ++static struct i2c_driver kb9002_driver = { ++ .driver = { ++ .name = KB9002_DEV_NAME, ++ .of_match_table = kb9002_of_match, ++ }, ++ .probe = kb9002_probe, ++ .id_table = kb9002_id, ++}; ++module_i2c_driver(kb9002_driver); ++ ++MODULE_AUTHOR("Andy Chung "); ++MODULE_DESCRIPTION("Kandou KB9002 PCIe retimer hwmon driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c +--- a/drivers/hwmon/lm90.c ++++ b/drivers/hwmon/lm90.c +@@ -101,6 +101,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -108,7 +109,7 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include +@@ -295,7 +296,7 @@ static const struct i2c_device_id lm90_id[] = { + }; + MODULE_DEVICE_TABLE(i2c, lm90_id); + +-static const struct of_device_id __maybe_unused lm90_of_match[] = { ++static const struct of_device_id lm90_of_match[] = { + { + .compatible = "adi,adm1032", + .data = (void *)adm1032 +@@ -2602,7 +2603,6 @@ static void lm90_stop_work(void *_data) + + static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + { +- struct device_node *np = client->dev.of_node; + int config, convrate; + + if (data->flags & LM90_HAVE_CONVRATE) { +@@ -2626,7 +2626,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) + + /* Check Temperature Range Select */ + if (data->flags & LM90_HAVE_EXTENDED_TEMP) { +- if (of_property_read_bool(np, "ti,extended-range-enable")) ++ if (device_property_read_bool(&client->dev, "ti,extended-range-enable")) + config |= 0x04; + if (!(config & 0x04)) + data->flags &= ~LM90_HAVE_EXTENDED_TEMP; +@@ -2692,36 +2692,41 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id) + return IRQ_NONE; + } + +-static int lm90_probe_channel_from_dt(struct i2c_client *client, +- struct device_node *child, +- struct lm90_data *data) ++static int lm90_probe_channel(struct i2c_client *client, ++ struct fwnode_handle *child, ++ struct lm90_data *data) + { + u32 id; + s32 val; + int err; + struct device *dev = &client->dev; + +- err = of_property_read_u32(child, "reg", &id); ++ err = fwnode_property_read_u32(child, "reg", &id); + if (err) { +- dev_err(dev, "missing reg property of %pOFn\n", child); ++ dev_err(dev, "missing reg property of %pfw\n", child); + return err; + } + + if (id >= MAX_CHANNELS) { +- dev_err(dev, "invalid reg property value %d in %pOFn\n", id, child); ++ dev_err(dev, "invalid reg property value %d in %pfw\n", id, child); + return -EINVAL; + } + +- err = of_property_read_string(child, "label", &data->channel_label[id]); ++ err = fwnode_property_read_string(child, "label", &data->channel_label[id]); + if (err == -ENODATA || err == -EILSEQ) { +- dev_err(dev, "invalid label property in %pOFn\n", child); ++ dev_err(dev, "invalid label property in %pfw\n", child); + return err; + } + + if (data->channel_label[id]) + data->channel_config[id] |= HWMON_T_LABEL; + +- err = of_property_read_s32(child, "temperature-offset-millicelsius", &val); ++ /* ++ * fwnode_property_read_u32() has no signed equivalent. ++ * temperature-offset-millicelsius is signed, so read and reinterpret it as s32 to ++ * preserve negative offsets values (same behavior as the old of_property_read_s32()). ++ */ ++ err = fwnode_property_read_u32(child, "temperature-offset-millicelsius", (u32 *)&val); + if (!err) { + if (id == 0) { + dev_err(dev, "temperature-offset-millicelsius can't be set for internal channel\n"); +@@ -2739,18 +2744,17 @@ static int lm90_probe_channel_from_dt(struct i2c_client *client, + return 0; + } + +-static int lm90_parse_dt_channel_info(struct i2c_client *client, +- struct lm90_data *data) ++static int lm90_parse_channel_info(struct i2c_client *client, ++ struct lm90_data *data) + { + int err; + struct device *dev = &client->dev; +- const struct device_node *np = dev->of_node; + +- for_each_child_of_node_scoped(np, child) { +- if (strcmp(child->name, "channel")) ++ device_for_each_child_node_scoped(dev, child) { ++ if (!fwnode_name_eq(child, "channel")) + continue; + +- err = lm90_probe_channel_from_dt(client, child, data); ++ err = lm90_probe_channel(client, child, data); + if (err) + return err; + } +@@ -2887,12 +2891,10 @@ static int lm90_probe(struct i2c_client *client) + /* Set maximum conversion rate */ + data->max_convrate = lm90_params[data->kind].max_convrate; + +- /* Parse device-tree channel information */ +- if (client->dev.of_node) { +- err = lm90_parse_dt_channel_info(client, data); +- if (err) +- return err; +- } ++ /* Parse channel information */ ++ err = lm90_parse_channel_info(client, data); ++ if (err) ++ return err; + + /* Initialize the LM90 chip */ + err = lm90_init_client(client, data); +@@ -2918,10 +2920,8 @@ static int lm90_probe(struct i2c_client *client) + err = devm_request_threaded_irq(dev, client->irq, + NULL, lm90_irq_thread, + IRQF_ONESHOT, "lm90", client); +- if (err < 0) { +- dev_err(dev, "cannot request IRQ %d\n", client->irq); ++ if (err < 0) + return err; +- } + } + + return 0; +@@ -2985,7 +2985,7 @@ static struct i2c_driver lm90_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "lm90", +- .of_match_table = of_match_ptr(lm90_of_match), ++ .of_match_table = lm90_of_match, + .pm = pm_sleep_ptr(&lm90_pm_ops), + }, + .probe = lm90_probe, +diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c +--- a/drivers/hwmon/nct6775-core.c ++++ b/drivers/hwmon/nct6775-core.c +@@ -33,6 +33,7 @@ + * (0xd451) + * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * (0xd429) ++ * nct5585d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3 + * nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3 + * +diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c +--- a/drivers/hwmon/nct6775-platform.c ++++ b/drivers/hwmon/nct6775-platform.c +@@ -34,7 +34,7 @@ static const char * const nct6775_sio_names[] __initconst = { + [nct6795] = "NCT6795D", + [nct6796] = "NCT6796D", + [nct6797] = "NCT6797D", +- [nct6798] = "NCT6798D", ++ [nct6798] = "NCT6798D/NCT5585D", + [nct6799] = "NCT6796D-S/NCT6799D-R", + }; + +diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c +--- a/drivers/hwmon/npcm750-pwm-fan.c ++++ b/drivers/hwmon/npcm750-pwm-fan.c +@@ -996,10 +996,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) + sprintf(name, "NPCM7XX-FAN-MD%d", i); + ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr, + 0, name, (void *)data); +- if (ret) { +- dev_err(dev, "register IRQ fan%d failed\n", i); ++ if (ret) + return ret; +- } + } + + for_each_child_of_node_scoped(np, child) { +diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c +--- a/drivers/hwmon/nzxt-kraken3.c ++++ b/drivers/hwmon/nzxt-kraken3.c +@@ -1,6 +1,7 @@ + // SPDX-License-Identifier: GPL-2.0+ + /* +- * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. ++ * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73, 2023/2023 Elite, and ++ * 2024 Elite all in one coolers. + * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). + * 2023 models use the Z53 code paths. + * +@@ -25,6 +26,7 @@ + #define USB_PRODUCT_ID_Z53 0x3008 + #define USB_PRODUCT_ID_KRAKEN2023 0x300E + #define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C ++#define USB_PRODUCT_ID_KRAKEN2024_ELITE 0x3012 + + enum kinds { X53, Z53, KRAKEN2023 } __packed; + enum pwm_enable { off, manual, curve } __packed; +@@ -927,6 +929,10 @@ static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id + priv->kind = KRAKEN2023; + device_name = "kraken2023elite"; + break; ++ case USB_PRODUCT_ID_KRAKEN2024_ELITE: ++ priv->kind = KRAKEN2023; ++ device_name = "kraken2024elite"; ++ break; + default: + ret = -ENODEV; + goto fail_and_close; +@@ -994,6 +1000,7 @@ static const struct hid_device_id kraken3_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2024_ELITE) }, + { } + }; + +diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h +--- a/drivers/hwmon/peci/common.h ++++ b/drivers/hwmon/peci/common.h +@@ -31,7 +31,7 @@ struct peci_sensor_data { + + /** + * peci_sensor_need_update() - check whether sensor update is needed or not +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + * + * Return: true if update is needed, false if not. + */ +@@ -44,7 +44,7 @@ static inline bool peci_sensor_need_update(struct peci_sensor_state *state) + + /** + * peci_sensor_mark_updated() - mark the sensor is updated +- * @sensor: pointer to sensor data struct ++ * @state: pointer to sensor state struct + */ + static inline void peci_sensor_mark_updated(struct peci_sensor_state *state) + { +diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig +--- a/drivers/hwmon/pmbus/Kconfig ++++ b/drivers/hwmon/pmbus/Kconfig +@@ -52,8 +52,8 @@ config SENSORS_ADM1275 + help + If you say yes here you get hardware monitoring support for Analog + Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, +- ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and +- Digital Power Monitors. ++ ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C ++ Hot-Swap Controller and Digital Power Monitors. + + This driver can also be built as a module. If so, the module will + be called adm1275. +@@ -394,10 +394,10 @@ config SENSORS_MAX20751 + be called max20751. + + config SENSORS_MAX20830 +- tristate "Analog Devices MAX20830" ++ tristate "Analog Devices MAX20830 and compatibles" + help + If you say yes here you get hardware monitoring support for Analog +- Devices MAX20830. ++ Devices MAX20830, MAX20830C, and MAX20840C. + + This driver can also be built as a module. If so, the module will + be called max20830. +@@ -434,8 +434,9 @@ config SENSORS_MAX34440 + tristate "Maxim MAX34440 and compatibles" + help + If you say yes here you get hardware monitoring support for Maxim +- MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. +- Other compatible are ADPM12160, and ADPM12200. ++ MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and ++ MAX34461. Other compatible devices are ADPM12160, ADPM12200, and ++ ADPM12250. + + This driver can also be built as a module. If so, the module will + be called max34440. +@@ -607,6 +608,15 @@ config SENSORS_MPQ7932 + This driver can also be built as a module. If so, the module will + be called mpq7932. + ++config SENSORS_MPQ82D00 ++ tristate "MPS MPQ82D00" ++ help ++ If you say yes here you get hardware monitoring functionality support ++ for power management IC MPS MPQ82D00. ++ ++ This driver can also be built as a module. If so, the module will ++ be called mpq82d00. ++ + config SENSORS_MPQ8785 + tristate "MPS MPQ8785" + help +@@ -616,6 +626,37 @@ config SENSORS_MPQ8785 + This driver can also be built as a module. If so, the module will + be called mpq8785. + ++config SENSORS_MPQ8646 ++ tristate "MPS MPQ8646" ++ depends on REGULATOR || !REGULATOR ++ depends on NVMEM || !NVMEM ++ help ++ If you say yes here you get hardware monitoring support for the ++ Monolithic Power Systems MPQ8646. ++ ++ This driver can also be built as a module. If so, the module ++ will be called mpq8646. ++ ++config SENSORS_MPQ8646_DEBUG_UNSAFE ++ bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)" ++ depends on SENSORS_MPQ8646 && DEBUG_FS ++ default n ++ help ++ Expose additional WRITE-able debugfs files in the client's ++ pmbus debugfs directory, ++ /sys/kernel/debug/i2c/i2c-/-/ ++ ++ These are provisioning and bring-up aids. On many designs the ++ MPQ8646 powers the SoC core rail, so a wrong write can brown out ++ or permanently mis-provision the board. ++ ++ WARNING: Wrong register writes can and likely will physically ++ damage or destroy the chip and/or the board. ++ ++ It shall never be set in a production, shipping, or default configuration. ++ ++ If unsure, say N. ++ + config SENSORS_PIM4328 + tristate "Flex PIM4328 and compatibles" + help +@@ -677,6 +718,25 @@ config SENSORS_STEF48H28 + This driver can also be built as a module. If so, the module will + be called stef48h28. + ++config SENSORS_SQ24860 ++ tristate "Silergy SQ24860" ++ help ++ If you say yes here you get hardware monitoring support for Silergy ++ SQ24860 eFuse. ++ ++ This driver can also be built as a module. If so, the module will ++ be called sq24860. ++ ++config SENSORS_SQ24860_REGULATOR ++ bool "Regulator support for SQ24860" ++ depends on SENSORS_SQ24860 && REGULATOR ++ default SENSORS_SQ24860 ++ help ++ If you say yes here you get regulator support for Silergy SQ24860. ++ The regulator is registered through the PMBus regulator framework and ++ can be used to control the output exposed by the device. ++ This option is only useful if regulator framework support is needed. ++ + config SENSORS_STPDDC60 + tristate "ST STPDDC60" + help +@@ -767,6 +827,15 @@ config SENSORS_UCD9200 + This driver can also be built as a module. If so, the module will + be called ucd9200. + ++config SENSORS_VT7505 ++ tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505" ++ help ++ If you say yes here you get hardware monitoring support for Analog ++ Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers. ++ ++ This driver can also be built as a module. If so, the module will ++ be called vt7505. ++ + config SENSORS_XDP710 + tristate "Infineon XDP710 family" + help +@@ -803,6 +872,16 @@ config SENSORS_XDPE1A2G7B + This driver can also be built as a module. If so, the module will + be called xdpe1a2g7b. + ++config SENSORS_XDPE1A2G7B_REGULATOR ++ bool "Regulator support for XDPE1A2G7B and compatibles" ++ depends on SENSORS_XDPE1A2G7B && REGULATOR ++ help ++ If you say yes here you get regulator support for Infineon ++ XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers. ++ ++ This enables the controllers to be used as regulator devices, ++ providing voltage control through the regulator framework. ++ + config SENSORS_XDPE122 + tristate "Infineon XDPE122 family" + help +diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile +--- a/drivers/hwmon/pmbus/Makefile ++++ b/drivers/hwmon/pmbus/Makefile +@@ -59,12 +59,15 @@ obj-$(CONFIG_SENSORS_MP5990) += mp5990.o + obj-$(CONFIG_SENSORS_MP9941) += mp9941.o + obj-$(CONFIG_SENSORS_MP9945) += mp9945.o + obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o ++obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o + obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o ++obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.o + obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o + obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o + obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o + obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o + obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o ++obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o + obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o + obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o + obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o +@@ -73,6 +76,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o + obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o + obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o + obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o ++obj-$(CONFIG_SENSORS_VT7505) += vt7505.o + obj-$(CONFIG_SENSORS_XDP710) += xdp710.o + obj-$(CONFIG_SENSORS_XDP720) += xdp720.o + obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o +diff --git a/drivers/hwmon/pmbus/acbel-fsg032.c b/drivers/hwmon/pmbus/acbel-fsg032.c +--- a/drivers/hwmon/pmbus/acbel-fsg032.c ++++ b/drivers/hwmon/pmbus/acbel-fsg032.c +@@ -52,6 +52,7 @@ static const struct i2c_device_id acbel_fsg032_id[] = { + { .name = "acbel_fsg032" }, + { } + }; ++MODULE_DEVICE_TABLE(i2c, acbel_fsg032_id); + + static struct pmbus_driver_info acbel_fsg032_info = { + .pages = 1, +diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c +--- a/drivers/hwmon/pmbus/adm1275.c ++++ b/drivers/hwmon/pmbus/adm1275.c +@@ -19,7 +19,7 @@ + #include "pmbus.h" + + enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, +- adm1293, adm1294, sq24905c }; ++ adm1293, adm1294, bd12780, bd12790, sq24905c }; + + #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) + #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) +@@ -47,6 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, + #define ADM1278_VOUT_EN BIT(1) + + #define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) ++/* The BD127[89]0 data sheets mark TSFILT bit as reserved. */ ++#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN) + + #define ADM1293_IRANGE_25 0 + #define ADM1293_IRANGE_50 BIT(6) +@@ -134,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = { + + }; + ++/* ++ * BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18) ++ * and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m. ++ * ++ * Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V) ++ * -> m = 6770, R=-2 / m = 4062, R=-2 ++ * Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV) ++ * -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R ++ * Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72 ++ * -> m = round(k * 10^(-3-R)), R=-2 for 60V/15mV, R=-3 for the other three ++ * Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1 ++ */ ++static const struct coefficients bd12790_coefficients[] = { ++ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ ++ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ ++ [2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */ ++ [3] = { 664, 20480, -1 }, /* current, vsense range 30mV */ ++ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ ++ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ ++ [6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */ ++ [7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */ ++ [8] = { 42, 31880, -1 }, /* temperature */ ++}; ++ + static const struct coefficients adm1275_coefficients[] = { + [0] = { 19199, 0, -2 }, /* voltage, vrange set */ + [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ +@@ -487,6 +513,8 @@ static const struct i2c_device_id adm1275_id[] = { + { .name = "adm1281", .driver_data = adm1281 }, + { .name = "adm1293", .driver_data = adm1293 }, + { .name = "adm1294", .driver_data = adm1294 }, ++ { .name = "bd12780", .driver_data = bd12780 }, ++ { .name = "bd12790", .driver_data = bd12790 }, + { .name = "mc09c", .driver_data = sq24905c }, + { } + }; +@@ -494,12 +522,13 @@ MODULE_DEVICE_TABLE(i2c, adm1275_id); + + /* Enable VOUT & TEMP1 if not enabled (disabled by default) */ + static int adm1275_enable_vout_temp(struct adm1275_data *data, +- struct i2c_client *client, int config) ++ struct i2c_client *client, int config, ++ u16 defconfig) + { + int ret; + +- if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) { +- config |= ADM1278_PMON_DEFCONFIG; ++ if ((config & defconfig) != defconfig) { ++ config |= defconfig; + ret = adm1275_write_pmon_config(data, client, config); + if (ret < 0) { + dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n"); +@@ -524,23 +553,19 @@ static int adm1275_probe(struct i2c_client *client) + u32 shunt; + u32 avg; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer ID\n"); + return ret; + } + if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) && +- (ret != 2 || strncmp(block_buffer, "SY", 2))) { ++ (ret != 2 || strncmp(block_buffer, "SY", 2)) && ++ (ret != 4 || strncmp(block_buffer, "ROHM", 4))) { + dev_err(&client->dev, "Unsupported Manufacturer ID\n"); + return -ENODEV; + } + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "Failed to read Manufacturer Model\n"); + return ret; +@@ -562,6 +587,7 @@ static int adm1275_probe(struct i2c_client *client) + if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || + mid->driver_data == adm1278 || mid->driver_data == adm1281 || + mid->driver_data == adm1293 || mid->driver_data == adm1294 || ++ mid->driver_data == bd12780 || mid->driver_data == bd12790 || + mid->driver_data == sq24905c) + config_read_fn = i2c_smbus_read_word_data; + else +@@ -642,6 +668,7 @@ static int adm1275_probe(struct i2c_client *client) + data->have_power_sampling = true; + + coefficients = adm1272_coefficients; ++ + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ +@@ -666,7 +693,53 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ break; ++ ++ /* ++ * The BD12790 is almost identical to the adm1272. Only the defconfig ++ * and coefficients have minor differences. ++ */ ++ case bd12790: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = bd12790_coefficients; ++ ++ vindex = (config & ADM1275_VRANGE) ? 1 : 0; ++ cindex = (config & ADM1272_IRANGE) ? 3 : 2; ++ /* pindex depends on the combination of the above */ ++ switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { ++ case 0: ++ default: ++ pindex = 4; ++ break; ++ case ADM1275_VRANGE: ++ pindex = 5; ++ break; ++ case ADM1272_IRANGE: ++ pindex = 6; ++ break; ++ case ADM1275_VRANGE | ADM1272_IRANGE: ++ pindex = 7; ++ break; ++ } ++ tindex = 8; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); + if (ret) + return ret; + +@@ -728,13 +801,45 @@ static int adm1275_probe(struct i2c_client *client) + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +- ret = adm1275_enable_vout_temp(data, client, config); ++ ret = adm1275_enable_vout_temp(data, client, config, ++ ADM1278_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; ++ ++ /* ++ * The BD12780 is almost functionally identical with the adm1278 above. ++ * Only differences visible to the driver are lack of TSFILT bits and ++ * different identification register contents. ++ */ ++ case bd12780: ++ data->have_vout = true; ++ data->have_pin_max = true; ++ data->have_temp_max = true; ++ data->have_power_sampling = true; ++ ++ coefficients = adm1278_coefficients; ++ vindex = 0; ++ cindex = 1; ++ pindex = 2; ++ tindex = 3; ++ ++ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; ++ ++ ret = adm1275_enable_vout_temp(data, client, config, ++ BD12780_PMON_DEFCONFIG); ++ if (ret) ++ return ret; ++ ++ if (config & ADM1278_VIN_EN) ++ info->func[0] |= PMBUS_HAVE_VIN; ++ ++ break; + case adm1293: + case adm1294: + data->have_iout_min = true; +@@ -870,9 +975,27 @@ static int adm1275_probe(struct i2c_client *client) + return pmbus_do_probe(client, info); + } + ++static const struct of_device_id adm1275_of_match[] = { ++ { .compatible = "adi,adm1075", }, ++ { .compatible = "adi,adm1272", }, ++ { .compatible = "adi,adm1273", }, ++ { .compatible = "adi,adm1275", }, ++ { .compatible = "adi,adm1276", }, ++ { .compatible = "adi,adm1278", }, ++ { .compatible = "adi,adm1281", }, ++ { .compatible = "adi,adm1293", }, ++ { .compatible = "adi,adm1294", }, ++ { .compatible = "rohm,bd12780", }, ++ { .compatible = "rohm,bd12790", }, ++ { .compatible = "silergy,mc09c", }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, adm1275_of_match); ++ + static struct i2c_driver adm1275_driver = { + .driver = { + .name = "adm1275", ++ .of_match_table = adm1275_of_match, + }, + .probe = adm1275_probe, + .id_table = adm1275_id, +diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c +--- a/drivers/hwmon/pmbus/ir36021.c ++++ b/drivers/hwmon/pmbus/ir36021.c +@@ -31,21 +31,13 @@ static int ir36021_probe(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BYTE_DATA +- | I2C_FUNC_SMBUS_READ_WORD_DATA +- | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf); +- if (ret < 0) { +- dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); +- return ret; +- } +- if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) { +- dev_err(&client->dev, "MFR_MODEL unrecognised\n"); +- return -ENODEV; +- } ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read PMBUS_MFR_MODEL\n"); ++ if (ret != 1 || buf[0] != 0x2d) ++ return dev_err_probe(&client->dev, -ENODEV, ++ "MFR_MODEL unrecognised\n"); + + return pmbus_do_probe(client, &ir36021_info); + } +diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c +--- a/drivers/hwmon/pmbus/lm25066.c ++++ b/drivers/hwmon/pmbus/lm25066.c +@@ -35,6 +35,7 @@ enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; + #define LM25066_READ_AVG_PIN 0xdf + + #define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ ++#define LM25066_DEV_SETUP_CL_CFG BIT(2) /* Current limit configuration */ + + #define LM25066_SAMPLES_FOR_AVG_MAX 4096 + +@@ -485,6 +486,42 @@ static int lm25066_probe(struct i2c_client *client) + + data->id = (enum chips)(unsigned long)i2c_get_match_data(client); + ++ if (data->id != lm25056) { ++ int config_new = config; ++ const char *cl_setting; ++ int ret; ++ ++ if (!of_property_read_string(client->dev.of_node, ++ "ti,current-range", &cl_setting)) { ++ config_new |= LM25066_DEV_SETUP_CL_CFG; ++ if (strcmp(cl_setting, "high") == 0) { ++ if (data->id == lm25066) ++ config_new |= LM25066_DEV_SETUP_CL; ++ else ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ } else if (strcmp(cl_setting, "low") == 0) { ++ if (data->id == lm25066) ++ config_new &= ~LM25066_DEV_SETUP_CL; ++ else ++ config_new |= LM25066_DEV_SETUP_CL; ++ } else { ++ dev_err(&client->dev, ++ "invalid current-range setting: %s\n", ++ cl_setting); ++ return -EINVAL; ++ } ++ } ++ ++ if (config_new != config) { ++ ret = i2c_smbus_write_byte_data(client, ++ LM25066_DEVICE_SETUP, ++ config_new); ++ if (ret < 0) ++ return ret; ++ config = config_new; ++ } ++ } ++ + info = &data->info; + + info->pages = 1; +diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c +--- a/drivers/hwmon/pmbus/ltc2978.c ++++ b/drivers/hwmon/pmbus/ltc2978.c +@@ -611,17 +611,13 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- if (!i2c_check_functionality(client->adapter, +- I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +- return -ENODEV; +- +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, buf); + if (ret < 0) + return ret; + if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3))) + return -ENODEV; + +- ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return ret; + for (id = <c2978_id[0]; strlen(id->name); id++) { +@@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client) + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int ret; + +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- sizeof(buf), buf); ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, ++ buf); + if (ret < 0) + return ret; + +- if (!strncmp(buf + 1, "LT7170", 6) || +- !strncmp(buf + 1, "LT7170-1", 8)) ++ if (ret < 6) ++ return -ENODEV; ++ ++ if (!strncmp(buf, "LT7170", 6)) + return lt7170; +- if (!strncmp(buf + 1, "LT7171", 6) || +- !strncmp(buf + 1, "LT7171-1", 8)) ++ if (!strncmp(buf, "LT7171", 6)) + return lt7171; + + return -ENODEV; +diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c +--- a/drivers/hwmon/pmbus/max20830.c ++++ b/drivers/hwmon/pmbus/max20830.c +@@ -7,12 +7,69 @@ + + #include + #include ++#include + #include + #include + #include "pmbus.h" + + #define MAX20830_IC_DEVICE_ID_LENGTH 9 + ++struct max20830_data { ++ struct pmbus_driver_info info; ++ u32 vout_rfb1; ++ u32 vout_rfb2; ++}; ++ ++static const char * const supported_chip_ids[] = { ++ "MAX20830", ++ "MAX20830C", ++ "MAX20840C", ++}; ++ ++/* ++ * MAX20830 only supports READ_VOUT for VOUT monitoring. ++ * ++ * Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not ++ * supported by this driver and return -ENODATA. This means sysfs attributes ++ * like in1_max, in1_crit, etc. will not be available. Only in1_input (the ++ * scaled output voltage) is supported. ++ * ++ * MAX20830 uses an external resistor divider for voltage sensing: ++ * - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin ++ * - READ_VOUT reports the feedback voltage, which needs to be scaled for actual ++ * output voltage ++ * ++ * Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2) ++ * ++ * If regulator support is added in the future, some adjustments are needed to ++ * ensure correct feedback voltages are set. ++ */ ++static int max20830_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ const struct max20830_data *data = container_of(info, struct max20830_data, info); ++ int ret; ++ u64 temp; ++ ++ switch (reg) { ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ /* Apply voltage divider scaling if resistors are non-zero */ ++ if (data->vout_rfb1 && data->vout_rfb2) { ++ temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2; ++ temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2); ++ ret = clamp_val(temp, 0, 0xFFFF); ++ } ++ return ret; ++ default: ++ return -ENODATA; ++ } ++} ++ + static struct pmbus_driver_info max20830_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = linear, +@@ -23,61 +80,53 @@ static struct pmbus_driver_info max20830_info = { + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, ++ .read_word_data = max20830_read_word_data, ++ .have_pmbus_revision = true, ++ .pmbus_revision = PMBUS_REV_13, + }; + + static int max20830_probe(struct i2c_client *client) + { + u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {}; +- int ret; ++ struct max20830_data *data; ++ int i, ret; + +- if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) && +- !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) +- return -ENODEV; ++ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; + +- /* +- * Use i2c_smbus_read_block_data() if supported, otherwise fall back +- * to i2c_smbus_read_i2c_block_data() to support I2C controllers +- * which do not support SMBus block reads. +- */ +- if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { +- /* Reads 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- } else { +- /* Reads 1 length byte + 9 Data bytes from MAX20830 */ +- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, +- MAX20830_IC_DEVICE_ID_LENGTH + 1, +- buf); +- if (ret < 0) +- return dev_err_probe(&client->dev, ret, +- "Failed to read IC_DEVICE_ID\n"); +- /* +- * Moves data forward, removing the length byte, this is to +- * match the format of i2c_smbus_read_block_data(). +- * Also adjust return value to reflect length byte removal. +- */ +- memmove(buf, buf + 1, MAX20830_IC_DEVICE_ID_LENGTH); +- ret = ret - 1; +- } ++ data->info = max20830_info; + +- /* +- * MAX20830 IC_DEVICE_ID sends string data "MAX20830\0". +- * Return value should at least be 9 bytes of data. +- */ ++ /* Read optional voltage divider resistor values */ ++ device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1); ++ device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2); ++ ++ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to read IC_DEVICE_ID\n"); ++ ++ /* Verify we read the expected number of bytes */ + if (ret < MAX20830_IC_DEVICE_ID_LENGTH) + return dev_err_probe(&client->dev, -ENODEV, +- "IC_DEVICE_ID too short: expected at least 9 bytes, got %d\n", +- ret); ++ "IC_DEVICE_ID too short: expected %d bytes, got %d\n", ++ MAX20830_IC_DEVICE_ID_LENGTH, ret); + +- /* 9 bytes of data, buf[0]-buf[7] = "MAX20830", buf[8] = '\0' */ +- buf[MAX20830_IC_DEVICE_ID_LENGTH - 1] = '\0'; +- if (strncmp(buf, "MAX20830", MAX20830_IC_DEVICE_ID_LENGTH - 1)) ++ /* Null-terminate the string */ ++ buf[ret] = '\0'; ++ ++ /* Verify the device ID matches what we expect */ ++ for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) { ++ if (!strcmp(buf, supported_chip_ids[i])) ++ break; ++ } ++ ++ /* No match found - unsupported device */ ++ if (i == ARRAY_SIZE(supported_chip_ids)) + return dev_err_probe(&client->dev, -ENODEV, +- "Unsupported device: '%s'\n", buf); ++ "Unsupported device: '%*pE'\n", ret, buf); + +- return pmbus_do_probe(client, &max20830_info); ++ return pmbus_do_probe(client, &data->info); + } + + static const struct i2c_device_id max20830_id[] = { +diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c +--- a/drivers/hwmon/pmbus/max34440.c ++++ b/drivers/hwmon/pmbus/max34440.c +@@ -23,6 +23,7 @@ enum chips { + max34441, + max34446, + max34451, ++ max34452, + max34460, + max34461, + }; +@@ -59,6 +60,7 @@ enum chips { + #define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A + + #define MAX34451ETNA6_MFR_REV 0x0012 ++#define MAX34451ETNA8_MFR_REV 0x0014 + + #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 + #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f +@@ -105,16 +107,21 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + case PMBUS_UT_FAULT_LIMIT: + case PMBUS_MFR_MAX_TEMP_1: + /* +- * MAX34451/ADPM family do not support VIN/IIN limit registers, +- * manufacturer-specific min/max registers, or undercurrent/ +- * undertemperature fault limits. Accessing these triggers CML +- * error and asserts ALERT. ++ * MAX34451/MAX34452/ADPM family do not support VIN/IIN limit ++ * registers, manufacturer-specific min/max registers, or ++ * undercurrent/undertemperature fault limits. Accessing these ++ * triggers CML error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + ret = -ENODATA; + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + MAX34440_MFR_VOUT_MIN); +@@ -125,8 +132,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, + break; + case PMBUS_VIRT_READ_IOUT_AVG: + if (data->id != max34446 && data->id != max34451 && +- data->id != adpm12160 && data->id != adpm12200 && +- data->id != adpm12250) ++ data->id != max34452 && data->id != adpm12160 && ++ data->id != adpm12200 && data->id != adpm12250) + return -ENXIO; + ret = pmbus_read_word_data(client, page, phase, + MAX34446_MFR_IOUT_AVG); +@@ -191,6 +198,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit, + word); + break; ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ if (data->id == max34452) ++ return -ENXIO; ++ return -ENODATA; + case PMBUS_VIRT_RESET_POUT_HISTORY: + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_POUT_PEAK, 0); +@@ -211,8 +222,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, + ret = pmbus_write_word_data(client, page, + MAX34440_MFR_IOUT_PEAK, 0); + if (!ret && (data->id == max34446 || data->id == max34451 || +- data->id == adpm12160 || data->id == adpm12200 || +- data->id == adpm12250)) ++ data->id == max34452 || data->id == adpm12160 || ++ data->id == adpm12200 || data->id == adpm12250)) + ret = pmbus_write_word_data(client, page, + MAX34446_MFR_IOUT_AVG, 0); + +@@ -280,12 +291,13 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg) + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Accessing them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -303,12 +315,13 @@ static int max34451_write_byte_data(struct i2c_client *client, int page, + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_OTHER: + /* +- * MAX34451/ADPM family do not support STATUS_BYTE or ++ * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or + * STATUS_OTHER registers. Writing to them triggers CML + * error and asserts ALERT. + */ +- if (data->id == max34451 || data->id == adpm12160 || +- data->id == adpm12200 || data->id == adpm12250) ++ if (data->id == max34451 || data->id == max34452 || ++ data->id == adpm12160 || data->id == adpm12200 || ++ data->id == adpm12250) + return -ENXIO; + return -ENODATA; + default: +@@ -339,16 +352,28 @@ static int max34451_set_supported_funcs(struct i2c_client *client, + if (rv < 0) + return rv; + +- if (rv >= MAX34451ETNA6_MFR_REV) { ++ if (data->id == max34451 && rv >= MAX34451ETNA6_MFR_REV) { + max34451_na6 = true; + data->info.format[PSC_VOLTAGE_IN] = direct; + data->info.format[PSC_CURRENT_IN] = direct; +- data->info.m[PSC_VOLTAGE_IN] = 1; ++ + data->info.b[PSC_VOLTAGE_IN] = 0; +- data->info.R[PSC_VOLTAGE_IN] = 3; +- data->info.m[PSC_CURRENT_IN] = 1; + data->info.b[PSC_CURRENT_IN] = 0; +- data->info.R[PSC_CURRENT_IN] = 2; ++ if (rv >= MAX34451ETNA8_MFR_REV) { ++ data->info.m[PSC_VOLTAGE_IN] = 125; ++ data->info.R[PSC_VOLTAGE_IN] = 0; ++ data->info.m[PSC_VOLTAGE_OUT] = 125; ++ data->info.R[PSC_VOLTAGE_OUT] = 0; ++ data->info.m[PSC_CURRENT_IN] = 250; ++ data->info.R[PSC_CURRENT_IN] = -1; ++ data->info.m[PSC_CURRENT_OUT] = 250; ++ data->info.R[PSC_CURRENT_OUT] = -1; ++ } else { ++ data->info.m[PSC_VOLTAGE_IN] = 1; ++ data->info.R[PSC_VOLTAGE_IN] = 3; ++ data->info.m[PSC_CURRENT_IN] = 1; ++ data->info.R[PSC_CURRENT_IN] = 2; ++ } + data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; + } +@@ -665,6 +690,32 @@ static struct pmbus_driver_info max34440_info[] = { + .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, + }, ++ [max34452] = { ++ .pages = 21, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .m[PSC_CURRENT_OUT] = 1, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 2, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 2, ++ /* func 0-15 is set dynamically before probing */ ++ .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, ++ .read_byte_data = max34451_read_byte_data, ++ .read_word_data = max34440_read_word_data, ++ .write_byte_data = max34451_write_byte_data, ++ .write_word_data = max34440_write_word_data, ++ .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, ++ }, + [max34460] = { + .pages = 18, + .format[PSC_VOLTAGE_OUT] = direct, +@@ -748,7 +799,7 @@ static int max34440_probe(struct i2c_client *client) + data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; + data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; + +- if (data->id == max34451) { ++ if (data->id == max34451 || data->id == max34452) { + rv = max34451_set_supported_funcs(client, data); + if (rv) + return rv; +@@ -769,6 +820,7 @@ static const struct i2c_device_id max34440_id[] = { + { .name = "max34441", .driver_data = max34441 }, + { .name = "max34446", .driver_data = max34446 }, + { .name = "max34451", .driver_data = max34451 }, ++ { .name = "max34452", .driver_data = max34452 }, + { .name = "max34460", .driver_data = max34460 }, + { .name = "max34461", .driver_data = max34461 }, + { } +diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c +--- a/drivers/hwmon/pmbus/mp2975.c ++++ b/drivers/hwmon/pmbus/mp2975.c +@@ -176,7 +176,6 @@ mp2975_vid2direct(int vrf, int val) + static u16 mp2975_data2reg_linear11(s64 val) + { + s16 exponent = 0, mantissa; +- bool negative = false; + + /* simple case */ + if (val == 0) +@@ -196,10 +195,6 @@ static u16 mp2975_data2reg_linear11(s64 val) + /* Convert mantissa from milli-units to units */ + mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); + +- /* restore sign */ +- if (negative) +- mantissa = -mantissa; +- + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); + } +diff --git a/drivers/hwmon/pmbus/mpq82d00.c b/drivers/hwmon/pmbus/mpq82d00.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/mpq82d00.c +@@ -0,0 +1,249 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00) ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++#define MPQ82D00_VOUT_DIV 64 ++ ++#define MPQ82D00_PAGE_NUM 1 ++ ++#define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \ ++ PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \ ++ PMBUS_HAVE_STATUS_VOUT | \ ++ PMBUS_HAVE_STATUS_IOUT | \ ++ PMBUS_HAVE_STATUS_TEMP | \ ++ PMBUS_HAVE_STATUS_INPUT) ++ ++struct mpq82d00_data { ++ struct pmbus_driver_info info; ++ int vout_scale; ++}; ++ ++#define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info) ++ ++static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent) ++{ ++ s16 exponent, mantissa, target_exponent; ++ ++ exponent = ((s16)word) >> 11; ++ mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; ++ target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11; ++ ++ /* ++ * The MPQ82D00 does not support negtive limit value, if a negtive ++ * limit value is written, the limit value will become to 0. And ++ * the maximum positive limit value is limitted to 0x3FF. ++ */ ++ if (mantissa < 0) { ++ mantissa = 0; ++ } else { ++ if (exponent > target_exponent) { ++ mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ? ++ mantissa << (exponent - target_exponent) : ++ 0x3FF; ++ } else { ++ mantissa = clamp_val(mantissa >> (target_exponent - exponent), ++ 0, 0x3FF); ++ } ++ } ++ ++ return mantissa | ((expect_exponent << 11) & 0xf800); ++} ++ ++static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ /* ++ * The MPQ82D00 does not follow standard PMBus protocol completely, ++ * and the calculation of vout in this driver is based on direct ++ * format. As a result, the format of vout is enforced to direct. ++ */ ++ ret = PB_VOUT_MODE_DIRECT; ++ break; ++ default: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_STATUS_WORD: ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_POUT: ++ case PMBUS_READ_PIN: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * These registers are not explicitly handled by the driver, ++ * as a result, return -ENODATA directly. ++ */ ++ ret = -ENODATA; ++ break; ++ case PMBUS_READ_VOUT: ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ return ret; ++ ++ ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale, ++ MPQ82D00_VOUT_DIV); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other telemetry and limit ++ * value reading, so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg, ++ u16 word) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_OT_FAULT_LIMIT: ++ /* ++ * The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b00000), so the ++ * exponent of word parameter should be converted to 5'b00000. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x00)); ++ break; ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ /* ++ * The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format, ++ * and the exponent is a constant value(5'b11100), so the ++ * exponent of word parameter should be converted to 5'b11100. ++ */ ++ ret = pmbus_write_word_data(client, page, reg, ++ mpq82d00_linear_exp_transfer(word, 0x1C)); ++ break; ++ default: ++ /* ++ * The MPQ82D00 do not support other limit value configuration, ++ * so, return -EINVAL directly. ++ */ ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info) ++{ ++ struct mpq82d00_data *data = to_mpq82d00_data(info); ++ int ret; ++ ++ ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); ++ if (ret < 0) ++ return ret; ++ ++ ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE ++ * bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5 ++ * is 0, it is linear mode, the vout scale is 1.953125mv/LSB. ++ */ ++ if (FIELD_GET(GENMASK(5, 5), ret)) ++ data->vout_scale = 320; ++ else ++ data->vout_scale = 125; ++ ++ return 0; ++} ++ ++static struct pmbus_driver_info mpq82d00_info = { ++ .pages = MPQ82D00_PAGE_NUM, ++ .format[PSC_VOLTAGE_IN] = linear, ++ .format[PSC_CURRENT_OUT] = linear, ++ .format[PSC_TEMPERATURE] = linear, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .format[PSC_POWER] = linear, ++ ++ .m[PSC_VOLTAGE_OUT] = 1, ++ .R[PSC_VOLTAGE_OUT] = 3, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ ++ .func[0] = MPQ82D00_RAIL1_FUNC, ++ .read_word_data = mpq82d00_read_word_data, ++ .read_byte_data = mpq82d00_read_byte_data, ++ .write_word_data = mpq82d00_write_word_data, ++ .identify = mpq82d00_identify, ++}; ++ ++static int mpq82d00_probe(struct i2c_client *client) ++{ ++ struct mpq82d00_data *data; ++ ++ data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info)); ++ ++ return pmbus_do_probe(client, &data->info); ++} ++ ++static const struct i2c_device_id mpq82d00_id[] = { ++ {.name = "mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, mpq82d00_id); ++ ++static const struct of_device_id __maybe_unused mpq82d00_of_match[] = { ++ {.compatible = "mps,mpq82d00"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq82d00_of_match); ++ ++static struct i2c_driver mpq82d00_driver = { ++ .driver = { ++ .name = "mpq82d00", ++ .of_match_table = mpq82d00_of_match, ++ }, ++ .probe = mpq82d00_probe, ++ .id_table = mpq82d00_id, ++}; ++ ++module_i2c_driver(mpq82d00_driver); ++ ++MODULE_AUTHOR("Wensheng Wang ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pmbus.h" ++ ++/* Default cadence for the in-driver alarm-poll fallback (ms) */ ++#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 ++ ++/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ ++#define MPS_CLEAR_LAST_FAULT 0x08 ++#define MPS_MFR_CFG_EXT 0xF5 ++#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6) ++#define MPS_PROTECTION_LAST 0xFB ++ ++/* ++ * PMBus 1.3 NVM commit / revert commands. MPS equivalent of ++ * STORE_ALL (15h) and RESTORE_ALL (16h). ++ */ ++#define PMBUS_STORE_USER_ALL 0x15 ++#define PMBUS_RESTORE_USER_ALL 0x16 ++ ++/* PMBus 1.3 timing / UVLO command codes */ ++#define PMBUS_VIN_ON 0x35 ++#define PMBUS_VIN_OFF 0x36 ++#define PMBUS_TON_DELAY 0x60 ++#define PMBUS_TON_RISE 0x61 ++#define PMBUS_TOFF_DELAY 0x64 ++#define PMBUS_TOFF_FALL 0x65 ++ ++/* MPS vendor-extended observability / identity registers */ ++#define MPS_MFR_CONFIG_ID 0xC0 ++#define MPS_MFR_CONFIG_CODE_REV 0xC1 ++#define MPS_MFR_PRODUCT_REV_USER 0xC2 ++#define MPS_MFR_SILICON_REV 0xC3 ++#define MPS_MFR_RETRY_TIMES 0xF4 ++#define MPS_MFR_VBOOT_CFG 0xFC ++ ++/* ++ * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate ++ * subsequent PMBus writes ++ * bits[1:0] = 00 -- unlocked (POR default) ++ * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21) ++ * so the operator can still DVFS the rail ++ * 11 -- lock all writes ++ * A negative-going PG edge resets these bits to 00, the lock ++ * is operationally reversible without a full chip POR. ++ */ ++#define MPS_MFR_PMBUS_LOCK 0xEE ++ ++/* ++ * Retry parameters for the MFR_CFG_EXT gate-close write after ++ * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this ++ * silicon is about 1 ms; the datasheet does not have information. ++ */ ++#define MPQ8646_NVM_RETRY_MAX 5 ++#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000 ++#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000 ++ ++#define MPQ8646_DEBUG(client, fmt, ...) \ ++ dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) ++ ++/* ++ * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon ++ * The chip uses an 11-bit VOUT feedback-divider scale register. ++ */ ++#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0) ++ ++/* Forward declaration */ ++struct mpq8646_dbg_reg_ctx; ++ ++/* Per-instance state */ ++struct mpq8646_priv { ++ struct pmbus_driver_info info; /* must be first, container_of target */ ++ struct i2c_client *client; ++ ++ /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ ++ struct mutex mps_lock; ++ ++ /* Set alarm_poll_interval_ms = 0 to disable. */ ++ struct delayed_work alarm_poll_work; ++ u32 alarm_poll_interval_ms; ++ ++#ifdef CONFIG_DEBUG_FS ++ /* the only debugfs file that can re-arm the poll worker */ ++ struct dentry *dbg_poll; ++ struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; ++#endif ++}; ++ ++static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client) ++{ ++ const struct pmbus_driver_info *info = pmbus_get_driver_info(client); ++ ++ return container_of(info, struct mpq8646_priv, info); ++} ++ ++/* ++ * VID-mode m/b/R coefficients, same values as the mpq8785 driver for ++ * the MPS VID encoding. Per the PMBus Direct formula used by ++ * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields ++ * 1.5625 mV per LSB. ++ */ ++#define MPQ8646_VID_M 64 ++#define MPQ8646_VID_B 0 ++#define MPQ8646_VID_R 1 ++ ++static int mpq8646_identify(struct i2c_client *client, ++ struct pmbus_driver_info *info) ++{ ++ int vout_mode; ++ ++ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); ++ if (vout_mode < 0) ++ return vout_mode; ++ ++ switch (vout_mode & PB_VOUT_MODE_MODE_MASK) { ++ case PB_VOUT_MODE_LINEAR: ++ info->format[PSC_VOLTAGE_OUT] = linear; ++ break; ++ case PB_VOUT_MODE_VID: ++ case PB_VOUT_MODE_DIRECT: ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M; ++ info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B; ++ info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R; ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ return 0; ++}; ++ ++static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg) ++{ ++ int ret; ++ ++ MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg); ++ ++ switch (reg) { ++ case PMBUS_VOUT_MODE: ++ ret = pmbus_read_byte_data(client, page, reg); ++ MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret); ++ if (ret < 0) ++ return ret; ++ ++ if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID) ++ return PB_VOUT_MODE_DIRECT; ++ ++ return ret; ++ case PMBUS_STATUS_BYTE: ++ case PMBUS_STATUS_CML: ++ case PMBUS_STATUS_OTHER: ++ case PMBUS_STATUS_MFR_SPECIFIC: ++ case PMBUS_STATUS_FAN_12: ++ case PMBUS_STATUS_FAN_34: ++ return -ENXIO; ++ case PMBUS_MFR_LOCATION: ++ case PMBUS_MFR_DATE: ++ case PMBUS_MFR_SERIAL: ++ case PMBUS_IC_DEVICE_ID: ++ case PMBUS_IC_DEVICE_REV: ++ MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); ++ return -ENXIO; ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * Reference: ltc2978.c::ltc2978_write_word_data which uses the ++ * same virtual-register channel for its real chip-side peak reset. ++ */ ++static int mpq8646_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ int rc; ++ ++ switch (reg) { ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n", ++ reg); ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); ++ if (rc < 0) ++ MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc); ++ return rc < 0 ? rc : 0; ++ default: ++ return -ENODATA; /* let pmbus_core do the direct write */ ++ } ++} ++ ++static int mpq8646_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ int rc; ++ ++ MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n", ++ page, phase, reg); ++ ++ switch (reg) { ++ case PMBUS_READ_VIN: ++ case PMBUS_READ_VOUT: ++ case PMBUS_READ_IOUT: ++ case PMBUS_READ_TEMPERATURE_1: ++ case PMBUS_STATUS_WORD: ++ case PMBUS_VOUT_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_OV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_VOUT_UV_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_FAULT_LIMIT: ++ case PMBUS_IOUT_OC_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_MFR_VIN_MAX: ++ case PMBUS_MFR_VOUT_MAX: ++ case PMBUS_MFR_IOUT_MAX: ++ case PMBUS_MFR_MAX_TEMP_1: ++ break; ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++ ++ rc = i2c_smbus_read_word_data(client, reg); ++ ++ MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc); ++ return rc; ++} ++ ++static struct pmbus_driver_info mpq8646_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .format[PSC_CURRENT_OUT] = direct, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_VOLTAGE_IN] = 4, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 1, ++ .m[PSC_CURRENT_OUT] = 16, ++ .b[PSC_CURRENT_OUT] = 0, ++ .R[PSC_CURRENT_OUT] = 0, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, ++}; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++static const struct regulator_desc mpq8646_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++}; ++#endif /* CONFIG_REGULATOR */ ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++/* ++ * Expose using ++ * /sys/bus/nvmem/devices//nvmem ++ * ++ * Layout (16 bytes): ++ * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM ++ * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM ++ * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM ++ * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM ++ * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM ++ * offset 9..15 reserved (zero-fill, leaves room for additions) ++ */ ++#define MPQ8646_NVMEM_SIZE 16 ++ ++struct mpq8646_nvmem_entry { ++ unsigned int off; ++ u8 reg; ++ bool is_word; ++}; ++ ++static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = { ++ { 0, MPS_PROTECTION_LAST, true }, ++ { 2, MPS_MFR_RETRY_TIMES, true }, ++ { 4, MPS_MFR_CONFIG_ID, true }, ++ { 6, MPS_MFR_VBOOT_CFG, true }, ++ { 8, MPS_MFR_SILICON_REV, false }, ++}; ++ ++static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val, ++ size_t bytes) ++{ ++ struct mpq8646_priv *priv = data; ++ u8 *out = val; ++ size_t i; ++ ++ if (offset >= MPQ8646_NVMEM_SIZE) ++ return -EINVAL; ++ if (offset + bytes > MPQ8646_NVMEM_SIZE) ++ bytes = MPQ8646_NVMEM_SIZE - offset; ++ ++ memset(out, 0, bytes); ++ ++ guard(mutex)(&priv->mps_lock); ++ for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { ++ const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i]; ++ unsigned int e_start = e->off; ++ unsigned int e_end = e_start + (e->is_word ? 2 : 1); ++ u8 raw[2]; ++ int rc; ++ unsigned int j; ++ ++ if (e_end <= offset || e_start >= offset + bytes) ++ continue; /* outside requested slice */ ++ ++ if (e->is_word) ++ rc = i2c_smbus_read_word_data(priv->client, e->reg); ++ else ++ rc = i2c_smbus_read_byte_data(priv->client, e->reg); ++ if (rc < 0) ++ continue; /* leave the zero-fill in place */ ++ ++ raw[0] = rc & 0xff; ++ raw[1] = (rc >> 8) & 0xff; ++ ++ for (j = 0; j < e_end - e_start; j++) { ++ unsigned int abs = e_start + j; ++ ++ if (abs >= offset && abs < offset + bytes) ++ out[abs - offset] = raw[j]; ++ } ++ } ++ return 0; ++} ++#endif /* CONFIG_NVMEM */ ++ ++static const struct i2c_device_id mpq8646_id[] = { ++ { .name = "mpq8646" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(i2c, mpq8646_id); ++ ++static const struct of_device_id __maybe_unused mpq8646_of_match[] = { ++ { .compatible = "mps,mpq8646" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, mpq8646_of_match); ++ ++static struct pmbus_platform_data mpq8646_no_pec_pdata = { ++ .flags = PMBUS_NO_CAPABILITY, ++}; ++ ++#ifdef CONFIG_DEBUG_FS ++/* ++ * Read-only decode cases in the client's pmbus debugfs directory: ++ * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the ++ * identity/timing registers. ++ */ ++ ++struct mpq_status_bit { ++ u16 mask; ++ const char *name; ++}; ++ ++static const struct mpq_status_bit mpq8646_status_word_bits[] = { ++ { PB_STATUS_VOUT, "VOUT" }, ++ { PB_STATUS_IOUT_POUT, "IOUT_POUT" }, ++ { PB_STATUS_INPUT, "INPUT" }, ++ { PB_STATUS_WORD_MFR, "NVM_SUMMARY" }, ++ { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" }, ++ { PB_STATUS_FANS, "FANS" }, ++ { PB_STATUS_OTHER, "OTHER" }, ++ { PB_STATUS_UNKNOWN, "WATCH_DOG" }, ++ { PB_STATUS_BUSY, "BUSY" }, ++ { PB_STATUS_OFF, "OFF" }, ++ { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" }, ++ { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" }, ++ { PB_STATUS_VIN_UV, "VIN_UV_FAULT" }, ++ { PB_STATUS_TEMPERATURE, "TEMP" }, ++ { PB_STATUS_CML, "CML" }, ++ { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" }, ++ { /* sentinel */ } ++}; ++ ++/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */ ++static const struct mpq_status_bit mpq8646_protection_last_bits[] = { ++ { BIT(15), "INIT_FAULT" }, ++ { BIT(14), "NVM_CRC_ERROR" }, ++ { BIT(13), "NVM_FAULT" }, ++ { BIT(12), "OC_PHASE_FAULT" }, ++ { BIT(11), "OTP_SELF_FAULT" }, ++ { BIT(9), "SWITCH_PRD_FAULT" }, ++ { BIT(8), "VIN_OV_FAULT" }, ++ { BIT(7), "VOUT_OV_FAULT" }, ++ { BIT(6), "VOUT_UV_FAULT" }, ++ { BIT(5), "OC_TOT_FAULT" }, ++ { BIT(4), "VIN_UVLO_FAULT" }, ++ { BIT(3), "DRMOS_OTP" }, ++ { /* sentinel */ } ++}; ++ ++static void mpq8646_print_bits(struct seq_file *s, u16 v, ++ const struct mpq_status_bit *tab) ++{ ++ const struct mpq_status_bit *t; ++ bool first = true; ++ ++ seq_printf(s, "0x%04x", v); ++ if (!v) { ++ seq_puts(s, " [clean]\n"); ++ return; ++ } ++ seq_puts(s, " ["); ++ for (t = tab; t->mask; t++) { ++ if (v & t->mask) { ++ if (!first) ++ seq_putc(s, ' '); ++ seq_puts(s, t->name); ++ first = false; ++ } ++ } ++ seq_puts(s, "]\n"); ++} ++ ++static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ scoped_guard(pmbus_lock, priv->client) ++ rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); ++ if (rc < 0) { ++ seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "STATUS_WORD: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits); ++ seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); ++ ++static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_priv *priv = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc); ++ return 0; ++ } ++ seq_puts(s, "PROTECTION_LAST: "); ++ mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits); ++ seq_puts(s, "(NVM-backed, survives chip POR)\n"); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); ++ ++struct mpq8646_dbg_reg { ++ u8 reg; ++ bool is_word; ++ const char *name; ++}; ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = { ++ /* MPS vendor extensions (read-only identity / observability) */ ++ { MPS_MFR_CONFIG_ID, true, "mfr_config_id" }, ++ { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" }, ++ { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" }, ++ { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" }, ++ { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" }, ++ /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ ++ { PMBUS_VIN_ON, true, "vin_on" }, ++ { PMBUS_VIN_OFF, true, "vin_off" }, ++ { PMBUS_TON_DELAY, true, "ton_delay" }, ++ { PMBUS_TON_RISE, true, "ton_rise" }, ++ { PMBUS_TOFF_DELAY, true, "toff_delay" }, ++ { PMBUS_TOFF_FALL, true, "toff_fall" }, ++}; ++ ++struct mpq8646_dbg_reg_ctx { ++ struct mpq8646_priv *priv; ++ const struct mpq8646_dbg_reg *desc; ++}; ++ ++static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = s->private; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ ++ if (rc < 0) { ++ seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n", ++ ctx->desc->reg, ctx->desc->name, rc); ++ return 0; ++ } ++ seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc); ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); ++ ++#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE ++/* ++ * Write/provisioning data, disabled by default: NVM commit and ++ * revert, the CLEAR_LAST_FAULT sequences and a small set of named ++ * writable registers. ++ */ ++ ++static void mpq8646_unsafe_banner(struct mpq8646_priv *priv) ++{ ++ dev_warn(&priv->client->dev, ++ "**********************************************************\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "** **\n" ++ "** The MPQ8646 provisioning debugfs writes are enabled. **\n" ++ "** Wrong register writes can and likely will physically **\n" ++ "** damage or destroy the chip and/or the board. **\n" ++ "** **\n" ++ "** If you see this message and you are not debugging **\n" ++ "** the kernel, report this immediately to your system **\n" ++ "** administrator! **\n" ++ "** **\n" ++ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" ++ "**********************************************************\n"); ++} ++ ++static int mpq8646_dbg_clear_protection_last(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops, ++ NULL, mpq8646_dbg_clear_protection_last, "%llu\n"); ++ ++static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc, ret; ++ int wp_orig, cfg_orig; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ guard(mutex)(&priv->mps_lock); ++ ++ wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); ++ if (wp_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n", ++ wp_orig); ++ return wp_orig; ++ } ++ cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); ++ if (cfg_orig < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n", ++ cfg_orig); ++ return cfg_orig; ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, 0); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP clear failed (%d), aborting\n", ++ rc); ++ return rc; ++ } ++ } ++ ++ ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, ++ (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); ++ if (ret < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: gate open failed (%d)\n", ++ ret); ++ } else { ++ ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); ++ if (ret < 0) ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n", ++ ret); ++ ++ for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { ++ rc = i2c_smbus_write_word_data(priv->client, ++ MPS_MFR_CFG_EXT, ++ (u16)cfg_orig); ++ if (rc >= 0) ++ break; ++ usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN, ++ MPQ8646_NVM_RETRY_DELAY_US_MAX); ++ } ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ ++ if (wp_orig != 0) { ++ rc = i2c_smbus_write_byte_data(priv->client, ++ PMBUS_WRITE_PROTECT, ++ (u8)wp_orig); ++ if (rc < 0) { ++ dev_warn(&priv->client->dev, ++ "clear_protection_last_force: WP restore failed (%d)\n", ++ rc); ++ if (ret == 0) ++ ret = rc; ++ } ++ } ++ return ret; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops, ++ NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n"); ++ ++static int mpq8646_dbg_store_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "store_all: STORE_USER_ALL (0x15) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops, ++ NULL, mpq8646_dbg_store_all, "%llu\n"); ++ ++static int mpq8646_dbg_restore_all(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ int rc; ++ ++ if (!val) ++ return 0; ++ ++ guard(pmbus_lock)(priv->client); ++ scoped_guard(mutex, &priv->mps_lock) ++ rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); ++ if (rc < 0) ++ dev_warn(&priv->client->dev, ++ "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n", ++ rc); ++ return rc; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, ++ NULL, mpq8646_dbg_restore_all, "%llu\n"); ++ ++static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = { ++ /* PMBus 1.3 control / margin */ ++ { PMBUS_ON_OFF_CONFIG, false, "on_off_config" }, ++ { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" }, ++ { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" }, ++ /* MPS PMBus-level write-protect */ ++ { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" }, ++ /* MPS user-writable product revision */ ++ { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" }, ++}; ++ ++static int mpq8646_dbg_reg_get(void *data, u64 *val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_read_word_data(ctx->priv->client, ++ ctx->desc->reg); ++ else ++ rc = i2c_smbus_read_byte_data(ctx->priv->client, ++ ctx->desc->reg); ++ if (rc < 0) ++ return rc; ++ *val = rc; ++ return 0; ++} ++ ++static int mpq8646_dbg_reg_set(void *data, u64 val) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx = data; ++ int rc; ++ ++ guard(pmbus_lock)(ctx->priv->client); ++ if (ctx->desc->is_word) ++ rc = i2c_smbus_write_word_data(ctx->priv->client, ++ ctx->desc->reg, (u16)val); ++ else ++ rc = i2c_smbus_write_byte_data(ctx->priv->client, ++ ctx->desc->reg, (u8)val); ++ return rc < 0 ? rc : 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops, ++ mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n"); ++ ++static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) ++{ ++ struct mpq8646_dbg_reg_ctx *ctx; ++ size_t i; ++ ++ mpq8646_unsafe_banner(priv); ++ ++ debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv, ++ &mpq8646_dbg_clear_protection_last_fops); ++ debugfs_create_file_unsafe("clear_protection_last_force", 0200, root, ++ priv, ++ &mpq8646_dbg_clear_protection_last_force_fops); ++ debugfs_create_file_unsafe("store_all", 0200, root, priv, ++ &mpq8646_dbg_store_all_fops); ++ debugfs_create_file_unsafe("restore_all", 0200, root, priv, ++ &mpq8646_dbg_restore_all_fops); ++ ++ ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs_unsafe), ++ sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) { ++ ctx[i].priv = priv; ++ ctx[i].desc = &mpq8646_dbg_regs_unsafe[i]; ++ debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name, ++ 0600, root, &ctx[i], ++ &mpq8646_dbg_reg_rw_fops); ++ } ++} ++#else ++static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, ++ struct dentry *root) {} ++#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */ ++ ++static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) ++{ ++ struct mpq8646_priv *priv = data; ++ ++ *val = priv->alarm_poll_interval_ms; ++ return 0; ++} ++ ++static int mpq8646_dbg_poll_interval_set(void *data, u64 val) ++{ ++ struct mpq8646_priv *priv = data; ++ bool was_off = !priv->alarm_poll_interval_ms; ++ ++ priv->alarm_poll_interval_ms = (u32)val; ++ ++ if (val && was_off && !priv->client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies((u32)val)); ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops, ++ mpq8646_dbg_poll_interval_get, ++ mpq8646_dbg_poll_interval_set, "%llu\n"); ++ ++static void mpq8646_debugfs_register(struct mpq8646_priv *priv) ++{ ++ struct dentry *root; ++ size_t i; ++ ++ root = pmbus_get_debugfs_dir(priv->client); ++ if (!root) ++ return; ++ ++ /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */ ++ debugfs_create_file("status_decoded", 0400, root, priv, ++ &mpq8646_dbg_status_decoded_fops); ++ debugfs_create_file("protection_last", 0400, root, priv, ++ &mpq8646_dbg_protection_last_fops); ++ priv->dbg_poll = ++ debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600, ++ root, priv, ++ &mpq8646_dbg_poll_interval_fops); ++ ++ priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev, ++ ARRAY_SIZE(mpq8646_dbg_regs), ++ sizeof(*priv->dbg_reg_ctx), ++ GFP_KERNEL); ++ if (!priv->dbg_reg_ctx) ++ return; ++ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { ++ priv->dbg_reg_ctx[i].priv = priv; ++ priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i]; ++ debugfs_create_file(mpq8646_dbg_regs[i].name, 0400, ++ root, &priv->dbg_reg_ctx[i], ++ &mpq8646_dbg_reg_fops); ++ } ++ ++ mpq8646_debugfs_register_unsafe(priv, root); ++} ++ ++static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) ++{ ++ debugfs_remove(priv->dbg_poll); ++} ++#else ++static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {} ++static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {} ++#endif /* CONFIG_DEBUG_FS */ ++ ++static void mpq8646_alarm_poll_work(struct work_struct *work) ++{ ++ struct mpq8646_priv *priv = container_of(to_delayed_work(work), ++ struct mpq8646_priv, ++ alarm_poll_work); ++ ++ if (priv->client->irq) ++ return; /* SMBALERT# wired; polling not needed */ ++ ++ if (!priv->alarm_poll_interval_ms) ++ return; /* polling disabled; don't re-arm */ ++ ++ pmbus_check_and_notify_faults(priv->client); ++ ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++} ++ ++static int mpq8646_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ struct mpq8646_priv *priv; ++ u32 voltage_scale; ++ int ret; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ priv->client = client; ++ mutex_init(&priv->mps_lock); ++ memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); ++ info = &priv->info; ++ ++ info->identify = mpq8646_identify; ++ info->read_byte_data = mpq8646_read_byte_data; ++ info->read_word_data = mpq8646_read_word_data; ++ info->write_word_data = mpq8646_write_word_data; ++ dev->platform_data = &mpq8646_no_pec_pdata; ++ ++#if IS_ENABLED(CONFIG_REGULATOR) ++ info->reg_desc = mpq8646_reg_desc; ++ info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc); ++#endif ++ ++ INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); ++ priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT; ++ ++ if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", ++ &voltage_scale)) { ++ if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX) ++ return -EINVAL; ++ ++ ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, ++ voltage_scale); ++ if (ret) ++ return ret; ++ } ++ ++ ret = pmbus_do_probe(client, info); ++ if (ret) ++ return ret; ++ ++ mpq8646_debugfs_register(priv); ++ ++ if (!client->irq) ++ schedule_delayed_work(&priv->alarm_poll_work, ++ msecs_to_jiffies(priv->alarm_poll_interval_ms)); ++ ++#if IS_ENABLED(CONFIG_NVMEM) ++ { ++ struct nvmem_config cfg = { ++ .dev = &client->dev, ++ .name = dev_name(&client->dev), ++ .owner = THIS_MODULE, ++ .read_only = true, ++ .root_only = true, ++ .word_size = 1, ++ .stride = 1, ++ .size = MPQ8646_NVMEM_SIZE, ++ .reg_read = mpq8646_nvmem_read, ++ .priv = priv, ++ }; ++ struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg); ++ ++ if (IS_ERR(nv)) ++ dev_warn(&client->dev, ++ "nvmem snapshot register failed (%pe)\n", ++ nv); ++ } ++#endif ++ return 0; ++}; ++ ++static void mpq8646_remove(struct i2c_client *client) ++{ ++ struct mpq8646_priv *priv = mpq8646_priv_from_client(client); ++ ++ mpq8646_debugfs_unregister(priv); ++ cancel_delayed_work_sync(&priv->alarm_poll_work); ++} ++ ++static struct i2c_driver mpq8646_driver = { ++ .driver = { ++ .name = "mpq8646", ++ .of_match_table = of_match_ptr(mpq8646_of_match), ++ }, ++ .probe = mpq8646_probe, ++ .remove = mpq8646_remove, ++ .id_table = mpq8646_id, ++}; ++ ++module_i2c_driver(mpq8646_driver); ++ ++MODULE_AUTHOR("Vincent Jardin "); ++MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h +--- a/drivers/hwmon/pmbus/pmbus.h ++++ b/drivers/hwmon/pmbus/pmbus.h +@@ -421,10 +421,12 @@ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; + enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; + + /* PMBus revision identifiers */ +-#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ +-#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ +-#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ +-#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ ++#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ ++#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ ++#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ ++#define PMBUS_REV_131 0x44 /* PMBus revision 1.3.1 */ ++#define PMBUS_REV_14 0x55 /* PMBus revision 1.4 */ + + /* Operation type flags for pmbus_update_ts */ + #define PMBUS_OP_WRITE BIT(0) +@@ -489,6 +491,16 @@ struct pmbus_driver_info { + int access_delay; /* in microseconds */ + int write_delay; /* in microseconds */ + int page_change_delay; /* in microseconds */ ++ ++ /* ++ * Some chips do not support the PMBUS_REVISION command. ++ * Drivers for such chips can report the supported PMBus revision here. ++ * ++ * Drivers must set have_pmbus_revision to true and provide the ++ * supported PMBus version in pmbus_revision. ++ */ ++ bool have_pmbus_revision; /* true if pmbus_revision is valid */ ++ u8 pmbus_revision; /* PMBus revision */ + }; + + /* Regulator ops */ +@@ -550,6 +562,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); + void pmbus_wait(struct i2c_client *client); + void pmbus_update_ts(struct i2c_client *client, int op); + int pmbus_set_page(struct i2c_client *client, int page, int phase); ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf); + int pmbus_read_word_data(struct i2c_client *client, int page, int phase, + u8 reg); + int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, +@@ -561,6 +574,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, + int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + u8 mask, u8 value); + void pmbus_clear_faults(struct i2c_client *client); ++void pmbus_check_and_notify_faults(struct i2c_client *client); + bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); + bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); + int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); +diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c +--- a/drivers/hwmon/pmbus/pmbus_core.c ++++ b/drivers/hwmon/pmbus/pmbus_core.c +@@ -96,7 +96,8 @@ struct pmbus_data { + + u32 flags; /* from platform data */ + +- u8 revision; /* The PMBus revision the device is compliant with */ ++ bool have_pmbus_revision; ++ u8 revision; /* The PMBus revision the device is compliant with */ + + int exponent[PMBUS_PAGES]; + /* linear mode: exponent for output voltages */ +@@ -181,7 +182,12 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); + void pmbus_wait(struct i2c_client *client) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); ++ s64 delay; ++ ++ if (!data) ++ return; ++ ++ delay = ktime_us_delta(data->next_access_backoff, ktime_get()); + + if (delay > 0) + fsleep(delay); +@@ -192,8 +198,14 @@ EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); + void pmbus_update_ts(struct i2c_client *client, int op) + { + struct pmbus_data *data = i2c_get_clientdata(client); +- const struct pmbus_driver_info *info = data->info; +- int delay = info->access_delay; ++ const struct pmbus_driver_info *info; ++ int delay; ++ ++ if (!data) ++ return; ++ ++ info = data->info; ++ delay = info->access_delay; + + if (op & PMBUS_OP_WRITE) + delay = max(delay, info->write_delay); +@@ -517,6 +529,81 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, + } + EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS"); + ++/** ++ * pmbus_read_smbus_i2c_block_data() - Read SMBus/I2C block data ++ * @client: Handle to slave device ++ * @reg: Byte interpreted by slave ++ * @data_buf: Byte array into which data will be read ++ * Return: Negative errno or number of bytes read ++ * ++ * PMBus internal function to read a SMBus block from a PMBus chip. ++ * ++ * PMBus chips report various properties using SMBus block read operations. ++ * However, not all I2C controllers support this operation. ++ * ++ * Execute SMBus block read if supported. If not supported, but SMBus I2C block ++ * read is supported, use it instead. Note that at most 31 data bytes can be ++ * read from the device if i2c_smbus_read_i2c_block_data() is used to read the ++ * data. This is a SMBUs protocol limit which can not be avoided. ++ * ++ * Return -EOPNOTSUPP if neither I2C_FUNC_SMBUS_READ_BLOCK_DATA nor ++ * I2C_FUNC_SMBUS_READ_I2C_BLOCK is supported. ++ * ++ * Callers must hold pmbus_lock or execute calls from the probe function. ++ */ ++int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf) ++{ ++ u8 buf[I2C_SMBUS_BLOCK_MAX]; ++ int blen, len, ret; ++ ++ if (i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { ++ pmbus_wait(client); ++ ret = i2c_smbus_read_block_data(client, reg, data_buf); ++ pmbus_update_ts(client, 0); ++ return ret; ++ } ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { ++ dev_err_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_I2C_BLOCK\n"); ++ return -EOPNOTSUPP; ++ } ++ ++ /* ++ * If the returned data is valid SMBus block data, the first byte ++ * must be the data length. ++ * ++ * i2c_smbus_read_i2c_block_data() may return an error if the chip ++ * sends NACK before the number of requested bytes is received. ++ * Handle this by reading the data length first, then reading the ++ * entire message up to I2C_SMBUS_BLOCK_MAX bytes. This ensures ++ * that requested number of bytes never exceeds the number of ++ * bytes sent by the chip. ++ */ ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ ++ len = buf[0]; ++ if (len == 0) ++ return 0; ++ blen = len; ++ if (len >= I2C_SMBUS_BLOCK_MAX) ++ len = I2C_SMBUS_BLOCK_MAX - 1; ++ pmbus_wait(client); ++ ret = i2c_smbus_read_i2c_block_data(client, reg, len + 1, buf); ++ pmbus_update_ts(client, 0); ++ if (ret < 0) ++ return ret; ++ if (buf[0] != blen) ++ return -EIO; ++ memcpy(data_buf, buf + 1, len); ++ return len; ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_read_smbus_i2c_block_data, "PMBUS"); ++ + static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + char *data_buf) + { +@@ -526,11 +613,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, + if (rv < 0) + return rv; + +- pmbus_wait(client); +- rv = i2c_smbus_read_block_data(client, reg, data_buf); +- pmbus_update_ts(client, 0); +- +- return rv; ++ return pmbus_read_smbus_i2c_block_data(client, reg, data_buf); + } + + static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, +@@ -2849,9 +2932,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, + if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) + pmbus_init_wp(client, data); + +- ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); +- if (ret >= 0) +- data->revision = ret; ++ if (info->have_pmbus_revision) { ++ data->have_pmbus_revision = true; ++ data->revision = info->pmbus_revision; ++ } else { ++ ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); ++ if (ret >= 0) { ++ data->have_pmbus_revision = true; ++ data->revision = ret; ++ } ++ } + + if (data->info->pages) + pmbus_clear_faults(client); +@@ -3455,10 +3545,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, + return ret; + } + +-static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++void pmbus_check_and_notify_faults(struct i2c_client *client) + { +- struct pmbus_data *data = pdata; +- struct i2c_client *client = to_i2c_client(data->dev); ++ struct pmbus_data *data = i2c_get_clientdata(client); + int i, status, event; + + guard(pmbus_lock)(client); +@@ -3471,6 +3560,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) + } + + pmbus_clear_faults(client); ++} ++EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS"); ++ ++static irqreturn_t pmbus_fault_handler(int irq, void *pdata) ++{ ++ struct pmbus_data *data = pdata; ++ struct i2c_client *client = to_i2c_client(data->dev); ++ ++ pmbus_check_and_notify_faults(client); + + return IRQ_HANDLED; + } +@@ -3514,10 +3612,8 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data) + /* Register notifiers */ + err = devm_request_threaded_irq(dev, client->irq, NULL, pmbus_fault_handler, + IRQF_ONESHOT, "pmbus-irq", data); +- if (err) { +- dev_err(dev, "failed to request an irq %d\n", err); ++ if (err) + return err; +- } + + return 0; + } +@@ -3543,6 +3639,17 @@ static int pmbus_debugfs_get(void *data, u64 *val) + DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, + "0x%02llx\n"); + ++static int pmbus_debugfs_get_revision(void *data, u64 *val) ++{ ++ struct pmbus_data *pdata = data; ++ ++ *val = pdata->revision; ++ ++ return 0; ++} ++DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_revision_ops, pmbus_debugfs_get_revision, NULL, ++ "0x%02llx\n"); ++ + static int pmbus_debugfs_get_status(void *data, u64 *val) + { + struct pmbus_debugfs_entry *entry = data; +@@ -3698,14 +3805,9 @@ static void pmbus_init_debugfs(struct i2c_client *client, + &entries[idx++], + &pmbus_debugfs_ops); + } +- if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) { +- entries[idx].client = client; +- entries[idx].page = 0; +- entries[idx].reg = PMBUS_REVISION; +- debugfs_create_file("pmbus_revision", 0444, debugfs, +- &entries[idx++], +- &pmbus_debugfs_ops); +- } ++ if (data->have_pmbus_revision) ++ debugfs_create_file("pmbus_revision", 0444, debugfs, data, ++ &pmbus_debugfs_revision_ops); + + for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) { + const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i]; +diff --git a/drivers/hwmon/pmbus/sq24860.c b/drivers/hwmon/pmbus/sq24860.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/sq24860.c +@@ -0,0 +1,430 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Author: Ziming Zhu ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define SQ24860_IIN_CAL_GAIN 0x38 ++#define SQ24860_READ_VAUX 0xd0 ++#define SQ24860_READ_VIN_MIN 0xd1 ++#define SQ24860_READ_VIN_PEAK 0xd2 ++#define SQ24860_READ_IIN_PEAK 0xd4 ++#define SQ24860_READ_PIN_PEAK 0xd5 ++#define SQ24860_READ_TEMP_AVG 0xd6 ++#define SQ24860_READ_TEMP_PEAK 0xd7 ++#define SQ24860_READ_VOUT_MIN 0xda ++#define SQ24860_READ_VIN_AVG 0xdc ++#define SQ24860_READ_VOUT_AVG 0xdd ++#define SQ24860_READ_IIN_AVG 0xde ++#define SQ24860_READ_PIN_AVG 0xdf ++#define SQ24860_VIREF 0xe0 ++#define SQ24860_PK_MIN_AVG 0xea ++#define PK_MIN_AVG_RST_PEAK BIT(7) ++#define PK_MIN_AVG_RST_AVG BIT(6) ++#define PK_MIN_AVG_RST_MIN BIT(5) ++#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0) ++#define SQ24860_MFR_WRITE_PROTECT 0xf8 ++#define SQ24860_UNLOCKED BIT(7) ++ ++#define SQ24860_8B_SHIFT 2 ++#define SQ24860_IIN_OCF_NUM 1000000 ++#define SQ24860_IIN_OCF_DIV 129278 ++#define SQ24860_IIN_OCF_OFF 165 ++ ++#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ ++ PK_MIN_AVG_RST_AVG | \ ++ PK_MIN_AVG_RST_MIN) ++#define SQ24860_MAX_SAMPLES BIT(FIELD_MAX(PK_MIN_AVG_AVG_CNT)) ++/* ++ * Arbitrary default Rimon value: 1.6kOhm ++ */ ++#define SQ24860_DEFAULT_RIMON 1600000000 ++#define SQ24860_GIMON 18180 ++ ++#define SQ24860_VAUX_DIV 20 ++ ++static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon) ++{ ++ u64 temp = 6400ULL * 1000000000ULL * 1000ULL; ++ u64 denom; ++ u64 word; ++ ++ if (!rimon) ++ return -EINVAL; ++ ++ denom = (u64)rimon * SQ24860_GIMON; ++ word = div64_u64(temp, denom); ++ if (!word || word > U16_MAX) ++ return -EINVAL; ++ ++ return i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN, ++ (u16)word); ++} ++ ++static int sq24860_mfr_write_protect_set(struct i2c_client *client, ++ u8 protect) ++{ ++ u8 val; ++ ++ switch (protect) { ++ case 0: ++ val = 0xa2; ++ break; ++ case PB_WP_ALL: ++ val = 0x0; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT, ++ val); ++} ++ ++static int sq24860_mfr_write_protect_get(struct i2c_client *client) ++{ ++ int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT); ++ ++ if (ret < 0) ++ return ret; ++ ++ return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL; ++} ++ ++static int sq24860_read_word_data(struct i2c_client *client, ++ int page, int phase, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_MIN: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_MIN); ++ break; ++ ++ case PMBUS_VIRT_READ_VOUT_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VOUT_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_IIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_IIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_TEMP_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_AVG: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_AVG); ++ break; ++ ++ case PMBUS_VIRT_READ_PIN_MAX: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_PIN_PEAK); ++ break; ++ ++ case PMBUS_VIRT_READ_VMON: ++ ret = pmbus_read_word_data(client, page, phase, ++ SQ24860_READ_VAUX); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV); ++ break; ++ ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ /* ++ * These registers provide an 8 bits value instead of a ++ * 10bits one. Just shifting twice the register value is ++ * enough to make the sensor type conversion work, even ++ * if the datasheet provides different m, b and R for ++ * those. ++ */ ++ ret = pmbus_read_word_data(client, page, phase, reg); ++ if (ret < 0) ++ break; ++ ret <<= SQ24860_8B_SHIFT; ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ /* ++ * VIREF directly sets the over-current limit at which the eFuse ++ * will turn the FET off and trigger a fault. Expose it through ++ * this generic property instead of a manufacturer specific one. ++ */ ++ ret = pmbus_read_byte_data(client, page, SQ24860_VIREF); ++ if (ret < 0) ++ break; ++ ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM, ++ SQ24860_IIN_OCF_DIV); ++ ret += SQ24860_IIN_OCF_OFF; ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG); ++ if (ret < 0) ++ break; ++ ret = BIT(FIELD_GET(PK_MIN_AVG_AVG_CNT, ret)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ ret = 0; ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_word_data(struct i2c_client *client, ++ int page, int reg, u16 value) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_VIN_UV_WARN_LIMIT: ++ case PMBUS_VIN_UV_FAULT_LIMIT: ++ case PMBUS_VIN_OV_WARN_LIMIT: ++ case PMBUS_VIN_OV_FAULT_LIMIT: ++ case PMBUS_VOUT_UV_WARN_LIMIT: ++ case PMBUS_IIN_OC_WARN_LIMIT: ++ case PMBUS_OT_WARN_LIMIT: ++ case PMBUS_OT_FAULT_LIMIT: ++ case PMBUS_PIN_OP_WARN_LIMIT: ++ value = max_t(s16, (s16)value, 0); ++ value >>= SQ24860_8B_SHIFT; ++ value = clamp_val(value, 0, 0xff); ++ ret = pmbus_write_word_data(client, page, reg, value); ++ break; ++ ++ case PMBUS_IIN_OC_FAULT_LIMIT: ++ value = max_t(s16, (s16)value, SQ24860_IIN_OCF_OFF); ++ value -= SQ24860_IIN_OCF_OFF; ++ value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV, ++ SQ24860_IIN_OCF_NUM); ++ value = clamp_val(value, 0, 0x3f); ++ ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value); ++ break; ++ ++ case PMBUS_VIRT_SAMPLES: ++ value = clamp_val(value, 1, SQ24860_MAX_SAMPLES); ++ value = ilog2(value); ++ ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_AVG_CNT, ++ FIELD_PREP(PK_MIN_AVG_AVG_CNT, value)); ++ break; ++ ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IIN_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ /* ++ * SQ24860 has history resets based on MIN/AVG/PEAK instead of per ++ * sensor type. Exposing this quirk in hwmon is not desirable so ++ * reset MIN, AVG and PEAK together. Even is there effectively only ++ * one reset, which resets everything, expose the 5 entries so ++ * userspace is not required map a sensor type to another to trigger ++ * a reset ++ */ ++ ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG, ++ PK_MIN_AVG_RST_MASK, ++ PK_MIN_AVG_RST_MASK); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_read_byte_data(struct i2c_client *client, ++ int page, int reg) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_get(client); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++static int sq24860_write_byte_data(struct i2c_client *client, ++ int page, int reg, u8 byte) ++{ ++ int ret; ++ ++ switch (reg) { ++ case PMBUS_WRITE_PROTECT: ++ ret = sq24860_mfr_write_protect_set(client, byte); ++ break; ++ ++ default: ++ ret = -ENODATA; ++ break; ++ } ++ ++ return ret; ++} ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++static const struct regulator_desc sq24860_reg_desc[] = { ++ PMBUS_REGULATOR_ONE_NODE("vout"), ++}; ++#endif ++ ++static const struct pmbus_driver_info sq24860_base_info = { ++ .pages = 1, ++ .format[PSC_VOLTAGE_IN] = direct, ++ .m[PSC_VOLTAGE_IN] = 64, ++ .b[PSC_VOLTAGE_IN] = 0, ++ .R[PSC_VOLTAGE_IN] = 0, ++ .format[PSC_VOLTAGE_OUT] = direct, ++ .m[PSC_VOLTAGE_OUT] = 64, ++ .b[PSC_VOLTAGE_OUT] = 0, ++ .R[PSC_VOLTAGE_OUT] = 0, ++ .format[PSC_TEMPERATURE] = direct, ++ .m[PSC_TEMPERATURE] = 1, ++ .b[PSC_TEMPERATURE] = 0, ++ .R[PSC_TEMPERATURE] = 0, ++ /* ++ * Current and power measurements depend on the calibration gain ++ * programmed from the board-specific IMON resistor value. ++ */ ++ .format[PSC_CURRENT_IN] = direct, ++ .m[PSC_CURRENT_IN] = 16, ++ .b[PSC_CURRENT_IN] = 0, ++ .R[PSC_CURRENT_IN] = 0, ++ .format[PSC_POWER] = direct, ++ .m[PSC_POWER] = 2, ++ .b[PSC_POWER] = 0, ++ .R[PSC_POWER] = 0, ++ .func[0] = PMBUS_HAVE_VIN | ++ PMBUS_HAVE_VOUT | ++ PMBUS_HAVE_VMON | ++ PMBUS_HAVE_IIN | ++ PMBUS_HAVE_PIN | ++ PMBUS_HAVE_TEMP | ++ PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_SAMPLES, ++ .read_word_data = sq24860_read_word_data, ++ .write_word_data = sq24860_write_word_data, ++ .read_byte_data = sq24860_read_byte_data, ++ .write_byte_data = sq24860_write_byte_data, ++ ++#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) ++ .reg_desc = sq24860_reg_desc, ++ .num_regulators = ARRAY_SIZE(sq24860_reg_desc), ++#endif ++}; ++ ++static const struct i2c_device_id sq24860_i2c_id[] = { ++ { "sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id); ++ ++static const struct of_device_id sq24860_of_match[] = { ++ { .compatible = "silergy,sq24860" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, sq24860_of_match); ++ ++static int sq24860_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct pmbus_driver_info *info; ++ u32 rimon; ++ int ret; ++ ++ if (device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon)) ++ rimon = SQ24860_DEFAULT_RIMON; ++ ret = sq24860_write_iin_cal_gain(client, rimon); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "Failed to set gain\n"); ++ info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static struct i2c_driver sq24860_driver = { ++ .driver = { ++ .name = "sq24860", ++ .of_match_table = sq24860_of_match, ++ }, ++ .probe = sq24860_probe, ++ .id_table = sq24860_i2c_id, ++}; ++module_i2c_driver(sq24860_driver); ++ ++MODULE_AUTHOR("Ziming Zhu "); ++MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/vt7505.c b/drivers/hwmon/pmbus/vt7505.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/pmbus/vt7505.c +@@ -0,0 +1,287 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and ++ * Volterra VT7505 PMBus controllers. ++ * ++ * Copyright 2026 Hewlett Packard Enterprise Development LP ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmbus.h" ++ ++#define VT7505_MFR_CONFIG 0xd0 ++#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14) ++ ++#define VT7505_MFR_PEAK_VIN 0xd1 ++#define VT7505_MFR_PEAK_IOUT 0xd2 ++#define VT7505_MFR_PEAK_PIN 0xd3 ++#define VT7505_MFR_PEAK_TEMP 0xd4 ++#define VT7505_MFR_CLEAR_PEAKS 0xd5 ++#define VT7505_MFR_PEAK_VOUT 0xfd ++ ++#define VT7505_RLOAD_DEFAULT 4750 ++/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */ ++#define VT7505_RLOAD_MAX 283383959U ++ ++struct vt7505_chip_data { ++ int temp_m; ++ int temp_b; ++ bool has_ocp_filter; ++}; ++ ++static const struct vt7505_chip_data max16545_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++}; ++ ++static const struct vt7505_chip_data max16550_data = { ++ .temp_m = 199, ++ .temp_b = 7046, ++ .has_ocp_filter = true, ++}; ++ ++static const struct vt7505_chip_data vt7505_data = { ++ .temp_m = 205, ++ .temp_b = 6545, ++ .has_ocp_filter = true, ++}; ++ ++static int vt7505_read_word_data(struct i2c_client *client, int page, ++ int phase, int reg) ++{ ++ switch (reg) { ++ case PMBUS_VIRT_READ_VIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VIN); ++ case PMBUS_VIRT_READ_IOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_IOUT); ++ case PMBUS_VIRT_READ_PIN_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_PIN); ++ case PMBUS_VIRT_READ_TEMP_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_TEMP); ++ case PMBUS_VIRT_READ_VOUT_MAX: ++ return pmbus_read_word_data(client, page, phase, ++ VT7505_MFR_PEAK_VOUT); ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return 0; ++ default: ++ return -ENODATA; ++ } ++} ++ ++static int vt7505_write_word_data(struct i2c_client *client, int page, ++ int reg, u16 word) ++{ ++ switch (reg) { ++ /* ++ * A single reset command clears all peak values. CLEAR_PEAKS is a ++ * send-byte command; the device NAKs a word or byte-data write to it. ++ */ ++ case PMBUS_VIRT_RESET_VIN_HISTORY: ++ case PMBUS_VIRT_RESET_IOUT_HISTORY: ++ case PMBUS_VIRT_RESET_PIN_HISTORY: ++ case PMBUS_VIRT_RESET_TEMP_HISTORY: ++ case PMBUS_VIRT_RESET_VOUT_HISTORY: ++ return pmbus_write_byte(client, page, ++ VT7505_MFR_CLEAR_PEAKS); ++ default: ++ return -ENODATA; ++ } ++} ++ ++/* ++ * None of these controllers implement the standard PMBus WRITE_PROTECT ++ * (0x10) register, so tell the core not to access it. ++ */ ++static struct pmbus_platform_data vt7505_pdata = { ++ .flags = PMBUS_NO_WRITE_PROTECT, ++}; ++ ++static int vt7505_set_ocp_filter(struct i2c_client *client) ++{ ++ u32 ocp_us; ++ u8 field; ++ int ret; ++ u16 word; ++ ++ if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us", ++ &ocp_us)) ++ return 0; ++ ++ switch (ocp_us) { ++ case 0: ++ field = 0; ++ break; ++ case 1: ++ field = 1; ++ break; ++ case 2: ++ field = 2; ++ break; ++ case 10: ++ field = 3; ++ break; ++ default: ++ return dev_err_probe(&client->dev, -EINVAL, ++ "invalid adi,ocp-severe-filter-us value %u\n", ++ ocp_us); ++ } ++ ++ ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to read MFR_CONFIG\n"); ++ ++ word = ret & ~VT7505_CFG_OCP_S_FILT_MASK; ++ word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field); ++ ++ ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word); ++ if (ret < 0) ++ return dev_err_probe(&client->dev, ret, ++ "failed to write MFR_CONFIG\n"); ++ ++ return 0; ++} ++ ++static void vt7505_set_m(int *m, u32 rload) ++{ ++ u64 val = (u64)*m * rload; ++ ++ /* rload is range-checked in probe, so the result fits in int. */ ++ *m = DIV_ROUND_CLOSEST_ULL(val, 1000); ++} ++ ++static int vt7505_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ const struct vt7505_chip_data *chip; ++ struct pmbus_driver_info *info; ++ u32 rload; ++ int ret; ++ ++ chip = i2c_get_match_data(client); ++ if (!chip) ++ return -ENODEV; ++ ++ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return -ENOMEM; ++ ++ dev->platform_data = &vt7505_pdata; ++ ++ /* ++ * The m coefficient used in the direct-format current and power ++ * calculations depends on RLOAD, the external current-report resistor ++ * connected between the ILOAD pin and ground. Use the default value if ++ * none is specified. ++ */ ++ if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload)) ++ rload = VT7505_RLOAD_DEFAULT; ++ ++ if (!rload || rload > VT7505_RLOAD_MAX) ++ return dev_err_probe(dev, -EINVAL, ++ "adi,rload-ohms must be 1-%u\n", ++ VT7505_RLOAD_MAX); ++ ++ info->pages = 1; ++ info->read_word_data = vt7505_read_word_data; ++ info->write_word_data = vt7505_write_word_data; ++ ++ info->format[PSC_VOLTAGE_IN] = direct; ++ info->format[PSC_VOLTAGE_OUT] = direct; ++ info->format[PSC_CURRENT_IN] = direct; ++ info->format[PSC_CURRENT_OUT] = direct; ++ info->format[PSC_POWER] = direct; ++ info->format[PSC_TEMPERATURE] = direct; ++ ++ /* ++ * Direct data format coefficients from the device datasheet ("PMBus ++ * Equation Parameters"). The current and power m coefficients scale ++ * with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving ++ * m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The ++ * temperature coefficients are chip specific (see the chip data). ++ */ ++ info->m[PSC_VOLTAGE_IN] = 7578; ++ info->R[PSC_VOLTAGE_IN] = -2; ++ info->m[PSC_VOLTAGE_OUT] = 7578; ++ info->R[PSC_VOLTAGE_OUT] = -2; ++ info->m[PSC_CURRENT_IN] = 3824; ++ info->b[PSC_CURRENT_IN] = -4300; ++ info->R[PSC_CURRENT_IN] = -3; ++ info->m[PSC_CURRENT_OUT] = 3824; ++ info->b[PSC_CURRENT_OUT] = -4300; ++ info->R[PSC_CURRENT_OUT] = -3; ++ info->m[PSC_POWER] = 895; ++ info->b[PSC_POWER] = -9100; ++ info->R[PSC_POWER] = -2; ++ ++ vt7505_set_m(&info->m[PSC_CURRENT_IN], rload); ++ vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload); ++ vt7505_set_m(&info->m[PSC_POWER], rload); ++ ++ info->m[PSC_TEMPERATURE] = chip->temp_m; ++ info->b[PSC_TEMPERATURE] = chip->temp_b; ++ info->R[PSC_TEMPERATURE] = -2; ++ ++ info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | ++ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | ++ PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | ++ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | ++ PMBUS_HAVE_IIN | PMBUS_HAVE_PIN; ++ ++ /* ++ * The severe OCP deglitch filter is programmable on the MAX16550 and ++ * the VT7505, but fixed on the MAX16545. ++ */ ++ if (chip->has_ocp_filter) { ++ ret = vt7505_set_ocp_filter(client); ++ if (ret) ++ return ret; ++ } ++ ++ return pmbus_do_probe(client, info); ++} ++ ++static const struct i2c_device_id vt7505_id[] = { ++ { .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data }, ++ { .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data }, ++ { .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, vt7505_id); ++ ++static const struct of_device_id vt7505_of_match[] = { ++ { .compatible = "adi,max16545", .data = &max16545_data }, ++ { .compatible = "adi,max16550", .data = &max16550_data }, ++ { .compatible = "adi,vt7505", .data = &vt7505_data }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, vt7505_of_match); ++ ++static struct i2c_driver vt7505_driver = { ++ .driver = { ++ .name = "vt7505", ++ .of_match_table = vt7505_of_match, ++ }, ++ .probe = vt7505_probe, ++ .id_table = vt7505_id, ++}; ++module_i2c_driver(vt7505_driver); ++ ++MODULE_AUTHOR("Georgi Vlaev "); ++MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505"); ++MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("PMBUS"); +diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c +--- a/drivers/hwmon/pmbus/xdpe1a2g7b.c ++++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c +@@ -57,6 +57,13 @@ static int xdpe1a2g7b_identify(struct i2c_client *client, + return 0; + } + ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++static const struct regulator_desc xdpe1a2g7b_reg_desc[] = { ++ PMBUS_REGULATOR("vout", 0), ++ PMBUS_REGULATOR("vout", 1), ++}; ++#endif ++ + static struct pmbus_driver_info xdpe1a2g7b_info = { + .pages = XDPE1A2G7B_PAGE_NUM, + .identify = xdpe1a2g7b_identify, +@@ -72,6 +79,10 @@ static struct pmbus_driver_info xdpe1a2g7b_info = { + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, ++#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) ++ .num_regulators = XDPE1A2G7B_PAGE_NUM, ++ .reg_desc = xdpe1a2g7b_reg_desc, ++#endif + }; + + static int xdpe1a2g7b_probe(struct i2c_client *client) +diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c +--- a/drivers/hwmon/pwm-fan.c ++++ b/drivers/hwmon/pwm-fan.c +@@ -634,12 +634,8 @@ static int pwm_fan_probe(struct platform_device *pdev) + if (tach->irq > 0) { + ret = devm_request_irq(dev, tach->irq, pulse_handler, + IRQF_NO_THREAD, pdev->name, tach); +- if (ret) { +- dev_err(dev, +- "Failed to request interrupt: %d\n", +- ret); ++ if (ret) + return ret; +- } + } + + if (!ctx->pulses_per_revolution[i]) { +diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c +--- a/drivers/hwmon/sht15.c ++++ b/drivers/hwmon/sht15.c +@@ -981,10 +981,8 @@ static int sht15_probe(struct platform_device *pdev) + IRQF_TRIGGER_FALLING, + "sht15 data", + data); +- if (ret) { +- dev_err(&pdev->dev, "failed to get irq for data line\n"); ++ if (ret) + goto err_release_reg; +- } + disable_irq_nosync(gpiod_to_irq(data->data)); + ret = sht15_connection_reset(data); + if (ret) +diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c +--- a/drivers/hwmon/sht3x.c ++++ b/drivers/hwmon/sht3x.c +@@ -62,7 +62,7 @@ static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 }; + #define SHT3X_MAX_HUMIDITY 100000 + + enum sht3x_chips { +- sht3x, ++ sht3x = 1, + sts3x, + }; + +@@ -940,8 +940,19 @@ static const struct i2c_device_id sht3x_ids[] = { + + MODULE_DEVICE_TABLE(i2c, sht3x_ids); + ++static const struct of_device_id sht3x_of_match[] = { ++ { .compatible = "sensirion,sht30", .data = (void *)(uintptr_t)sht3x }, ++ { .compatible = "sensirion,sts30", .data = (void *)(uintptr_t)sts3x }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(of, sht3x_of_match); ++ + static struct i2c_driver sht3x_i2c_driver = { +- .driver.name = "sht3x", ++ .driver = { ++ .name = "sht3x", ++ .of_match_table = sht3x_of_match, ++ }, + .probe = sht3x_probe, + .id_table = sht3x_ids, + }; +diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c +--- a/drivers/hwmon/tmp102.c ++++ b/drivers/hwmon/tmp102.c +@@ -396,6 +396,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume + + static const struct i2c_device_id tmp102_id[] = { + { .name = "tmp102" }, ++ { .name = "tmp110" }, ++ { .name = "tmp113" }, + { } + }; + MODULE_DEVICE_TABLE(i2c, tmp102_id); +diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c +--- a/drivers/hwmon/tmp401.c ++++ b/drivers/hwmon/tmp401.c +@@ -695,7 +695,7 @@ static int tmp401_probe(struct i2c_client *client) + info->type = hwmon_chip; + info->config = data->chip_channel_config; + +- data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; ++ data->chip_channel_config[0] = HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL; + + info = &data->temp_info; + info->type = hwmon_temp; +diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c +new file mode 100644 +--- /dev/null ++++ b/drivers/hwmon/tvs-mpfs.c +@@ -0,0 +1,390 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Author: Lars Randers ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MPFS_TVS_CTRL 0x08 ++#define MPFS_TVS_OUTPUT0 0x24 ++#define MPFS_TVS_OUTPUT1 0x28 ++ ++#define MPFS_TVS_CTRL_TEMP_VALID BIT(19) ++#define MPFS_TVS_CTRL_V2P5_VALID BIT(18) ++#define MPFS_TVS_CTRL_V1P8_VALID BIT(17) ++#define MPFS_TVS_CTRL_V1P05_VALID BIT(16) ++ ++#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3) ++#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2) ++#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1) ++#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0) ++#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0) ++ ++/* ++ * For all of these the value in millivolts is stored in 16 bits, with an upper ++ * sign bit and a lower 3 bits of decimal. These masks discard the sign bit and ++ * decimal places, because if Linux is running these voltages cannot be negative ++ * and so avoid having to convert to two's complement. ++ */ ++#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19) ++#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3) ++#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3) ++ ++/* ++ * The register map claims that the temperature is stored in bits 31:16, but ++ * application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor" ++ * says that 31 is reserved. Temperature is in kelvin, so what's probably a ++ * sign bit has no value anyway. ++ */ ++#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16) ++ ++#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8) ++#define MPFS_TVS_INTERVAL_OFFSET 8 ++/* The interval register is in increments of 32 us */ ++#define MPFS_TVS_INTERVAL_SCALE 32 ++/* with 254 usable increments of 32 us available, 8 ms is the integer limit */ ++#define MPFS_TVS_INTERVAL_MAX_MS 8 ++ ++/* 273.1875 in 11.4 fixed-point notation */ ++#define MPFS_TVS_K_TO_C 0x1113 ++ ++enum mpfs_tvs_sensors { ++ SENSOR_V1P05 = 0, ++ SENSOR_V1P8, ++ SENSOR_V2P5, ++}; ++ ++static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" }; ++ ++struct mpfs_tvs { ++ struct regmap *regmap; ++}; ++ ++static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr, ++ int channel, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_in_input && attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V2P5_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp); ++ break; ++ case SENSOR_V1P8: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P8_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp); ++ break; ++ case SENSOR_V1P05: ++ if (attr == hwmon_in_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control); ++ break; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_V1P05_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr, ++ int channel, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_in_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ switch (channel) { ++ case SENSOR_V2P5: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V2P5_ENABLE, tmp); ++ break; ++ case SENSOR_V1P8: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P8_ENABLE, tmp); ++ break; ++ case SENSOR_V1P05: ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_V1P05_ENABLE, tmp); ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp, control; ++ ++ if (attr != hwmon_temp_input && attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &control); ++ ++ if (attr == hwmon_temp_enable) { ++ *val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control); ++ return 0; ++ } ++ ++ if (!(control & MPFS_TVS_CTRL_TEMP_VALID)) ++ return -ENODATA; ++ ++ regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); ++ *val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp); ++ *val -= MPFS_TVS_K_TO_C; ++ *val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */ ++ ++ return 0; ++} ++ ++static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_temp_enable) ++ return -EOPNOTSUPP; ++ ++ if (val > 1 || val < 0) ++ return -EINVAL; ++ ++ tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val); ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_CTRL_TEMP_ENABLE, tmp); ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val) ++{ ++ u32 tmp; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp); ++ *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp); ++ *val *= MPFS_TVS_INTERVAL_SCALE; ++ *val = roundup(*val, 1000); ++ *val /= 1000; ++ ++ return 0; ++} ++ ++static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val) ++{ ++ long temp = val; ++ ++ if (attr != hwmon_chip_update_interval) ++ return -EOPNOTSUPP; ++ ++ temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS); ++ ++ temp *= 1000; ++ temp /= MPFS_TVS_INTERVAL_SCALE; ++ ++ temp <<= MPFS_TVS_INTERVAL_OFFSET; ++ regmap_update_bits(data->regmap, MPFS_TVS_CTRL, ++ MPFS_TVS_INTERVAL_MASK, temp); ++ ++ return 0; ++} ++ ++static umode_t mpfs_tvs_is_visible(const void *data, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ if (type == hwmon_chip && attr == hwmon_chip_update_interval) ++ return 0644; ++ ++ if (type == hwmon_temp) { ++ switch (attr) { ++ case hwmon_temp_enable: ++ return 0644; ++ case hwmon_temp_input: ++ case hwmon_temp_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ if (type == hwmon_in) { ++ switch (attr) { ++ case hwmon_in_enable: ++ return 0644; ++ case hwmon_in_input: ++ case hwmon_in_label: ++ return 0444; ++ default: ++ return 0; ++ } ++ } ++ ++ return 0; ++} ++ ++static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_read(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_read(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_read(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ struct mpfs_tvs *data = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_temp: ++ return mpfs_tvs_temp_write(data, attr, val); ++ case hwmon_in: ++ return mpfs_tvs_voltage_write(data, attr, channel, val); ++ case hwmon_chip: ++ return mpfs_tvs_interval_write(data, attr, val); ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mpfs_tvs_read_labels(struct device *dev, ++ enum hwmon_sensor_types type, ++ u32 attr, int channel, ++ const char **str) ++{ ++ switch (type) { ++ case hwmon_temp: ++ *str = "Die Temp"; ++ return 0; ++ case hwmon_in: ++ *str = mpfs_tvs_voltage_labels[channel]; ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static const struct hwmon_ops mpfs_tvs_ops = { ++ .is_visible = mpfs_tvs_is_visible, ++ .read_string = mpfs_tvs_read_labels, ++ .read = mpfs_tvs_read, ++ .write = mpfs_tvs_write, ++}; ++ ++static const struct hwmon_channel_info *mpfs_tvs_info[] = { ++ HWMON_CHANNEL_INFO(chip, ++ HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_ENABLE), ++ HWMON_CHANNEL_INFO(in, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, ++ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE), ++ NULL ++}; ++ ++static const struct hwmon_chip_info mpfs_tvs_chip_info = { ++ .ops = &mpfs_tvs_ops, ++ .info = mpfs_tvs_info, ++}; ++ ++static int mpfs_tvs_probe(struct platform_device *pdev) ++{ ++ struct device *hwmon_dev; ++ struct mpfs_tvs *data; ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->regmap = device_node_to_regmap(pdev->dev.parent->of_node); ++ if (IS_ERR(data->regmap)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap), ++ "Failed to find syscon regmap\n"); ++ ++ /* ++ * It's an MMIO regmap with no resources, there's nothing that can fail ++ * and return an error ++ */ ++ regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL); ++ ++ hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs", ++ data, ++ &mpfs_tvs_chip_info, ++ NULL); ++ if (IS_ERR(hwmon_dev)) ++ return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev), ++ "hwmon device registration failed.\n"); ++ ++ return 0; ++} ++ ++static struct platform_driver mpfs_tvs_driver = { ++ .probe = mpfs_tvs_probe, ++ .driver = { ++ .name = "mpfs-tvs", ++ }, ++}; ++module_platform_driver(mpfs_tvs_driver); ++ ++MODULE_AUTHOR("Lars Randers "); ++MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c +--- a/drivers/hwmon/xgene-hwmon.c ++++ b/drivers/hwmon/xgene-hwmon.c +@@ -133,10 +133,6 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) + init_completion(&ctx->rd_complete); + ctx->resp_pending = true; + +- /* Write signature for subspace */ +- WRITE_ONCE(generic_comm_base->signature, +- cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx)); +- + /* Write to the shared command region */ + WRITE_ONCE(generic_comm_base->command, + cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR)); +diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c +--- a/drivers/hwmon/yogafan.c ++++ b/drivers/hwmon/yogafan.c +@@ -53,6 +53,7 @@ struct yoga_fan_data { + }; + + /* Specific configurations mapped via DMI */ ++ + static const struct yogafan_config yoga_8bit_fans_cfg = { + .multiplier = 100, + .fan_count = 1, +@@ -71,6 +72,24 @@ static const struct yogafan_config legion_16bit_dual_cfg = { + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } + }; + ++static const struct yogafan_config loq_15iax9_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config xiaoxin_8bit_dual_cfg = { ++ .multiplier = 100, ++ .fan_count = 2, ++ .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } ++}; ++ ++static const struct yogafan_config yoga_pro_7_14iah10_cfg = { ++ .multiplier = 100, ++ .fan_count = 1, ++ .paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL } ++}; ++ + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) + { + ktime_t now = ktime_get_boottime(); +@@ -170,6 +189,54 @@ static const struct hwmon_chip_info yoga_fan_chip_info = { + }; + + static const struct dmi_system_id yogafan_quirks[] = { ++ { ++ .ident = "Lenovo LOQ 15IAX9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), ++ }, ++ .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo XiaoXin Pro 13ARE 2020", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "XiaoXinPro-13ARE 2020"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo IdeaPad 3 15ALC6", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6"), ++ }, ++ .driver_data = (void *)&ideapad_8bit_fan0_cfg, ++ }, ++ { ++ .ident = "Lenovo Legion Pro 7 16AFR10H", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion Pro 7 16AFR10H"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga Pro 7 14IAH10", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga Pro 7 14IAH10"), ++ }, ++ .driver_data = (void *)&yoga_pro_7_14iah10_cfg, ++ }, ++ { ++ .ident = "Lenovo Yoga 7 16ARP8", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 7 16ARP8"), ++ }, ++ .driver_data = (void *)&xiaoxin_8bit_dual_cfg, ++ }, + { + .ident = "Lenovo Yoga", + .matches = { +diff --git a/include/linux/hwmon-sysfs.h b/include/linux/hwmon-sysfs.h +--- a/include/linux/hwmon-sysfs.h ++++ b/include/linux/hwmon-sysfs.h +@@ -15,10 +15,10 @@ struct sensor_device_attribute{ + int index; + }; + #define to_sensor_dev_attr(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute, dev_attr) + +-#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ ++#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ + .index = _index } + + #define SENSOR_ATTR_RO(_name, _func, _index) \ +@@ -49,11 +49,11 @@ struct sensor_device_attribute_2 { + u8 nr; + }; + #define to_sensor_dev_attr_2(_dev_attr) \ +- container_of(_dev_attr, struct sensor_device_attribute_2, dev_attr) ++ container_of_const(_dev_attr, struct sensor_device_attribute_2, dev_attr) + +-#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ +- { .dev_attr = __ATTR(_name, _mode, _show, _store), \ +- .index = _index, \ ++#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ ++ { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ ++ .index = _index, \ + .nr = _nr } + + #define SENSOR_ATTR_2_RO(_name, _func, _nr, _index) \ diff --git a/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch.sig new file mode 100644 index 0000000..9a153ff Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0740-hwmon-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch b/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch new file mode 100644 index 0000000..15533e0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch @@ -0,0 +1,179 @@ +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -1481,9 +1481,9 @@ static int __init applesmc_init(void) + applesmc_fan_config[smcreg.fan_count] = 0; + applesmc_pwm_config[smcreg.fan_count] = 0; + +- applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); +- applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); +- applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); ++ applesmc_info_temp = kzalloc_obj(*applesmc_info_temp); ++ applesmc_info_fan = kzalloc_obj(*applesmc_info_fan); ++ applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm); + if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { + ret = -ENOMEM; + goto out_info; +@@ -1498,7 +1498,7 @@ static int __init applesmc_init(void) + applesmc_info_pwm->type = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + +- applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); ++ applesmc_info_arr = kzalloc_objs(*applesmc_info_arr, 4); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; +@@ -1509,7 +1509,7 @@ static int __init applesmc_init(void) + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + +- applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); ++ applesmc_chip = kzalloc_obj(*applesmc_chip); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; +@@ -1519,10 +1519,9 @@ static int __init applesmc_init(void) + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ +- fan_safe_attrs = kcalloc(smcreg.fan_count, +- sizeof(*fan_safe_attrs), GFP_KERNEL); +- fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, +- sizeof(*fan_safe_attr_list), GFP_KERNEL); ++ fan_safe_attrs = kzalloc_objs(*fan_safe_attrs, smcreg.fan_count); ++ fan_safe_attr_list = kzalloc_objs(*fan_safe_attr_list, ++ smcreg.fan_count + 1); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; +diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c +--- a/drivers/hwmon/asus_rog_ryujin.c ++++ b/drivers/hwmon/asus_rog_ryujin.c +@@ -184,6 +184,7 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd + static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, + struct completion *status_completion) + { ++ unsigned long flags; + int ret; + + /* +@@ -191,9 +192,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i + * completion. Reinit is done because hidraw could have triggered + * the raw event parsing and marked the passed in completion as done. + */ +- spin_lock_bh(&priv->status_report_request_lock); ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); + reinit_completion(status_completion); +- spin_unlock_bh(&priv->status_report_request_lock); ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + + /* Send command for getting data */ + ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); +@@ -421,15 +422,18 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + int size) + { + struct rog_ryujin_data *priv = hid_get_drvdata(hdev); ++ unsigned long flags; + + if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) + return 0; + ++ spin_lock_irqsave(&priv->status_report_request_lock, flags); ++ + if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { + if (size <= priv->info->temp_offset + 1 || + size <= priv->info->pump_speed_offset + 1 || + size <= priv->info->fan_speed_offset + 1) +- return 0; ++ goto unlock; + + /* Received coolant temp and speeds of pump and internal fan */ + priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + +@@ -443,7 +447,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_status_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) +- return 0; ++ goto unlock; + + /* Received speeds of four fans attached to the controller */ + priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); +@@ -455,7 +459,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_status_received); + } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_INTERNAL_FAN_DUTY) +- return 0; ++ goto unlock; + + /* Received report for pump and internal fan duties (in %) */ + if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { +@@ -474,7 +478,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_cooler_duty; +- return 0; ++ goto unlock; + } + read_cooler_duty: + priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); +@@ -484,7 +488,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->cooler_duty_received); + } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { + if (size <= RYUJIN_CONTROLLER_DUTY) +- return 0; ++ goto unlock; + + /* Received report for controller duty for fans (in PWM) */ + if (data[RYUJIN_CONTROLLER_DUTY] == 0) { +@@ -503,7 +507,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + * We're expecting a report, so parse it. + */ + goto read_controller_duty; +- return 0; ++ goto unlock; + } + read_controller_duty: + priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; +@@ -512,6 +516,8 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo + complete_all(&priv->controller_duty_received); + } + ++unlock: ++ spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + return 0; + } + +diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c +--- a/drivers/hwmon/hwmon.c ++++ b/drivers/hwmon/hwmon.c +@@ -318,6 +318,11 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + return 1; + } + ++static bool is_hwmon_device(struct device *dev) ++{ ++ return dev->class == &hwmon_class; ++} ++ + #if IS_REACHABLE(CONFIG_I2C) + + /* +@@ -338,7 +343,7 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) + + static int hwmon_match_device(struct device *dev, const void *data) + { +- return dev->class == &hwmon_class; ++ return is_hwmon_device(dev); + } + + static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, +@@ -781,6 +786,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, + const char *template; + int base; + ++ if (WARN(!is_hwmon_device(dev), "%s is not a hardware monitoring device\n", ++ dev_name(dev))) ++ return -EINVAL; + if (type >= ARRAY_SIZE(__templates)) + return -EINVAL; + if (attr >= __templates_size[type]) diff --git a/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch.sig b/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch.sig new file mode 100644 index 0000000..11778c9 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0741-hwmon-fixes-1.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch b/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch new file mode 100644 index 0000000..9552822 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c +--- a/drivers/hwmon/k10temp.c ++++ b/drivers/hwmon/k10temp.c +@@ -523,7 +523,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) + } + } else if (boot_cpu_data.x86 == 0x1a) { + switch (boot_cpu_data.x86_model) { +- case 0x00 ... 0x2f: /* Zen5 Turin */ ++ case 0x00 ... 0x1f: /* Zen5 Turin */ + data->ccd_offset = 0x1F0; + k10temp_get_ccd_support(data, 16); + break; diff --git a/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch.sig b/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch.sig new file mode 100644 index 0000000..db34c4b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0742-hwmon-fixes-2.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch b/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch new file mode 100644 index 0000000..da85b37 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch @@ -0,0 +1,23 @@ +diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c +--- a/drivers/acpi/power.c ++++ b/drivers/acpi/power.c +@@ -1121,6 +1121,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), + } + ++ }, ++ { ++ /* ++ * Acer Swift 3 SF314-56G with NVIDIA MX250 suffers from the ++ * same ACPI power resource regression as the Thunderobot ZERO. ++ * The power resource controlling the dGPU is incorrectly turned off ++ * during initialization, causing the GPU to fall off the bus (D3cold). ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Acer"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-56G"), ++ } ++ + }, + {} + }; diff --git a/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig b/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig new file mode 100644 index 0000000..8744164 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0770-acpi-pm-acer-swift3-sf314-56g-power-resource.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch b/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch new file mode 100644 index 0000000..c6018e3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch @@ -0,0 +1,8290 @@ +diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h +--- a/arch/x86/include/uapi/asm/amd_hsmp.h ++++ b/arch/x86/include/uapi/asm/amd_hsmp.h +@@ -53,9 +53,21 @@ enum hsmp_message_ids { + HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */ + HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */ + HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */ ++ HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */ ++ HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */ + HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */ + HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */ + HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */ ++ HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */ ++ HSMP_READ_CCD_POWER, /* 34h Get average CCD power */ ++ HSMP_READ_TDELTA, /* 35h Get thermal behaviour */ ++ HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */ ++ HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */ ++ HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */ ++ HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */ ++ HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */ ++ HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */ ++ HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */ + HSMP_MSG_ID_MAX, + }; + +@@ -170,16 +182,27 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 1, HSMP_GET}, + + /* +- * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0 +- * input: args[0] = min link width[15:8] + max link width[7:0] ++ * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) + ++ * min link width[15:8] + max link width[7:0] ++ * Link width encoding: 0 = x4, 1 = x8, 2 = x16. ++ * On SET, max must be >= min. On GET, [15:0] are reserved. ++ * output: args[0] = reserved[31:16] + min link width[15:8] + ++ * max link width[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0 +- * input: args[0] = df pstate[7:0] ++ * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set APB_DISABLE / get APB state[31] ++ * (0 = set & lock DF P-state, 1 = get) + ++ * reserved[30:8] + ++ * DF P-state[7:0] (0..2; reserved on GET) ++ * output: args[0] = reserved[31:9] + ++ * APB state[8] (1 = disabled, 0 = enabled) + ++ * locked DF P-state[7:0] if [8] = 1, else reserved + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */ + {0, 0, HSMP_SET}, +@@ -305,16 +328,32 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {1, 1, HSMP_SET}, + + /* +- * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0 +- * input: args[0] = power efficiency mode[2:0] ++ * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection), ++ * num_args = 1, response_sz = 1 ++ * input: args[0] = set/get policy[31] (0 = set, 1 = get) + ++ * high util point[30:24] + ++ * low util point[23:17] + ++ * PPT limit[16:5] + ++ * reserved[4:3] + mode selection[2:0] ++ * [30:5] are valid only when [2:0] is a balanced core mode ++ * (4 or 5). [2:0] is reserved when getting (bit[31] = 1). ++ * output: args[0] same layout, [31] reserved, [2:0] = arbitrated ++ * current efficiency mode. + */ + {1, 1, HSMP_SET_GET}, + + /* +- * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0 +- * input: args[0] = min df pstate[15:8] + max df pstate[7:0] ++ * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) + ++ * reserved[30:16] + ++ * min DF P-state[15:8] + max DF P-state[7:0] ++ * DF P-state encoding: 0 = DFP0 (high performance), ++ * 1 = DFP1, 2 = DFP2 (low performance). ++ * [15:0] are reserved when getting (args[0] bit[31] = 1). ++ * output: args[0] = reserved[31:16] + min DF P-state[15:8] + ++ * max DF P-state[7:0] + */ +- {1, 0, HSMP_SET}, ++ {1, 1, HSMP_SET_GET}, + + /* + * HSMP_GET_METRIC_TABLE_VER, num_args = 0, response_sz = 1 +@@ -328,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + {0, 0, HSMP_GET}, + + /* +- * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2 ++ * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3 + * output: args[0] = lower 32 bits of the address + * output: args[1] = upper 32 bits of the address ++ * output: args[2] = DRAM region size in bytes + */ +- {0, 2, HSMP_GET}, ++ {0, 3, HSMP_GET}, + + /* + * HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0 +@@ -355,9 +395,31 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {1, 1, HSMP_SET_GET}, + +- /* RESERVED(0x29-0x2f) */ +- {0, 0, HSMP_RSVD}, +- {0, 0, HSMP_RSVD}, ++ /* ++ * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1 ++ * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable PC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current PC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1 ++ * Configures CC6 enable for all cores; changing the setting does ++ * not by itself transition cores in or out of CC6. ++ * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) + ++ * reserved[30:1] + ++ * enable CC6[0] (0 = disable, 1 = enable; ++ * reserved on GET) ++ * output: args[0] = reserved[31:1] + current CC6 control[0] ++ * (last value configured via HSMP or APML) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* RESERVED(0x2B-0x2F) */ + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, + {0, 0, HSMP_RSVD}, +@@ -385,6 +447,89 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] + */ + {0, 2, HSMP_GET}, + ++ /* ++ * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1 ++ * input: args[0] = reg space[23] + reg offset[22:12] + ++ * device LID[11:8] + DIMM address[7:0] ++ * output: args[0] = read data byte[3:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1 ++ * input: args[0] = apic id of core[15:0] ++ * output: args[0] = CCD power(mWatts)[31:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_READ_TDELTA, num_args = 0, response_sz = 1 ++ * output: args[0] = thermal behaviour[31:0] ++ */ ++ {0, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1 ++ * input: args[0] = SVI3 rail index[3:1] + read temperature[0] ++ * output: args[0] = SVI3 rail index[30:28] + ++ * rail temperature in degree C[27:0] ++ */ ++ {1, 1, HSMP_GET}, ++ ++ /* ++ * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3 ++ * input: args[0] = HSMP command mask[0] ++ * output: status of HSMP command = args[0], args[1], args[2] ++ */ ++ {1, 3, HSMP_GET}, ++ ++ /* ++ * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + reserved[29:28] + ++ * apic id[27:16] + floor frequency MHz[15:0] ++ * op encoding: 00 = set per-core floor, ++ * 01 = set all-cores floor (apic id reserved), ++ * 10 = get per-core floor, ++ * 11 = get per-core effective floor. ++ * Floor frequency field is reserved on GET (bit[31] = 1). ++ * output: args[0] = floor frequency MHz[15:0] ++ * (effective for op 11, configured for op 10; ++ * reserved on SET) ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0 ++ * input: args[0] = write data[31:24] + reg space[23] + ++ * reg offset[22:12] + device LID[11:8] + ++ * DIMM address[7:0] ++ */ ++ {1, 0, HSMP_SET}, ++ ++ /* ++ * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1 ++ * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) + ++ * SDPS limit[30:0] ++ * output: args[0] = SDPS limit[30:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1 ++ * input: args[0] = op[31:30] + priority sel[27:26] + ++ * priority val[21:20] + input[19:0] ++ * output: args[0] = supported priorities or priority val[1:0] ++ */ ++ {1, 1, HSMP_SET_GET}, ++ ++ /* ++ * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2 ++ * input: args[0] = op[31] + sub-op[30:29] + params[28:0] ++ * output: args[0] = discovery bits or floating/global memory BW (Gbps) ++ * output: args[1] = reserved or config (drop adj, sampling delay, ++ * hysteresis) ++ */ ++ {1, 2, HSMP_SET_GET}, + }; + + /* Metrics table (supported only with proto version 6) */ +@@ -467,6 +612,39 @@ struct hsmp_metric_table { + __u32 gfxclk_frequency[8]; + }; + ++/** ++ * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL ++ * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout ++ * stable between 32-bit and 64-bit callers) to the destination ++ * buffer that receives the metric table. ++ * @size: Input. Size in bytes of the buffer pointed to by @buf, and the ++ * number of bytes copied out on success. Must be non-zero and no ++ * larger than the metric table size firmware reports for this ++ * socket; a larger value is rejected with -EINVAL rather than ++ * short-written. A smaller value returns the leading @size bytes ++ * of the snapshot. The kernel does not write this field back. ++ * @sock_ind: Input. Socket index from which the metric table is read. ++ * @reserved: Reserved for future use. Callers should set this to zero; ++ * future kernels may begin interpreting the field, so passing ++ * a non-zero value today is not forwards compatible. ++ * ++ * Placing @buf first lets all fields fall on their natural alignment under ++ * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with ++ * the same wire layout on 32-bit and 64-bit userspace. ++ * ++ * The metric table layout depends on the HSMP protocol version reported by ++ * firmware, which userspace can read from the protocol_version sysfs ++ * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on ++ * that version pass sizeof(struct hsmp_metric_table). Later version metrics ++ * table layout is documented in the Public PPR. ++ */ ++struct hsmp_telemetry_data { ++ __u64 buf; ++ __u32 size; ++ __u16 sock_ind; ++ __u16 reserved; ++}; ++ + /* Reset to default packing */ + #pragma pack() + +@@ -474,4 +652,16 @@ struct hsmp_metric_table { + #define HSMP_BASE_IOCTL_NR 0xF8 + #define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) + ++/* ++ * Fetch the firmware metric (telemetry) table for a given socket via the ++ * HSMP character device. This avoids the PAGE_SIZE limitation of the ++ * sysfs binary attribute path for tables larger than one page (such as the ++ * ~13 KB table used by HSMP protocol version 7). ++ * ++ * The direction is _IOW because the kernel only reads the request struct; ++ * the table itself is written to the buffer that @buf points at. ++ */ ++#define HSMP_IOCTL_GET_TELEMETRY_DATA \ ++ _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data) ++ + #endif /*_ASM_X86_AMD_HSMP_H_*/ +diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c +--- a/arch/x86/platform/olpc/olpc-xo15-sci.c ++++ b/arch/x86/platform/olpc/olpc-xo15-sci.c +@@ -18,8 +18,6 @@ + + #define DRV_NAME "olpc-xo15-sci" + #define PFX DRV_NAME ": " +-#define XO15_SCI_CLASS DRV_NAME +-#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" + + static unsigned long xo15_sci_gpe; + static bool lid_wake_on_close; +@@ -148,9 +146,6 @@ static int xo15_sci_probe(struct platform_device *pdev) + if (!device) + return -ENODEV; + +- strscpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_SCI_CLASS); +- + /* Get GPE bit assignment (EC events). */ + status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); + if (ACPI_FAILURE(status)) +diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c +--- a/drivers/char/sonypi.c ++++ b/drivers/char/sonypi.c +@@ -1125,8 +1125,6 @@ static int sonypi_acpi_probe(struct platform_device *pdev) + return -ENODEV; + + sonypi_acpi_device = device; +- strscpy(acpi_device_name(device), "Sony laptop hotkeys"); +- strscpy(acpi_device_class(device), "sony/hotkey"); + return 0; + } + +diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c ++++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +@@ -7,6 +7,7 @@ + * + * Author: Basavaraj Natikar + */ ++#include + #include + #include + #include +@@ -102,20 +103,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) + *platform_type = mode.op_mode.devicemode; + + if (mode.op_mode.ontablestate == 1) { +- *laptop_placement = ON_TABLE; ++ *laptop_placement = AMD_PMF_ON_TABLE; + } else if (mode.op_mode.ontablestate == 2) { +- *laptop_placement = ON_LAP_MOTION; ++ *laptop_placement = AMD_PMF_ON_LAP_MOTION; + } else if (mode.op_mode.inbagstate == 1) { +- *laptop_placement = IN_BAG; ++ *laptop_placement = AMD_PMF_IN_BAG; + } else if (mode.op_mode.outbagstate == 1) { +- *laptop_placement = OUT_OF_BAG; ++ *laptop_placement = AMD_PMF_OUT_OF_BAG; + } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 || + mode.op_mode.outbagstate == 0) { +- *laptop_placement = LP_UNKNOWN; ++ *laptop_placement = AMD_PMF_LP_UNKNOWN; + pr_warn_once("Unknown laptop placement\n"); + } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 || + mode.op_mode.outbagstate == 3) { +- *laptop_placement = LP_UNDEFINED; ++ *laptop_placement = AMD_PMF_LP_UNDEFINED; + pr_warn_once("Undefined laptop placement\n"); + } + +diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c +--- a/drivers/platform/surface/surface_aggregator_hub.c ++++ b/drivers/platform/surface/surface_aggregator_hub.c +@@ -348,8 +348,13 @@ static const struct ssam_hub_desc kip_hub = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_hub_match[] = { +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, +- { SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, ++ { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_KIP, 0x00), ++ .driver_data = (unsigned long)&kip_hub, ++ }, { ++ SSAM_VDEV(HUB, SAM, SSAM_SSH_TC_BAS, 0x00), ++ .driver_data = (unsigned long)&base_hub, ++ }, + { } + }; + MODULE_DEVICE_TABLE(ssam, ssam_hub_match); +diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c +--- a/drivers/platform/surface/surface_aggregator_tabletsw.c ++++ b/drivers/platform/surface/surface_aggregator_tabletsw.c +@@ -622,8 +622,13 @@ static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { + /* -- Driver registration. -------------------------------------------------- */ + + static const struct ssam_device_id ssam_tablet_sw_match[] = { +- { SSAM_SDEV(KIP, SAM, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, +- { SSAM_SDEV(POS, SAM, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, ++ { ++ SSAM_SDEV(KIP, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_kip_sw_desc, ++ }, { ++ SSAM_SDEV(POS, SAM, 0x00, 0x01), ++ .driver_data = (unsigned long)&ssam_pos_sw_desc, ++ }, + { }, + }; + MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -20,7 +20,6 @@ + #define SURFACE_PRO3_BUTTON_HID "MSHW0028" + #define SURFACE_PRO4_BUTTON_HID "MSHW0040" + #define SURFACE_BUTTON_OBJ_NAME "VGBI" +-#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" + + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -212,11 +211,10 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); + snprintf(button->phys, sizeof(button->phys), "%s/buttons", + acpi_device_hid(device)); + +- input->name = acpi_device_name(device); ++ input->name = "Surface Pro 3/4 Buttons"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; +@@ -239,8 +237,8 @@ static int surface_button_probe(struct platform_device *pdev) + goto err_free_button; + } + +- dev_info(&pdev->dev, "%s [%s]\n", acpi_device_name(device), +- acpi_device_bid(device)); ++ dev_info(&pdev->dev, "%s [%s]\n", input->name, acpi_device_bid(device)); ++ + return 0; + + err_free_input: +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -802,7 +802,6 @@ config LG_LAPTOP + tristate "LG Laptop Extras" + depends on ACPI + depends on ACPI_BATTERY +- depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + select NEW_LEDS +diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig +--- a/drivers/platform/x86/amd/Kconfig ++++ b/drivers/platform/x86/amd/Kconfig +@@ -34,6 +34,17 @@ config AMD_WBRF + This mechanism will only be activated on platforms that advertise a + need for it. + ++config AMD_HALO_LED ++ tristate "AMD Halo Box RGB LED Driver" ++ depends on ACPI_WMI && LEDS_CLASS_MULTICOLOR ++ help ++ This driver provides RGB LED control for AMD Halo Box devices ++ through the LED multicolor subsystem. The Halo Box light bar can ++ be controlled via sysfs to display any RGB color combination. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called amd_halo_led. ++ + config AMD_ISP_PLATFORM + tristate "AMD ISP4 platform driver" + depends on I2C && X86_64 && ACPI +diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile +--- a/drivers/platform/x86/amd/Makefile ++++ b/drivers/platform/x86/amd/Makefile +@@ -10,5 +10,6 @@ obj-$(CONFIG_AMD_PMC) += pmc/ + obj-$(CONFIG_AMD_HSMP) += hsmp/ + obj-$(CONFIG_AMD_PMF) += pmf/ + obj-$(CONFIG_AMD_WBRF) += wbrf.o ++obj-$(CONFIG_AMD_HALO_LED) += amd_halo_led.o + obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o + obj-$(CONFIG_AMD_HFI) += hfi/ +diff --git a/drivers/platform/x86/amd/amd_halo_led.c b/drivers/platform/x86/amd/amd_halo_led.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/amd_halo_led.c +@@ -0,0 +1,315 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * AMD Halo Box RGB LED Driver ++ * ++ * Copyright (C) 2026 Advanced Micro Devices, Inc. ++ * ++ * This driver provides RGB LED control for AMD Halo Box devices through ++ * the LED multicolor subsystem. The Halo Box light bar can be controlled ++ * via sysfs to display any RGB color combination. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define AMD_HALO_GUID "081E747B-E028-4232-AF24-EAAEAB2B1E86" ++ ++/* WMI method IDs from MOF */ ++enum { ++ AMD_HALO_WMI_TURN_OFF = 0x04, ++ AMD_HALO_WMI_RGB = 0x07, ++}; ++ ++/* Arg0 of the AMD_HALO_WMI_RGB Method */ ++enum { ++ AMD_HALO_RGB_CMD_GET = 0x0, ++ AMD_HALO_RGB_CMD_SET = 0x1, ++}; ++ ++/* Status codes from spec */ ++#define AMD_HALO_STATUS_SUCCESS 0x0000 ++#define AMD_HALO_STATUS_INVALID_PARAM 0xFFFD ++ ++/* Brightness uses 0-100 range */ ++#define AMD_HALO_MAX_HW_BRIGHTNESS 100 ++ ++/** ++ * struct amd_halo_led_data - Driver private data ++ * @wdev: WMI device pointer ++ * @led_mc: LED multicolor class device ++ * @subled_info: RGB channel information ++ * @lock: Mutex to protect WMI calls ++ */ ++struct amd_halo_led_data { ++ struct wmi_device *wdev; ++ struct led_classdev_mc led_mc; ++ struct mc_subled subled_info[3]; ++ struct mutex lock; ++}; ++ ++struct amd_halo_wmi_args { ++ __le32 arg0; ++ __le32 arg1; ++}; ++ ++struct amd_halo_wmi_args_rgb { ++ __le32 cmd; ++ __le32 red; ++ __le32 green; ++ __le32 blue; ++}; ++ ++struct amd_halo_wmi_output_rgb { ++ __le16 status; ++ u8 red; ++ u8 green; ++ u8 blue; ++} __packed; ++ ++static inline int wmi_status_to_err(u16 status) ++{ ++ switch (status) { ++ case AMD_HALO_STATUS_SUCCESS: ++ return 0; ++ case AMD_HALO_STATUS_INVALID_PARAM: ++ return -EINVAL; ++ default: ++ return -EIO; ++ } ++} ++ ++static int __amd_halo_wmi_call(struct wmi_device *wdev, ++ u32 method_id, void *data, size_t length) ++{ ++ struct wmi_buffer input = { ++ .length = length, ++ .data = data, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ /* Return buffer per spec: Bytes[0:1] = Status (little-endian) */ ++ ret = wmidev_invoke_method(wdev, 0, method_id, ++ &input, &output, sizeof(__le16)); ++ if (ret) ++ return ret; ++ ++ __le16 *result_status __free(kfree) = output.data; ++ ++ return wmi_status_to_err(le16_to_cpu(*result_status)); ++} ++ ++/** ++ * amd_halo_wmi_turn_off - Turn off all LED channels ++ * @wdev: WMI device pointer ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_turn_off(struct wmi_device *wdev) ++{ ++ struct amd_halo_wmi_args args = { }; ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_TURN_OFF, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_set_rgb - Set all RGB channels atomically ++ * @wdev: WMI device pointer ++ * @r: brightness for red channel (0 - 100) ++ * @g: brightness for green channel (0 - 100) ++ * @b: brightness for blue channel (0 - 100) ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_set_rgb(struct wmi_device *wdev, u32 r, u32 g, u32 b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_SET), ++ .red = cpu_to_le32(r), ++ .green = cpu_to_le32(g), ++ .blue = cpu_to_le32(b), ++ }; ++ ++ if (r > AMD_HALO_MAX_HW_BRIGHTNESS || ++ g > AMD_HALO_MAX_HW_BRIGHTNESS || ++ b > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EINVAL; ++ } ++ ++ return __amd_halo_wmi_call(wdev, AMD_HALO_WMI_RGB, &args, sizeof(args)); ++} ++ ++/** ++ * amd_halo_wmi_get_rgb - Get RGB values ++ * @wdev: WMI device pointer ++ * @r: output buffer for red value ++ * @g: output buffer for green value ++ * @b: output buffer for blue value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_wmi_get_rgb(struct wmi_device *wdev, u8 *r, u8 *g, u8 *b) ++{ ++ struct amd_halo_wmi_args_rgb args = { ++ .cmd = cpu_to_le32(AMD_HALO_RGB_CMD_GET), ++ }; ++ struct wmi_buffer input = { ++ .length = sizeof(args), ++ .data = &args, ++ }; ++ struct wmi_buffer output = { }; ++ int ret; ++ ++ ret = wmidev_invoke_method(wdev, 0, AMD_HALO_WMI_RGB, ++ &input, &output, sizeof(struct amd_halo_wmi_output_rgb)); ++ if (ret) ++ return ret; ++ ++ struct amd_halo_wmi_output_rgb *data __free(kfree) = output.data; ++ ++ ret = wmi_status_to_err(le16_to_cpu(data->status)); ++ if (ret) ++ return ret; ++ ++ if (data->red > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->green > AMD_HALO_MAX_HW_BRIGHTNESS || ++ data->blue > AMD_HALO_MAX_HW_BRIGHTNESS) { ++ return -EPROTO; ++ } ++ ++ *r = data->red; ++ *g = data->green; ++ *b = data->blue; ++ ++ return 0; ++} ++ ++/** ++ * amd_halo_brightness_set - Set LED brightness ++ * @cdev: LED class device ++ * @brightness: Brightness value ++ * ++ * Return: 0 on success, negative error code on failure ++ */ ++static int amd_halo_brightness_set(struct led_classdev *cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); ++ struct amd_halo_led_data *data = container_of(mc_cdev, ++ struct amd_halo_led_data, ++ led_mc); ++ u32 red_hw, green_hw, blue_hw; ++ int ret; ++ ++ guard(mutex)(&data->lock); ++ ++ led_mc_calc_color_components(mc_cdev, brightness); ++ ++ if (brightness == 0) ++ return amd_halo_wmi_turn_off(data->wdev); ++ ++ red_hw = mc_cdev->subled_info[0].brightness; ++ green_hw = mc_cdev->subled_info[1].brightness; ++ blue_hw = mc_cdev->subled_info[2].brightness; ++ ++ ret = amd_halo_wmi_set_rgb(data->wdev, red_hw, green_hw, blue_hw); ++ if (ret) ++ goto out; ++ ++ return 0; ++ ++out: ++ /* ++ * Consider the light bar non-functional if AMD_HALO_WMI_RGB failed. ++ * Attempt to turn the LED off completely as clean-up. ++ */ ++ if (amd_halo_wmi_turn_off(data->wdev)) ++ dev_warn_ratelimited(&data->wdev->dev, "Failed to turn LED off on cleanup\n"); ++ ++ return ret; ++} ++ ++static int amd_halo_probe(struct wmi_device *wdev, const void *context) ++{ ++ struct led_init_data led_init_data = { ++ .devicename = "amd_halo", ++ .default_label = "multicolor:" LED_FUNCTION_STATUS, ++ .devname_mandatory = true, ++ }; ++ struct amd_halo_led_data *data; ++ u8 r, g, b; ++ int ret; ++ ++ data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ ret = devm_mutex_init(&wdev->dev, &data->lock); ++ if (ret) ++ return ret; ++ ++ data->wdev = wdev; ++ dev_set_drvdata(&wdev->dev, data); ++ ++ data->subled_info[0].color_index = LED_COLOR_ID_RED; ++ data->subled_info[1].color_index = LED_COLOR_ID_GREEN; ++ data->subled_info[2].color_index = LED_COLOR_ID_BLUE; ++ ++ data->led_mc.led_cdev.brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.max_brightness = AMD_HALO_MAX_HW_BRIGHTNESS; ++ data->led_mc.led_cdev.brightness_set_blocking = amd_halo_brightness_set; ++ data->led_mc.led_cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; ++ data->led_mc.num_colors = ARRAY_SIZE(data->subled_info); ++ data->led_mc.subled_info = data->subled_info; ++ ++ ret = amd_halo_wmi_get_rgb(wdev, &r, &g, &b); ++ if (ret) ++ return ret; ++ ++ data->subled_info[0].intensity = r; ++ data->subled_info[1].intensity = g; ++ data->subled_info[2].intensity = b; ++ ++ ret = devm_led_classdev_multicolor_register_ext(&wdev->dev, &data->led_mc, ++ &led_init_data); ++ if (ret) ++ return dev_err_probe(&wdev->dev, ret, ++ "Failed to register multicolor LED\n"); ++ return 0; ++} ++ ++static const struct wmi_device_id amd_halo_id_table[] = { ++ { .guid_string = AMD_HALO_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, amd_halo_id_table); ++ ++static struct wmi_driver amd_halo_driver = { ++ .driver = { ++ .name = "amd_halo_led", ++ }, ++ .id_table = amd_halo_id_table, ++ .probe = amd_halo_probe, ++ .no_singleton = true, ++}; ++ ++module_wmi_driver(amd_halo_driver); ++ ++MODULE_AUTHOR("Mario Limonciello (AMD) "); ++MODULE_AUTHOR("Yo-Jung Leo Lin (AMD) "); ++MODULE_DESCRIPTION("AMD Halo Box RGB LED Control Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c +--- a/drivers/platform/x86/amd/hsmp/acpi.c ++++ b/drivers/platform/x86/amd/hsmp/acpi.c +@@ -15,12 +15,18 @@ + #include + #include + #include ++#include + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -38,6 +44,17 @@ + + static struct hsmp_plat_device *hsmp_pdev; + ++/* ++ * Tracks the ACPI socket platform devices that share the socket array and the ++ * /dev/hsmp misc device. The first probe initializes it, each further probe ++ * takes a reference and every remove (or probe failure) drops one; the last ++ * put frees the shared state via hsmp_acpi_sock_release(). All get/put run ++ * under hsmp_sock_rwsem held for write, so the counting is already serialized ++ * and the atomic in kref is not strictly needed; kref is used for the clearer ++ * get/put interface and its release callback. ++ */ ++static struct kref hsmp_acpi_sock_kref; ++ + struct hsmp_sys_attr { + struct device_attribute dattr; + u32 msg_id; +@@ -77,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) + * bytes to integer. + */ + uid = acpi_device_uid(ACPI_COMPANION(dev)); ++ if (!uid || strlen(uid) < 3) ++ return -EINVAL; + + return kstrtou16(uid + 2, 10, sock_ind); + } +@@ -104,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) + return AE_OK; + } + +-static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) ++static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) + { + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *guid, *mailbox_package; +@@ -113,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + int ret = 0; + int j; + +- status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, ++ status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, + &buf, ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", ++ dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + acpi_format_exception(status)); + return -ENODEV; + } +@@ -139,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + guid = &dsd->package.elements[0]; + mailbox_package = &dsd->package.elements[1]; + if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { +- dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); ++ dev_err(dev, "Invalid hsmp _DSD table data\n"); + ret = -EINVAL; + goto free_buf; + } +@@ -148,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + union acpi_object *msgobj, *msgstr, *msgint; + + msgobj = &mailbox_package->package.elements[j]; +- msgstr = &msgobj->package.elements[0]; +- msgint = &msgobj->package.elements[1]; + + /* package should have 1 string and 1 integer object */ + if (msgobj->type != ACPI_TYPE_PACKAGE || +- msgstr->type != ACPI_TYPE_STRING || ++ msgobj->package.count < 2) { ++ ret = -EINVAL; ++ goto free_buf; ++ } ++ ++ msgstr = &msgobj->package.elements[0]; ++ msgint = &msgobj->package.elements[1]; ++ ++ if (msgstr->type != ACPI_TYPE_STRING || + msgint->type != ACPI_TYPE_INTEGER) { + ret = -EINVAL; + goto free_buf; +@@ -183,14 +208,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) + return ret; + } + +-static int hsmp_read_acpi_crs(struct hsmp_socket *sock) ++static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) + { + acpi_status status; + +- status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, ++ status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, + hsmp_resource, sock); + if (ACPI_FAILURE(status)) { +- dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", ++ dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + acpi_format_exception(status)); + return -EINVAL; + } +@@ -198,10 +223,10 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) + return -EINVAL; + + /* The mapped region should be un-cached */ +- sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, ++ sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, + sock->mbinfo.size); + if (!sock->virt_base_addr) { +- dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); ++ dev_err(dev, "Failed to ioremap MP1 base address\n"); + return -ENOMEM; + } + +@@ -215,7 +240,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + int ret; + + sock->sock_ind = sock_ind; +- sock->dev = dev; + sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; + + sema_init(&sock->hsmp_sem, 1); +@@ -223,12 +247,27 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) + dev_set_drvdata(dev, sock); + + /* Read MP1 base address from CRS method */ +- ret = hsmp_read_acpi_crs(sock); ++ ret = hsmp_read_acpi_crs(dev, sock); + if (ret) + return ret; + + /* Read mailbox offsets from DSD table */ +- return hsmp_read_acpi_dsd(sock); ++ ret = hsmp_read_acpi_dsd(dev, sock); ++ if (ret) ++ return ret; ++ ++ /* ++ * Publish sock->dev last. hsmp_send_message() uses it (via ++ * smp_load_acquire()) as the readiness gate for the lock-free data ++ * plane, so it must become visible only after virt_base_addr, the ++ * mailbox offsets and the semaphore are fully initialized. On a ++ * multi-socket system socket 0 exposes /dev/hsmp before later sockets ++ * finish probing, so without this an ioctl aimed at a socket still in ++ * bring-up could pass the gate and dereference a NULL virt_base_addr. ++ */ ++ smp_store_release(&sock->dev, dev); ++ ++ return 0; + } + + static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, +@@ -238,13 +277,31 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj + struct device *dev = container_of(kobj, struct device, kobj); + struct hsmp_socket *sock = dev_get_drvdata(dev); + ++ /* ++ * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. ++ * It can therefore only carry the protocol version 6 metric table ++ * (struct hsmp_metric_table). The larger tables defined from protocol ++ * version 7 onwards do not fit; userspace on those systems must read ++ * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. ++ * Surface the unsupported case here as -EOPNOTSUPP rather than ++ * silently truncating the snapshot. ++ */ ++ if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) ++ return -EOPNOTSUPP; ++ + return hsmp_metric_tbl_read(sock, buf, count); + } + + static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, + const struct bin_attribute *battr, int id) + { +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) ++ /* ++ * Keep metrics_bin visible on protocol version 7 and later as well, ++ * so that userspace which expects the file to exist gets a clear ++ * -EOPNOTSUPP from the read handler instead of -ENOENT, and is ++ * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. ++ */ ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) + return battr->attr.mode; + + return 0; +@@ -459,11 +516,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att + return len; + } + ++/* ++ * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox ++ * handshake and register its sysfs/hwmon interfaces. ++ * ++ * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the ++ * per-socket bring-up cannot race a concurrent probe or remove. ++ */ + static int init_acpi(struct device *dev) + { + u16 sock_ind; + int ret; + ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ + ret = hsmp_get_uid(dev, &sock_ind); + if (ret) + return ret; +@@ -491,7 +557,7 @@ static int init_acpi(struct device *dev) + return ret; + } + +- if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { ++ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { + ret = hsmp_get_tbl_dram_base(sock_ind); + if (ret) + dev_info(dev, "Failed to init metric table\n"); +@@ -576,6 +642,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { + }; + MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); + ++/* ++ * kref release: tear down the shared ACPI socket state once the last socket ++ * drops its reference. Deregister /dev/hsmp if it was registered, unmap any ++ * metric-table DRAM, destroy the per-socket mutexes and free the socket array. ++ * ++ * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove ++ * and probe-failure paths both drop their reference under that lock. The write ++ * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox ++ * and freeing the array cannot race the data plane. ++ */ ++static void hsmp_acpi_sock_release(struct kref *kref) ++{ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) ++ hsmp_misc_deregister(); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ kfree(hsmp_pdev->sock); ++ hsmp_pdev->sock = NULL; ++ hsmp_pdev->num_sockets = 0; ++ hsmp_pdev->proto_ver = 0; ++} ++ ++/** ++ * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. ++ * @dev: ACPI companion device whose probe failed. ++ * ++ * This device already took a reference on entry to hsmp_acpi_probe(), so clear ++ * its sock->dev and drop that reference; the shared state is released if it was ++ * the last one. ++ * ++ * Clearing sock->dev matters on multi-socket systems: when a non-first socket ++ * fails, the array stays alive (owned by an already-probed socket) and ++ * remove() is never called for this device, yet devres unmaps its mailbox once ++ * probe() returns. Without clearing dev, a later message to this index would ++ * pass every gate in hsmp_send_message() and reach the unmapped mailbox. ++ * ++ * sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata. ++ * ++ * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. ++ */ ++static void hsmp_acpi_probe_failure_cleanup(struct device *dev) ++{ ++ struct hsmp_socket *sock = dev_get_drvdata(dev); ++ ++ lockdep_assert_held_write(&hsmp_sock_rwsem); ++ ++ if (sock) ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); ++} ++ + static int hsmp_acpi_probe(struct platform_device *pdev) + { + int ret; +@@ -584,34 +704,61 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + if (!hsmp_pdev) + return -ENOMEM; + +- if (!hsmp_pdev->is_probed) { ++ /* ++ * Multiple ACPI socket devices probe in parallel, but the one-time ++ * socket-array allocation and /dev/hsmp registration below must run ++ * exactly once. Hold the socket rwsem for write across the whole ++ * bring-up so it cannot race a concurrent probe or remove, and so the ++ * probe-failure teardown drains the data plane. ++ */ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ if (!hsmp_pdev->sock) { + hsmp_pdev->num_sockets = topology_max_packages(); + if (!hsmp_pdev->num_sockets) { + dev_err(&pdev->dev, "No CPU sockets detected\n"); + return -ENODEV; + } + +- hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, +- sizeof(*hsmp_pdev->sock), +- GFP_KERNEL); ++ hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets, ++ sizeof(*hsmp_pdev->sock), ++ GFP_KERNEL); + if (!hsmp_pdev->sock) + return -ENOMEM; ++ ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ kref_init(&hsmp_acpi_sock_kref); ++ } else { ++ kref_get(&hsmp_acpi_sock_kref); + } + ++ /* ++ * This socket now holds a reference (kref_init on the first socket, ++ * kref_get afterwards). Every failure path below drops it via ++ * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to ++ * hsmp_acpi_remove(). ++ */ + ret = init_acpi(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } + +- if (!hsmp_pdev->is_probed) { +- ret = hsmp_misc_register(&pdev->dev); ++ if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { ++ /* ++ * Register /dev/hsmp unparented. It is a singleton shared by all ++ * ACPI sockets and outlives all but the last of them, so ++ * parenting it to this socket's device would leave a dangling ++ * parent once that socket is unbound. ++ */ ++ ret = hsmp_misc_register(NULL); + if (ret) { + dev_err(&pdev->dev, "Failed to register misc device\n"); ++ hsmp_acpi_probe_failure_cleanup(&pdev->dev); + return ret; + } +- hsmp_pdev->is_probed = true; +- dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); ++ dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); + } + + return 0; +@@ -619,14 +766,26 @@ static int hsmp_acpi_probe(struct platform_device *pdev) + + static void hsmp_acpi_remove(struct platform_device *pdev) + { ++ struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); ++ + /* +- * We register only one misc_device even on multi-socket system. +- * So, deregister should happen only once. ++ * Serialize the kref_put() and any release it triggers against a ++ * concurrent probe, and drain the data plane for the whole ++ * teardown: this covers the per-socket unbind, whose mailbox devres ++ * unmaps once we return, and the last unbind that frees the socket ++ * array in hsmp_acpi_sock_release(). + */ +- if (hsmp_pdev->is_probed) { +- hsmp_misc_deregister(); +- hsmp_pdev->is_probed = false; +- } ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ ++ /* ++ * Clear this socket's dev so hsmp_send_message() rejects it before ++ * devres unmaps the mailbox. On a non-final unbind the socket array ++ * stays alive, so without this a later message to this index would ++ * reach an unmapped iomem region. ++ */ ++ sock->dev = NULL; ++ ++ kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); + } + + static struct platform_driver amd_hsmp_driver = { +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c +--- a/drivers/platform/x86/amd/hsmp/hsmp.c ++++ b/drivers/platform/x86/amd/hsmp/hsmp.c +@@ -10,10 +10,17 @@ + #include + + #include ++#include + #include + #include ++#include ++#include ++#include ++#include + #include ++#include + #include ++#include + + #include "hsmp.h" + +@@ -40,6 +47,17 @@ + + static struct hsmp_plat_device hsmp_pdev; + ++/* ++ * Gates the AMD HSMP data plane against socket bring-up and teardown. ++ * ++ * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads ++ * run concurrently. Probe and remove take it for write: probe brings sockets ++ * up (running the mailbox handshake via hsmp_send_message_locked()) and remove ++ * tears them down, both excluding and draining the data plane. ++ */ ++DECLARE_RWSEM(hsmp_sock_rwsem); ++EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); ++ + /* + * Send a message to the HSMP port via PCI-e config space registers + * or by writing to MMIO space. +@@ -182,29 +200,33 @@ static int validate_message(struct hsmp_message *msg) + return -EINVAL; + + /* +- * Some older HSMP SET messages are updated to add GET in the same message. +- * In these messages, GET returns the current value and SET also returns +- * the successfully set value. To support this GET and SET in same message +- * while maintaining backward compatibility for the HSMP users, +- * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. ++ * As the HSMP protocol evolves, newer platforms may define more ++ * response arguments for existing messages. Use an upper-bound ++ * check so that older userspace callers requesting fewer response ++ * words than what the current hsmp_msg_desc_table[] defines are ++ * still accepted, while rejecting requests that exceed the ++ * hardware capability. + */ +- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { +- if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } else { +- /* only HSMP_SET or HSMP_GET messages go through this strict check */ +- if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) +- return -EINVAL; +- } ++ if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) ++ return -EINVAL; ++ + return 0; + } + +-int hsmp_send_message(struct hsmp_message *msg) ++/* ++ * Core message send. The caller must hold hsmp_sock_rwsem: the data plane ++ * takes it for read so many messages run concurrently, while the probe-time ++ * senders run under the write lock taken by probe. Holding it here serializes ++ * every message against socket teardown, which also holds it for write. ++ */ ++static int hsmp_send_message_locked(struct hsmp_message *msg) + { + struct hsmp_socket *sock; + unsigned int sock_ind; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!msg) + return -EINVAL; + ret = validate_message(msg); +@@ -223,6 +245,20 @@ int hsmp_send_message(struct hsmp_message *msg) + sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + ++ /* ++ * A slot exists for every possible socket, but it is only usable once ++ * that socket has actually been probed. Reject messages aimed at a ++ * socket that was never brought up or is still in bring-up, so we never ++ * operate on a zero-initialized semaphore or an unmapped mailbox. A ++ * non-NULL dev also guarantees virt_base_addr, the mailbox offsets and ++ * the semaphore are visible. ++ * ++ * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev) ++ * in hsmp_parse_acpi_table(). ++ */ ++ if (!smp_load_acquire(&sock->dev)) ++ return -ENODEV; ++ + ret = down_interruptible(&sock->hsmp_sem); + if (ret < 0) + return ret; +@@ -233,6 +269,19 @@ int hsmp_send_message(struct hsmp_message *msg) + + return ret; + } ++ ++int hsmp_send_message(struct hsmp_message *msg) ++{ ++ /* ++ * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue ++ * messages from here. Take hsmp_sock_rwsem for read so messages run ++ * concurrently with each other but are drained and kept out while ++ * probe/remove hold it for write to tear a socket down. ++ */ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_send_message_locked(msg); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); + + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) +@@ -273,7 +322,7 @@ int hsmp_test(u16 sock_ind, u32 value) + msg.args[0] = value; + msg.sock_ind = sock_ind; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -301,7 +350,7 @@ static bool is_get_msg(struct hsmp_message *msg) + return false; + } + +-long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) + { + int __user *arguser = (int __user *)arg; + struct hsmp_message msg = { 0 }; +@@ -370,11 +419,139 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) + return 0; + } + +-ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size); ++ ++/* ++ * Fetch the firmware metric (telemetry) table for the requested socket and ++ * copy it to the userspace buffer described by the request. ++ * ++ * The metric table size is variable across HSMP protocol versions and on ++ * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer ++ * size, which may be anything up to the size firmware reported for this ++ * socket's table. ++ */ ++static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) ++{ ++ void *kbuf __free(kvfree) = NULL; ++ void __user *arguser = (void __user *)arg; ++ struct hsmp_telemetry_data req; ++ struct hsmp_socket *sock; ++ void __user *user_buf; ++ size_t tbl_size; ++ unsigned int sock_ind; ++ int ret; ++ ++ /* Telemetry data is read-only; require read access on the fd. */ ++ if (!(fp->f_mode & FMODE_READ)) ++ return -EPERM; ++ ++ if (copy_from_user(&req, arguser, sizeof(req))) ++ return -EFAULT; ++ ++ /* ++ * Reserved fields must be zero so future kernels can safely ++ * repurpose them without breaking already-deployed userspace. ++ */ ++ if (req.reserved) ++ return -EINVAL; ++ ++ user_buf = u64_to_user_ptr(req.buf); ++ ++ /* ++ * /dev/hsmp is a singleton character device that outlives an individual ++ * socket unbind, so an ioctl on an already-open fd can run concurrently ++ * with socket teardown. Hold hsmp_sock_rwsem for read across the socket ++ * lookup, the checks on its metric-table state and the read itself: ++ * probe and remove take the same lock for write, so they cannot free the ++ * socket array, unmap the table or destroy the per-socket mutex while ++ * this runs. ++ * ++ * The lock is dropped before the copy_to_user() below. Faulting in the ++ * destination can block indefinitely on a userfaultfd-backed buffer, ++ * which would leave a socket unbind waiting for the write lock. ++ */ ++ scoped_guard(rwsem_read, &hsmp_sock_rwsem) { ++ if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) ++ return -ENODEV; ++ ++ /* ++ * Sanitize the user-controlled socket index against speculative ++ * execution. The bounds check above retires the out-of-range ++ * case with -ENODEV, but a mispredicted branch can still let the ++ * CPU speculatively use sock_ind as an index into ++ * hsmp_pdev.sock[] and pull arbitrary kernel memory into the ++ * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns ++ * the bounds check into a data-flow clamp so the speculative ++ * load is in-range too. ++ */ ++ sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); ++ sock = &hsmp_pdev.sock[sock_ind]; ++ if (!sock->metric_tbl_addr) ++ return -ENODEV; ++ ++ tbl_size = sock->metric_tbl_size; ++ if (!tbl_size) ++ return -ENODEV; ++ ++ /* ++ * A request shorter than the firmware table is served with the ++ * leading @size bytes of the snapshot, so userspace built ++ * against an older table layout keeps working on firmware that ++ * grew the table. Asking for more than firmware provides is ++ * rejected rather than short-written, so a caller can never ++ * mistake a partial copy for a full one. ++ */ ++ if (!req.size || req.size > tbl_size) ++ return -EINVAL; ++ ++ /* ++ * The bounce buffer is overwritten in full by memcpy_fromio() ++ * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid ++ * the zeroing cost of kvzalloc() on the ~13 KB allocation done ++ * on every ioctl call. ++ */ ++ kbuf = kvmalloc(tbl_size, GFP_KERNEL); ++ if (!kbuf) ++ return -ENOMEM; ++ ++ ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); ++ } ++ ++ if (ret < 0) ++ return ret; ++ ++ if (copy_to_user(user_buf, kbuf, req.size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) ++{ ++ switch (cmd) { ++ case HSMP_IOCTL_CMD: ++ return hsmp_ioctl_msg(fp, arg); ++ case HSMP_IOCTL_GET_TELEMETRY_DATA: ++ return hsmp_ioctl_get_telemetry(fp, arg); ++ default: ++ return -ENOTTY; ++ } ++} ++ ++/* ++ * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping ++ * and its metric_read_lock alive: probe and remove take the same lock for ++ * write while they bring sockets up and tear them down. ++ */ ++static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, ++ size_t size) + { + struct hsmp_message msg = { 0 }; + int ret; + ++ lockdep_assert_held(&hsmp_sock_rwsem); ++ + if (!sock || !buf) + return -EINVAL; + +@@ -383,8 +560,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + return -ENOMEM; + } + +- /* Do not support lseek(), also don't allow more than the size of metric table */ +- if (size != sizeof(struct hsmp_metric_table)) { ++ if (size != sock->metric_tbl_size) { + dev_err(sock->dev, "Wrong buffer size\n"); + return -EINVAL; + } +@@ -392,27 +568,77 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) + msg.msg_id = HSMP_GET_METRIC_TABLE; + msg.sock_ind = sock->sock_ind; + +- ret = hsmp_send_message(&msg); ++ /* ++ * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared ++ * metric DRAM region, which is then copied out below. Hold the ++ * per-socket lock across the fill-and-copy so concurrent readers of the ++ * same socket cannot return a torn snapshot. ++ */ ++ guard(mutex)(&sock->metric_read_lock); ++ ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + memcpy_fromio(buf, sock->metric_tbl_addr, size); + + return size; + } ++ ++ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) ++{ ++ guard(rwsem_read)(&hsmp_sock_rwsem); ++ ++ return hsmp_metric_tbl_read_locked(sock, buf, size); ++} + EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); + ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_init(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) ++{ ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) ++ mutex_destroy(&pdev->sock[i].metric_read_lock); ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); ++ ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) ++{ ++ struct hsmp_socket *sock; ++ u16 i; ++ ++ for (i = 0; i < pdev->num_sockets; i++) { ++ sock = &pdev->sock[i]; ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ } ++} ++EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); ++ + int hsmp_get_tbl_dram_base(u16 sock_ind) + { + struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; + struct hsmp_message msg = { 0 }; + phys_addr_t dram_addr; ++ size_t tbl_size; + int ret; + + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; + msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (ret) + return ret; + +@@ -425,12 +651,33 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) + dev_err(sock->dev, "Invalid DRAM address for metric table\n"); + return -ENOMEM; + } +- sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, +- sizeof(struct hsmp_metric_table)); ++ /* ++ * The ACPI socket array is shared across sockets and outlives a ++ * per-socket unbind, so metric_tbl_addr may hold a mapping from an ++ * earlier bind of this socket. Unmap it before remapping so an ++ * unbind/rebind cycle does not leak a metric-table mapping. This runs ++ * during probe before the metric sysfs attribute is exposed, so no ++ * reader can be using it. ++ */ ++ if (sock->metric_tbl_addr) { ++ iounmap(sock->metric_tbl_addr); ++ sock->metric_tbl_addr = NULL; ++ } ++ sock->metric_tbl_size = 0; ++ ++ /* SMU returns table size from Family 1Ah Model 50h and forward */ ++ if (msg.args[2]) ++ tbl_size = msg.args[2]; ++ else ++ tbl_size = sizeof(struct hsmp_metric_table); ++ ++ sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); + if (!sock->metric_tbl_addr) { + dev_err(sock->dev, "Failed to ioremap metric table addr\n"); + return -ENOMEM; + } ++ sock->metric_tbl_size = tbl_size; ++ + return 0; + } + EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP"); +@@ -444,7 +691,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) + msg.sock_ind = sock_ind; + msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; + +- ret = hsmp_send_message(&msg); ++ ret = hsmp_send_message_locked(&msg); + if (!ret) + hsmp_pdev.proto_ver = msg.args[0]; + +@@ -463,6 +710,14 @@ int hsmp_misc_register(struct device *dev) + hsmp_pdev.mdev.name = HSMP_CDEV_NAME; + hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; + hsmp_pdev.mdev.fops = &hsmp_fops; ++ /* ++ * The caller chooses the parent. The platform driver has a single ++ * device whose lifetime matches /dev/hsmp and parents it there. The ++ * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by ++ * per-socket devices that can be unbound individually and out of order, ++ * so parenting it to one would leave it attached to an already-removed ++ * device. ++ */ + hsmp_pdev.mdev.parent = dev; + hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; + hsmp_pdev.mdev.mode = 0644; +@@ -474,6 +729,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP"); + void hsmp_misc_deregister(void) + { + misc_deregister(&hsmp_pdev.mdev); ++ hsmp_pdev.mdev.this_device = NULL; + } + EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); + +diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h +--- a/drivers/platform/x86/amd/hsmp/hsmp.h ++++ b/drivers/platform/x86/amd/hsmp/hsmp.h +@@ -15,9 +15,12 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include ++#include + + #define HSMP_METRICS_TABLE_NAME "metrics_bin" + +@@ -27,7 +30,7 @@ + #define HSMP_DEVNODE_NAME "hsmp" + #define ACPI_HSMP_DEVICE_HID "AMDI0097" + +-#define DRIVER_VERSION "2.5" ++#define DRIVER_VERSION "2.6" + + struct hsmp_mbaddr_info { + u32 base_addr; +@@ -41,8 +44,12 @@ struct hsmp_socket { + struct bin_attribute hsmp_attr; + struct hsmp_mbaddr_info mbinfo; + void __iomem *metric_tbl_addr; ++ /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ ++ size_t metric_tbl_size; + void __iomem *virt_base_addr; + struct semaphore hsmp_sem; ++ /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ ++ struct mutex metric_read_lock; + char name[HSMP_ATTR_GRP_NAME_SIZE]; + struct device *dev; + u16 sock_ind; +@@ -54,7 +61,6 @@ struct hsmp_plat_device { + struct hsmp_socket *sock; + u32 proto_ver; + u16 num_sockets; +- bool is_probed; + }; + + int hsmp_cache_proto_ver(u16 sock_ind); +@@ -63,6 +69,9 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); + void hsmp_misc_deregister(void); + int hsmp_misc_register(struct device *dev); + int hsmp_get_tbl_dram_base(u16 sock_ind); ++void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); ++void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); ++void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); + ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); + struct hsmp_plat_device *get_hsmp_pdev(void); + #if IS_ENABLED(CONFIG_HWMON) +@@ -71,4 +80,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); + static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } + #endif + int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); ++ ++/* ++ * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and ++ * remove take it for write to bring sockets up and tear them down. ++ */ ++extern struct rw_semaphore hsmp_sock_rwsem; + #endif /* HSMP_H */ +diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c +--- a/drivers/platform/x86/amd/hsmp/plat.c ++++ b/drivers/platform/x86/amd/hsmp/plat.c +@@ -13,12 +13,14 @@ + + #include + #include ++#include + #include + #include + #include + #include + #include + #include ++#include + #include + + #include +@@ -201,6 +203,25 @@ static int init_platform_device(struct device *dev) + return 0; + } + ++/* ++ * The socket array is devm-managed and freed by the driver core, but the ++ * metric-table DRAM regions are mapped with plain ioremap() during probe and ++ * the per-socket mutexes need an explicit mutex_destroy(), neither of which ++ * devres covers. ++ * ++ * Take the data-plane rwsem for write to drain any in-flight ++ * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop ++ * the global socket pointer, all before devres frees the array. Registered as ++ * a devres action so it runs on both remove and probe failure. ++ */ ++static void hsmp_pltdrv_release(void *data) ++{ ++ guard(rwsem_write)(&hsmp_sock_rwsem); ++ hsmp_unmap_metric_tbls(hsmp_pdev); ++ hsmp_destroy_metric_read_locks(hsmp_pdev); ++ hsmp_pdev->sock = NULL; ++} ++ + static int hsmp_pltdrv_probe(struct platform_device *pdev) + { + int ret; +@@ -211,7 +232,22 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) + if (!hsmp_pdev->sock) + return -ENOMEM; + +- ret = init_platform_device(&pdev->dev); ++ hsmp_init_metric_read_locks(hsmp_pdev); ++ ++ ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); ++ if (ret) ++ return ret; ++ ++ /* ++ * init_platform_device() runs the mailbox handshake via the probe-only ++ * senders, which issue messages through hsmp_send_message_locked() and ++ * so require hsmp_sock_rwsem held. Hold it for write, matching probe's ++ * role as a socket bring-up path. The lock is not held across ++ * devm_add_action_or_reset() above so the release action, which also ++ * takes it for write, does not deadlock if that registration fails. ++ */ ++ scoped_guard(rwsem_write, &hsmp_sock_rwsem) ++ ret = init_platform_device(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); + return ret; +diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c +--- a/drivers/platform/x86/amd/pmc/mp1_stb.c ++++ b/drivers/platform/x86/amd/pmc/mp1_stb.c +@@ -299,10 +299,7 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + if (!enable_stb) + return 0; + +- if (amd_is_stb_supported(dev)) { +- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, +- &amd_stb_debugfs_fops_v2); +- } else { ++ if (!amd_is_stb_supported(dev)) { + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_stb_debugfs_fops); + return 0; +@@ -342,8 +339,18 @@ int amd_stb_s2d_init(struct amd_pmc_dev *dev) + } + + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); +- if (!dev->stb_virt_addr) ++ if (!dev->stb_virt_addr) { + ret = -ENOMEM; ++ goto out; ++ } ++ ++ /* ++ * Only expose stb_read once the buffer is mapped; otherwise a read ++ * faults on a NULL dev->stb_virt_addr, now that a failed STB init no ++ * longer aborts probe. ++ */ ++ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, ++ &amd_stb_debugfs_fops_v2); + + out: + /* Restore the default message port for subsequent SMU operations */ +diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c +--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c ++++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c +@@ -71,6 +71,14 @@ static const struct dmi_system_id fwbug_list[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "20XK"), + } + }, ++ { ++ .ident = "T14 Gen2 AMD", ++ .driver_data = &quirk_s2idle_spurious_8042, ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "20XL"), ++ } ++ }, + { + .ident = "T14 Gen1 AMD", + .driver_data = &quirk_s2idle_spurious_8042, +diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c +--- a/drivers/platform/x86/amd/pmc/pmc.c ++++ b/drivers/platform/x86/amd/pmc/pmc.c +@@ -939,19 +939,22 @@ static int amd_pmc_probe(struct platform_device *pdev) + } + + amd_pmc_dbgfs_register(dev); ++ ++ /* ++ * STB is an optional debug facility (enable_stb); a failure to set it ++ * up must not stop the rest of the driver - most importantly the s0i3 ++ * LPS0 handler - from working, so treat it as non-fatal. ++ */ + err = amd_stb_s2d_init(dev); + if (err) +- goto err_dbgfs_unregister; ++ dev_warn(dev->dev, "STB initialization failed (%d), continuing without STB support\n", ++ err); + + if (IS_ENABLED(CONFIG_AMD_MP2_STB)) + amd_mp2_stb_init(dev); + pm_report_max_hw_sleep(U64_MAX); + return 0; + +-err_dbgfs_unregister: +- amd_pmc_dbgfs_unregister(dev); +- if (IS_ENABLED(CONFIG_SUSPEND)) +- acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); + err_pci_dev_put: + pci_dev_put(rdev); + return err; +diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig +--- a/drivers/platform/x86/amd/pmf/Kconfig ++++ b/drivers/platform/x86/amd/pmf/Kconfig +@@ -30,3 +30,13 @@ config AMD_PMF_DEBUG + in the PMF config store. + + Say Y here to enable more debug logs and Say N here if you are not sure. ++ ++config AMD_PMF_UTIL_SUPPORT ++ bool "AMD PMF Util layer support" ++ depends on AMD_PMF ++ help ++ Enabling this option provides a character device for userspace to capture ++ PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic ++ Power Slider AC/DC) along with PMF metrics from the AMD PMF driver. ++ ++ Say Y here to enable it and Say N here if you are not sure. +diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile +--- a/drivers/platform/x86/amd/pmf/Makefile ++++ b/drivers/platform/x86/amd/pmf/Makefile +@@ -7,4 +7,6 @@ + obj-$(CONFIG_AMD_PMF) += amd-pmf.o + amd-pmf-y := core.o acpi.o sps.o \ + auto-mode.o cnqf.o \ +- tee-if.o spc.o ++ tee-if.o spc.o metrics.o ++# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled ++amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o +diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c +--- a/drivers/platform/x86/amd/pmf/core.c ++++ b/drivers/platform/x86/amd/pmf/core.c +@@ -11,13 +11,13 @@ + #include + #include + #include ++#include + #include + #include + #include + #include + #include + #include +-#include + #include + #include "pmf.h" + +@@ -26,6 +26,13 @@ + #define AMD_PMF_REGISTER_RESPONSE 0xA78 + #define AMD_PMF_REGISTER_ARGUMENT 0xA58 + ++/* PMF-SMU communication registers for 1AH_M80H */ ++#define AMD_PMF_REGISTER_MESSAGE_V2 0xA04 ++#define AMD_PMF_REGISTER_RESPONSE_V2 0xA08 ++#define AMD_PMF_REGISTER_ARGUMENT0_V2 0xA0C ++#define AMD_PMF_REGISTER_ARGUMENT1_V2 0xAAC ++#define AMD_PMF_REGISTER_ARGUMENT2_V2 0xAB0 ++ + /* Base address of SMU for mapping physical address to virtual address */ + #define AMD_PMF_MAPPING_SIZE 0x01000 + #define AMD_PMF_BASE_ADDR_OFFSET 0x10000 +@@ -48,7 +55,7 @@ + #define DELAY_MAX_US 3000 + + /* override Metrics Table sample size time (in ms) */ +-static int metrics_table_loop_ms = 1000; ++int metrics_table_loop_ms = 1000; + module_param(metrics_table_loop_ms, int, 0644); + MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)"); + +@@ -61,7 +68,15 @@ static bool smart_pc_support = true; + module_param(smart_pc_support, bool, 0444); + MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)"); + +-static struct device *pmf_device; ++static bool amd_pmf_supports_accumulator_metrics(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return true; ++ default: ++ return false; ++ } ++} + + static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data) + { +@@ -131,37 +146,6 @@ int amd_pmf_get_power_source(void) + return POWER_SOURCE_DC; + } + +-static void amd_pmf_get_metrics(struct work_struct *work) +-{ +- struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); +- ktime_t time_elapsed_ms; +- int socket_power; +- +- guard(mutex)(&dev->update_mutex); +- +- /* Transfer table contents */ +- memset(dev->buf, 0, sizeof(dev->m_table)); +- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); +- +- time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; +- /* Calculate the avg SoC power consumption */ +- socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; +- +- if (dev->amt_enabled) { +- /* Apply the Auto Mode transition */ +- amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); +- } +- +- if (dev->cnqf_enabled) { +- /* Apply the CnQF transition */ +- amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); +- } +- +- dev->start_time = ktime_to_ms(ktime_get()); +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); +-} +- + static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset) + { + return ioread32(dev->regbase + reg_offset); +@@ -176,13 +160,19 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev) + { + u32 value; + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->resp_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value); + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value); ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[1]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT1:%d\n", value); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[2]); ++ dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT2:%d\n", value); ++ } + +- value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE); ++ value = amd_pmf_reg_read(dev, dev->smu_regs->msg_reg); + dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value); + } + +@@ -208,7 +198,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + guard(mutex)(&dev->lock); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -217,16 +207,16 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + } + + /* Write zero to response register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0); ++ amd_pmf_reg_write(dev, dev->smu_regs->resp_reg, 0); + + /* Write argument into argument register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg); ++ amd_pmf_reg_write(dev, dev->smu_regs->arg_reg[0], arg); + + /* Write message ID to message ID register */ +- amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message); ++ amd_pmf_reg_write(dev, dev->smu_regs->msg_reg, message); + + /* Wait until we get a valid response */ +- rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE, ++ rc = readx_poll_timeout(ioread32, dev->regbase + dev->smu_regs->resp_reg, + val, val != 0, PMF_MSG_DELAY_MIN_US, + PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { +@@ -239,7 +229,14 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + if (get) { + /* PMFW may take longer time to return back the data */ + usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); +- *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT); ++ *data = amd_pmf_reg_read(dev, dev->smu_regs->arg_reg[0]); ++ if (amd_pmf_supports_accumulator_metrics(dev) && ++ message == GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR) { ++ dev->dram_addr.hi = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[1]); ++ dev->dram_addr.size = amd_pmf_reg_read(dev, ++ dev->smu_regs->arg_reg[2]); ++ } + } + break; + case AMD_PMF_RESULT_CMD_REJECT_BUSY: +@@ -262,132 +259,32 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 + return rc; + } + +-static const struct pci_device_id pmf_pci_ids[] = { +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) }, +- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) }, +- { } ++/* RMB, PS, 1AH_M20H and 1AH_M60H share the same v1 SMU mailbox registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v1 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE, ++ .arg_reg = { AMD_PMF_REGISTER_ARGUMENT, 0, 0 }, + }; + +-int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) +-{ +- u64 phys_addr; +- u32 hi, low; ++/* 1AH_M80H uses an extended mailbox with three argument registers */ ++static const struct amd_pmf_smu_regs amd_pmf_smu_regs_v2 = { ++ .msg_reg = AMD_PMF_REGISTER_MESSAGE_V2, ++ .resp_reg = AMD_PMF_REGISTER_RESPONSE_V2, ++ .arg_reg = { ++ AMD_PMF_REGISTER_ARGUMENT0_V2, ++ AMD_PMF_REGISTER_ARGUMENT1_V2, ++ AMD_PMF_REGISTER_ARGUMENT2_V2, ++ }, ++}; + +- /* Get Metrics Table Address */ +- if (alloc_buffer) { +- switch (dev->cpu_id) { +- case AMD_CPU_ID_PS: +- case AMD_CPU_ID_RMB: +- dev->mtable_size = sizeof(dev->m_table); +- break; +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- dev->mtable_size = sizeof(dev->m_table_v2); +- break; +- default: +- dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id); +- } +- +- dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); +- if (!dev->buf) +- return -ENOMEM; +- } +- +- phys_addr = virt_to_phys(dev->buf); +- hi = phys_addr >> 32; +- low = phys_addr & GENMASK(31, 0); +- +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); +- amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); +- +- return 0; +-} +- +-int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) +-{ +- int ret; +- +- INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- /* +- * Start collecting the metrics data after a small delay +- * or else, we might end up getting stale values from PMFW. +- */ +- schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); +- +- return 0; +-} +- +-static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) +-{ +- switch (pdev->cpu_id) { +- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: +- case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: +- return 0; +- default: +- return -EOPNOTSUPP; +- } +-} +- +-static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) +-{ +- int ret, i; +- +- guard(mutex)(&dev->metrics_mutex); +- +- ret = is_npu_metrics_supported(dev); +- if (ret) +- return ret; +- +- ret = amd_pmf_set_dram_addr(dev, true); +- if (ret) +- return ret; +- +- memset(dev->buf, 0, dev->mtable_size); +- +- /* Send SMU command to get NPU metrics */ +- ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); +- if (ret) { +- dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); +- return ret; +- } +- +- memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); +- +- data->npuclk_freq = dev->m_table_v2.npuclk_freq; +- for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) +- data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; +- data->npu_power = dev->m_table_v2.npu_power; +- data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; +- data->npu_reads = dev->m_table_v2.npu_reads; +- data->npu_writes = dev->m_table_v2.npu_writes; +- +- return 0; +-} +- +-int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) +-{ +- struct amd_pmf_dev *pdev; +- +- if (!info) +- return -EINVAL; +- +- if (!pmf_device) +- return -ENODEV; +- +- pdev = dev_get_drvdata(pmf_device); +- if (!pdev) +- return -ENODEV; +- +- return amd_pmf_get_smu_metrics(pdev, info); +-} +-EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); ++static const struct pci_device_id pmf_pci_ids[] = { ++ { PCI_DEVICE_DATA(AMD, CPU_ID_RMB, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, CPU_ID_PS, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M20H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M60H_ROOT, &amd_pmf_smu_regs_v1) }, ++ { PCI_DEVICE_DATA(AMD, 1AH_M80H_ROOT, &amd_pmf_smu_regs_v2) }, ++ { } ++}; + + static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev) + { +@@ -536,6 +433,19 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev) + } + } + ++static int amd_pmf_get_smu_mb_offset(struct amd_pmf_dev *pdev, struct pci_dev *rdev) ++{ ++ const struct pci_device_id *id; ++ ++ id = pci_match_id(pmf_pci_ids, rdev); ++ if (!id) ++ return -ENODEV; ++ ++ pdev->smu_regs = (const struct amd_pmf_smu_regs *)id->driver_data; ++ ++ return 0; ++} ++ + static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0100", 0x100}, + {"AMDI0102", 0}, +@@ -543,6 +453,7 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = { + {"AMDI0105", 0}, + {"AMDI0107", 0}, + {"AMDI0108", 0}, ++ {"AMDI0109", 0}, + { } + }; + MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids); +@@ -624,6 +535,17 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (err) + return err; + ++ /* Populate smu_regs with SoC-specific SMU mailbox register offsets */ ++ err = amd_pmf_get_smu_mb_offset(dev, rdev); ++ if (err) ++ return err; ++ ++ if (amd_pmf_supports_accumulator_metrics(dev)) { ++ err = amd_pmf_get_tbl_dram_addr(dev); ++ if (err) ++ return err; ++ } ++ + apmf_acpi_init(dev); + platform_set_drvdata(pdev, dev); + amd_pmf_dbgfs_register(dev); +@@ -632,7 +554,11 @@ static int amd_pmf_probe(struct platform_device *pdev) + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD); + +- pmf_device = dev->dev; ++ amd_pmf_set_device(dev->dev); ++ ++ err = amd_pmf_cdev_register(dev); ++ if (err) ++ dev_warn(dev->dev, "failed to register util interface: %d\n", err); + + dev_info(dev->dev, "registered PMF device successfully\n"); + +@@ -643,6 +569,7 @@ static void amd_pmf_remove(struct platform_device *pdev) + { + struct amd_pmf_dev *dev = platform_get_drvdata(pdev); + ++ amd_pmf_cdev_unregister(); + amd_pmf_deinit_features(dev); + if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) + amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD); +diff --git a/drivers/platform/x86/amd/pmf/metrics.c b/drivers/platform/x86/amd/pmf/metrics.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/metrics.c +@@ -0,0 +1,330 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Driver - Metrics Support ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Patil Rajesh Reddy ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct device *pmf_device; ++ ++static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT; ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u16 amd_pmf_q10_acc_to_int(u64 avg_acc) ++{ ++ u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024); ++ ++ return min_t(u16, val, U16_MAX); ++} ++ ++static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter, ++ u64 curr_value, u64 prev_value) ++{ ++ u32 counter_diff; ++ u64 val_diff; ++ ++ /* Check for counter reset */ ++ if (curr_counter <= prev_counter) ++ return 0; ++ ++ counter_diff = curr_counter - prev_counter; ++ ++ if (curr_value < prev_value) ++ return 0; ++ ++ val_diff = curr_value - prev_value; ++ ++ return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff); ++} ++ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD, ++ ARG_NONE, &dev->dram_addr.lo); ++ if (ret) { ++ dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret); ++ return ret; ++ } ++ ++ dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo; ++ dev->mtable_size = dev->dram_addr.size; ++ ++ if (dev->mtable_size != sizeof(dev->mtable_v3)) { ++ dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n", ++ dev->dram_addr.size, sizeof(dev->mtable_v3)); ++ return -EINVAL; ++ } ++ ++ dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size); ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n"); ++ dev->metrics_table_phys = 0; ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ if (!dev->metrics_table_virt) { ++ dev_err(dev->dev, "Metrics DRAM not mapped\n"); ++ return -EINVAL; ++ } ++ ++ /* Send command to PMFW to update metrics table log sample */ ++ ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL); ++ if (ret) { ++ dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void amd_pmf_get_metrics(struct work_struct *work) ++{ ++ struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work); ++ ktime_t time_elapsed_ms; ++ int socket_power; ++ ++ guard(mutex)(&dev->update_mutex); ++ ++ /* Transfer table contents */ ++ memset(dev->buf, 0, sizeof(dev->m_table)); ++ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table)); ++ ++ time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time; ++ /* Calculate the avg SoC power consumption */ ++ socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power; ++ ++ if (dev->amt_enabled) { ++ /* Apply the Auto Mode transition */ ++ amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms); ++ } ++ ++ if (dev->cnqf_enabled) { ++ /* Apply the CnQF transition */ ++ amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms); ++ } ++ ++ dev->start_time = ktime_to_ms(ktime_get()); ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms)); ++} ++ ++int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer) ++{ ++ u64 phys_addr; ++ u32 hi, low; ++ ++ /* Get Metrics Table Address */ ++ if (alloc_buffer) { ++ switch (dev->cpu_id) { ++ case AMD_CPU_ID_PS: ++ case AMD_CPU_ID_RMB: ++ dev->mtable_size = sizeof(dev->m_table); ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ dev->mtable_size = sizeof(dev->m_table_v2); ++ break; ++ default: ++ dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id); ++ } ++ ++ dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL); ++ if (!dev->buf) ++ return -ENOMEM; ++ } ++ ++ phys_addr = virt_to_phys(dev->buf); ++ hi = upper_32_bits(phys_addr); ++ low = lower_32_bits(phys_addr); ++ ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL); ++ amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL); ++ ++ return 0; ++} ++ ++int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics); ++ ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ /* ++ * Start collecting the metrics data after a small delay ++ * or else, we might end up getting stale values from PMFW. ++ */ ++ schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3)); ++ ++ return 0; ++} ++ ++void amd_pmf_set_device(struct device *p_device) ++{ ++ pmf_device = p_device; ++} ++ ++static int is_npu_metrics_supported(struct amd_pmf_dev *pdev) ++{ ++ switch (pdev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ return 0; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev, ++ struct amd_pmf_npu_metrics *data) ++{ ++ struct amd_pmf_metrics_iod *curr, *prev; ++ u64 val; ++ int i; ++ ++ curr = &dev->mtable_v3.iod; ++ prev = &dev->prev_metrics.iod; ++ ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_temp_acc, prev->npu_temp_acc); ++ data->npu_temp = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_power_acc, prev->npu_power_acc); ++ data->npu_power = amd_pmf_q10_acc_to_mW(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npuhclk_freq_eff_acc, ++ prev->npuhclk_freq_eff_acc); ++ data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val); ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->aieclk_freq_eff_acc, ++ prev->aieclk_freq_eff_acc); ++ data->npuclk_freq = amd_pmf_q10_acc_to_int(val); ++ ++ for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) { ++ val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc, ++ curr->npu_busy_acc[i], ++ prev->npu_busy_acc[i]); ++ data->npu_busy[i] = amd_pmf_q10_acc_to_int(val); ++ } ++} ++ ++static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data) ++{ ++ int ret, i; ++ ++ guard(mutex)(&dev->metrics_mutex); ++ ++ ret = is_npu_metrics_supported(dev); ++ if (ret) ++ return ret; ++ ++ memset(data, 0, sizeof(*data)); ++ ++ switch (dev->cpu_id) { ++ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: ++ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: ++ ret = amd_pmf_set_dram_addr(dev, true); ++ if (ret) ++ return ret; ++ ++ memset(dev->buf, 0, dev->mtable_size); ++ ++ /* Send SMU command to get NPU metrics */ ++ ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL); ++ if (ret) { ++ dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret); ++ return ret; ++ } ++ ++ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size); ++ ++ data->npuclk_freq = dev->m_table_v2.npuclk_freq; ++ for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++) ++ data->npu_busy[i] = dev->m_table_v2.npu_busy[i]; ++ data->npu_power = dev->m_table_v2.npu_power; ++ data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq; ++ data->npu_reads = dev->m_table_v2.npu_reads; ++ data->npu_writes = dev->m_table_v2.npu_writes; ++ break; ++ case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT: ++ ret = amd_pmf_get_metrics_table_log_sample(dev); ++ if (ret) ++ return ret; ++ ++ memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3)); ++ ++ /* ++ * Ignore the first sample, as previous metrics is uninitialized (zero) ++ * Metrics are calculated as: ++ * metrics = current_metrics - previous_metrics ++ * Skipping the initial sample ensures accurate delta calculations. ++ */ ++ if (!dev->npu_metrics_have_prev) { ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ dev->npu_metrics_have_prev = true; ++ return 0; ++ } ++ ++ amd_pmf_calculate_acc_npu_metrics(dev, data); ++ memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3)); ++ break; ++ } ++ ++ return 0; ++} ++ ++int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info) ++{ ++ struct amd_pmf_dev *pdev; ++ ++ if (!info) ++ return -EINVAL; ++ ++ if (!pmf_device) ++ return -ENODEV; ++ ++ pdev = dev_get_drvdata(pmf_device); ++ if (!pdev) ++ return -ENODEV; ++ ++ return amd_pmf_get_smu_metrics(pdev, info); ++} ++EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF"); +diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h +--- a/drivers/platform/x86/amd/pmf/pmf.h ++++ b/drivers/platform/x86/amd/pmf/pmf.h +@@ -14,10 +14,13 @@ + #include + #include + #include ++#include ++#include + #include + #include + #include + #include ++#include + + #define POLICY_BUF_MAX_SZ 0x4b000 + #define POLICY_SIGN_COOKIE 0x31535024 +@@ -28,6 +31,11 @@ + #define AMD_CPU_ID_PS 0x14e8 + #define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507 + #define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122 ++#define PCI_DEVICE_ID_AMD_1AH_M80H_ROOT 0x115b ++ ++/* Aliases required by PCI_DEVICE_DATA() macro naming convention */ ++#define PCI_DEVICE_ID_AMD_CPU_ID_RMB AMD_CPU_ID_RMB ++#define PCI_DEVICE_ID_AMD_CPU_ID_PS AMD_CPU_ID_PS + + struct cookie_header { + u32 sign; +@@ -70,6 +78,10 @@ struct cookie_header { + #define SET_PMF_PPT 0x25 + #define SET_PMF_PPT_APU_ONLY 0x26 + ++/* Message IDs for 1AH_M80H platform */ ++#define GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE 0x0E ++#define GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR 0x0F ++ + /* OS slider update notification */ + #define DC_BEST_PERF 0 + #define DC_BETTER_PERF 1 +@@ -130,6 +142,14 @@ struct cookie_header { + #define GET_CMD true + + #define METRICS_TABLE_ID 7 ++#define BIOS_OUTPUT_MAX 10 ++ ++extern int metrics_table_loop_ms; ++ ++#define AMD_PMF_METRIC_CORE_TYPE_MAX 4 ++#define AMD_PMF_METRIC_CCX_MAX 4 ++#define AMD_PMF_NUM_CLK_DPM_LEVELS 8 ++#define AMD_PMF_NUM_MAX_CORES 12 + + typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); + +@@ -242,6 +262,199 @@ struct apmf_fan_idx { + u32 fan_ctl_idx; + } __packed; + ++struct amd_pmf_metrics_iod { ++ u32 counter_acc; ++ /* Set Voltage */ ++ u64 vddcr_set_voltage; ++ u64 vddcr_soc_set_voltage; ++ u64 vddcr_npu_set_voltage; ++ u64 vddcr_lp_set_voltage; ++ u64 vddcr_gfx_set_voltage; ++ u64 vdd_misc_set_voltage; ++ /* Telemetry Voltages */ ++ u64 vddcr_telemetry_voltage; ++ u64 vddcr_soc_telemetry_voltage; ++ u64 vddcr_npu_telemetry_voltage; ++ u64 vddcr_lp_telemetry_voltage; ++ u64 vddcr_gfx_telemetry_voltage; ++ u64 vdd_misc_telemetry_voltage; ++ /* Telemetry Powers */ ++ u64 vddcr_telemetry_power; ++ u64 vddcr_soc_telemetry_power; ++ u64 vddcr_npu_telemetry_power; ++ u64 vddcr_lp_telemetry_power; ++ u64 vddcr_gfx_telemetry_power; ++ u64 vdd_misc_telemetry_power; ++ /* Throttlers - Fast PPT */ ++ u32 fppt_fused_limit; ++ u32 fppt_max_irm_limit; ++ u32 fppt_max_pbo_limit; ++ u32 fppt_limit; ++ u64 fppt_value_acc; ++ u32 fppt_residency_acc; ++ /* Throttlers - Slow PPT */ ++ u32 sppt_fused_limit; ++ u32 sppt_max_irm_limit; ++ u32 sppt_max_pbo_limit; ++ u32 sppt_limit; ++ u64 sppt_value_acc; ++ u32 sppt_residency_acc; ++ /* Throttlers - STAPM */ ++ u32 spl_fused_limit; ++ u32 spl_max_irm_limit; ++ u32 spl_max_pbo_limit; ++ u32 spl_limit; ++ u64 spl_value_acc; ++ u32 spl_residency_acc; ++ /* Throttlers - TDC VDDCR */ ++ u32 tdc_vddcr_fused_limit; ++ u32 tdc_vddcr_max_irm_limit; ++ u32 tdc_vddcr_max_pbo_limit; ++ u32 tdc_vddcr_limit; ++ u64 tdc_vddcr_value_acc; ++ u32 tdc_vddcr_residency_acc; ++ /* Throttlers - TDC VDDCR_SOC */ ++ u32 tdc_vddcr_soc_fused_limit; ++ u32 tdc_vddcr_soc_max_irm_limit; ++ u32 tdc_vddcr_soc_max_pbo_limit; ++ u32 tdc_vddcr_soc_limit; ++ u64 tdc_vddcr_soc_value_acc; ++ u32 tdc_vddcr_soc_residency_acc; ++ /* Throttlers - TDC VDDCR_NPU */ ++ u32 tdc_vddcr_npu_fused_limit; ++ u32 tdc_vddcr_npu_max_irm_limit; ++ u32 tdc_vddcr_npu_max_pbo_limit; ++ u32 tdc_vddcr_npu_limit; ++ u64 tdc_vddcr_npu_value_acc; ++ u32 tdc_vddcr_npu_residency_acc; ++ /* Throttlers - TDC VDDCR_LP */ ++ u32 tdc_vddcr_lp_fused_limit; ++ u32 tdc_vddcr_lp_max_irm_limit; ++ u32 tdc_vddcr_lp_max_pbo_limit; ++ u32 tdc_vddcr_lp_limit; ++ u64 tdc_vddcr_lp_value_acc; ++ u32 tdc_vddcr_lp_residency_acc; ++ /* Throttlers - TDC VDDCR_GFX */ ++ u32 tdc_vddcr_gfx_fused_limit; ++ u32 tdc_vddcr_gfx_max_irm_limit; ++ u32 tdc_vddcr_gfx_max_pbo_limit; ++ u32 tdc_vddcr_gfx_limit; ++ u64 tdc_vddcr_gfx_value_acc; ++ u32 tdc_vddcr_gfx_residency_acc; ++ /* Throttlers - EDC VDDCR */ ++ u32 edc_vddcr_fused_limit; ++ u32 edc_vddcr_max_irm_limit; ++ u32 edc_vddcr_max_pbo_limit; ++ u32 edc_vddcr_limit; ++ /* Throttlers - Thermal */ ++ u32 thm_fused_limit; ++ u32 thm_limit; ++ u64 thm_value_acc; ++ u32 thm_residency_acc; ++ u32 prochot_residency_acc; ++ u64 gfx_temp_acc; ++ u64 soc_temp_acc; ++ u32 p3t_fused_limit; ++ u64 p3t_value_acc; ++ /* Power */ ++ u64 system_power_acc; ++ u64 apu_power_acc; ++ u64 dgpu_power_acc; ++ u64 npu_power_acc; ++ /* Frequencies */ ++ u64 fclk_freq_eff_acc; ++ u64 memclk_freq_eff_acc; ++ u64 lclk_freq_eff_acc; ++ u64 gfxclk_freq_eff_acc; ++ u64 socclk_freq_eff_acc; ++ u64 vclk_freq_eff_acc; ++ u64 vpeclk_freq_eff_acc; ++ u64 aieclk_freq_eff_acc; ++ u64 npuhclk_freq_eff_acc; ++ /* Bandwidth */ ++ u64 dram_read_bandwidth; ++ u64 dram_write_bandwidth; ++ /* Activity Monitors */ ++ u64 gfx_busy_acc; ++ u64 vcn_busy_acc; ++ u64 npu_busy_acc[3]; ++ /* STT Limits */ ++ u32 stt_min_limit; ++ u64 stt_apu_hotspot_temp_acc; ++ u64 stt_hs2_hotspot_temp_acc; ++ u32 stt_apu_temp_limit; ++ u64 stt_apu_skin_temp_acc; ++ /* Residencies */ ++ u64 cpuoff_residency_ccx0; ++ u64 cpuoff_residency_ccx1; ++ u64 cpuoff_residency_ccx2; ++ u64 cpuoff_residency_ccx3; ++ /* DF-pstates */ ++ u32 fclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 uclk_freq_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 ddr_rate_table[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u8 dfpstate_source[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ /* System */ ++ u8 gfx_disabled; ++ u8 spare2[3]; ++ u32 gfxclk_fmax; ++ u8 cclk_core_fuse_enable[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u8 cclk_core_enabled[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ u32 cclk_fmax[AMD_PMF_METRIC_CORE_TYPE_MAX][AMD_PMF_NUM_MAX_CORES]; ++ /* Overclock Capable */ ++ u8 cpu_precise_and_direct_oc_capable; ++ u8 gfx_precise_and_direct_oc_capable; ++ u8 pbo_basic_oc_capable; ++ u8 pbo_advanced_oc_capable; ++ u8 pbo_nitro_oc_capable; ++ u8 memory_and_fabric_oc_capable; ++ u8 misc_oc_capable; ++ u8 extreme_cold_oc_capable; ++ u8 down_config_control_capable; ++ u8 spare0[3]; ++ /* Overclock Status */ ++ u32 fit_limit_scalar; ++ u8 ln2_enabled; ++ u8 cpu_precise_and_direct_oc_enabled; ++ u8 gfx_precise_and_direct_oc_enabled; ++ u8 spare1[2]; ++ /* Voltage Guardband in PSM count */ ++ s8 psm_guardband[5][5][3]; ++ s32 core_power_limit_offset; ++ u32 max_freq_offset[5]; ++ u64 npu_temp_acc; ++ u64 dfpstate_residency_acc[AMD_PMF_NUM_CLK_DPM_LEVELS]; ++ u32 cclk_fboost; ++ /* PMF */ ++ u32 pmf_fast_apu_ppt_limit; ++ u64 pmf_fast_apu_ppt_value_acc; ++ u32 pmf_fast_apu_ppt_residency_acc; ++ u32 pmf_slow_apu_ppt_limit; ++ u64 pmf_slow_apu_ppt_value_acc; ++ u32 pmf_slow_apu_ppt_residency_acc; ++ u32 pmf_fast_spm_limit; ++ u64 pmf_fast_spm_value_acc; ++ u32 pmf_fast_spm_residency_acc; ++ u32 pmf_slow_spm_limit; ++ u64 pmf_slow_spm_value_acc; ++ u32 pmf_slow_spm_residency_acc; ++ u32 spare3[5]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_ccx { ++ u64 core_c0[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_cc6[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freq[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_freqeff[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_temp[AMD_PMF_NUM_MAX_CORES]; ++ u64 core_power[AMD_PMF_NUM_MAX_CORES]; ++} __packed __aligned(4); ++ ++struct amd_pmf_metrics_v3 { ++ struct amd_pmf_metrics_iod iod; ++ struct amd_pmf_metrics_ccx ccx[AMD_PMF_METRIC_CCX_MAX]; ++} __packed __aligned(4); ++ + struct smu_pmf_metrics_v2 { + u16 core_frequency[16]; /* MHz */ + u16 core_power[16]; /* mW */ +@@ -391,6 +604,19 @@ struct pmf_cbi_ring_buffer { + int tail; + }; + ++/* SoC-specific SMU mailbox register offsets */ ++struct amd_pmf_smu_regs { ++ u32 msg_reg; ++ u32 resp_reg; ++ u32 arg_reg[3]; ++}; ++ ++struct amd_pmf_arg_data { ++ u32 lo; ++ u32 hi; ++ u32 size; ++}; ++ + struct amd_pmf_dev { + void __iomem *regbase; + void __iomem *smu_virt_addr; +@@ -442,6 +668,14 @@ struct amd_pmf_dev { + struct pmf_cbi_ring_buffer cbi_buf; + struct mutex cbi_mutex; /* Protects ring buffer access */ + struct mutex metrics_mutex; ++ u32 bios_output[BIOS_OUTPUT_MAX]; ++ const struct amd_pmf_smu_regs *smu_regs; ++ void __iomem *metrics_table_virt; /* Mapped DRAM virtual address for metrics table */ ++ phys_addr_t metrics_table_phys; /* DRAM physical address for metrics table */ ++ struct amd_pmf_metrics_v3 mtable_v3; /* IOD and CCX */ ++ struct amd_pmf_arg_data dram_addr; ++ struct amd_pmf_metrics_v3 prev_metrics; /* Previous metrics for delta calculation */ ++ bool npu_metrics_have_prev; + }; + + struct apmf_sps_prop_granular_v2 { +@@ -680,14 +914,6 @@ enum system_state { + SYSTEM_STATE_MAX, + }; + +-enum ta_slider { +- TA_BEST_BATTERY, +- TA_BETTER_BATTERY, +- TA_BETTER_PERFORMANCE, +- TA_BEST_PERFORMANCE, +- TA_MAX, +-}; +- + struct amd_pmf_pb_bitmap { + const char *name; + u32 bit_mask; +@@ -719,20 +945,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { + {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, + }; + +-enum platform_type { +- PTYPE_UNKNOWN = 0, +- LID_CLOSE, +- CLAMSHELL, +- FLAT, +- TENT, +- STAND, +- TABLET, +- BOOK, +- PRESENTATION, +- PULL_FWD, +- PTYPE_INVALID = 0xf, +-}; +- + /* Command ids for TA communication */ + enum ta_pmf_command { + TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, +@@ -871,6 +1083,10 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer); + int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag); + u32 fixp_q88_fromint(u32 val); + int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev); ++void amd_pmf_set_device(struct device *p_device); ++ ++/* Metrics layer */ ++int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev); + + /* SPS Layer */ + int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf); +@@ -923,9 +1139,19 @@ int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq + void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in); + int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev); ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index); + + int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); + void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); + int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); + ++/* Util Layer */ ++#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT) ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev); ++void amd_pmf_cdev_unregister(void); ++#else ++static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; } ++static inline void amd_pmf_cdev_unregister(void) {} ++#endif ++ + #endif /* PMF_H */ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -10,6 +10,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -17,61 +18,7 @@ + #include "pmf.h" + + #ifdef CONFIG_AMD_PMF_DEBUG +-static const char *platform_type_as_str(u16 platform_type) +-{ +- switch (platform_type) { +- case CLAMSHELL: +- return "CLAMSHELL"; +- case FLAT: +- return "FLAT"; +- case TENT: +- return "TENT"; +- case STAND: +- return "STAND"; +- case TABLET: +- return "TABLET"; +- case BOOK: +- return "BOOK"; +- case PRESENTATION: +- return "PRESENTATION"; +- case PULL_FWD: +- return "PULL_FWD"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *laptop_placement_as_str(u16 device_state) +-{ +- switch (device_state) { +- case ON_TABLE: +- return "ON_TABLE"; +- case ON_LAP_MOTION: +- return "ON_LAP_MOTION"; +- case IN_BAG: +- return "IN_BAG"; +- case OUT_OF_BAG: +- return "OUT_OF_BAG"; +- default: +- return "UNKNOWN"; +- } +-} +- +-static const char *ta_slider_as_str(unsigned int state) +-{ +- switch (state) { +- case TA_BEST_PERFORMANCE: +- return "PERFORMANCE"; +- case TA_BETTER_PERFORMANCE: +- return "BALANCED"; +- case TA_BEST_BATTERY: +- return "POWER_SAVER"; +- default: +- return "Unknown TA Slider State"; +- } +-} +- +-static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) ++u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { + case 0 ... 1: +@@ -82,13 +29,15 @@ static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int + return 0; + } + } ++EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) + { + int i; + + dev_dbg(dev->dev, "==== TA inputs START ====\n"); +- dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider)); ++ dev_dbg(dev->dev, "Slider State: %s\n", ++ amd_pmf_get_slider_position(in->ev_info.power_slider)); + dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); + dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage); + dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design); +@@ -102,9 +51,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * + dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open"); + dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away"); + dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light); +- dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); ++ dev_dbg(dev->dev, "Platform type: %s\n", ++ amd_pmf_get_platform_type(in->ev_info.platform_type)); + dev_dbg(dev->dev, "Laptop placement: %s\n", +- laptop_placement_as_str(in->ev_info.device_state)); ++ amd_pmf_get_laptop_placement(in->ev_info.device_state)); + for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) + dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1, + amd_pmf_get_ta_custom_bios_inputs(in, i)); +@@ -287,14 +237,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ + switch (dev->current_profile) { + case PLATFORM_PROFILE_PERFORMANCE: + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: +- val = TA_BEST_PERFORMANCE; ++ val = AMD_PMF_TA_BEST_PERFORMANCE; + break; + case PLATFORM_PROFILE_BALANCED: +- val = TA_BETTER_PERFORMANCE; ++ val = AMD_PMF_TA_BETTER_PERFORMANCE; + break; + case PLATFORM_PROFILE_LOW_POWER: + case PLATFORM_PROFILE_QUIET: +- val = TA_BEST_BATTERY; ++ val = AMD_PMF_TA_BEST_BATTERY; + break; + default: + dev_err(dev->dev, "Unknown Platform Profile.\n"); +diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c +--- a/drivers/platform/x86/amd/pmf/tee-if.c ++++ b/drivers/platform/x86/amd/pmf/tee-if.c +@@ -8,7 +8,9 @@ + * Author: Shyam Sundar S K + */ + ++#include + #include ++#include + #include + #include + #include "pmf.h" +@@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx) + + static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action) + { +- u32 bios_idx; ++ int bios_idx; ++ int ret; + + bios_idx = amd_pmf_get_bios_output_idx(action->action_index); ++ if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) { ++ dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx); ++ return; ++ } + +- amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); ++ if (!ret) ++ pdev->bios_output[bios_idx] = action->value; + } + + static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out) +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -0,0 +1,153 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * AMD Platform Management Framework Util Layer ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * Authors: Shyam Sundar S K ++ * Sanket Goswami ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "pmf.h" ++ ++static struct amd_pmf_dev *pmf_dev_handle; ++static DEFINE_MUTEX(pmf_util_lock); ++ ++static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info *info) ++{ ++ struct ta_pmf_shared_memory *ta_sm = NULL; ++ struct ta_pmf_enact_table *in = NULL; ++ int idx; ++ ++ if (!pdev || !info) ++ return -EINVAL; ++ ++ if (!pdev->shbuf) ++ return -EINVAL; ++ ++ ta_sm = pdev->shbuf; ++ in = &ta_sm->pmf_input.enact_table; ++ ++ /* Set size */ ++ info->size = sizeof(*info); ++ ++ /* PMF Feature support flags */ ++ if (is_apmf_func_supported(pdev, APMF_FUNC_AUTO_MODE)) ++ info->features_supported |= AMD_PMF_FEAT_AUTO_MODE; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) ++ info->features_supported |= AMD_PMF_FEAT_STATIC_POWER_SLIDER; ++ if (pdev->smart_pc_enabled) ++ info->features_supported |= AMD_PMF_FEAT_POLICY_BUILDER; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_AC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC; ++ if (is_apmf_func_supported(pdev, APMF_FUNC_DYN_SLIDER_DC)) ++ info->features_supported |= AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC; ++ ++ /* Device States */ ++ info->platform_type = in->ev_info.platform_type; ++ info->laptop_placement = in->ev_info.device_state; ++ info->lid_state = in->ev_info.lid_state; ++ info->user_presence = in->ev_info.user_present; ++ info->slider_position = in->ev_info.power_slider; ++ ++ /* Thermal and Power Metrics */ ++ info->power_source = in->ev_info.power_source; ++ info->skin_temp = in->ev_info.skin_temperature; ++ info->gfx_busy = in->ev_info.gfx_busy; ++ info->ambient_light = in->ev_info.ambient_light; ++ info->avg_c0_residency = in->ev_info.avg_c0residency; ++ info->max_c0_residency = in->ev_info.max_c0residency; ++ info->socket_power = in->ev_info.socket_power; ++ ++ /* Custom BIOS input parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_input[idx] = amd_pmf_get_ta_custom_bios_inputs(in, idx); ++ ++ /* BIOS output parameters */ ++ for (idx = 0; idx < AMD_PMF_BIOS_PARAMS_MAX; idx++) ++ info->bios_output[idx] = pdev->bios_output[idx]; ++ ++ return 0; ++} ++ ++static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ++{ ++ struct amd_pmf_dev *pdev = filp->private_data; ++ void __user *argp = (void __user *)arg; ++ struct amd_pmf_info info = {}; ++ size_t copy_size; ++ __u64 user_size; ++ int ret; ++ ++ if (cmd != IOCTL_AMD_PMF_POPULATE_DATA) ++ return -ENOTTY; ++ ++ /* First read just the size field from userspace */ ++ if (copy_from_user(&user_size, argp, sizeof(user_size))) ++ return -EFAULT; ++ ++ guard(mutex)(&pmf_util_lock); ++ ret = amd_pmf_populate_data(pdev, &info); ++ if (ret) ++ return ret; ++ ++ copy_size = min_t(size_t, user_size, sizeof(info)); ++ ++ /* Set actual size being copied */ ++ info.size = copy_size; ++ ++ if (copy_to_user(argp, &info, copy_size)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int amd_pmf_open(struct inode *inode, struct file *filp) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (!pmf_dev_handle) ++ return -ENODEV; ++ ++ filp->private_data = pmf_dev_handle; ++ return 0; ++} ++ ++static const struct file_operations pmf_if_ops = { ++ .owner = THIS_MODULE, ++ .open = amd_pmf_open, ++ .unlocked_ioctl = amd_pmf_set_ioctl, ++}; ++ ++static struct miscdevice amd_pmf_util_if = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = "amdpmf_interface", ++ .fops = &pmf_if_ops, ++}; ++ ++int amd_pmf_cdev_register(struct amd_pmf_dev *dev) ++{ ++ int ret; ++ ++ guard(mutex)(&pmf_util_lock); ++ pmf_dev_handle = dev; ++ ret = misc_register(&amd_pmf_util_if); ++ if (ret) ++ pmf_dev_handle = NULL; ++ ++ return ret; ++} ++ ++void amd_pmf_cdev_unregister(void) ++{ ++ guard(mutex)(&pmf_util_lock); ++ if (pmf_dev_handle) ++ misc_deregister(&amd_pmf_util_if); ++ pmf_dev_handle = NULL; ++} +diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c +--- a/drivers/platform/x86/asus-armoury.c ++++ b/drivers/platform/x86/asus-armoury.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -93,6 +94,8 @@ struct asus_armoury_priv { + + u32 mini_led_dev_id; + u32 gpu_mux_dev_id; ++ ++ bool requires_fan_curve; + }; + + static struct asus_armoury_priv asus_armoury = { +@@ -216,6 +219,22 @@ static int armoury_set_devstate(struct kobj_attribute *attr, + u32 result; + int err; + ++ /* On some models, PPT changes require an active fan curve */ ++ if (asus_armoury.requires_fan_curve) { ++ switch (dev_id) { ++ case ASUS_WMI_DEVID_PPT_PL1_SPL: ++ case ASUS_WMI_DEVID_PPT_PL2_SPPT: ++ case ASUS_WMI_DEVID_PPT_PL3_FPPT: ++ case ASUS_WMI_DEVID_PPT_APU_SPPT: ++ case ASUS_WMI_DEVID_PPT_PLAT_SPPT: ++ if (!asus_wmi_custom_fan_curve_is_enabled()) { ++ pr_warn_once("PPT change requires an active fan curve on this model. Enable a custom fan curve first.\n"); ++ return -EBUSY; ++ } ++ break; ++ } ++ } ++ + /* + * Prevent developers from bricking devices or issuing dangerous + * commands that can be difficult or impossible to recover from. +@@ -989,10 +1008,11 @@ static int asus_fw_attr_add(void) + /* Init / exit ****************************************************************/ + + /* Set up the min/max and defaults for ROG tunables */ +-static void init_rog_tunables(void) ++static int init_rog_tunables(void) + { + const struct power_limits *ac_limits, *dc_limits; +- struct rog_tunables *ac_rog_tunables = NULL, *dc_rog_tunables = NULL; ++ struct rog_tunables *ac_rog_tunables __free(kfree) = NULL; ++ struct rog_tunables *dc_rog_tunables __free(kfree) = NULL; + const struct power_data *power_data; + const struct dmi_system_id *dmi_id; + +@@ -1000,24 +1020,25 @@ static void init_rog_tunables(void) + dmi_id = dmi_first_match(power_limits); + if (!dmi_id) { + pr_warn("No matching power limits found for this system\n"); +- return; ++ return 0; + } + + /* Get the power data for this system */ + power_data = dmi_id->driver_data; + if (!power_data) { + pr_info("No power data available for this system\n"); +- return; ++ return 0; + } + ++ asus_armoury.requires_fan_curve = power_data->requires_fan_curve; ++ + /* Initialize AC power tunables */ + ac_limits = power_data->ac_data; + if (ac_limits) { +- ac_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ ac_rog_tunables = kzalloc_obj(*ac_rog_tunables); + if (!ac_rog_tunables) +- goto err_nomem; ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = ac_rog_tunables; + ac_rog_tunables->power_limits = ac_limits; + + /* Set initial AC values */ +@@ -1060,13 +1081,10 @@ static void init_rog_tunables(void) + /* Initialize DC power tunables */ + dc_limits = power_data->dc_data; + if (dc_limits) { +- dc_rog_tunables = kzalloc_obj(*asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); +- if (!dc_rog_tunables) { +- kfree(ac_rog_tunables); +- goto err_nomem; +- } ++ dc_rog_tunables = kzalloc_obj(*dc_rog_tunables); ++ if (!dc_rog_tunables) ++ return -ENOMEM; + +- asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = dc_rog_tunables; + dc_rog_tunables->power_limits = dc_limits; + + /* Set initial DC values */ +@@ -1106,15 +1124,16 @@ static void init_rog_tunables(void) + pr_debug("No DC PPT limits defined\n"); + } + +- return; ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC] = no_free_ptr(ac_rog_tunables); ++ asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC] = no_free_ptr(dc_rog_tunables); + +-err_nomem: +- pr_err("Failed to allocate memory for tunables\n"); ++ return 0; + } + + static int __init asus_fw_init(void) + { + char *wmi_uid; ++ int err; + + wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); + if (!wmi_uid) +@@ -1127,10 +1146,21 @@ static int __init asus_fw_init(void) + if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) + return -ENODEV; + +- init_rog_tunables(); ++ err = init_rog_tunables(); ++ if (err) ++ return err; + + /* Must always be last step to ensure data is available */ +- return asus_fw_attr_add(); ++ err = asus_fw_attr_add(); ++ if (err) ++ goto err_free_tunables; ++ ++ return 0; ++ ++err_free_tunables: ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_AC]); ++ kfree(asus_armoury.rog_tunables[ASUS_ROG_TUNABLE_DC]); ++ return err; + } + + static void __exit asus_fw_exit(void) +diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h +--- a/drivers/platform/x86/asus-armoury.h ++++ b/drivers/platform/x86/asus-armoury.h +@@ -369,6 +369,38 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA401KM"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 15, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 95, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 80, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 80, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 80, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_max = 35, ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl2_sppt_max = 44, ++ .ppt_pl2_sppt_min = 31, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 45, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401UM"), +@@ -754,6 +786,40 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FA608WV"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .nv_dynamic_boost_max = 25, ++ .nv_dynamic_boost_min = 5, ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .nv_tgp_max = 115, ++ .nv_tgp_min = 55, ++ .ppt_pl1_spl_max = 90, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_max = 90, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 90, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .dc_data = &(struct power_limits) { ++ .nv_temp_target_max = 87, ++ .nv_temp_target_min = 75, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 65, ++ .ppt_pl1_spl_min = 15, ++ .ppt_pl2_sppt_def = 54, ++ .ppt_pl2_sppt_max = 65, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl3_fppt_max = 65, ++ .ppt_pl3_fppt_min = 35, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA617NS"), +@@ -909,6 +975,32 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "FX517ZR"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 135, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 45, ++ .ppt_pl2_sppt_min = 35, ++ .ppt_pl2_sppt_max = 60, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX607VU"), +@@ -2079,6 +2171,35 @@ static const struct dmi_system_id power_limits[] = { + .requires_fan_curve = true, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "G635LX"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 28, ++ .ppt_pl1_spl_def = 140, ++ .ppt_pl1_spl_max = 175, ++ .ppt_pl2_sppt_min = 28, ++ .ppt_pl2_sppt_max = 175, ++ .nv_dynamic_boost_min = 5, ++ .nv_dynamic_boost_max = 25, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ .nv_tgp_min = 80, ++ .nv_tgp_max = 150, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 25, ++ .ppt_pl1_spl_max = 55, ++ .ppt_pl2_sppt_min = 25, ++ .ppt_pl2_sppt_max = 70, ++ .nv_temp_target_min = 75, ++ .nv_temp_target_max = 87, ++ }, ++ .requires_fan_curve = true, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G713PV"), +@@ -2322,6 +2443,35 @@ static const struct dmi_system_id power_limits[] = { + }, + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_NAME, "HN7306EA"), ++ }, ++ .driver_data = &(struct power_data) { ++ .ac_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 60, ++ .ppt_pl1_spl_max = 85, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 70, ++ .ppt_pl2_sppt_max = 95, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 85, ++ .ppt_pl3_fppt_max = 115, ++ }, ++ .dc_data = &(struct power_limits) { ++ .ppt_pl1_spl_min = 35, ++ .ppt_pl1_spl_def = 45, ++ .ppt_pl1_spl_max = 60, ++ .ppt_pl2_sppt_min = 40, ++ .ppt_pl2_sppt_def = 55, ++ .ppt_pl2_sppt_max = 70, ++ .ppt_pl3_fppt_min = 50, ++ .ppt_pl3_fppt_def = 65, ++ .ppt_pl3_fppt_max = 85, ++ }, ++ }, ++ }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "RC71"), +diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c +--- a/drivers/platform/x86/asus-laptop.c ++++ b/drivers/platform/x86/asus-laptop.c +@@ -42,8 +42,6 @@ + #define ASUS_LAPTOP_VERSION "0.42" + + #define ASUS_LAPTOP_NAME "Asus Laptop Support" +-#define ASUS_LAPTOP_CLASS "hotkey" +-#define ASUS_LAPTOP_DEVICE_NAME "Hotkey" + #define ASUS_LAPTOP_FILE KBUILD_MODNAME + #define ASUS_LAPTOP_PREFIX "\\_SB.ATKD." + +@@ -1524,9 +1522,8 @@ static void asus_acpi_notify(acpi_handle handle, u32 event, void *data) + + /* TODO Find a better way to handle events count. */ + count = asus->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(asus->device->pnp.device_class, +- dev_name(&asus->device->dev), event, +- count); ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&asus->device->dev), ++ event, count); + + if (event >= ATKD_BRNUP_MIN && event <= ATKD_BRNUP_MAX) + event = ATKD_BRNUP; +@@ -1840,8 +1837,6 @@ static int asus_acpi_probe(struct platform_device *pdev) + if (!asus) + return -ENOMEM; + asus->handle = device->handle; +- strscpy(acpi_device_name(device), ASUS_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ASUS_LAPTOP_CLASS); + asus->device = device; + + asus_dmi_check(); +diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c +--- a/drivers/platform/x86/asus-nb-wmi.c ++++ b/drivers/platform/x86/asus-nb-wmi.c +@@ -632,6 +632,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { + { KE_KEY, 0x86, { KEY_PROG1 } }, /* MyASUS Key */ + { KE_KEY, 0x88, { KEY_RFKILL } }, /* Radio Toggle Key */ + { KE_KEY, 0x8A, { KEY_PROG1 } }, /* Color enhancement mode */ ++ { KE_KEY, 0x8B, { KEY_PROG3 } }, /* ProArt key (PX13/HN7306) */ + { KE_KEY, 0x8C, { KEY_SWITCHVIDEOMODE } }, /* SDSP DVI only */ + { KE_KEY, 0x8D, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + DVI */ + { KE_KEY, 0x8E, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + DVI */ +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -4073,6 +4073,30 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus) + return 0; + } + ++/* ++ * Returns true if at least one custom fan curve is active ++ * ++ * Used by asus-armoury to check if PPT writes will be accepted by the BIOS ++ * on models that require an active fan curve for TDP changes. ++ */ ++bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ struct fan_curve_data *curves; ++ struct asus_wmi *asus; ++ ++ guard(spinlock_irqsave)(&asus_ref.lock); ++ asus = asus_ref.asus; ++ if (!asus) ++ return false; ++ ++ curves = asus->custom_fan_curves; ++ ++ return (asus->cpu_fan_curve_available && curves[FAN_CURVE_DEV_CPU].enabled) || ++ (asus->gpu_fan_curve_available && curves[FAN_CURVE_DEV_GPU].enabled) || ++ (asus->mid_fan_curve_available && curves[FAN_CURVE_DEV_MID].enabled); ++} ++EXPORT_SYMBOL_NS_GPL(asus_wmi_custom_fan_curve_is_enabled, "ASUS_WMI"); ++ + /* Throttle thermal policy ****************************************************/ + static int throttle_thermal_policy_write(struct asus_wmi *asus) + { +diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c +--- a/drivers/platform/x86/dell/dell-smbios-base.c ++++ b/drivers/platform/x86/dell/dell-smbios-base.c +@@ -40,7 +40,7 @@ struct smbios_device { + struct list_head list; + struct device *device; + int priority; +- int (*call_fn)(struct calling_interface_buffer *arg); ++ smbios_callback_fn_t call_fn; + }; + + struct smbios_call { +@@ -146,7 +146,7 @@ int dell_smbios_error(int value) + } + EXPORT_SYMBOL_GPL(dell_smbios_error); + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn) ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn) + { + struct smbios_device *priv; + +@@ -312,7 +312,7 @@ int dell_smbios_call(struct calling_interface_buffer *buffer) + goto out_smbios_call; + } + +- ret = selected->call_fn(buffer); ++ ret = selected->call_fn(selected->device, buffer); + + out_smbios_call: + mutex_unlock(&smbios_mutex); +diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c +--- a/drivers/platform/x86/dell/dell-smbios-smm.c ++++ b/drivers/platform/x86/dell/dell-smbios-smm.c +@@ -49,7 +49,7 @@ static void find_cmd_address(const struct dmi_header *dm, void *dummy) + } + } + +-static int dell_smbios_smm_call(struct calling_interface_buffer *input) ++static int dell_smbios_smm_call(struct device *dev, struct calling_interface_buffer *input) + { + struct smi_cmd command; + size_t size; +@@ -70,7 +70,7 @@ static int dell_smbios_smm_call(struct calling_interface_buffer *input) + } + + /* When enabled this indicates that SMM won't work */ +-static bool test_wsmt_enabled(void) ++static bool test_wsmt_enabled(struct device *dev) + { + struct calling_interface_token *wsmt; + +@@ -88,7 +88,7 @@ static bool test_wsmt_enabled(void) + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = wsmt->location; + buffer->output[0] = 99; +- dell_smbios_smm_call(buffer); ++ dell_smbios_smm_call(dev, buffer); + if (buffer->output[0] == 99) + return true; + +@@ -109,7 +109,7 @@ int init_dell_smbios_smm(void) + + dmi_walk(find_cmd_address, NULL); + +- if (test_wsmt_enabled()) { ++ if (test_wsmt_enabled(&platform_device->dev)) { + pr_debug("Disabling due to WSMT enabled\n"); + ret = -ENODEV; + goto fail_wsmt; +diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c +--- a/drivers/platform/x86/dell/dell-smbios-wmi.c ++++ b/drivers/platform/x86/dell/dell-smbios-wmi.c +@@ -6,21 +6,20 @@ + */ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + ++#include + #include + #include + #include +-#include + #include + #include + #include ++#include + #include + #include + #include + #include "dell-smbios.h" + #include "dell-wmi-descriptor.h" + +-static DEFINE_MUTEX(call_mutex); +-static DEFINE_MUTEX(list_mutex); + static int wmi_supported; + + struct misc_bios_flags_structure { +@@ -32,21 +31,16 @@ struct misc_bios_flags_structure { + #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" + + struct wmi_smbios_priv { ++ struct mutex call_lock; /* Protects the content of the SMBIOS buffer */ + struct dell_wmi_smbios_buffer *buf; +- struct list_head list; + struct wmi_device *wdev; + struct device *child; + u64 req_buf_size; + struct miscdevice char_dev; + }; +-static LIST_HEAD(wmi_list); + +-static inline struct wmi_smbios_priv *get_first_smbios_priv(void) +-{ +- return list_first_entry_or_null(&wmi_list, +- struct wmi_smbios_priv, +- list); +-} ++static DECLARE_RWSEM(chardev_lock); /* Protects chardev_priv */ ++static struct wmi_smbios_priv *chardev_priv; + + static int run_smbios_call(struct wmi_device *wdev) + { +@@ -80,50 +74,36 @@ static int run_smbios_call(struct wmi_device *wdev) + return 0; + } + +-static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) ++static int dell_smbios_wmi_call(struct device *dev, struct calling_interface_buffer *buffer) + { +- struct wmi_smbios_priv *priv; ++ struct wmi_smbios_priv *priv = dev_get_drvdata(dev); + size_t difference; + size_t size; + int ret; + +- mutex_lock(&call_mutex); +- priv = get_first_smbios_priv(); +- if (!priv) { +- ret = -ENODEV; +- goto out_wmi_call; +- } +- + size = sizeof(struct calling_interface_buffer); + difference = priv->req_buf_size - sizeof(u64) - size; + ++ guard(mutex)(&priv->call_lock); ++ + memset(&priv->buf->ext, 0, difference); + memcpy(&priv->buf->std, buffer, size); + ret = run_smbios_call(priv->wdev); + memcpy(buffer, &priv->buf->std, size); +-out_wmi_call: +- mutex_unlock(&call_mutex); + + return ret; + } + +-static int dell_smbios_wmi_open(struct inode *inode, struct file *filp) +-{ +- struct wmi_smbios_priv *priv; +- +- priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev); +- filp->private_data = priv; +- +- return nonseekable_open(inode, filp); +-} +- + static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length, + loff_t *offset) + { +- struct wmi_smbios_priv *priv = filp->private_data; ++ guard(rwsem_read)(&chardev_lock); + +- return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size, +- sizeof(priv->req_buf_size)); ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ return simple_read_from_buffer(buffer, length, offset, &chardev_priv->req_buf_size, ++ sizeof(chardev_priv->req_buf_size)); + } + + static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, +@@ -167,22 +147,22 @@ static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv, + static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { + struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg; +- struct wmi_smbios_priv *priv = filp->private_data; +- long ret; + + if (cmd != DELL_WMI_SMBIOS_CMD) + return -ENOIOCTLCMD; + +- mutex_lock(&call_mutex); +- ret = dell_smbios_wmi_do_ioctl(priv, input); +- mutex_unlock(&call_mutex); ++ guard(rwsem_read)(&chardev_lock); + +- return ret; ++ if (!chardev_priv) ++ return -ENODEV; ++ ++ guard(mutex)(&chardev_priv->call_lock); ++ ++ return dell_smbios_wmi_do_ioctl(chardev_priv, input); + } + + static const struct file_operations dell_smbios_wmi_fops = { + .owner = THIS_MODULE, +- .open = dell_smbios_wmi_open, + .read = dell_smbios_wmi_read, + .unlocked_ioctl = dell_smbios_wmi_ioctl, + .compat_ioctl = compat_ptr_ioctl, +@@ -195,10 +175,29 @@ static void dell_smbios_wmi_unregister_chardev(void *data) + misc_deregister(char_dev); + } + ++static void dell_smbios_wmi_clear_chardev(void *data) ++{ ++ guard(rwsem_write)(&chardev_lock); ++ ++ chardev_priv = NULL; ++} ++ + static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv) + { + int ret; + ++ scoped_guard(rwsem_write, &chardev_lock) { ++ /* We can only have a single chardev at a time */ ++ if (chardev_priv) ++ return -EBUSY; ++ ++ chardev_priv = priv; ++ } ++ ++ ret = devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_clear_chardev, NULL); ++ if (ret < 0) ++ return ret; ++ + priv->char_dev.minor = MISC_DYNAMIC_MINOR; + priv->char_dev.name = "wmi/dell-smbios"; + priv->char_dev.fops = &dell_smbios_wmi_fops; +@@ -254,31 +253,20 @@ static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) + if (!priv->buf) + return -ENOMEM; + ++ ret = devm_mutex_init(&wdev->dev, &priv->call_lock); ++ if (ret) ++ return ret; ++ + ret = dell_smbios_wmi_register_chardev(priv); + if (ret) + return ret; + +- ret = dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); +- if (ret) +- return ret; +- +- mutex_lock(&list_mutex); +- list_add_tail(&priv->list, &wmi_list); +- mutex_unlock(&list_mutex); +- +- return 0; ++ return dell_smbios_register_device(&wdev->dev, 1, &dell_smbios_wmi_call); + } + + static void dell_smbios_wmi_remove(struct wmi_device *wdev) + { +- struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); +- +- mutex_lock(&call_mutex); +- mutex_lock(&list_mutex); +- list_del(&priv->list); +- mutex_unlock(&list_mutex); + dell_smbios_unregister_device(&wdev->dev); +- mutex_unlock(&call_mutex); + } + + static const struct wmi_device_id dell_smbios_wmi_id_table[] = { +@@ -316,6 +304,7 @@ static struct wmi_driver dell_smbios_wmi_driver = { + .probe = dell_smbios_wmi_probe, + .remove = dell_smbios_wmi_remove, + .id_table = dell_smbios_wmi_id_table, ++ .no_singleton = true, + }; + + int init_dell_smbios_wmi(void) +diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h +--- a/drivers/platform/x86/dell/dell-smbios.h ++++ b/drivers/platform/x86/dell/dell-smbios.h +@@ -47,6 +47,8 @@ + + struct notifier_block; + ++typedef int (*smbios_callback_fn_t)(struct device *dev, struct calling_interface_buffer *buffer); ++ + struct calling_interface_token { + u16 tokenID; + u16 location; +@@ -64,7 +66,7 @@ struct calling_interface_structure { + struct calling_interface_token tokens[]; + } __packed; + +-int dell_smbios_register_device(struct device *d, int priority, void *call_fn); ++int dell_smbios_register_device(struct device *d, int priority, smbios_callback_fn_t call_fn); + void dell_smbios_unregister_device(struct device *d); + + int dell_smbios_error(int value); +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -196,40 +196,30 @@ static int dell_wmi_ddv_query_integer(struct wmi_device *wdev, enum dell_ddv_met + static int dell_wmi_ddv_query_buffer(struct wmi_device *wdev, enum dell_ddv_method method, + u32 arg, struct dell_wmi_buffer **result) + { +- struct dell_wmi_buffer *buffer; + struct wmi_buffer output; + size_t buffer_size; + int ret; + +- ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(*buffer)); ++ ret = dell_wmi_ddv_query(wdev, method, arg, &output, sizeof(struct dell_wmi_buffer)); + if (ret < 0) + return ret; + +- buffer = output.data; +- if (!le32_to_cpu(buffer->raw_size)) { +- ret = -ENODATA; ++ struct dell_wmi_buffer *buffer __free(kfree) = output.data; + +- goto err_free; +- } ++ if (!le32_to_cpu(buffer->raw_size)) ++ return -ENODATA; + + buffer_size = struct_size(buffer, raw_data, le32_to_cpu(buffer->raw_size)); + if (buffer_size > output.length) { + dev_warn(&wdev->dev, + FW_WARN "Dell WMI buffer size (%zu) exceeds WMI buffer size (%zu)\n", + buffer_size, output.length); +- ret = -EMSGSIZE; +- +- goto err_free; ++ return -EMSGSIZE; + } + +- *result = buffer; ++ *result = no_free_ptr(buffer); + + return 0; +- +-err_free: +- kfree(output.data); +- +- return ret; + } + + static ssize_t dell_wmi_ddv_query_string(struct wmi_device *wdev, enum dell_ddv_method method, +diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c +--- a/drivers/platform/x86/eeepc-laptop.c ++++ b/drivers/platform/x86/eeepc-laptop.c +@@ -34,8 +34,6 @@ + #define EEEPC_LAPTOP_NAME "Eee PC Hotkey Driver" + #define EEEPC_LAPTOP_FILE "eeepc" + +-#define EEEPC_ACPI_CLASS "hotkey" +-#define EEEPC_ACPI_DEVICE_NAME "Hotkey" + #define EEEPC_ACPI_HID "ASUS010" + + MODULE_AUTHOR("Corentin Chary, Eric Cooper"); +@@ -1214,8 +1212,7 @@ static void eeepc_acpi_notify(acpi_handle handle, u32 event, void *data) + if (event > ACPI_MAX_SYS_NOTIFY) + return; + count = eeepc->event_count[event % 128]++; +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), event, ++ acpi_bus_generate_netlink_event("hotkey", dev_name(&device->dev), event, + count); + + /* Brightness events are special */ +@@ -1376,8 +1373,6 @@ static int eeepc_acpi_probe(struct platform_device *pdev) + if (!eeepc) + return -ENOMEM; + eeepc->handle = device->handle; +- strscpy(acpi_device_name(device), EEEPC_ACPI_DEVICE_NAME); +- strscpy(acpi_device_class(device), EEEPC_ACPI_CLASS); + eeepc->device = device; + + platform_set_drvdata(pdev, eeepc); +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -56,7 +56,6 @@ + + #define FUJITSU_LCD_N_LEVELS 8 + +-#define ACPI_FUJITSU_CLASS "fujitsu" + #define ACPI_FUJITSU_BL_HID "FUJ02B1" + #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver" + #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1" +@@ -466,7 +465,7 @@ static int acpi_fujitsu_bl_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_BL_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + priv->input->id.product = 0x06; +@@ -546,13 +545,11 @@ static int acpi_fujitsu_bl_probe(struct platform_device *pdev) + return -ENOMEM; + + fujitsu_bl = priv; +- strscpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); + + platform_set_drvdata(pdev, priv); + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_BL_DEVICE_NAME, ++ acpi_device_bid(device)); + + if (get_max_brightness(&pdev->dev) <= 0) + priv->max_brightness = FUJITSU_LCD_N_LEVELS; +@@ -681,7 +678,7 @@ static int acpi_fujitsu_laptop_input_setup(struct device *dev) + snprintf(priv->phys, sizeof(priv->phys), "%s/input0", + acpi_device_hid(device)); + +- priv->input->name = acpi_device_name(device); ++ priv->input->name = ACPI_FUJITSU_LAPTOP_DEVICE_NAME; + priv->input->phys = priv->phys; + priv->input->id.bustype = BUS_HOST; + +@@ -1012,9 +1009,6 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended."); + fext = &pdev->dev; + +- strscpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_FUJITSU_CLASS); +- + platform_set_drvdata(pdev, priv); + + /* kfifo */ +@@ -1024,8 +1018,8 @@ static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) + if (ret) + return ret; + +- pr_info("ACPI: %s [%s]\n", +- acpi_device_name(device), acpi_device_bid(device)); ++ pr_info("ACPI: %s [%s]\n", ACPI_FUJITSU_LAPTOP_DEVICE_NAME, ++ acpi_device_bid(device)); + + while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 && + i++ < MAX_HOTKEY_RINGBUFFER_SIZE) +diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c +--- a/drivers/platform/x86/fujitsu-tablet.c ++++ b/drivers/platform/x86/fujitsu-tablet.c +@@ -22,8 +22,6 @@ + + #define MODULENAME "fujitsu-tablet" + +-#define ACPI_FUJITSU_CLASS "fujitsu" +- + #define INVERT_TABLET_MODE_BIT 0x01 + #define INVERT_DOCK_STATE_BIT 0x02 + #define FORCE_TABLET_MODE_IF_UNDOCK 0x04 +@@ -160,6 +158,7 @@ static struct { + struct fujitsu_config config; + unsigned long prev_keymask; + ++ char name[17]; + char phys[21]; + + int irq; +@@ -458,14 +457,10 @@ static int acpi_fujitsu_probe(struct platform_device *pdev) + if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) + return -ENODEV; + +- sprintf(acpi_device_name(adev), "Fujitsu %s", acpi_device_hid(adev)); +- sprintf(acpi_device_class(adev), "%s", ACPI_FUJITSU_CLASS); ++ scnprintf(fujitsu.name, sizeof(fujitsu.name), "Fujitsu %s", acpi_device_hid(adev)); ++ scnprintf(fujitsu.phys, sizeof(fujitsu.phys), "%s/input0", acpi_device_hid(adev)); + +- snprintf(fujitsu.phys, sizeof(fujitsu.phys), +- "%s/input0", acpi_device_hid(adev)); +- +- error = input_fujitsu_setup(&pdev->dev, +- acpi_device_name(adev), fujitsu.phys); ++ error = input_fujitsu_setup(&pdev->dev, fujitsu.name, fujitsu.phys); + if (error) + return error; + +diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c +--- a/drivers/platform/x86/hp/hp-wmi.c ++++ b/drivers/platform/x86/hp/hp-wmi.c +@@ -14,6 +14,8 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include ++#include + #include + #include + #include +@@ -23,6 +25,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -58,6 +61,12 @@ enum hp_ec_offsets { + #define HP_FAN_SPEED_AUTOMATIC 0x00 + #define HP_POWER_LIMIT_DEFAULT 0x00 + #define HP_POWER_LIMIT_NO_CHANGE 0xFF ++#define HPWMI_MUX_MODE_UMA BIT(0) ++#define HPWMI_MUX_MODE_HYBRID BIT(1) ++#define HPWMI_MUX_MODE_DISCRETE BIT(2) ++#define HPWMI_MUX_MODE_OPTIMUS BIT(3) ++#define HPWMI_MUX_MODE_MASK GENMASK(6, 0) ++#define HPWMI_MUX_LEGACY_MASK (HPWMI_MUX_MODE_HYBRID | HPWMI_MUX_MODE_DISCRETE) + + #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required + +@@ -133,24 +142,41 @@ static const struct thermal_profile_params omen_v1_no_ec_thermal_params = { + .ec_tp_offset = HP_NO_THERMAL_PROFILE_OFFSET, + }; + ++struct hp_wmi_fan_profile_params { ++ int (*get_fan_speed)(int fan); ++ bool fan_table; ++}; ++ + struct hp_wmi_board_params { + const struct thermal_profile_params *thermal_profile; ++ const struct hp_wmi_fan_profile_params *fan_profile; ++}; ++ ++static int hp_wmi_get_fan_speed_victus_s(int fan); ++ ++static const struct hp_wmi_fan_profile_params victus_s_fan_profile_params = { ++ .get_fan_speed = hp_wmi_get_fan_speed_victus_s, ++ .fan_table = true, + }; + + static const struct hp_wmi_board_params victus_s_board_params = { + .thermal_profile = &victus_s_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_board_params = { + .thermal_profile = &omen_v1_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_legacy_board_params = { + .thermal_profile = &omen_v1_legacy_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params omen_v1_no_ec_board_params = { + .thermal_profile = &omen_v1_no_ec_thermal_params, ++ .fan_profile = &victus_s_fan_profile_params, + }; + + static const struct hp_wmi_board_params *active_board_params; +@@ -181,7 +207,7 @@ static const char * const omen_thermal_profile_boards[] = { + "886B", "886C", "88C8", "88CB", "88D1", "88D2", "88F4", "88F5", "88F6", + "88F7", "88FD", "88FE", "88FF", + "8900", "8901", "8902", "8912", "8917", "8918", "8949", "894A", "89EB", +- "8A15", "8A42", ++ "8A15", "8A42", "8A43", + "8BAD", + "8C58", + "8E41", +@@ -217,6 +243,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A3D") }, ++ .driver_data = (void *)&victus_s_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, + .driver_data = (void *)&omen_v1_legacy_board_params, +@@ -225,6 +255,14 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A4D") }, + .driver_data = (void *)&omen_v1_legacy_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAA") }, ++ .driver_data = (void *)&omen_v1_board_params, ++ }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BA9") }, ++ .driver_data = (void *)&omen_v1_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BAB") }, + .driver_data = (void *)&omen_v1_board_params, +@@ -233,6 +271,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8B2F") }, + .driver_data = (void *)&victus_s_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BB3") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8BBE") }, + .driver_data = (void *)&victus_s_board_params, +@@ -289,6 +331,10 @@ static const struct dmi_system_id hp_wmi_feature_boards[] __initconst = { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D87") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, + }, ++ { ++ .matches = { DMI_MATCH(DMI_BOARD_NAME, "8D88") }, ++ .driver_data = (void *)&omen_v1_no_ec_board_params, ++ }, + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8DD6") }, + .driver_data = (void *)&omen_v1_no_ec_board_params, +@@ -360,6 +406,7 @@ enum hp_wmi_commandtype { + HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, + HPWMI_THERMAL_PROFILE_QUERY = 0x4c, ++ HPWMI_GRAPHICS_MUX_QUERY = 0x52, + }; + + struct victus_power_limits { +@@ -528,9 +575,9 @@ struct hp_wmi_hwmon_priv { + struct mutex lock; /* protects mode, pwm */ + u8 min_rpm; + u8 max_rpm; +- int gpu_delta; + u8 mode; +- u8 pwm; ++ u8 cpu_pwm; ++ u8 gpu_pwm; + struct delayed_work keep_alive_dwork; + }; + +@@ -857,24 +904,20 @@ static int hp_wmi_fan_speed_max_set(int enabled) + return enabled; + } + +-static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) ++static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv) + { + u8 fan_speed[2]; +- int gpu_speed, ret; ++ int ret; + +- fan_speed[CPU_FAN] = speed; +- fan_speed[GPU_FAN] = speed; ++ if (priv->cpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[CPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[CPU_FAN] = pwm_to_rpm(priv->cpu_pwm, priv); + +- /* +- * GPU fan speed is always a little higher than CPU fan speed, we fetch +- * this delta value from the fan table during hwmon init. +- * Exception: Speed is set to HP_FAN_SPEED_AUTOMATIC, to revert to +- * automatic mode. +- */ +- if (speed != HP_FAN_SPEED_AUTOMATIC) { +- gpu_speed = speed + priv->gpu_delta; +- fan_speed[GPU_FAN] = clamp_val(gpu_speed, 0, U8_MAX); +- } ++ if (priv->gpu_pwm == HP_FAN_SPEED_AUTOMATIC) ++ fan_speed[GPU_FAN] = HP_FAN_SPEED_AUTOMATIC; ++ else ++ fan_speed[GPU_FAN] = pwm_to_rpm(priv->gpu_pwm, priv); + + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) +@@ -891,7 +934,9 @@ static int hp_wmi_fan_speed_set(struct hp_wmi_hwmon_priv *priv, u8 speed) + + static int hp_wmi_fan_speed_reset(struct hp_wmi_hwmon_priv *priv) + { +- return hp_wmi_fan_speed_set(priv, HP_FAN_SPEED_AUTOMATIC); ++ priv->cpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ priv->gpu_pwm = HP_FAN_SPEED_AUTOMATIC; ++ return hp_wmi_fan_speed_set(priv); + } + + static int hp_wmi_fan_speed_max_reset(struct hp_wmi_hwmon_priv *priv) +@@ -1152,12 +1197,134 @@ static int camera_shutter_input_setup(void) + return err; + } + ++static const u8 mux_bitmask_map[] = { ++ [0] = HPWMI_MUX_MODE_HYBRID, ++ [1] = HPWMI_MUX_MODE_DISCRETE, ++ [2] = HPWMI_MUX_MODE_OPTIMUS, ++ [3] = HPWMI_MUX_MODE_UMA, ++}; ++ ++static int hp_wmi_get_mux_supported_modes(u8 *supported) ++{ ++ u8 legacy_buffer[4] = {}; ++ u8 buffer[128] = {}; ++ u32 req_packet = 0; ++ int ret; ++ ++ if (!supported) ++ return -EINVAL; ++ ++ /* Try modern BIOS design data query (128-byte buffer) */ ++ ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, ++ buffer, zero_if_sup(req_packet), sizeof(buffer)); ++ if (ret == 0) { ++ *supported = buffer[7]; ++ return 0; ++ } ++ ++ /* ++ * (Fallback): Legacy BIOS behavior based on Omen Gaming Hub. ++ * If the modern query is not supported, check if the MUX query endpoint ++ * responds to a read request. If it succeeds, the hardware has MUX ++ * capability but lacks the mode map, defaulting to Hybrid + Discrete. ++ */ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ legacy_buffer, sizeof(legacy_buffer), 0); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ *supported = HPWMI_MUX_LEGACY_MASK; ++ return 0; ++} ++ ++static int hp_wmi_get_mux_mode(u8 *mode) ++{ ++ u8 buffer[4] = {}; ++ int ret; ++ ++ if (!mode) ++ return -EINVAL; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_READ, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ /* Mask the highest bit, which might be used as a BIOS status flag */ ++ *mode = buffer[0] & HPWMI_MUX_MODE_MASK; ++ return 0; ++} ++ ++static int hp_wmi_set_mux_mode(u8 mode) ++{ ++ u8 buffer[4] = { mode, 0x00, 0x00, 0x00 }; ++ int ret; ++ ++ ret = hp_wmi_perform_query(HPWMI_GRAPHICS_MUX_QUERY, HPWMI_WRITE, ++ buffer, sizeof(buffer), sizeof(buffer)); ++ if (ret < 0) ++ return ret; ++ if (ret > 0) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static ssize_t gpu_mux_mode_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ u8 mode; ++ int ret; ++ ++ ret = hp_wmi_get_mux_mode(&mode); ++ if (ret) ++ return ret; ++ ++ return sysfs_emit(buf, "%u\n", mode); ++} ++ ++static ssize_t gpu_mux_mode_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, ++ size_t count) ++{ ++ u32 requested; ++ u8 supported; ++ int ret; ++ ++ ret = kstrtou32(buf, 0, &requested); ++ if (ret) ++ return ret; ++ if (requested >= ARRAY_SIZE(mux_bitmask_map)) ++ return -EINVAL; ++ ++ ret = hp_wmi_get_mux_supported_modes(&supported); ++ if (ret) ++ return ret; ++ ++ /* Verify if the requested mode is allowed by the hardware mask */ ++ if (!(supported & mux_bitmask_map[requested])) ++ return -EOPNOTSUPP; ++ ++ ret = hp_wmi_set_mux_mode(requested); ++ if (ret) ++ return ret; ++ ++ return count; ++} ++ + static DEVICE_ATTR_RO(display); + static DEVICE_ATTR_RO(hddtemp); + static DEVICE_ATTR_RW(als); + static DEVICE_ATTR_RO(dock); + static DEVICE_ATTR_RO(tablet); + static DEVICE_ATTR_RW(postcode); ++static DEVICE_ATTR_RW(gpu_mux_mode); + + static struct attribute *hp_wmi_attrs[] = { + &dev_attr_display.attr, +@@ -1166,6 +1333,7 @@ static struct attribute *hp_wmi_attrs[] = { + &dev_attr_dock.attr, + &dev_attr_tablet.attr, + &dev_attr_postcode.attr, ++ &dev_attr_gpu_mux_mode.attr, + NULL, + }; + ATTRIBUTE_GROUPS(hp_wmi); +@@ -1817,6 +1985,38 @@ static bool is_victus_s_thermal_profile(void) + return is_victus_s_board; + } + ++static const struct hp_wmi_fan_profile_params *hp_wmi_fan_profile(void) ++{ ++ if (!active_board_params) ++ return NULL; ++ ++ return active_board_params->fan_profile; ++} ++ ++static bool hp_wmi_fan_control_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->get_fan_speed; ++} ++ ++static bool hp_wmi_fan_table_supported(void) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ return params && params->fan_table; ++} ++ ++static int hp_wmi_get_active_fan_speed(int fan) ++{ ++ const struct hp_wmi_fan_profile_params *params = hp_wmi_fan_profile(); ++ ++ if (!params || !params->get_fan_speed) ++ return -EOPNOTSUPP; ++ ++ return params->get_fan_speed(fan); ++} ++ + static int victus_s_gpu_thermal_profile_get(bool *ctgp_enable, + bool *ppab_enable, + u8 *dstate, +@@ -2436,7 +2636,7 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + + switch (priv->mode) { + case PWM_MODE_MAX: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2448,16 +2648,16 @@ static int hp_wmi_apply_fan_settings(struct hp_wmi_hwmon_priv *priv) + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; +- ret = hp_wmi_fan_speed_set(priv, pwm_to_rpm(priv->pwm, priv)); ++ ret = hp_wmi_fan_speed_set(priv); + if (ret < 0) + return ret; + mod_delayed_work(system_dfl_wq, &priv->keep_alive_dwork, + secs_to_jiffies(KEEP_ALIVE_DELAY_SECS)); + return 0; + case PWM_MODE_AUTO: +- if (is_victus_s_thermal_profile()) { ++ if (hp_wmi_fan_control_supported()) { + ret = hp_wmi_get_fan_count_userdefine_trigger(); + if (ret < 0) + return ret; +@@ -2481,12 +2681,12 @@ static umode_t hp_wmi_hwmon_is_visible(const void *data, + { + switch (type) { + case hwmon_pwm: +- if (attr == hwmon_pwm_input && !is_victus_s_thermal_profile()) ++ if (attr == hwmon_pwm_input && !hp_wmi_fan_control_supported()) + return 0; + return 0644; + case hwmon_fan: +- if (is_victus_s_thermal_profile()) { +- if (hp_wmi_get_fan_speed_victus_s(channel) >= 0) ++ if (hp_wmi_fan_control_supported()) { ++ if (hp_wmi_get_active_fan_speed(channel) >= 0) + return 0444; + } else { + if (hp_wmi_get_fan_speed(channel) >= 0) +@@ -2510,8 +2710,8 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + priv = dev_get_drvdata(dev); + switch (type) { + case hwmon_fan: +- if (is_victus_s_thermal_profile()) +- ret = hp_wmi_get_fan_speed_victus_s(channel); ++ if (hp_wmi_fan_control_supported()) ++ ret = hp_wmi_get_active_fan_speed(channel); + else + ret = hp_wmi_get_fan_speed(channel); + if (ret < 0) +@@ -2520,10 +2720,10 @@ static int hp_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + return 0; + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + +- rpm = hp_wmi_get_fan_speed_victus_s(channel); ++ rpm = hp_wmi_get_active_fan_speed(channel); + if (rpm < 0) + return rpm; + *val = rpm_to_pwm(rpm / 100, priv); +@@ -2550,14 +2750,15 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) + { + struct hp_wmi_hwmon_priv *priv; +- int rpm; ++ int cpu_rpm, gpu_rpm; + + priv = dev_get_drvdata(dev); + guard(mutex)(&priv->lock); + switch (type) { + case hwmon_pwm: + if (attr == hwmon_pwm_input) { +- if (!is_victus_s_thermal_profile()) ++ int rpm; ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* PWM input is invalid when not in manual mode */ + if (priv->mode != PWM_MODE_MANUAL) +@@ -2566,7 +2767,10 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + /* ensure PWM input is within valid fan speeds */ + rpm = pwm_to_rpm(val, priv); + rpm = clamp_val(rpm, priv->min_rpm, priv->max_rpm); +- priv->pwm = rpm_to_pwm(rpm, priv); ++ if (channel == CPU_FAN) ++ priv->cpu_pwm = rpm_to_pwm(rpm, priv); ++ else if (channel == GPU_FAN) ++ priv->gpu_pwm = rpm_to_pwm(rpm, priv); + return hp_wmi_apply_fan_settings(priv); + } + switch (val) { +@@ -2574,16 +2778,20 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + priv->mode = PWM_MODE_MAX; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_MANUAL: +- if (!is_victus_s_thermal_profile()) ++ if (!hp_wmi_fan_control_supported()) + return -EOPNOTSUPP; + /* + * When switching to manual mode, set fan speed to + * current RPM values to ensure a smooth transition. + */ +- rpm = hp_wmi_get_fan_speed_victus_s(channel); +- if (rpm < 0) +- return rpm; +- priv->pwm = rpm_to_pwm(rpm / 100, priv); ++ cpu_rpm = hp_wmi_get_active_fan_speed(CPU_FAN); ++ if (cpu_rpm < 0) ++ return cpu_rpm; ++ gpu_rpm = hp_wmi_get_active_fan_speed(GPU_FAN); ++ if (gpu_rpm < 0) ++ return gpu_rpm; ++ priv->cpu_pwm = rpm_to_pwm(cpu_rpm / 100, priv); ++ priv->gpu_pwm = rpm_to_pwm(gpu_rpm / 100, priv); + priv->mode = PWM_MODE_MANUAL; + return hp_wmi_apply_fan_settings(priv); + case PWM_MODE_AUTO: +@@ -2599,7 +2807,7 @@ static int hp_wmi_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + + static const struct hwmon_channel_info * const info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT, HWMON_F_INPUT), +- HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT), ++ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE | HWMON_PWM_INPUT, HWMON_PWM_INPUT), + NULL + }; + +@@ -2640,14 +2848,14 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + struct victus_s_fan_table *fan_table; + u8 min_rpm, max_rpm; + u8 cpu_rpm, gpu_rpm, noise_db; +- int gpu_delta, i, num_entries, ret; ++ int i, num_entries, ret; + size_t header_size, entry_size; + + /* Default behaviour on hwmon init is automatic mode */ + priv->mode = PWM_MODE_AUTO; + +- /* Bypass all non-Victus S devices */ +- if (!is_victus_s_thermal_profile()) ++ /* Bypass devices without fan control support. */ ++ if (!hp_wmi_fan_table_supported()) + return 0; + + ret = hp_wmi_perform_query(HPWMI_VICTUS_S_GET_FAN_TABLE_QUERY, +@@ -2686,10 +2894,8 @@ static int hp_wmi_setup_fan_settings(struct hp_wmi_hwmon_priv *priv) + if (min_rpm == U8_MAX || max_rpm == 0) + return -EINVAL; + +- gpu_delta = fan_table->entries[0].gpu_rpm - fan_table->entries[0].cpu_rpm; + priv->min_rpm = min_rpm; + priv->max_rpm = max_rpm; +- priv->gpu_delta = gpu_delta; + + return 0; + } +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -527,11 +528,104 @@ static void huawei_wmi_battery_exit(struct device *dev) + + /* Fn lock */ + ++/* GFRS byte[1] / SFRS byte[2] (FRSR) fn-lock state values */ ++#define FN_LOCK_ACPI_OFF 1 ++#define FN_LOCK_ACPI_ON 2 ++#define FN_LOCK_ACPI_STAT_OK 0 ++ ++/* ++ * Newer Huawei models (e.g. HUAWEI FLMH-XX / MateBook 14 2024) use direct ++ * ACPI methods \GFRS / \SFRS (Get/Set Fn key Reversal Status) to control ++ * Fn-lock via EC registers 0x6B (read) and 0x6C (write). ++ * ++ * GFRS response buffer layout: ++ * byte[0] = STAT (FN_LOCK_ACPI_STAT_OK = success) ++ * byte[1] = FN_LOCK_ACPI_OFF (fn-lock off) or FN_LOCK_ACPI_ON (fn-lock on) ++ * ++ * SFRS argument layout (CreateByteField(Arg0, 0x02, FRSR)): ++ * Value is read from byte[2] of the integer argument, so it must be ++ * passed as (value << 16): ++ * (FN_LOCK_ACPI_OFF << 16) = fn-lock off (writes 0x55 to EC 0x6C) ++ * (FN_LOCK_ACPI_ON << 16) = fn-lock on (writes 0x5A to EC 0x6C) ++ */ ++ ++static int huawei_acpi_fn_lock_get(int *on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = 0; ++ ++ status = acpi_evaluate_object(NULL, "\\GFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) ++ return -ENODATA; ++ ++ /* byte[0] = STAT, byte[1] = fn-lock state */ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ switch (obj->buffer.pointer[1]) { ++ case FN_LOCK_ACPI_OFF: ++ if (on) ++ *on = 0; ++ break; ++ case FN_LOCK_ACPI_ON: ++ if (on) ++ *on = 1; ++ break; ++ default: ++ return -ENODATA; ++ } ++ ++ return 0; ++} ++ ++static int huawei_acpi_fn_lock_set(int on) ++{ ++ union acpi_object acpi_arg; ++ struct acpi_object_list arg_list = { .count = 1, .pointer = &acpi_arg }; ++ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_status status; ++ ++ /* ++ * SFRS reads byte[2] of its argument via CreateByteField(Arg0, 0x02). ++ * on=0 → FRSR=FN_LOCK_ACPI_OFF → EC gets 0x55 (fn-lock off) ++ * on=1 → FRSR=FN_LOCK_ACPI_ON → EC gets 0x5A (fn-lock on) ++ */ ++ acpi_arg.type = ACPI_TYPE_INTEGER; ++ acpi_arg.integer.value = (on ? FN_LOCK_ACPI_ON : FN_LOCK_ACPI_OFF) << 16; ++ ++ status = acpi_evaluate_object(NULL, "\\SFRS", &arg_list, &output); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ union acpi_object *obj __free(kfree) = output.pointer; ++ if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 1) ++ return -ENODATA; ++ ++ if (obj->buffer.pointer[0] != FN_LOCK_ACPI_STAT_OK) ++ return -EIO; ++ ++ return 0; ++} ++ + static int huawei_wmi_fn_lock_get(int *on) + { + u8 ret[0x100] = { 0 }; + int err, i; + ++ /* Newer models: use direct ACPI \GFRS method */ ++ if (acpi_has_method(NULL, "\\GFRS")) ++ return huawei_acpi_fn_lock_get(on); ++ ++ /* WMI fallback */ + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; +@@ -550,6 +644,11 @@ static int huawei_wmi_fn_lock_set(int on) + { + union hwmi_arg arg; + ++ /* Newer models: use direct ACPI \SFRS method */ ++ if (acpi_has_method(NULL, "\\SFRS")) ++ return huawei_acpi_fn_lock_set(on); ++ ++ /* WMI fallback */ + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + +diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c +--- a/drivers/platform/x86/intel/int1092/intel_sar.c ++++ b/drivers/platform/x86/intel/int1092/intel_sar.c +@@ -91,10 +91,10 @@ static acpi_status parse_package(struct wwan_sar_context *context, union acpi_ob + item->package.count <= data->total_dev_mode) + return AE_ERROR; + +- data->device_mode_info = devm_kmalloc_array(&context->sar_device->dev, +- data->total_dev_mode, +- sizeof(*data->device_mode_info), +- GFP_KERNEL); ++ data->device_mode_info = devm_kcalloc(&context->sar_device->dev, ++ data->total_dev_mode, ++ sizeof(*data->device_mode_info), ++ GFP_KERNEL); + if (!data->device_mode_info) + return AE_ERROR; + +diff --git a/drivers/platform/x86/intel/pmc/core.c b/drivers/platform/x86/intel/pmc/core.c +--- a/drivers/platform/x86/intel/pmc/core.c ++++ b/drivers/platform/x86/intel/pmc/core.c +@@ -1583,7 +1583,7 @@ int pmc_core_pmt_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc, struct tel + { + const u8 *lpm_indices; + int num_maps, mode_offset = 0; +- int ret, lpm_size; ++ int ret = 0, lpm_size; + u8 mode; + + lpm_indices = pmc->map->lpm_reg_index; +diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig +--- a/drivers/platform/x86/lenovo/Kconfig ++++ b/drivers/platform/x86/lenovo/Kconfig +@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA + To compile this driver as a module, choose M here: the module + will be called lenovo-wmi-camera. + ++config LENOVO_YB9_KBDOCK ++ tristate "Lenovo Yoga Book 9 keyboard dock detection" ++ depends on ACPI_WMI ++ depends on DMI ++ depends on INPUT ++ help ++ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9 ++ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to ++ either screen; this driver reports SW_TABLET_MODE input events based ++ on the attachment state and exposes the raw position in sysfs. ++ ++ To compile this driver as a module, choose M here: the module will be ++ called lenovo-yb9-kbdock. ++ + config LENOVO_YMC + tristate "Lenovo Yoga Tablet Mode Control" + depends on ACPI_WMI +diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile +--- a/drivers/platform/x86/lenovo/Makefile ++++ b/drivers/platform/x86/lenovo/Makefile +@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o + obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o + + lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o ++lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o + lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o + lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o + lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2564,8 +2564,8 @@ module_init(ideapad_laptop_init) + + static void __exit ideapad_laptop_exit(void) + { +- ideapad_wmi_driver_unregister(); + platform_driver_unregister(&ideapad_acpi_driver); ++ ideapad_wmi_driver_unregister(); + } + module_exit(ideapad_laptop_exit) + +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -38,6 +38,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +68,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -186,6 +188,7 @@ enum tpacpi_hkey_event_t { + TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ + TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ + TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ ++ TP_HKEY_EV_USB_C_SECURITY = 0x131e, /* USB C Security (Fn+U, Fn+S) */ + TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ + TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ + +@@ -309,6 +312,7 @@ struct tp_acpi_drv_struct { + + struct ibm_struct { + char *name; ++ acpi_device_class device_class; + + int (*read) (struct seq_file *); + int (*write) (char *); +@@ -374,6 +378,8 @@ static struct { + u32 has_adaptive_kbd:1; + u32 kbd_lang:1; + u32 trackpoint_doubletap_enable:1; ++ u32 usbc_security_supported:1; ++ bool usbc_security_enabled; + struct quirk_entry *quirks; + } tp_features; + +@@ -838,9 +844,8 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) + } + + ibm->acpi->device->driver_data = ibm; +- scnprintf(acpi_device_class(ibm->acpi->device), +- sizeof(acpi_device_class(ibm->acpi->device)), +- "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); ++ scnprintf(ibm->device_class, sizeof(ibm->device_class), "%s/%s", ++ TPACPI_ACPI_EVENT_PREFIX, ibm->name); + + status = acpi_install_notify_handler(*ibm->acpi->handle, + ibm->acpi->type, dispatch_acpi_notify, ibm); +@@ -3869,7 +3874,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + pr_err("unknown HKEY notification event %d\n", event); + /* forward it to userspace, maybe it knows how to handle it */ + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, 0); + return; +@@ -3949,7 +3954,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) + /* netlink events */ + if (send_acpi_ev) { + acpi_bus_generate_netlink_event( +- ibm->acpi->device->pnp.device_class, ++ ibm->device_class, + dev_name(&ibm->acpi->device->dev), + event, hkey); + } +@@ -8840,6 +8845,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { + TPACPI_Q_LNV3('R', '0', 'T', TPACPI_FAN_NS), /* 11e Gen5 GL */ + TPACPI_Q_LNV3('R', '1', 'D', TPACPI_FAN_NS), /* 11e Gen5 GL-R */ + TPACPI_Q_LNV3('R', '0', 'V', TPACPI_FAN_NS), /* 11e Gen5 KL-Y */ ++ TPACPI_Q_LNV('H', '3', TPACPI_FAN_NS), /* Edge E330 */ + TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ + TPACPI_Q_LNV3('R', '0', 'Q', TPACPI_FAN_DECRPM),/* L480 */ + TPACPI_Q_LNV('8', 'F', TPACPI_FAN_TPR), /* ThinkPad x120e */ +@@ -11285,6 +11291,114 @@ static struct ibm_struct hwdd_driver_data = { + .name = "hwdd", + }; + ++/************************************************************************* ++ * USB-C Security subdriver ++ * ++ * HKEY.USCS(0) is a read-only ACPI method; its argument is ignored. ++ * It always returns: ++ * bit 16 - USB-C security capability present on this SKU or not ++ * bit 0 - USB-C Security state (enable or disable) ++ * ++ * Hotkey ++ * ------ ++ * 0x131e (Fn+U, Fn+S): firmware toggles USBS before firing the event. ++ * The driver reads back the new state and notifies the sysfs attribute. ++ */ ++ ++/* USCS() return word bit layout */ ++#define USCS_CAP_BIT BIT(16) /* capability: feature present on SKU */ ++#define USCS_STATUS_BIT BIT(0) /* current security state */ ++ ++/* Protects USCS() ACPI method calls in usbc_security_query() */ ++static DEFINE_MUTEX(usbc_security_mutex); ++ ++/** ++ * usbc_security_query - read current USB-C security state via USCS() ++ * @enabled: out - true when security is ON (data connections blocked) ++ * ++ * Returns: ++ * 0 success, @enabled contains the current state ++ * -EIO ACPI evaluation failed ++ * -ENODEV capability bit absent; feature not present on this SKU* ++ */ ++static int usbc_security_query(bool *enabled) ++{ ++ int status; ++ ++ guard(mutex)(&usbc_security_mutex); ++ if (!acpi_evalf(hkey_handle, &status, "USCS", "dd", 0)) ++ return -EIO; ++ ++ if (!(status & USCS_CAP_BIT)) { ++ pr_debug("USCS cap bit absent (raw=0x%x)\n", status); ++ return -ENODEV; ++ } ++ ++ *enabled = status & USCS_STATUS_BIT; ++ return 0; ++} ++ ++/* sysfs: /sys/devices/platform/thinkpad_acpi/usb_c_security ---------- */ ++static ssize_t usb_c_security_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return sysfs_emit(buf, "%s\n", ++ str_enabled_disabled(tp_features.usbc_security_enabled)); ++} ++ ++static DEVICE_ATTR_RO(usb_c_security); ++ ++static struct attribute *usbc_security_attributes[] = { ++ &dev_attr_usb_c_security.attr, ++ NULL, ++}; ++ ++static umode_t usbc_security_attr_is_visible(struct kobject *kobj, ++ struct attribute *attr, int n) ++{ ++ return tp_features.usbc_security_supported ? attr->mode : 0; ++} ++ ++static const struct attribute_group usbc_security_attr_group = { ++ .is_visible = usbc_security_attr_is_visible, ++ .attrs = usbc_security_attributes, ++}; ++ ++static int tpacpi_usbc_security_init(struct ibm_init_struct *iibm) ++{ ++ int err; ++ ++ if (!acpi_has_method(hkey_handle, "USCS")) ++ return -ENODEV; ++ ++ err = usbc_security_query(&tp_features.usbc_security_enabled); ++ if (err == -ENODEV) ++ return 0; ++ if (err) ++ return err; ++ ++ tp_features.usbc_security_supported = true; ++ return 0; ++} ++ ++/* tpacpi_usbc_security_hotkey - handle Fn+U Fn+S hotkey (0x131e) */ ++static bool tpacpi_usbc_security_hotkey(void) ++{ ++ if (!tp_features.usbc_security_supported) ++ return false; ++ ++ if (usbc_security_query(&tp_features.usbc_security_enabled)) ++ return false; ++ ++ sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "usb_c_security"); ++ return true; ++} ++ ++static struct ibm_struct usbc_security_driver_data = { ++ .name = "usbc_security", ++}; ++ + /* --------------------------------------------------------------------- */ + + static struct attribute *tpacpi_driver_attributes[] = { +@@ -11345,6 +11459,7 @@ static const struct attribute_group *tpacpi_groups[] = { + &dprc_attr_group, + &auxmac_attr_group, + &hwdd_attr_group, ++ &usbc_security_attr_group, + NULL, + }; + +@@ -11499,6 +11614,8 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) + case TP_HKEY_EV_PROFILE_TOGGLE2: + platform_profile_cycle(); + return true; ++ case TP_HKEY_EV_USB_C_SECURITY: ++ return tpacpi_usbc_security_hotkey(); + } + + return false; +@@ -11964,6 +12081,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { + .init = tpacpi_hwdd_init, + .data = &hwdd_driver_data, + }, ++ { ++ .init = tpacpi_usbc_security_init, ++ .data = &usbc_security_driver_data, ++ }, + }; + + static int __init set_ibm_param(const char *val, const struct kernel_param *kp) +diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c +new file mode 100644 +--- /dev/null ++++ b/drivers/platform/x86/lenovo/yb9-kbdock.c +@@ -0,0 +1,323 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Lenovo Yoga Book 9 keyboard-dock detection ++ * ++ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically ++ * attaches to the bottom screen in one of two positions. The EC tracks ++ * attachment state in a 2-bit field called BKBD and signals changes via WMI ++ * event 0xEB on the WM10 ACPI device (_UID "GMZN"). ++ * ++ * BKBD values: ++ * 0 = keyboard detached ++ * 1 = keyboard docked on the top half of the bottom screen ++ * 2 = keyboard docked on the bottom half of the bottom screen ++ * 3 = reserved / not observed ++ * ++ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes): ++ * ++ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...) ++ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly. ++ * The notify callback receives BKBD as an integer; no separate query needed. ++ * ++ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...) ++ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector) ++ * WmiDataId(2) uint32 Status = BKBD value ++ * Used on probe and resume to read initial state. ++ * ++ * The event driver (LENOVO_BTKBD_EVENT) fires a notifier chain on each WMI ++ * event. The block driver (LENOVO_FEATURE_STATUS_DATA) owns the input_dev ++ * and registers a notifier_block to receive those events, eliminating the ++ * need for shared global state or a mutex. ++ * ++ * SW_TABLET_MODE=1 is reported when the keyboard is detached; ++ * SW_TABLET_MODE=0 when docked in either position (keyboard present). ++ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position". ++ * ++ * Copyright (C) 2026 Dave Carey ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285" ++#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6" ++ ++/* BKBD encoding */ ++#define BKBD_DETACHED 0 ++ ++/* LENOVO_FEATURE_STATUS_DATA feature selector */ ++#define YB9_FEATURE_STATUS_ID 0x00060000u ++ ++/* ++ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {uint32 IDs, uint32 Status}. ++ * IDs is always 0x00060000; Status holds the BKBD value (0–3). ++ */ ++struct lenovo_feature_status { ++ __le32 id; ++ __le32 status; ++} __packed; ++ ++/* ------------------------------------------------------------------ ++ * Notifier chain — event driver fires it, block driver listens ++ * ------------------------------------------------------------------ */ ++ ++static BLOCKING_NOTIFIER_HEAD(yb9_kbdock_chain_head); ++ ++static void devm_yb9_kbdock_unregister_notifier(void *data) ++{ ++ struct notifier_block *nb = data; ++ ++ blocking_notifier_chain_unregister(&yb9_kbdock_chain_head, nb); ++} ++ ++static int devm_yb9_kbdock_register_notifier(struct device *dev, ++ struct notifier_block *nb) ++{ ++ int ret; ++ ++ ret = blocking_notifier_chain_register(&yb9_kbdock_chain_head, nb); ++ if (ret < 0) ++ return ret; ++ ++ return devm_add_action_or_reset(dev, devm_yb9_kbdock_unregister_notifier, nb); ++} ++ ++/* ------------------------------------------------------------------ ++ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA ++ * (owns input_dev, sysfs, PM resume) ++ * ------------------------------------------------------------------ */ ++ ++struct yb9_kbdock_data { ++ struct wmi_device *wdev; ++ struct input_dev *input_dev; ++ struct notifier_block nb; ++ spinlock_t lock; /* protects input_report_switch + input_sync */ ++}; ++ ++static int yb9_kbdock_query(struct yb9_kbdock_data *d, u32 *bkbd) ++{ ++ struct wmi_buffer out; ++ int ret; ++ ++ ret = wmidev_query_block(d->wdev, 0, &out, ++ sizeof(struct lenovo_feature_status)); ++ if (ret) ++ return ret; ++ ++ struct lenovo_feature_status *fs __free(kfree) = out.data; ++ ++ if (le32_to_cpu(fs->id) != YB9_FEATURE_STATUS_ID) ++ return -EIO; ++ ++ *bkbd = le32_to_cpu(fs->status); ++ return 0; ++} ++ ++static void yb9_kbdock_report(struct yb9_kbdock_data *d, u32 bkbd) ++{ ++ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0; ++ ++ spin_lock(&d->lock); ++ input_report_switch(d->input_dev, SW_TABLET_MODE, tablet); ++ input_sync(d->input_dev); ++ spin_unlock(&d->lock); ++ dev_dbg(&d->wdev->dev, "BKBD=%u SW_TABLET_MODE=%d\n", bkbd, tablet); ++} ++ ++static int yb9_kbdock_sync(struct yb9_kbdock_data *d) ++{ ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ ++ yb9_kbdock_report(d, bkbd); ++ return 0; ++} ++ ++static int yb9_kbdock_nb_call(struct notifier_block *nb, ++ unsigned long bkbd, void *unused) ++{ ++ struct yb9_kbdock_data *d = ++ container_of(nb, struct yb9_kbdock_data, nb); ++ ++ yb9_kbdock_report(d, bkbd); ++ return NOTIFY_DONE; ++} ++ ++static ssize_t keyboard_position_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ u32 bkbd; ++ int ret; ++ ++ ret = yb9_kbdock_query(d, &bkbd); ++ if (ret) ++ return ret; ++ return sysfs_emit(buf, "%u\n", bkbd); ++} ++static DEVICE_ATTR_RO(keyboard_position); ++ ++static const struct attribute * const yb9_kbdock_attrs[] = { ++ &dev_attr_keyboard_position.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(yb9_kbdock); ++ ++static int yb9_kbdock_resume(struct device *dev) ++{ ++ struct yb9_kbdock_data *d = dev_get_drvdata(dev); ++ ++ return yb9_kbdock_sync(d); ++} ++static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume); ++ ++static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx) ++{ ++ struct yb9_kbdock_data *d; ++ struct input_dev *input_dev; ++ int ret; ++ ++ d = devm_kzalloc(&wdev->dev, sizeof(*d), GFP_KERNEL); ++ if (!d) ++ return -ENOMEM; ++ ++ d->wdev = wdev; ++ spin_lock_init(&d->lock); ++ ++ input_dev = devm_input_allocate_device(&wdev->dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch"; ++ input_dev->phys = YB9_KBDOCK_QUERY_GUID "/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); ++ ++ ret = input_register_device(input_dev); ++ if (ret) ++ return ret; ++ ++ d->input_dev = input_dev; ++ d->nb.notifier_call = yb9_kbdock_nb_call; ++ ++ ret = devm_yb9_kbdock_register_notifier(&wdev->dev, &d->nb); ++ if (ret) ++ return ret; ++ ++ dev_set_drvdata(&wdev->dev, d); ++ return yb9_kbdock_sync(d); ++} ++ ++static const struct wmi_device_id yb9_kbdock_block_id_table[] = { ++ { .guid_string = YB9_KBDOCK_QUERY_GUID }, ++ { } ++}; ++ ++static struct wmi_driver yb9_kbdock_block_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock", ++ .dev_groups = yb9_kbdock_groups, ++ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops), ++ }, ++ .id_table = yb9_kbdock_block_id_table, ++ .no_singleton = true, ++ .probe = yb9_kbdock_block_probe, ++}; ++ ++/* ------------------------------------------------------------------ ++ * Event WMI driver — LENOVO_BTKBD_EVENT ++ * (fires the notifier chain on each WMI event) ++ * ------------------------------------------------------------------ */ ++ ++static void yb9_kbdock_notify_new(struct wmi_device *wdev, ++ const struct wmi_buffer *data) ++{ ++ /* ++ * _WED(0xEB) returns EC.BKBD directly as a 32-bit integer ++ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status). ++ * Short-buffer guard is handled by .min_event_size below. ++ */ ++ u32 bkbd = le32_to_cpu(*(const __le32 *)data->data); ++ ++ blocking_notifier_call_chain(&yb9_kbdock_chain_head, bkbd, NULL); ++} ++ ++static const struct wmi_device_id yb9_kbdock_event_id_table[] = { ++ { .guid_string = YB9_KBDOCK_EVENT_GUID }, ++ { } ++}; ++MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table); ++ ++static struct wmi_driver yb9_kbdock_event_driver = { ++ .driver = { ++ .name = "lenovo-yb9-kbdock-event", ++ }, ++ .id_table = yb9_kbdock_event_id_table, ++ .no_singleton = true, ++ .notify_new = yb9_kbdock_notify_new, ++ .min_event_size = sizeof(__le32), ++}; ++ ++/* ------------------------------------------------------------------ ++ * Module init / exit ++ * ------------------------------------------------------------------ */ ++ ++static const struct dmi_system_id yb9_kbdock_dmi_table[] __initconst = { ++ { ++ /* Lenovo Yoga Book 9 14IAH10 */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ ++static int __init yb9_kbdock_init(void) ++{ ++ int ret; ++ ++ if (!dmi_check_system(yb9_kbdock_dmi_table)) ++ return -ENODEV; ++ ++ ret = wmi_driver_register(&yb9_kbdock_event_driver); ++ if (ret) ++ return ret; ++ ++ ret = wmi_driver_register(&yb9_kbdock_block_driver); ++ if (ret) { ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++ return ret; ++ } ++ ++ return 0; ++} ++module_init(yb9_kbdock_init); ++ ++static void __exit yb9_kbdock_exit(void) ++{ ++ wmi_driver_unregister(&yb9_kbdock_block_driver); ++ wmi_driver_unregister(&yb9_kbdock_event_driver); ++} ++module_exit(yb9_kbdock_exit); ++ ++MODULE_AUTHOR("Dave Carey "); ++MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c +--- a/drivers/platform/x86/lenovo/ymc.c ++++ b/drivers/platform/x86/lenovo/ymc.c +@@ -28,6 +28,22 @@ static bool force; + module_param(force, bool, 0444); + MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type"); + ++static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = { ++ { ++ /* ++ * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the ++ * yb9-kbdock driver. Suppress lenovo-ymc on this machine to ++ * avoid userspace seeing two input nodes that both advertise ++ * the SW_TABLET_MODE capability. ++ */ ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"), ++ }, ++ }, ++ { } ++}; ++ + static const struct dmi_system_id allowed_chasis_types_dmi_table[] = { + { + .matches = { +@@ -107,6 +123,9 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx) + struct input_dev *input_dev; + int err; + ++ if (dmi_check_system(lenovo_ymc_nosupport_dmi_table)) ++ return -ENODEV; ++ + if (!dmi_check_system(allowed_chasis_types_dmi_table)) { + if (force) + dev_info(&wdev->dev, "Force loading Lenovo YMC support\n"); +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -8,8 +8,11 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #include ++#include + #include + #include ++#include ++#include + #include + #include + #include +@@ -17,10 +20,12 @@ + #include + #include + #include ++#include + #include + #include + #include + #include ++#include + + #include + +@@ -57,13 +62,18 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define LG_ADDRESS_SPACE_DEBUG_MSG_START_ADR 0x3E8 + #define LG_ADDRESS_SPACE_DEBUG_MSG_END_ADR 0x5E8 + +-#define WMI_EVENT_GUID0 "E4FB94F9-7F2B-4173-AD1A-CD1D95086248" +-#define WMI_EVENT_GUID1 "023B133E-49D1-4E10-B313-698220140DC2" +-#define WMI_EVENT_GUID2 "37BE1AC0-C3F2-4B1F-BFBE-8FDEAF2814D6" +-#define WMI_EVENT_GUID3 "911BAD44-7DF8-4FBB-9319-BABA1C4B293B" +-#define WMI_METHOD_WMAB "C3A72B38-D3EF-42D3-8CBB-D5A57049F66D" +-#define WMI_METHOD_WMBB "2B4F501A-BD3C-4394-8DCF-00A7D2BC8210" +-#define WMI_EVENT_GUID WMI_EVENT_GUID0 ++#define LG_NOTIFY_TABLET_MODE_OFF 0x50 ++#define LG_NOTIFY_TABLET_MODE_ON 0x51 ++#define LG_NOTIFY_HOTKEY 0x80 ++#define LG_NOTIFY_THERMAL 0x81 ++#define LG_NOTIFY_MISC 0x82 ++ ++#define LG_OREP_READ_EC 0 ++#define LG_OREP_WRITE_EC 1 ++#define LG_OREP_DEBUG 2 ++#define LG_OREP_UPDATE_SYSTEM_STATE 3 ++#define LG_OREP_INTERCEPT_WMI_EVENTS 4 ++#define LG_OREP_WAKE_ON_LAN 6 + + #define SB_GGOV_METHOD "\\_SB.GGOV" + #define GOV_TLED 0x2020008 +@@ -78,26 +88,29 @@ MODULE_PARM_DESC(fw_debug, "Enable printing of firmware debug messages"); + #define WMBB_USB_CHARGE 0x10B + #define WMBB_BATT_LIMIT 0x10C + ++#define KBD_LED_BRIGHTNESS_MASK GENMASK(4, 0) ++#define KBD_LED_BRIGHTNESS_OFF 0x0 ++#define KBD_LED_BRIGHTNESS_HALF 0x2 ++#define KBD_LED_BRIGHTNESS_FULL 0x4 ++#define KBD_LED_MODE_MASK GENMASK(6, 5) ++#define KBD_LED_MODE_OFF 0x0 ++#define KBD_LED_MODE_ON 0x1 ++#define KBD_LED_STATUS BIT(7) ++#define KBD_LED_MAGIC_MASK GENMASK(15, 8) ++/* Exact purpose is unknown, maybe some sort of brightness limit? */ ++#define KBD_LED_MAGIC 0x05 ++ + #define FAN_MODE_LOWER GENMASK(1, 0) + #define FAN_MODE_UPPER GENMASK(5, 4) + + #define PLATFORM_NAME "lg-laptop" + +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID0); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID1); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID2); +-MODULE_ALIAS("wmi:" WMI_EVENT_GUID3); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMAB); +-MODULE_ALIAS("wmi:" WMI_METHOD_WMBB); ++struct lg_wmab_buffer_result { ++ __le32 value; ++ __le32 status; ++} __packed; + + static struct platform_device *pf_device; +-static struct input_dev *wmi_input_dev; +- +-static u32 inited; +-#define INIT_INPUT_WMI_0 0x01 +-#define INIT_INPUT_WMI_2 0x02 +-#define INIT_INPUT_ACPI 0x04 +-#define INIT_SPARSE_KEYMAP 0x80 + + static int battery_limit_use_wmbb; + static bool kbd_backlight_available; +@@ -105,16 +118,65 @@ static struct led_classdev kbd_backlight; + static enum led_brightness get_kbd_backlight_level(struct device *dev); + + static const struct key_entry wmi_keymap[] = { +- {KE_KEY, 0x70, {KEY_F15} }, /* LG control panel (F1) */ +- {KE_KEY, 0x74, {KEY_F21} }, /* Touchpad toggle (F5) */ +- {KE_KEY, 0xf020000, {KEY_F14} }, /* Read mode (F9) */ +- {KE_KEY, 0x10000000, {KEY_F16} },/* Keyboard backlight (F8) - pressing +- * this key both sends an event and +- * changes backlight level. +- */ ++ /* Placeholder value send by multiple hotkeys handled by ACPI */ ++ { KE_IGNORE, 0x0, { KEY_UNKNOWN }}, ++ /* LG control panel */ ++ { KE_KEY, 0x70, { KEY_F15 }}, ++ /* Touchpad toggle */ ++ { KE_KEY, 0x74, { KEY_F21 }}, ++ /* Mute Audio, already handled by ACPI */ ++ { KE_IGNORE, 0x78, { KEY_MUTE }}, ++ /* Read mode */ ++ { KE_KEY, 0xf020000, { KEY_F14 }}, ++ /* Open settings */ ++ { KE_KEY, 0xf070002, { KEY_CONFIG }}, ++ /* Keyboard backlight - pressing this key both sends an event and changes backlight level */ ++ { KE_KEY, 0x10000000, { KEY_F16 }}, ++ /* Hotkey combination pressed */ ++ { KE_IGNORE, 0x30010000, { KEY_UNKNOWN }}, ++ /* Hotkey combination released */ ++ { KE_IGNORE, 0x30010001, { KEY_UNKNOWN }}, ++ /* Change fan mode */ ++ { KE_KEY, 0x30010051, { KEY_PERFORMANCE }}, ++ /* Disable camera */ ++ { KE_KEY, 0x40020000, { KEY_CAMERA_ACCESS_TOGGLE }}, ++ /* Mute microphone */ ++ { KE_KEY, 0x40020001, { KEY_MICMUTE }}, ++ /* Fn-Lock */ ++ { KE_KEY, 0x40030001, { KEY_FN_ESC }}, + {KE_END, 0} + }; + ++static int lg_laptop_execute_orep(acpi_handle handle, u64 command, u64 value, ++ unsigned long long *result) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = command, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "OREP", &args, result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; ++} ++ + static int ggov(u32 arg0) + { + union acpi_object args[1]; +@@ -155,30 +217,97 @@ static int ggov(u32 arg0) + return res; + } + +-static union acpi_object *lg_wmab(struct device *dev, u32 method, u32 arg1, u32 arg2) ++static int lg_wmab_get(struct device *dev, u32 method, u32 *result) + { +- union acpi_object args[3]; +- acpi_status status; +- struct acpi_object_list arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_GET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = 0, ++ }, ++ }, ++ }; ++ struct lg_wmab_buffer_result *buffer_result; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; + +- args[0].type = ACPI_TYPE_INTEGER; +- args[0].integer.value = method; +- args[1].type = ACPI_TYPE_INTEGER; +- args[1].integer.value = arg1; +- args[2].type = ACPI_TYPE_INTEGER; +- args[2].integer.value = arg2; ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, &buffer); ++ if (ACPI_FAILURE(status)) ++ return -EIO; + +- arg.count = 3; +- arg.pointer = args; ++ union acpi_object *obj __free(kfree) = buffer.pointer; + +- status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &arg, &buffer); +- if (ACPI_FAILURE(status)) { +- dev_err(dev, "WMAB: call failed.\n"); +- return NULL; ++ if (!obj) ++ return -ENODATA; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ *result = obj->integer.value; ++ return 0; ++ case ACPI_TYPE_BUFFER: ++ if (obj->buffer.length != sizeof(*buffer_result)) ++ return -EPROTO; ++ ++ buffer_result = (struct lg_wmab_buffer_result *)obj->buffer.pointer; ++ if (get_unaligned_le32(&buffer_result->status)) ++ return -EIO; ++ ++ *result = get_unaligned_le32(&buffer_result->value); ++ return 0; ++ default: ++ return -EPROTO; + } ++} + +- return buffer.pointer; ++static int lg_wmab_set(struct device *dev, u32 method, u32 arg) ++{ ++ union acpi_object in[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = method, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = WM_SET, ++ }, ++ }, ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = arg, ++ }, ++ }, ++ }; ++ struct acpi_object_list input = { ++ .count = ARRAY_SIZE(in), ++ .pointer = in, ++ }; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(ACPI_HANDLE(dev), "WMAB", &input, NULL); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ return 0; + } + + static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u32 arg2) +@@ -212,76 +341,11 @@ static union acpi_object *lg_wmbb(struct device *dev, u32 method_id, u32 arg1, u + return (union acpi_object *)buffer.pointer; + } + +-static void wmi_notify(union acpi_object *obj, void *context) +-{ +- long data = (long)context; +- unsigned int brightness; +- +- pr_debug("event guid %li\n", data); +- if (!obj) +- return; +- +- if (obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; +- struct key_entry *key; +- +- if (eventcode == 0x10000000) { +- if (kbd_backlight_available) { +- brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); +- led_classdev_notify_brightness_hw_changed(&kbd_backlight, +- brightness); +- } +- } else { +- key = sparse_keymap_entry_from_scancode( +- wmi_input_dev, eventcode); +- if (key && key->type == KE_KEY) +- sparse_keymap_report_entry(wmi_input_dev, +- key, 1, true); +- } +- } +- +- pr_debug("Type: %i Eventcode: 0x%llx\n", obj->type, +- obj->integer.value); +-} +- +-static void wmi_input_setup(void) +-{ +- acpi_status status; +- +- wmi_input_dev = input_allocate_device(); +- if (wmi_input_dev) { +- wmi_input_dev->name = "LG WMI hotkeys"; +- wmi_input_dev->phys = "wmi/input0"; +- wmi_input_dev->id.bustype = BUS_HOST; +- +- if (sparse_keymap_setup(wmi_input_dev, wmi_keymap, NULL) || +- input_register_device(wmi_input_dev)) { +- pr_info("Cannot initialize input device"); +- input_free_device(wmi_input_dev); +- return; +- } +- +- inited |= INIT_SPARSE_KEYMAP; +- status = wmi_install_notify_handler(WMI_EVENT_GUID0, wmi_notify, +- (void *)0); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_0; +- +- status = wmi_install_notify_handler(WMI_EVENT_GUID2, wmi_notify, +- (void *)2); +- if (ACPI_SUCCESS(status)) +- inited |= INIT_INPUT_WMI_2; +- } else { +- pr_info("Cannot allocate input device"); +- } +-} +- + static ssize_t fan_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) + { + unsigned long value; +- union acpi_object *r; + int ret; + + ret = kstrtoul(buffer, 10, &value); +@@ -290,10 +354,10 @@ static ssize_t fan_mode_store(struct device *dev, + if (value >= 3) + return -EINVAL; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_SET, +- FIELD_PREP(FAN_MODE_LOWER, value) | +- FIELD_PREP(FAN_MODE_UPPER, value)); +- kfree(r); ++ ret = lg_wmab_set(dev, WM_FAN_MODE, FIELD_PREP(FAN_MODE_LOWER, value) | ++ FIELD_PREP(FAN_MODE_UPPER, value)); ++ if (ret < 0) ++ return ret; + + return count; + } +@@ -301,22 +365,14 @@ static ssize_t fan_mode_store(struct device *dev, + static ssize_t fan_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int mode; +- union acpi_object *r; ++ u32 mode; ++ int ret; + +- r = lg_wmab(dev, WM_FAN_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FAN_MODE, &mode); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- mode = FIELD_GET(FAN_MODE_LOWER, r->integer.value); +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", mode); ++ return sysfs_emit(buffer, "%lu\n", FIELD_GET(FAN_MODE_LOWER, mode)); + } + + static ssize_t usb_charge_store(struct device *dev, +@@ -366,41 +422,30 @@ static ssize_t reader_mode_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_READER_MODE, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t reader_mode_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_READER_MODE, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_READER_MODE, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->integer.value; +- +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t fn_lock_store(struct device *dev, +@@ -408,40 +453,30 @@ static ssize_t fn_lock_store(struct device *dev, + const char *buffer, size_t count) + { + bool value; +- union acpi_object *r; + int ret; + + ret = kstrtobool(buffer, &value); + if (ret) + return ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_SET, value); +- if (!r) +- return -EIO; ++ ret = lg_wmab_set(dev, WM_FN_LOCK, value); ++ if (ret < 0) ++ return ret; + +- kfree(r); + return count; + } + + static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, char *buffer) + { +- unsigned int status; +- union acpi_object *r; ++ u32 status; ++ int ret; + +- r = lg_wmab(dev, WM_FN_LOCK, WM_GET, 0); +- if (!r) +- return -EIO; ++ ret = lg_wmab_get(dev, WM_FN_LOCK, &status); ++ if (ret < 0) ++ return ret; + +- if (r->type != ACPI_TYPE_BUFFER) { +- kfree(r); +- return -EIO; +- } +- +- status = !!r->buffer.pointer[0]; +- kfree(r); +- +- return sysfs_emit(buffer, "%d\n", status); ++ return sysfs_emit(buffer, "%d\n", !!status); + } + + static ssize_t charge_control_end_threshold_store(struct device *dev, +@@ -458,14 +493,18 @@ static ssize_t charge_control_end_threshold_store(struct device *dev, + if (value == 100 || value == 80) { + union acpi_object *r; + +- if (battery_limit_use_wmbb) ++ if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_SET, value); +- else +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_SET, value); +- if (!r) +- return -EIO; ++ if (!r) ++ return -EIO; ++ ++ kfree(r); ++ } else { ++ ret = lg_wmab_set(&pf_device->dev, WM_BATT_LIMIT, value); ++ if (ret < 0) ++ return ret; ++ } + +- kfree(r); + return count; + } + +@@ -476,8 +515,9 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) + { +- unsigned int status; + union acpi_object *r; ++ u32 status; ++ int ret; + + if (battery_limit_use_wmbb) { + r = lg_wmbb(&pf_device->dev, WMBB_BATT_LIMIT, WM_GET, 0); +@@ -490,19 +530,13 @@ static ssize_t charge_control_end_threshold_show(struct device *device, + } + + status = r->buffer.pointer[0x10]; ++ kfree(r); + } else { +- r = lg_wmab(&pf_device->dev, WM_BATT_LIMIT, WM_GET, 0); +- if (!r) +- return -EIO; +- +- if (r->type != ACPI_TYPE_INTEGER) { +- kfree(r); +- return -EIO; +- } +- +- status = r->integer.value; ++ ret = lg_wmab_get(&pf_device->dev, WM_BATT_LIMIT, &status); ++ if (ret < 0) ++ return ret; + } +- kfree(r); ++ + if (status != 80 && status != 100) + status = 0; + +@@ -568,10 +602,7 @@ static const struct attribute_group dev_attribute_group = { + static void tpad_led_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- union acpi_object *r; +- +- r = lg_wmab(cdev->dev->parent, WM_TLED, WM_SET, brightness > LED_OFF); +- kfree(r); ++ lg_wmab_set(cdev->dev->parent, WM_TLED, brightness > LED_OFF); + } + + static enum led_brightness tpad_led_get(struct led_classdev *cdev) +@@ -584,47 +615,50 @@ static LED_DEVICE(tpad_led, 1, 0); + static void kbd_backlight_set(struct led_classdev *cdev, + enum led_brightness brightness) + { +- u32 val; +- union acpi_object *r; ++ /* Must always be written */ ++ u32 value = KBD_LED_STATUS; ++ u32 mode, bright; + +- val = 0x22; +- if (brightness <= LED_OFF) +- val = 0; +- if (brightness >= LED_FULL) +- val = 0x24; +- r = lg_wmab(cdev->dev->parent, WM_KEY_LIGHT, WM_SET, val); +- kfree(r); ++ if (brightness <= LED_OFF) { ++ mode = KBD_LED_MODE_OFF; ++ bright = KBD_LED_BRIGHTNESS_OFF; ++ } else { ++ mode = KBD_LED_MODE_ON; ++ if (brightness >= LED_FULL) ++ bright = KBD_LED_BRIGHTNESS_FULL; ++ else ++ bright = KBD_LED_BRIGHTNESS_HALF; ++ } ++ ++ value |= FIELD_PREP(KBD_LED_BRIGHTNESS_MASK, bright); ++ value |= FIELD_PREP(KBD_LED_MODE_MASK, mode); ++ ++ lg_wmab_set(cdev->dev->parent, WM_KEY_LIGHT, value); + } + + static enum led_brightness get_kbd_backlight_level(struct device *dev) + { +- union acpi_object *r; +- int val; ++ u32 value; ++ int ret; + +- r = lg_wmab(dev, WM_KEY_LIGHT, WM_GET, 0); +- +- if (!r) ++ ret = lg_wmab_get(dev, WM_KEY_LIGHT, &value); ++ if (ret < 0) + return LED_OFF; + +- if (r->type != ACPI_TYPE_BUFFER || r->buffer.pointer[1] != 0x05) { +- kfree(r); ++ if (FIELD_GET(KBD_LED_MAGIC_MASK, value) != KBD_LED_MAGIC) + return LED_OFF; +- } + +- switch (r->buffer.pointer[0] & 0x27) { +- case 0x24: +- val = LED_FULL; +- break; +- case 0x22: +- val = LED_HALF; +- break; ++ if (FIELD_GET(KBD_LED_MODE_MASK, value) == KBD_LED_MODE_OFF) ++ return LED_OFF; ++ ++ switch (FIELD_GET(KBD_LED_BRIGHTNESS_MASK, value)) { ++ case KBD_LED_BRIGHTNESS_FULL: ++ return LED_FULL; ++ case KBD_LED_BRIGHTNESS_HALF: ++ return LED_HALF; + default: +- val = LED_OFF; ++ return LED_OFF; + } +- +- kfree(r); +- +- return val; + } + + static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) +@@ -634,26 +668,163 @@ static enum led_brightness kbd_backlight_get(struct led_classdev *cdev) + + static LED_DEVICE(kbd_backlight, 255, LED_BRIGHT_HW_CHANGED); + +-static void wmi_input_destroy(void) +-{ +- if (inited & INIT_INPUT_WMI_2) +- wmi_remove_notify_handler(WMI_EVENT_GUID2); +- +- if (inited & INIT_INPUT_WMI_0) +- wmi_remove_notify_handler(WMI_EVENT_GUID0); +- +- if (inited & INIT_SPARSE_KEYMAP) +- input_unregister_device(wmi_input_dev); +- +- inited &= ~(INIT_INPUT_WMI_0 | INIT_INPUT_WMI_2 | INIT_SPARSE_KEYMAP); +-} +- + static struct platform_driver pf_driver = { + .driver = { + .name = PLATFORM_NAME, + } + }; + ++static int lg_laptop_get_event_data(acpi_handle handle, u32 value, u32 *data) ++{ ++ union acpi_object objs[] = { ++ { ++ .integer = { ++ .type = ACPI_TYPE_INTEGER, ++ .value = value, ++ }, ++ } ++ }; ++ struct acpi_object_list args = { ++ .count = ARRAY_SIZE(objs), ++ .pointer = objs, ++ }; ++ unsigned long long result; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, "_WED", &args, &result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ if (result > U32_MAX) ++ return -EPROTO; ++ ++ *data = result; ++ ++ return 0; ++} ++ ++static void lg_laptop_handle_input_event(struct input_dev *input_dev, u32 value, u32 data) ++{ ++ unsigned int kbd_brightness; ++ ++ switch (value) { ++ case LG_NOTIFY_HOTKEY: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ break; ++ case LG_NOTIFY_THERMAL: ++ /* Currently not supported */ ++ break; ++ case LG_NOTIFY_MISC: ++ switch (data) { ++ case 0x10000000: ++ if (!kbd_backlight_available) ++ break; ++ ++ kbd_brightness = get_kbd_backlight_level(kbd_backlight.dev->parent); ++ led_classdev_notify_brightness_hw_changed(&kbd_backlight, kbd_brightness); ++ break; ++ default: ++ sparse_keymap_report_event(input_dev, data, 1, true); ++ } ++ break; ++ default: ++ break; ++ } ++} ++ ++static void lg_laptop_notify_handler(acpi_handle handle, u32 value, void *context) ++{ ++ struct input_dev *input_dev = context; ++ u32 data; ++ int ret; ++ ++ switch (value) { ++ case LG_NOTIFY_TABLET_MODE_OFF: ++ case LG_NOTIFY_TABLET_MODE_ON: ++ /* Already handled by intel-hid */ ++ return; ++ case LG_NOTIFY_HOTKEY: ++ case LG_NOTIFY_THERMAL: ++ case LG_NOTIFY_MISC: ++ ret = lg_laptop_get_event_data(handle, value, &data); ++ if (ret < 0) { ++ dev_notice(input_dev->dev.parent, "Failed to get event data: %d\n", ret); ++ return; ++ } ++ ++ dev_dbg(input_dev->dev.parent, "Received event %u (%u)\n", value, data); ++ ++ lg_laptop_handle_input_event(input_dev, value, data); ++ return; ++ default: ++ dev_notice(input_dev->dev.parent, "Received unknown event %u\n", value); ++ } ++}; ++ ++static void lg_laptop_remove_notify_handler(void *context) ++{ ++ acpi_handle handle = context; ++ ++ acpi_remove_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler); ++} ++ ++static void lg_laptop_reenable_wmi_events(void *context) ++{ ++ acpi_handle handle = context; ++ unsigned long long dummy; ++ ++ lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 0, &dummy); ++} ++ ++static int lg_laptop_input_init(struct device *dev, acpi_handle handle) ++{ ++ struct input_dev *input_dev; ++ unsigned long long result; ++ acpi_status status; ++ int ret; ++ ++ if (!acpi_has_method(handle, "_WED")) ++ return 0; ++ ++ input_dev = devm_input_allocate_device(dev); ++ if (!input_dev) ++ return -ENOMEM; ++ ++ input_dev->name = "LG WMI hotkeys"; ++ input_dev->phys = "wmi/input0"; ++ input_dev->id.bustype = BUS_HOST; ++ ret = sparse_keymap_setup(input_dev, wmi_keymap, NULL); ++ if (ret < 0) ++ return ret; ++ ++ ret = input_register_device(input_dev); ++ if (ret < 0) ++ return ret; ++ ++ status = acpi_install_notify_handler(handle, ACPI_ALL_NOTIFY, lg_laptop_notify_handler, ++ input_dev); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_remove_notify_handler, handle); ++ if (ret < 0) ++ return ret; ++ ++ if (acpi_has_method(handle, "OREP")) { ++ ret = lg_laptop_execute_orep(handle, LG_OREP_INTERCEPT_WMI_EVENTS, 1, &result); ++ if (ret < 0) ++ return ret; ++ if (result) ++ return -EIO; ++ ++ ret = devm_add_action_or_reset(dev, lg_laptop_reenable_wmi_events, handle); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ + static acpi_status lg_laptop_address_space_write(struct device *dev, acpi_physical_address address, + size_t size, u64 value) + { +@@ -865,10 +1036,6 @@ static int acpi_probe(struct platform_device *pdev) + if (year >= 2019) + battery_limit_use_wmbb = 1; + +- ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); +- if (ret) +- goto out_platform_device; +- + /* LEDs are optional */ + ret = devm_led_classdev_register(&pdev->dev, &kbd_backlight); + if (ret < 0) +@@ -878,7 +1045,14 @@ static int acpi_probe(struct platform_device *pdev) + + devm_led_classdev_register(&pdev->dev, &tpad_led); + +- wmi_input_setup(); ++ ret = lg_laptop_input_init(&pdev->dev, device->handle); ++ if (ret < 0) ++ goto out_platform_device; ++ ++ ret = sysfs_create_group(&pf_device->dev.kobj, &dev_attribute_group); ++ if (ret) ++ goto out_platform_device; ++ + battery_hook_register(&battery_hook); + + return 0; +@@ -895,7 +1069,6 @@ static void acpi_remove(struct platform_device *pdev) + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + + battery_hook_unregister(&battery_hook); +- wmi_input_destroy(); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1055,6 +1055,7 @@ static struct msi_ec_conf CONF12 __initdata = { + + static const char * const ALLOWED_FW_13[] __initconst = { + "1594EMS1.109", // MSI Prestige 16 Studio A13VE ++ "17L1EMS1.107", // MSI Katana GF76 11UEK + NULL + }; + +@@ -1130,6 +1131,86 @@ static struct msi_ec_conf CONF13 __initdata = { + }, + }; + ++static const char * const ALLOWED_FW_14[] __initconst = { ++ "1824EMS1.108", // Raider 18 HX AI A2XWJG (MS-1824) ++ "182LIMS1.108", // Vector A18 HX A9WHG ++ "182LIMS1.111", // MSI Raider A18 HX A9WJG / Vector A18 HX A9WHG ++ "182KIMS1.113", // Raider A18 HX A7VIG ++ NULL ++}; ++ ++static struct msi_ec_conf CONF14 __initdata = { ++ .allowed_fw = ALLOWED_FW_14, ++ .charge_control = { ++ .address = 0xd7, ++ .offset_start = 0x8a, ++ .offset_end = 0x80, ++ .range_min = 0x8a, ++ .range_max = 0xe4, ++ }, ++ .webcam = { ++ .address = 0x2e, ++ .block_address = 0x2f, ++ .bit = 1, ++ }, ++ .fn_win_swap = { ++ .address = 0xe8, ++ .bit = 4, ++ }, ++ .cooler_boost = { ++ .address = 0x98, ++ .bit = 7, ++ }, ++ .shift_mode = { ++ .address = 0xd2, ++ .modes = { ++ { SM_ECO_NAME, 0xc2 }, ++ { SM_COMFORT_NAME, 0xc1 }, ++ { SM_TURBO_NAME, 0xc4 }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .super_battery = { ++ .address = 0xeb, ++ .mask = 0x0f, ++ }, ++ .fan_mode = { ++ .address = 0xd4, ++ .modes = { ++ { FM_AUTO_NAME, 0x0d }, ++ { FM_SILENT_NAME, 0x1d }, ++ { FM_ADVANCED_NAME, 0x8d }, ++ MSI_EC_MODE_NULL ++ }, ++ }, ++ .cpu = { ++ .rt_temp_address = 0x68, ++ .rt_fan_speed_address = 0x71, ++ .rt_fan_speed_base_min = 0x00, ++ .rt_fan_speed_base_max = 0x96, ++ .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, ++ .bs_fan_speed_base_min = 0x00, ++ .bs_fan_speed_base_max = 0x0f, ++ }, ++ .gpu = { ++ .rt_temp_address = 0x80, ++ .rt_fan_speed_address = 0x89, ++ }, ++ .leds = { ++ .micmute_led_address = 0x2c, ++ .mute_led_address = 0x2d, ++ .bit = 1, ++ }, ++ .kbd_bl = { ++ .bl_mode_address = MSI_EC_ADDR_UNSUPP, ++ .bl_modes = { 0x00, 0x08 }, ++ .max_mode = 1, ++ .bl_state_address = MSI_EC_ADDR_UNSUPP, ++ .state_base_value = 0x80, ++ .max_state = 3, ++ }, ++}; ++ + static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF0, + &CONF1, +@@ -1145,6 +1226,7 @@ static struct msi_ec_conf *CONFIGS[] __initdata = { + &CONF11, + &CONF12, + &CONF13, ++ &CONF14, + NULL + }; + +diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c +--- a/drivers/platform/x86/msi-wmi.c ++++ b/drivers/platform/x86/msi-wmi.c +@@ -46,6 +46,12 @@ enum msi_scancodes { + WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ + WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ + WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ ++ /* MSI Claw keys */ ++ CLAW_KEY_VOLUMEDOWN = 0x21, ++ CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ ++ CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ ++ CLAW_KEY_VOLUMEUP = 0x32, ++ CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ + }; + static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, +@@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { + { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, + { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + ++ /* These are MSI Claw keys, used for MSI M-Center in Windows */ ++ { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, ++ ++ /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ ++ { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, ++ { KE_IGNORE, CLAW_KEY_QUICK_LONG }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEUP }, ++ { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, ++ + { KE_END, 0 } + }; + +@@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = { + + static void msi_wmi_notify(union acpi_object *obj, void *context) + { +- struct key_entry *key; ++ struct key_entry *key = NULL; ++ int eventcode = 0; + +- if (obj && obj->type == ACPI_TYPE_INTEGER) { +- int eventcode = obj->integer.value; ++ if (!obj) ++ return; ++ ++ switch (obj->type) { ++ case ACPI_TYPE_INTEGER: ++ eventcode = obj->integer.value; + pr_debug("Eventcode: 0x%x\n", eventcode); +- key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, +- eventcode); +- if (!key) { +- pr_info("Unknown key pressed - %x\n", eventcode); ++ break; ++ case ACPI_TYPE_BUFFER: ++ /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ ++ if (obj->buffer.length < 2) ++ return; ++ ++ /* pointer[0] is key ID, pointer[1] is active state. We don't get release ++ * events, so ignore the active state and treat as autorelease. ++ */ ++ eventcode = obj->buffer.pointer[0]; ++ pr_debug("Eventcode: 0x%x\n", eventcode); ++ break; ++ default: ++ pr_info("Unknown event received\n"); ++ return; ++ } ++ ++ key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); ++ ++ if (!key) { ++ pr_info("Unknown key pressed - 0x%x\n", eventcode); ++ return; ++ } ++ ++ if (event_wmi->quirk_last_pressed) { ++ ktime_t cur = ktime_get_real(); ++ ktime_t diff = ktime_sub(cur, last_pressed); ++ /* Ignore event if any event happened in a 50 ms ++ * timeframe -> Key press may result in 10-20 GPEs ++ */ ++ if (ktime_to_us(diff) < 1000 * 50) { ++ pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", ++ key->code, ktime_to_us(diff)); + return; + } ++ last_pressed = cur; ++ } + +- if (event_wmi->quirk_last_pressed) { +- ktime_t cur = ktime_get_real(); +- ktime_t diff = ktime_sub(cur, last_pressed); +- /* Ignore event if any event happened in a 50 ms +- timeframe -> Key press may result in 10-20 GPEs */ +- if (ktime_to_us(diff) < 1000 * 50) { +- pr_debug("Suppressed key event 0x%X - " +- "Last press was %lld us ago\n", +- key->code, ktime_to_us(diff)); +- return; +- } +- last_pressed = cur; +- } ++ /* Brightness is served via acpi video driver */ ++ if (key->type == KE_KEY && ++ (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || ++ key->code == MSI_KEY_BRIGHTNESSDOWN))) ++ return; + +- if (key->type == KE_KEY && +- /* Brightness is served via acpi video driver */ +- (backlight || +- (key->code != MSI_KEY_BRIGHTNESSUP && +- key->code != MSI_KEY_BRIGHTNESSDOWN))) { +- pr_debug("Send key: 0x%X - Input layer keycode: %d\n", +- key->code, key->keycode); +- sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, +- true); +- } +- } else +- pr_info("Unknown event received\n"); ++ pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); ++ sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); + } + + static int __init msi_wmi_backlight_setup(void) +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -289,6 +289,13 @@ static const struct dmi_system_id dmi_table[] = { + }, + .driver_data = (void *)oxp_x1, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER X2Mini PRO"), ++ }, ++ .driver_data = (void *)oxp_fly, ++ }, + {}, + }; + +diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c +--- a/drivers/platform/x86/panasonic-laptop.c ++++ b/drivers/platform/x86/panasonic-laptop.c +@@ -159,8 +159,6 @@ MODULE_LICENSE("GPL"); + #define ECO_MODE_ON 0x80 + + #define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" +-#define ACPI_PCC_DEVICE_NAME "Hotkey" +-#define ACPI_PCC_CLASS "pcc" + + #define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" + +@@ -1026,8 +1024,6 @@ static int acpi_pcc_hotkey_probe(struct platform_device *pdev) + pcc->device = device; + pcc->handle = device->handle; + device->driver_data = pcc; +- strscpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); +- strscpy(acpi_device_class(device), ACPI_PCC_CLASS); + + result = acpi_pcc_init_input(pcc); + if (result) { +diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c +--- a/drivers/platform/x86/redmi-wmi.c ++++ b/drivers/platform/x86/redmi-wmi.c +@@ -29,24 +29,24 @@ static const struct key_entry redmi_wmi_keymap[] = { + {KE_KEY, 0x00011801, {KEY_ASSISTANT}}, + {KE_KEY, 0x00011901, {KEY_ASSISTANT}}, + +- /* Keyboard backlight */ +- {KE_IGNORE, 0x00000501, {}}, +- {KE_IGNORE, 0x00800501, {}}, +- {KE_IGNORE, 0x00050501, {}}, +- {KE_IGNORE, 0x000a0501, {}}, ++ /* Keyboard backlight: Off / Auto / Low / High (new state in byte 2) */ ++ {KE_KEY, 0x00000501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00800501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x00050501, {KEY_KBDILLUMTOGGLE}}, ++ {KE_KEY, 0x000a0501, {KEY_KBDILLUMTOGGLE}}, + + /* Xiaomi G Command Center */ + {KE_KEY, 0x00010a01, {KEY_VENDOR}}, + +- /* OEM preset power mode */ +- {KE_IGNORE, 0x00011601, {}}, +- {KE_IGNORE, 0x00021601, {}}, +- {KE_IGNORE, 0x00031601, {}}, +- {KE_IGNORE, 0x00041601, {}}, ++ /* OEM preset power mode: 1=Balanced 2=Silent 3=Turbo 4=Full speed */ ++ {KE_KEY, 0x00011601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00021601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00031601, {KEY_PERFORMANCE}}, ++ {KE_KEY, 0x00041601, {KEY_PERFORMANCE}}, + +- /* Fn Lock state */ +- {KE_IGNORE, 0x00000701, {}}, +- {KE_IGNORE, 0x00010701, {}}, ++ /* Fn Lock state: 1=on 0=off */ ++ {KE_KEY, 0x00000701, {KEY_FN_ESC}}, ++ {KE_KEY, 0x00010701, {KEY_FN_ESC}}, + + /* Fn+`/1/2/3/4 */ + {KE_KEY, 0x00011101, {KEY_F13}}, +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -1438,6 +1438,7 @@ static const struct acpi_device_id galaxybook_device_ids[] = { + { "SAM0428" }, + { "SAM0429" }, + { "SAM0430" }, ++ { "SAMB430" }, + {} + }; + MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); +diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c +--- a/drivers/platform/x86/sony-laptop.c ++++ b/drivers/platform/x86/sony-laptop.c +@@ -1264,7 +1264,7 @@ static void sony_nc_notify(acpi_handle ah, u32 event, void *data) + ev_type = HOTKEY; + sony_laptop_report_input_event(real_ev); + } +- acpi_bus_generate_netlink_event(sony_nc_acpi_device->pnp.device_class, ++ acpi_bus_generate_netlink_event("sony/hotkey", + dev_name(&sony_nc_acpi_device->dev), ev_type, real_ev); + } + +@@ -3157,8 +3157,6 @@ static int sony_nc_probe(struct platform_device *pdev) + return -ENODEV; + + sony_nc_acpi_device = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); +- + sony_nc_acpi_handle = device->handle; + + /* read device status */ +@@ -4523,7 +4521,6 @@ static int sony_pic_probe(struct platform_device *pdev) + return -ENODEV; + + spic_dev.acpi_dev = device; +- strscpy(acpi_device_class(device), "sony/hotkey"); + sony_pic_detect_device_type(&spic_dev); + mutex_init(&spic_dev.lock); + +diff --git a/drivers/platform/x86/topstar-laptop.c b/drivers/platform/x86/topstar-laptop.c +--- a/drivers/platform/x86/topstar-laptop.c ++++ b/drivers/platform/x86/topstar-laptop.c +@@ -299,8 +299,6 @@ static int topstar_acpi_probe(struct platform_device *pdev) + + platform_set_drvdata(pdev, topstar); + +- strscpy(acpi_device_name(device), "Topstar TPSACPI"); +- strscpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS); + topstar->device = device; + + err = topstar_acpi_init(topstar); +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -2505,8 +2505,7 @@ static void toshiba_acpi_kbd_bl_work(struct work_struct *work) + LED_FULL : LED_OFF); + + /* Emulate the keyboard backlight event */ +- acpi_bus_generate_netlink_event(toshiba_acpi->acpi_dev->pnp.device_class, +- dev_name(&toshiba_acpi->acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&toshiba_acpi->acpi_dev->dev), + 0x92, 0); + } + +@@ -3250,8 +3249,7 @@ static void toshiba_acpi_notify(acpi_handle handle, u32 event, void *data) + break; + } + +- acpi_bus_generate_netlink_event(acpi_dev->pnp.device_class, +- dev_name(&acpi_dev->dev), ++ acpi_bus_generate_netlink_event("", dev_name(&acpi_dev->dev), + event, (event == 0x80) ? + dev->last_key_event : 0); + } +diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c +--- a/drivers/platform/x86/toshiba_bluetooth.c ++++ b/drivers/platform/x86/toshiba_bluetooth.c +@@ -252,10 +252,8 @@ static int toshiba_bt_rfkill_probe(struct platform_device *pdev) + platform_set_drvdata(pdev, bt_dev); + + result = toshiba_bluetooth_sync_status(bt_dev); +- if (result) { +- kfree(bt_dev); +- return result; +- } ++ if (result) ++ goto err_free_bt_dev; + + bt_dev->rfk = rfkill_alloc("Toshiba Bluetooth", + &pdev->dev, +diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c +--- a/drivers/platform/x86/toshiba_haps.c ++++ b/drivers/platform/x86/toshiba_haps.c +@@ -136,9 +136,7 @@ static void toshiba_haps_notify(acpi_handle handle, u32 event, void *data) + + pr_debug("Received event: 0x%x\n", event); + +- acpi_bus_generate_netlink_event(device->pnp.device_class, +- dev_name(&device->dev), +- event, 0); ++ acpi_bus_generate_netlink_event("", dev_name(&device->dev), event, 0); + } + + static void toshiba_haps_remove(struct platform_device *pdev) +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -95,6 +95,9 @@ + + #define EC_ADDR_MAIN_FAN_RPM_2 0x0465 + ++#define EC_ADDR_SCREEN_STATUS 0x0466 ++#define SCREEN_SUSPENDED BIT(6) ++ + #define EC_ADDR_SECOND_FAN_RPM_1 0x046C + + #define EC_ADDR_SECOND_FAN_RPM_2 0x046D +@@ -109,9 +112,35 @@ + + #define EC_ADDR_BAT_CYCLE_COUNT_2 0x04A7 + ++#define EC_ADDR_OEM_9 0x0726 ++#define AC_AUTO_BOOT_ENABLE BIT(3) ++ + #define EC_ADDR_PROJECT_ID 0x0740 ++#define PROJECT_ID_NONE 0x00 ++#define PROJECT_ID_GI 0x01 ++#define PROJECT_ID_GJ 0x02 ++#define PROJECT_ID_GK 0x03 ++#define PROJECT_ID_GICN 0x04 ++#define PROJECT_ID_GJCN 0x05 ++#define PROJECT_ID_GK5CN_X 0x06 ++#define PROJECT_ID_GK7CN_S 0x07 ++#define PROJECT_ID_GK7CPCS_GK5CQ7Z 0x08 ++#define PROJECT_ID_PF 0x09 ++#define PROJECT_ID_GK5CP_4X_5X_6X 0x0A ++#define PROJECT_ID_IDP 0x0B ++#define PROJECT_ID_IDY_6Y 0x0C ++#define PROJECT_ID_IDY_7Y 0x0D ++#define PROJECT_ID_PF4MU_PF4MN_PF5MU 0x0E ++#define PROJECT_ID_CML_GAMING 0x0F ++#define PROJECT_ID_GK7NXXR 0x10 ++#define PROJECT_ID_GM5MU1Y 0x11 + #define PROJECT_ID_PH4TRX1 0x12 ++#define PROJECT_ID_PH4TUX1 0x13 ++#define PROJECT_ID_PH4TQX1 0x14 + #define PROJECT_ID_PH6TRX1 0x15 ++#define PROJECT_ID_PH6TQXX 0x16 ++#define PROJECT_ID_PHXAXXX 0x17 ++#define PROJECT_ID_PHXPXXX 0x18 + + #define EC_ADDR_AP_OEM 0x0741 + #define ENABLE_MANUAL_CTRL BIT(0) +@@ -214,6 +243,7 @@ + #define FAN_TABLE_OFFICE_MODE BIT(2) + #define FAN_V3 BIT(3) + #define DEFAULT_MODE BIT(4) ++#define ENABLE_CHINA_MODE BIT(6) + + #define EC_ADDR_PL1_SETTING 0x0783 + +@@ -225,11 +255,12 @@ + #define FAN_CURVE_LENGTH 5 + + #define EC_ADDR_KBD_STATUS 0x078C +-#define KBD_WHITE_ONLY BIT(0) // ~single color +-#define KBD_SINGLE_COLOR_OFF BIT(1) ++/* Unreliable on some models, use the device descriptor instead. */ ++#define KBD_WHITE_ONLY BIT(0) ++#define KBD_POWER_OFF BIT(1) + #define KBD_TURBO_LEVEL_MASK GENMASK(3, 2) + #define KBD_APPLY BIT(4) +-#define KBD_BRIGHTNESS GENMASK(7, 5) ++#define KBD_BRIGHTNESS_MASK GENMASK(7, 5) + + #define EC_ADDR_FAN_CTRL 0x078E + #define FAN3P5 BIT(1) +@@ -318,7 +349,9 @@ + #define FAN_TABLE_LENGTH 16 + + #define LED_CHANNELS 3 +-#define LED_MAX_BRIGHTNESS 200 ++ ++#define KBD_LED_CHANNELS 3 ++#define KBD_LED_MAX_INTENSITY 50 + + #define UNIWILL_FEATURE_FN_LOCK BIT(0) + #define UNIWILL_FEATURE_SUPER_KEY BIT(1) +@@ -333,6 +366,9 @@ + #define UNIWILL_FEATURE_SECONDARY_FAN BIT(9) + #define UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL BIT(10) + #define UNIWILL_FEATURE_USB_C_POWER_PRIORITY BIT(11) ++#define UNIWILL_FEATURE_KEYBOARD_BACKLIGHT BIT(12) ++#define UNIWILL_FEATURE_AC_AUTO_BOOT BIT(13) ++#define UNIWILL_FEATURE_USB_POWERSHARE BIT(14) + + enum usb_c_power_priority_options { + USB_C_POWER_PRIORITY_CHARGING = 0, +@@ -344,6 +380,7 @@ struct uniwill_data { + acpi_handle handle; + struct regmap *regmap; + unsigned int features; ++ u8 project_id; + struct acpi_battery_hook hook; + struct mutex battery_lock; /* Protects the list of currently registered batteries */ + union { +@@ -357,11 +394,25 @@ struct uniwill_data { + bool last_fn_lock_state; + bool last_super_key_enable_state; + bool last_touchpad_toggle_enable_state; ++ bool last_usb_powershare_high_state; + struct mutex super_key_lock; /* Protects the toggling of the super key lock state */ + struct list_head batteries; + struct mutex led_lock; /* Protects writes to the lightbar registers */ ++ u8 lightbar_max_brightness; + struct led_classdev_mc led_mc_cdev; + struct mc_subled led_mc_subled_info[LED_CHANNELS]; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ unsigned int last_kbd_status; ++ union { ++ struct { ++ /* Protects writes to the RGB keyboard backlight registers */ ++ struct mutex kbd_rgb_led_lock; ++ struct led_classdev_mc kbd_led_mc_cdev; ++ struct mc_subled kbd_led_mc_subled_info[KBD_LED_CHANNELS]; ++ }; ++ struct led_classdev kbd_led_cdev; ++ }; + struct mutex input_lock; /* Protects input sequence during notify */ + struct input_dev *input_device; + struct notifier_block nb; +@@ -376,6 +427,9 @@ struct uniwill_battery_entry { + + struct uniwill_device_descriptor { + unsigned int features; ++ bool kbd_led_single_color; ++ u8 kbd_led_max_brightness; ++ u8 lightbar_max_brightness; + /* Executed during driver probing */ + int (*probe)(struct uniwill_data *data); + }; +@@ -427,6 +481,9 @@ static const struct key_entry uniwill_keymap[] = { + { KE_KEY, UNIWILL_OSD_KBDILLUMDOWN, { KEY_KBDILLUMDOWN }}, + { KE_KEY, UNIWILL_OSD_KBDILLUMUP, { KEY_KBDILLUMUP }}, + ++ /* Reported when the EC changed the keyboard backlight brightness */ ++ { KE_IGNORE, UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the microphone mute status */ + { KE_KEY, UNIWILL_OSD_MIC_MUTE, { KEY_MICMUTE }}, + +@@ -435,11 +492,6 @@ static const struct key_entry uniwill_keymap[] = { + + /* Reported when the user wants to toggle the brightness of the keyboard */ + { KE_KEY, UNIWILL_OSD_KBDILLUMTOGGLE, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL0, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL1, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL2, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL3, { KEY_KBDILLUMTOGGLE }}, +- { KE_KEY, UNIWILL_OSD_KB_LED_LEVEL4, { KEY_KBDILLUMTOGGLE }}, + + /* FIXME: find out the exact meaning of those events */ + { KE_IGNORE, UNIWILL_OSD_BAT_CHARGE_FULL_24_H, { KEY_UNKNOWN }}, +@@ -448,6 +500,9 @@ static const struct key_entry uniwill_keymap[] = { + /* Reported when the user wants to toggle the benchmark mode status */ + { KE_IGNORE, UNIWILL_OSD_BENCHMARK_MODE_TOGGLE, { KEY_UNKNOWN }}, + ++ /* Reported when the screen is enabled/disabled during resume/suspend */ ++ { KE_IGNORE, UNIWILL_OSD_SCREEN_STATE_CHANGED, { KEY_UNKNOWN }}, ++ + /* Reported when the user wants to toggle the webcam */ + { KE_IGNORE, UNIWILL_OSD_WEBCAM_TOGGLE, { KEY_UNKNOWN }}, + +@@ -540,6 +595,7 @@ static const struct regmap_bus uniwill_ec_bus = { + static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + { + switch (reg) { ++ case EC_ADDR_OEM_9: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: + case EC_ADDR_LIGHTBAR_AC_RED: +@@ -547,6 +603,11 @@ static bool uniwill_writeable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_LIGHTBAR_AC_BLUE: + case EC_ADDR_BIOS_OEM: + case EC_ADDR_TRIGGER: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -574,6 +635,7 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_SECOND_FAN_RPM_1: + case EC_ADDR_SECOND_FAN_RPM_2: + case EC_ADDR_BAT_ALERT: ++ case EC_ADDR_OEM_9: + case EC_ADDR_PROJECT_ID: + case EC_ADDR_AP_OEM: + case EC_ADDR_LIGHTBAR_AC_CTRL: +@@ -583,8 +645,14 @@ static bool uniwill_readable_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_RGB_RED: ++ case EC_ADDR_RGB_GREEN: ++ case EC_ADDR_RGB_BLUE: ++ case EC_ADDR_BIOS_OEM_2: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_LIGHTBAR_BAT_CTRL: +@@ -616,8 +684,10 @@ static bool uniwill_volatile_reg(struct device *dev, unsigned int reg) + case EC_ADDR_BIOS_OEM: + case EC_ADDR_PWM_1: + case EC_ADDR_PWM_2: ++ case EC_ADDR_SUPPORT_2: + case EC_ADDR_TRIGGER: + case EC_ADDR_SWITCH_STATUS: ++ case EC_ADDR_KBD_STATUS: + case EC_ADDR_OEM_4: + case EC_ADDR_CHARGE_CTRL: + case EC_ADDR_USB_C_POWER_PRIORITY: +@@ -1077,6 +1147,109 @@ static int usb_c_power_priority_init(struct uniwill_data *data) + return 0; + } + ++static ssize_t ac_auto_boot_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ if (enable) ++ regval = AC_AUTO_BOOT_ENABLE; ++ else ++ regval = 0; ++ ++ ret = regmap_update_bits(data->regmap, EC_ADDR_OEM_9, AC_AUTO_BOOT_ENABLE, regval); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static ssize_t ac_auto_boot_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_OEM_9, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", !!(regval & AC_AUTO_BOOT_ENABLE)); ++} ++ ++static DEVICE_ATTR_RW(ac_auto_boot); ++ ++static int uniwill_write_usb_powershare_high(struct uniwill_data *data, bool status) ++{ ++ unsigned int value; ++ ++ if (status) ++ value = TRIGGER_USB_CHARGING; ++ else ++ value = 0; ++ ++ /* ++ * Normaly this RMW-sequence could also trigger the super key toggle, ++ * but the EC seems to take care that those bits are always read as 0. ++ */ ++ return regmap_update_bits(data->regmap, EC_ADDR_TRIGGER, TRIGGER_USB_CHARGING, value); ++} ++ ++static ssize_t usb_powershare_high_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool enable; ++ int ret; ++ ++ ret = kstrtobool(buf, &enable); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_write_usb_powershare_high(data, enable); ++ if (ret < 0) ++ return ret; ++ ++ return count; ++} ++ ++static int uniwill_read_usb_powershare_high(struct uniwill_data *data, bool *status) ++{ ++ unsigned int value; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_TRIGGER, &value); ++ if (ret < 0) ++ return ret; ++ ++ *status = !!(value & TRIGGER_USB_CHARGING); ++ ++ return 0; ++} ++ ++static ssize_t usb_powershare_high_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct uniwill_data *data = dev_get_drvdata(dev); ++ bool status; ++ int ret; ++ ++ ret = uniwill_read_usb_powershare_high(data, &status); ++ if (ret < 0) ++ return ret; ++ ++ return sysfs_emit(buf, "%d\n", status); ++} ++ ++static DEVICE_ATTR_RW(usb_powershare_high); ++ + static struct attribute *uniwill_attrs[] = { + /* Keyboard-related */ + &dev_attr_fn_lock.attr, +@@ -1088,6 +1261,8 @@ static struct attribute *uniwill_attrs[] = { + /* Power-management-related */ + &dev_attr_ctgp_offset.attr, + &dev_attr_usb_c_power_priority.attr, ++ &dev_attr_ac_auto_boot.attr, ++ &dev_attr_usb_powershare_high.attr, + NULL + }; + +@@ -1127,6 +1302,16 @@ static umode_t uniwill_attr_is_visible(struct kobject *kobj, struct attribute *a + return attr->mode; + } + ++ if (attr == &dev_attr_ac_auto_boot.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_AC_AUTO_BOOT)) ++ return attr->mode; ++ } ++ ++ if (attr == &dev_attr_usb_powershare_high.attr) { ++ if (uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return attr->mode; ++ } ++ + return 0; + } + +@@ -1332,7 +1517,7 @@ static int uniwill_led_brightness_set(struct led_classdev *led_cdev, enum led_br + + for (int i = 0; i < LED_CHANNELS; i++) { + /* Prevent the brightness values from overflowing */ +- value = min(LED_MAX_BRIGHTNESS, data->led_mc_subled_info[i].brightness); ++ value = min(data->lightbar_max_brightness, data->led_mc_subled_info[i].brightness); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1401,14 +1586,14 @@ static int uniwill_led_init(struct uniwill_data *data) + return ret; + + data->led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; +- data->led_mc_cdev.led_cdev.max_brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.max_brightness = data->lightbar_max_brightness; + data->led_mc_cdev.led_cdev.flags = LED_REJECT_NAME_CONFLICT; + data->led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_led_brightness_set; + + if (value & LIGHTBAR_S0_OFF) + data->led_mc_cdev.led_cdev.brightness = 0; + else +- data->led_mc_cdev.led_cdev.brightness = LED_MAX_BRIGHTNESS; ++ data->led_mc_cdev.led_cdev.brightness = data->lightbar_max_brightness; + + for (int i = 0; i < LED_CHANNELS; i++) { + data->led_mc_subled_info[i].color_index = color_indices[i]; +@@ -1421,7 +1606,7 @@ static int uniwill_led_init(struct uniwill_data *data) + * Make sure that the initial intensity value is not greater than + * the maximum brightness. + */ +- value = min(LED_MAX_BRIGHTNESS, value); ++ value = min(data->lightbar_max_brightness, value); + ret = regmap_write(data->regmap, uniwill_led_channel_to_ac_reg[i], value); + if (ret < 0) + return ret; +@@ -1441,6 +1626,246 @@ static int uniwill_led_init(struct uniwill_data *data) + &init_data); + } + ++static int uniwill_notify_kbd_led(struct uniwill_data *data, int brightness) ++{ ++ struct led_classdev *led_cdev; ++ int ret; ++ ++ if (data->kbd_led_single_color) ++ led_cdev = &data->kbd_led_cdev; ++ else ++ led_cdev = &data->kbd_led_mc_cdev.led_cdev; ++ ++ guard(mutex)(&led_cdev->led_access); ++ ++ /* Sync the LED brightness with the actual hardware state */ ++ ret = led_update_brightness(led_cdev); ++ if (ret < 0) ++ return ret; ++ ++ led_classdev_notify_brightness_hw_changed(led_cdev, brightness); ++ ++ return 0; ++} ++ ++#define KBD_LED_MASK (KBD_BRIGHTNESS_MASK | KBD_APPLY | KBD_POWER_OFF) ++ ++static int uniwill_kbd_led_write_brightness(struct uniwill_data *data, int brightness) ++{ ++ /* KBD_POWER_OFF is always implicitly cleared */ ++ unsigned int regval = FIELD_PREP(KBD_BRIGHTNESS_MASK, brightness) | KBD_APPLY; ++ ++ /* We must ensure that the "apply" bit is always written */ ++ return regmap_write_bits(data->regmap, EC_ADDR_KBD_STATUS, KBD_LED_MASK, regval); ++} ++ ++static int uniwill_kbd_led_read_brightness(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ return min(FIELD_GET(KBD_BRIGHTNESS_MASK, regval), data->kbd_led_max_brightness); ++} ++ ++static int uniwill_kbd_led_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct uniwill_data *data = container_of(led_cdev, struct uniwill_data, kbd_led_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static const unsigned int uniwill_kbd_led_channel_to_reg[KBD_LED_CHANNELS] = { ++ EC_ADDR_RGB_RED, ++ EC_ADDR_RGB_GREEN, ++ EC_ADDR_RGB_BLUE, ++}; ++ ++static int uniwill_kbd_led_mc_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ unsigned int min_intensity = 0; ++ unsigned int regval; ++ int ret; ++ ++ guard(mutex)(&data->kbd_rgb_led_lock); ++ ++ /* ++ * The EC interprets a RGB value of 0x000000 as a command to restore ++ * the device-specfic default RGB value. Work around this by writing ++ * a RGB value of 0x010101 (faint white) instead. ++ */ ++ if (data->kbd_led_mc_subled_info[0].intensity == 0 && ++ data->kbd_led_mc_subled_info[1].intensity == 0 && ++ data->kbd_led_mc_subled_info[2].intensity == 0) ++ min_intensity = 1; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ regval = max(data->kbd_led_mc_subled_info[i].intensity, min_intensity); ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ ++ return uniwill_kbd_led_write_brightness(data, brightness); ++} ++ ++static enum led_brightness uniwill_kbd_led_mc_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct led_classdev_mc *led_mc_cdev = lcdev_to_mccdev(led_cdev); ++ struct uniwill_data *data = container_of(led_mc_cdev, struct uniwill_data, kbd_led_mc_cdev); ++ ++ return uniwill_kbd_led_read_brightness(data); ++} ++ ++static int uniwill_white_kbd_led_init(struct uniwill_data *data) ++{ ++ struct led_init_data init_data = { ++ .default_label = "white:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ ++ data->kbd_led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_cdev.color = LED_COLOR_ID_WHITE; ++ data->kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_cdev.brightness_set_blocking = uniwill_kbd_led_brightness_set; ++ data->kbd_led_cdev.brightness_get = uniwill_kbd_led_brightness_get; ++ ++ return devm_led_classdev_register_ext(data->dev, &data->kbd_led_cdev, &init_data); ++} ++ ++static int uniwill_rgb_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int color_indices[KBD_LED_CHANNELS] = { ++ LED_COLOR_ID_RED, ++ LED_COLOR_ID_GREEN, ++ LED_COLOR_ID_BLUE, ++ }; ++ struct led_init_data init_data = { ++ .default_label = "multicolor:" LED_FUNCTION_KBD_BACKLIGHT, ++ .devicename = DRIVER_NAME, ++ .devname_mandatory = true, ++ }; ++ bool intensity_all_zeros = true; ++ bool needs_trigger = false; ++ unsigned int regval; ++ int ret; ++ ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].color_index = color_indices[i]; ++ ++ ret = regmap_read(data->regmap, uniwill_kbd_led_channel_to_reg[i], ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Make sure that the initial intensity value is not greater than ++ * the maximum intensity. ++ */ ++ if (regval > KBD_LED_MAX_INTENSITY) { ++ regval = KBD_LED_MAX_INTENSITY; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], regval); ++ if (ret < 0) ++ return ret; ++ ++ needs_trigger = true; ++ } ++ ++ if (regval) ++ intensity_all_zeros = false; ++ ++ data->kbd_led_mc_subled_info[i].intensity = regval; ++ data->kbd_led_mc_subled_info[i].max_intensity = KBD_LED_MAX_INTENSITY; ++ data->kbd_led_mc_subled_info[i].channel = i; ++ } ++ ++ /* See uniwill_kbd_led_mc_brightness_set() for an explaination. */ ++ if (intensity_all_zeros) { ++ for (int i = 0; i < KBD_LED_CHANNELS; i++) { ++ data->kbd_led_mc_subled_info[i].intensity = 1; ++ ret = regmap_write(data->regmap, uniwill_kbd_led_channel_to_reg[i], 1); ++ if (ret < 0) ++ return ret; ++ } ++ ++ needs_trigger = true; ++ } ++ ++ if (needs_trigger) { ++ ret = regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, ++ RGB_APPLY_COLOR); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = devm_mutex_init(data->dev, &data->kbd_rgb_led_lock); ++ if (ret < 0) ++ return ret; ++ ++ data->kbd_led_mc_cdev.led_cdev.max_brightness = data->kbd_led_max_brightness; ++ data->kbd_led_mc_cdev.led_cdev.color = LED_COLOR_ID_MULTI; ++ data->kbd_led_mc_cdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED | LED_REJECT_NAME_CONFLICT; ++ data->kbd_led_mc_cdev.led_cdev.brightness_set_blocking = uniwill_kbd_led_mc_brightness_set; ++ data->kbd_led_mc_cdev.led_cdev.brightness_get = uniwill_kbd_led_mc_brightness_get; ++ data->kbd_led_mc_cdev.subled_info = data->kbd_led_mc_subled_info; ++ data->kbd_led_mc_cdev.num_colors = KBD_LED_CHANNELS; ++ ++ return devm_led_classdev_multicolor_register_ext(data->dev, &data->kbd_led_mc_cdev, ++ &init_data); ++} ++ ++static int uniwill_kbd_led_init(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_SUPPORT_2, ®val); ++ if (ret < 0) ++ return ret; ++ ++ if (!(regval & CHINA_MODE)) { ++ ret = regmap_set_bits(data->regmap, EC_ADDR_BIOS_OEM_2, ENABLE_CHINA_MODE); ++ if (ret < 0) ++ return ret; ++ } ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ regval |= KBD_APPLY; ++ regval &= ~KBD_POWER_OFF; ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return uniwill_white_kbd_led_init(data); ++ ++ return uniwill_rgb_kbd_led_init(data); ++} ++ + static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) + { + /* 0 means "charging threshold not active" */ +@@ -1789,6 +2214,31 @@ static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action + sysfs_notify(&data->dev->kobj, NULL, "fn_lock"); + + return NOTIFY_OK; ++ case UNIWILL_OSD_KB_LED_LEVEL0: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 0)); ++ case UNIWILL_OSD_KB_LED_LEVEL1: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 1)); ++ case UNIWILL_OSD_KB_LED_LEVEL2: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 2)); ++ case UNIWILL_OSD_KB_LED_LEVEL3: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 3)); ++ case UNIWILL_OSD_KB_LED_LEVEL4: ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return NOTIFY_DONE; ++ ++ return notifier_from_errno(uniwill_notify_kbd_led(data, 4)); + default: + mutex_lock(&data->input_lock); + sparse_keymap_report_event(data->input_device, action, 1, true); +@@ -1842,6 +2292,7 @@ static int uniwill_ec_init(struct uniwill_data *data) + if (ret < 0) + return ret; + ++ data->project_id = value; + dev_dbg(data->dev, "Project ID: %u\n", value); + + ret = regmap_set_bits(data->regmap, EC_ADDR_AP_OEM, ENABLE_MANUAL_CTRL); +@@ -1885,6 +2336,9 @@ static int uniwill_probe(struct platform_device *pdev) + return ret; + + data->features = device_descriptor.features; ++ data->kbd_led_single_color = device_descriptor.kbd_led_single_color; ++ data->kbd_led_max_brightness = device_descriptor.kbd_led_max_brightness; ++ data->lightbar_max_brightness = device_descriptor.lightbar_max_brightness; + + /* + * Some devices might need to perform some device-specific initialization steps +@@ -1905,6 +2359,10 @@ static int uniwill_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + ++ ret = uniwill_kbd_led_init(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_hwmon_init(data); + if (ret < 0) + return ret; +@@ -1976,6 +2434,43 @@ static int uniwill_suspend_battery(struct uniwill_data *data) + return regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &data->last_charge_ctrl); + } + ++static int uniwill_suspend_kbd_led(struct uniwill_data *data) ++{ ++ unsigned int regval; ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_read(data->regmap, EC_ADDR_KBD_STATUS, ®val); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Save the current keyboard backlight settings in order to restore them ++ * during resume. We cannot use the regmap code for that since this register ++ * needs to be declared as volatile because the brightness can be changed ++ * by the EC. ++ */ ++ data->last_kbd_status = regval; ++ FIELD_MODIFY(KBD_BRIGHTNESS_MASK, ®val, 0); ++ regval |= KBD_APPLY | KBD_POWER_OFF; ++ ++ return regmap_write(data->regmap, EC_ADDR_KBD_STATUS, regval); ++} ++ ++static int uniwill_suspend_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ /* ++ * EC_ADDR_TRIGGER is marked as volatile, so we have to restore it ++ * ourselves. ++ */ ++ return uniwill_read_usb_powershare_high(data, &data->last_usb_powershare_high_state); ++} ++ + static int uniwill_suspend_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2006,6 +2501,14 @@ static int uniwill_suspend(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_suspend_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_suspend_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_suspend_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2052,6 +2555,31 @@ static int uniwill_resume_battery(struct uniwill_data *data) + return 0; + } + ++static int uniwill_resume_kbd_led(struct uniwill_data *data) ++{ ++ int ret; ++ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_KEYBOARD_BACKLIGHT)) ++ return 0; ++ ++ ret = regmap_write(data->regmap, EC_ADDR_KBD_STATUS, data->last_kbd_status | KBD_APPLY); ++ if (ret < 0) ++ return ret; ++ ++ if (data->kbd_led_single_color) ++ return 0; ++ ++ return regmap_write_bits(data->regmap, EC_ADDR_TRIGGER, RGB_APPLY_COLOR, RGB_APPLY_COLOR); ++} ++ ++static int uniwill_resume_usb_powershare(struct uniwill_data *data) ++{ ++ if (!uniwill_device_supports(data, UNIWILL_FEATURE_USB_POWERSHARE)) ++ return 0; ++ ++ return uniwill_write_usb_powershare_high(data, data->last_usb_powershare_high_state); ++} ++ + static int uniwill_resume_nvidia_ctgp(struct uniwill_data *data) + { + if (!uniwill_device_supports(data, UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL)) +@@ -2096,6 +2624,14 @@ static int uniwill_resume(struct device *dev) + if (ret < 0) + return ret; + ++ ret = uniwill_resume_kbd_led(data); ++ if (ret < 0) ++ return ret; ++ ++ ret = uniwill_resume_usb_powershare(data); ++ if (ret < 0) ++ return ret; ++ + ret = uniwill_resume_nvidia_ctgp(data); + if (ret < 0) + return ret; +@@ -2126,13 +2662,30 @@ static struct platform_driver uniwill_driver = { + .shutdown = uniwill_shutdown, + }; + ++static struct uniwill_device_descriptor machenike_l16p_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = false, ++ .kbd_led_max_brightness = 4, ++}; ++ + static struct uniwill_device_descriptor lapqc71a_lapqc71b_descriptor __initdata = { + .features = UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_LIGHTBAR | + UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT | + UNIWILL_FEATURE_CPU_TEMP | + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 36, + }; + + static struct uniwill_device_descriptor lapac71h_descriptor __initdata = { +@@ -2156,6 +2709,7 @@ static struct uniwill_device_descriptor lapkc71f_descriptor __initdata = { + UNIWILL_FEATURE_GPU_TEMP | + UNIWILL_FEATURE_PRIMARY_FAN | + UNIWILL_FEATURE_SECONDARY_FAN, ++ .lightbar_max_brightness = 200, + }; + + /* +@@ -2198,15 +2752,6 @@ static struct uniwill_device_descriptor tux_featureset_2_nvidia_descriptor __ini + UNIWILL_FEATURE_USB_C_POWER_PRIORITY, + }; + +-static struct uniwill_device_descriptor tux_featureset_3_descriptor __initdata = { +- .features = UNIWILL_FEATURE_FN_LOCK | +- UNIWILL_FEATURE_SUPER_KEY | +- UNIWILL_FEATURE_BATTERY_CHARGE_MODES | +- UNIWILL_FEATURE_CPU_TEMP | +- UNIWILL_FEATURE_PRIMARY_FAN | +- UNIWILL_FEATURE_SECONDARY_FAN, +-}; +- + static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __initdata = { + .features = UNIWILL_FEATURE_FN_LOCK | + UNIWILL_FEATURE_SUPER_KEY | +@@ -2218,6 +2763,30 @@ static struct uniwill_device_descriptor tux_featureset_3_nvidia_descriptor __ini + UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL, + }; + ++static struct uniwill_device_descriptor tux_featureset_4_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ ++static struct uniwill_device_descriptor tux_featureset_4_nvidia_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_GPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_NVIDIA_CTGP_CONTROL | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++}; ++ + static int phxtxx1_probe(struct uniwill_data *data) + { + unsigned int value; +@@ -2277,7 +2846,37 @@ static struct uniwill_device_descriptor pf5pu1g_descriptor __initdata = { + UNIWILL_FEATURE_PRIMARY_FAN, + }; + ++static struct uniwill_device_descriptor x4sp4nal_descriptor __initdata = { ++ .features = UNIWILL_FEATURE_FN_LOCK | ++ UNIWILL_FEATURE_SUPER_KEY | ++ UNIWILL_FEATURE_BATTERY_CHARGE_MODES | ++ UNIWILL_FEATURE_CPU_TEMP | ++ UNIWILL_FEATURE_PRIMARY_FAN | ++ UNIWILL_FEATURE_SECONDARY_FAN | ++ UNIWILL_FEATURE_KEYBOARD_BACKLIGHT | ++ UNIWILL_FEATURE_AC_AUTO_BOOT | ++ UNIWILL_FEATURE_USB_POWERSHARE, ++ .kbd_led_single_color = true, ++ .kbd_led_max_brightness = 2, ++}; ++ + static const struct dmi_system_id uniwill_dmi_table[] __initconst = { ++ { ++ .ident = "AiStone X4SP4NAL", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "AiStone"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "X4SP4NAL"), ++ }, ++ .driver_data = &x4sp4nal_descriptor, ++ }, ++ { ++ .ident = "MACHENIKE L16 Pro", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "MACHENIKE"), ++ DMI_EXACT_MATCH(DMI_BOARD_NAME, "L16P"), ++ }, ++ .driver_data = &machenike_l16p_descriptor, ++ }, + { + .ident = "XMG FUSION 15 (L19)", + .matches = { +@@ -2310,6 +2909,13 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + }, + .driver_data = &lapqc71a_lapqc71b_descriptor, + }, ++ { ++ .ident = "Avell A60 MUV", ++ .matches = { ++ DMI_MATCH(DMI_PRODUCT_NAME, "A60 MUV"), ++ }, ++ .driver_data = &lapqc71a_lapqc71b_descriptor, ++ }, + { + .ident = "Intel NUC x15", + .matches = { +@@ -2388,7 +2994,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxHRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen9 Intel/Commodore Omnia-Book 15 Gen9", +@@ -2396,7 +3002,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GXxMRXx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2404,7 +3010,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxHP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 14/15 Gen10 AMD", +@@ -2412,7 +3018,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxKK4NAx_XxSP4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Pro 15 Gen10 Intel", +@@ -2420,7 +3026,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "XxAR4NAx"), + }, +- .driver_data = &tux_featureset_3_descriptor, ++ .driver_data = &tux_featureset_4_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 AMD", +@@ -2428,7 +3034,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5KK45xS_X5SP45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2436,7 +3042,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6HP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 AMD", +@@ -2444,7 +3050,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6KK45xU_X6SP45xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 15 Gen10 Intel", +@@ -2452,7 +3058,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X5AR45xS"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO InfinityBook Max 16 Gen10 Intel", +@@ -2460,7 +3066,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR55xU"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Polaris 15 Gen1 AMD", +@@ -2620,7 +3226,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GMxHGxx"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris Slim 15 Gen6 Intel/Commodore ORION Slim 15 Gen6", +@@ -2628,7 +3234,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM5IXxA"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2636,7 +3242,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB1"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen6 Intel/Commodore ORION 16 Gen6", +@@ -2644,7 +3250,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM6IXxB_MB2"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 17 Gen6 Intel/Commodore ORION 17 Gen6", +@@ -2652,7 +3258,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "GM7IXxN"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 AMD", +@@ -2660,7 +3266,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6FR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2668,7 +3274,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Stellaris 16 Gen7 Intel", +@@ -2676,7 +3282,7 @@ static const struct dmi_system_id uniwill_dmi_table[] __initconst = { + DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "X6AR5xxY_mLED"), + }, +- .driver_data = &tux_featureset_3_nvidia_descriptor, ++ .driver_data = &tux_featureset_4_nvidia_descriptor, + }, + { + .ident = "TUXEDO Book BA15 Gen10 AMD", +@@ -2745,6 +3351,12 @@ static int __init uniwill_init(void) + if (force) { + /* Assume that the device supports all features except the charge limit */ + device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY_CHARGE_LIMIT; ++ /* Some models only have a (white) single color keyboard backlight */ ++ device_descriptor.kbd_led_single_color = false; ++ /* Some models only support 3 brightness levels */ ++ device_descriptor.kbd_led_max_brightness = 4; ++ /* Some models only support 36 brightness levels per color component */ ++ device_descriptor.lightbar_max_brightness = 200; + pr_warn("Enabling potentially unsupported features\n"); + } + +diff --git a/drivers/platform/x86/uniwill/uniwill-wmi.h b/drivers/platform/x86/uniwill/uniwill-wmi.h +--- a/drivers/platform/x86/uniwill/uniwill-wmi.h ++++ b/drivers/platform/x86/uniwill/uniwill-wmi.h +@@ -113,6 +113,8 @@ + + #define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0 + ++#define UNIWILL_OSD_SCREEN_STATE_CHANGED 0xCC ++ + #define UNIWILL_OSD_WEBCAM_TOGGLE 0xCF + + #define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0 +diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c +--- a/drivers/platform/x86/xo15-ebook.c ++++ b/drivers/platform/x86/xo15-ebook.c +@@ -19,13 +19,10 @@ + + #define MODULE_NAME "xo15-ebook" + +-#define XO15_EBOOK_CLASS MODULE_NAME + #define XO15_EBOOK_TYPE_UNKNOWN 0x00 + #define XO15_EBOOK_NOTIFY_STATUS 0x80 + +-#define XO15_EBOOK_SUBCLASS "ebook" + #define XO15_EBOOK_HID "XO15EBK" +-#define XO15_EBOOK_DEVICE_NAME "EBook Switch" + + MODULE_DESCRIPTION("OLPC XO-1.5 ebook switch driver"); + MODULE_LICENSE("GPL"); +@@ -105,12 +102,9 @@ static int ebook_switch_probe(struct platform_device *pdev) + if (!id) + return dev_err_probe(dev, -ENODEV, "Unsupported hid\n"); + +- strscpy(acpi_device_name(device), XO15_EBOOK_DEVICE_NAME); +- strscpy(acpi_device_class(device), XO15_EBOOK_CLASS "/" XO15_EBOOK_SUBCLASS); +- + snprintf(button->phys, sizeof(button->phys), "%s/button/input0", id->id); + +- input->name = acpi_device_name(device); ++ input->name = "EBook Switch"; + input->phys = button->phys; + input->id.bustype = BUS_HOST; + +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -852,9 +852,14 @@ static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { + }; + + static const struct ssam_device_id surface_battery_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, +- { SSAM_SDEV(BAT, KIP, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat1, ++ }, { ++ SSAM_SDEV(BAT, KIP, 0x01, 0x00), ++ .driver_data = (unsigned long)&spwr_psy_props_bat2_sb3, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_battery_match); + +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +--- a/drivers/power/supply/surface_charger.c ++++ b/drivers/power/supply/surface_charger.c +@@ -260,8 +260,11 @@ static const struct spwr_psy_properties spwr_psy_props_adp1 = { + }; + + static const struct ssam_device_id surface_ac_match[] = { +- { SSAM_SDEV(BAT, SAM, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, +- { }, ++ { ++ SSAM_SDEV(BAT, SAM, 0x01, 0x01), ++ .driver_data = (unsigned long)&spwr_psy_props_adp1, ++ }, ++ { } + }; + MODULE_DEVICE_TABLE(ssam, surface_ac_match); + +diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h +--- a/include/linux/amd-pmf-io.h ++++ b/include/linux/amd-pmf-io.h +@@ -52,15 +52,6 @@ struct amd_sfh_info { + u32 laptop_placement; + }; + +-enum laptop_placement { +- LP_UNKNOWN = 0, +- ON_TABLE, +- ON_LAP_MOTION, +- IN_BAG, +- OUT_OF_BAG, +- LP_UNDEFINED, +-}; +- + /** + * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver + * @npuclk_freq: NPU clock frequency [MHz] +@@ -69,6 +60,7 @@ enum laptop_placement { + * @mpnpuclk_freq: MPNPU [MHz] + * @npu_reads: NPU read bandwidth [MB/sec] + * @npu_writes: NPU write bandwidth [MB/sec] ++ * @npu_temp: NPU temperature [C] + */ + struct amd_pmf_npu_metrics { + u16 npuclk_freq; +@@ -77,6 +69,7 @@ struct amd_pmf_npu_metrics { + u16 mpnpuclk_freq; + u16 npu_reads; + u16 npu_writes; ++ u16 npu_temp; + }; + + int amd_get_sfh_info(struct amd_sfh_info *sfh_info, enum sfh_message_type op); +diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h +--- a/include/linux/platform_data/x86/asus-wmi.h ++++ b/include/linux/platform_data/x86/asus-wmi.h +@@ -203,6 +203,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); + int asus_hid_register_listener(struct asus_hid_listener *cdev); + void asus_hid_unregister_listener(struct asus_hid_listener *cdev); + int asus_hid_event(enum asus_hid_event event); ++bool asus_wmi_custom_fan_curve_is_enabled(void); + #else + static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) + { +@@ -234,6 +235,10 @@ static inline int asus_hid_event(enum asus_hid_event event) + { + return -ENODEV; + } ++static inline bool asus_wmi_custom_fan_curve_is_enabled(void) ++{ ++ return false; ++} + #endif + + #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ +diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h +new file mode 100644 +--- /dev/null ++++ b/include/uapi/linux/amd-pmf.h +@@ -0,0 +1,204 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ ++/* ++ * AMD Platform Management Framework (PMF) UAPI Header ++ * ++ * Copyright (c) 2026, Advanced Micro Devices, Inc. ++ * All Rights Reserved. ++ * ++ * This file defines the user-space API for interacting with the AMD PMF ++ * driver. It provides ioctl interfaces to query platform-specific metrics ++ * such as power source, slider position, platform type, laptop placement, ++ * and various BIOS input/output parameters. ++ */ ++ ++#ifndef _UAPI_LINUX_AMD_PMF_H ++#define _UAPI_LINUX_AMD_PMF_H ++ ++#include ++#include ++#include ++ ++/** ++ * AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands ++ * ++ * This magic number uniquely identifies AMD PMF ioctl operations. ++ */ ++#define AMD_PMF_IOC_MAGIC 'p' ++ ++/** ++ * IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data ++ * ++ * This ioctl command is used to populate the amd_pmf_info structure ++ * with the requested PMF metrics information. ++ */ ++#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64) ++ ++#define AMD_PMF_BIOS_PARAMS_MAX 10 ++ ++/* AMD PMF feature flags - bitmask indicating supported features */ ++#define AMD_PMF_FEAT_AUTO_MODE BIT(0) ++#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1) ++#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) ++#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) ++ ++/** ++ * enum amd_pmf_laptop_placement - Describes the physical placement of the laptop ++ * @AMD_PMF_LP_UNKNOWN: Placement cannot be determined ++ * @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk ++ * @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion ++ * @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case ++ * @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case ++ * @AMD_PMF_LP_UNDEFINED: Placement state is undefined ++ * ++ * This enumeration represents the physical placement state of the laptop ++ * as detected by platform sensors. Used for adaptive power management ++ * and thermal policies. ++ */ ++enum amd_pmf_laptop_placement { ++ AMD_PMF_LP_UNKNOWN, ++ AMD_PMF_ON_TABLE, ++ AMD_PMF_ON_LAP_MOTION, ++ AMD_PMF_IN_BAG, ++ AMD_PMF_OUT_OF_BAG, ++ AMD_PMF_LP_UNDEFINED, ++}; ++ ++/** ++ * enum amd_pmf_ta_slider - Trusted Application power slider positions ++ * @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance ++ * @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life ++ * @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance ++ * @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption ++ * @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value ++ * ++ * This enumeration defines the power slider positions used by the ++ * AMD PMF Trusted Application for dynamic power management decisions. ++ * These correspond to the Windows power slider UI positions. ++ */ ++enum amd_pmf_ta_slider { ++ AMD_PMF_TA_BEST_BATTERY, ++ AMD_PMF_TA_BETTER_BATTERY, ++ AMD_PMF_TA_BETTER_PERFORMANCE, ++ AMD_PMF_TA_BEST_PERFORMANCE, ++ AMD_PMF_TA_MAX, ++}; ++ ++/** ++ * enum amd_pmf_platform_type - Describes the physical form factor orientation ++ * @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined ++ * @AMD_PMF_LID_CLOSE: Laptop lid is closed ++ * @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen ++ * @AMD_PMF_FLAT: Device is lying flat on a surface ++ * @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing) ++ * @AMD_PMF_STAND: Device is propped up in stand orientation ++ * @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden ++ * @AMD_PMF_BOOK: Device is in book reading orientation ++ * @AMD_PMF_PRESENTATION: Device is in presentation mode ++ * @AMD_PMF_PULL_FWD: Screen is pulled forward towards user ++ * @AMD_PMF_PTYPE_INVALID: Invalid platform type marker ++ * ++ * This enumeration describes the current physical orientation or form ++ * factor of convertible/2-in-1 devices. Used for optimizing power and ++ * thermal management based on device posture. ++ */ ++enum amd_pmf_platform_type { ++ AMD_PMF_PTYPE_UNKNOWN, ++ AMD_PMF_LID_CLOSE, ++ AMD_PMF_CLAMSHELL, ++ AMD_PMF_FLAT, ++ AMD_PMF_TENT, ++ AMD_PMF_STAND, ++ AMD_PMF_TABLET, ++ AMD_PMF_BOOK, ++ AMD_PMF_PRESENTATION, ++ AMD_PMF_PULL_FWD, ++ AMD_PMF_PTYPE_INVALID = 0xf, ++}; ++ ++static inline const char *amd_pmf_get_platform_type(unsigned int platform_type) ++{ ++ switch (platform_type) { ++ case AMD_PMF_CLAMSHELL: ++ return "CLAMSHELL"; ++ case AMD_PMF_LID_CLOSE: ++ return "LID_CLOSE"; ++ case AMD_PMF_FLAT: ++ return "FLAT"; ++ case AMD_PMF_TENT: ++ return "TENT"; ++ case AMD_PMF_STAND: ++ return "STAND"; ++ case AMD_PMF_TABLET: ++ return "TABLET"; ++ case AMD_PMF_BOOK: ++ return "BOOK"; ++ case AMD_PMF_PRESENTATION: ++ return "PRESENTATION"; ++ case AMD_PMF_PULL_FWD: ++ return "PULL_FWD"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state) ++{ ++ switch (device_state) { ++ case AMD_PMF_ON_TABLE: ++ return "ON_TABLE"; ++ case AMD_PMF_ON_LAP_MOTION: ++ return "ON_LAP_MOTION"; ++ case AMD_PMF_IN_BAG: ++ return "IN_BAG"; ++ case AMD_PMF_OUT_OF_BAG: ++ return "OUT_OF_BAG"; ++ default: ++ return "UNKNOWN"; ++ } ++} ++ ++static inline const char *amd_pmf_get_slider_position(unsigned int state) ++{ ++ switch (state) { ++ case AMD_PMF_TA_BEST_PERFORMANCE: ++ return "PERFORMANCE"; ++ case AMD_PMF_TA_BETTER_PERFORMANCE: ++ return "BALANCED"; ++ case AMD_PMF_TA_BEST_BATTERY: ++ return "POWER_SAVER"; ++ case AMD_PMF_TA_BETTER_BATTERY: ++ return "BALANCED_BATTERY"; ++ default: ++ return "Unknown TA Slider State"; ++ } ++} ++ ++struct amd_pmf_info { ++ __u64 size; ++ ++ /* Feature info */ ++ __u32 features_supported; ++ ++ /* Power and state info */ ++ __u32 platform_type; ++ __u32 power_source; ++ __u32 laptop_placement; ++ __u32 lid_state; ++ __u32 user_presence; ++ __u32 slider_position; ++ ++ /* Thermal and power metrics */ ++ __s32 skin_temp; ++ __u32 gfx_busy; ++ __s32 ambient_light; ++ __u32 avg_c0_residency; ++ __u32 max_c0_residency; ++ __u32 socket_power; ++ ++ /* BIOS parameters */ ++ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; ++ __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX]; ++}; ++ ++#endif /* _UAPI_LINUX_AMD_PMF_H */ diff --git a/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch.sig b/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch.sig new file mode 100644 index 0000000..37ef2d1 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0800-platform-updates.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch b/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch new file mode 100644 index 0000000..029ebd2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch @@ -0,0 +1,34 @@ +diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c +index 1111111..2222222 100644 +--- a/drivers/platform/x86/amd/pmf/util.c ++++ b/drivers/platform/x86/amd/pmf/util.c +@@ -79,9 +79,9 @@ static int amd_pmf_populate_data(struct amd_pmf_dev *pdev, struct amd_pmf_info * + + static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { +- struct amd_pmf_dev *pdev = filp->private_data; + void __user *argp = (void __user *)arg; + struct amd_pmf_info info = {}; ++ struct amd_pmf_dev *pdev; + size_t copy_size; + __u64 user_size; + int ret; +@@ -94,6 +94,10 @@ static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long + return -EFAULT; + + guard(mutex)(&pmf_util_lock); ++ pdev = pmf_dev_handle; ++ if (!pdev) ++ return -ENODEV; ++ + ret = amd_pmf_populate_data(pdev, &info); + if (ret) + return ret; +@@ -115,7 +119,6 @@ static int amd_pmf_open(struct inode *inode, struct file *filp) + if (!pmf_dev_handle) + return -ENODEV; + +- filp->private_data = pmf_dev_handle; + return 0; + } + diff --git a/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch.sig b/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch.sig new file mode 100644 index 0000000..6bc45ff Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0801-amd-pmf-util-unbind-use-after-free.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch new file mode 100644 index 0000000..b7d82cc --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch @@ -0,0 +1,240 @@ +diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c +--- a/drivers/platform/x86/amd/pmf/spc.c ++++ b/drivers/platform/x86/amd/pmf/spc.c +@@ -17,7 +17,6 @@ + #include + #include "pmf.h" + +-#ifdef CONFIG_AMD_PMF_DEBUG + u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + { + switch (index) { +@@ -31,6 +30,7 @@ u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) + } + EXPORT_SYMBOL(amd_pmf_get_ta_custom_bios_inputs); + ++#ifdef CONFIG_AMD_PMF_DEBUG + void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) + { + int i; +diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c +--- a/drivers/platform/x86/x86-android-tablets/core.c ++++ b/drivers/platform/x86/x86-android-tablets/core.c +@@ -156,7 +156,6 @@ static struct platform_device **pdevs; + static struct serdev_device **serdevs; + static const struct software_node **gpio_button_swnodes; + static const struct software_node **swnode_group; +-static const struct software_node **gpiochip_node_group; + static void (*exit_handler)(void); + + static __init struct i2c_adapter * +@@ -362,6 +361,15 @@ static const struct software_node *cherryview_gpiochip_node_group[] = { + NULL + }; + ++const struct software_node crystalcove_gpiochip_node = { ++ .name = "INT33FD:00", ++}; ++ ++static const struct software_node *crystalcove_gpiochip_node_group[] = { ++ &crystalcove_gpiochip_node, ++ NULL ++}; ++ + static void gpio_secondary_unset(void *data) + { + struct device *dev = data; +@@ -377,27 +385,28 @@ static void gpio_secondary_unregister_node_group(void *data) + software_node_unregister_node_group(nodes); + } + +-static int gpio_secondary_fwnode_init(struct device *parent) ++static int gpio_secondary_fwnode_init(struct device *parent, ++ const struct software_node * const *node_group) + { + const struct software_node *const *swnode; + struct fwnode_handle *fwnode; + struct device *phys_dev; + int ret; + +- if (!gpiochip_node_group) ++ if (!node_group) + return 0; + +- ret = software_node_register_node_group(gpiochip_node_group); ++ ret = software_node_register_node_group(node_group); + if (ret) + return ret; + + ret = devm_add_action_or_reset(parent, + gpio_secondary_unregister_node_group, +- gpiochip_node_group); ++ (void *)node_group); + if (ret) + return ret; + +- for (swnode = gpiochip_node_group; *swnode; swnode++) { ++ for (swnode = node_group; *swnode; swnode++) { + struct device *dev __free(put_device) = + acpi_bus_find_device_by_name((*swnode)->name); + if (!dev) +@@ -460,6 +469,7 @@ static void x86_android_tablet_remove(struct platform_device *pdev) + + static __init int x86_android_tablet_probe(struct platform_device *pdev) + { ++ const struct software_node * const *gpiochip_node_group; + const struct x86_dev_info *dev_info; + const struct dmi_system_id *id; + int i, ret = 0; +@@ -491,12 +501,20 @@ static __init int x86_android_tablet_probe(struct platform_device *pdev) + break; + } + +- ret = gpio_secondary_fwnode_init(&pdev->dev); ++ ret = gpio_secondary_fwnode_init(&pdev->dev, gpiochip_node_group); + if (ret) { + x86_android_tablet_remove(pdev); + return ret; + } + ++ if (dev_info->has_crystalcove) { ++ ret = gpio_secondary_fwnode_init(&pdev->dev, crystalcove_gpiochip_node_group); ++ if (ret) { ++ x86_android_tablet_remove(pdev); ++ return ret; ++ } ++ } ++ + ret = software_node_register_node_group(dev_info->swnode_group); + if (ret) { + x86_android_tablet_remove(pdev); +diff --git a/drivers/platform/x86/x86-android-tablets/lenovo.c b/drivers/platform/x86/x86-android-tablets/lenovo.c +--- a/drivers/platform/x86/x86-android-tablets/lenovo.c ++++ b/drivers/platform/x86/x86-android-tablets/lenovo.c +@@ -61,13 +61,6 @@ static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = { + .initial_brightness = 128, + }; + +-static const struct software_node arizona_gpiochip_node = { +- .name = "arizona", +-}; +- +-static const struct software_node crystalcove_gpiochip_node = { +- .name = "gpio_crystalcove", +-}; + + /* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */ + +@@ -416,15 +409,17 @@ static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __ini + + #define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" + ++static const struct software_node lenovo_yoga_tab2_830_1050_wm5102; ++ + static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("reset-gpios", + &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", +- &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ &lenovo_yoga_tab2_830_1050_wm5102, 2, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", +- &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW), ++ &lenovo_yoga_tab2_830_1050_wm5102, 4, GPIO_ACTIVE_LOW), + { } + }; + +@@ -432,14 +427,6 @@ static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = { + .properties = lenovo_yoga_tab2_830_1050_wm1502_props, + }; + +-static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = { +- &crystalcove_gpiochip_node, +- &arizona_gpiochip_node, +- &lenovo_yoga_tab2_830_1050_wm5102, +- &generic_lipo_hv_4v35_battery_node, +- NULL +-}; +- + static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev); + static void lenovo_yoga_tab2_830_1050_exit(void); + +@@ -455,8 +442,9 @@ const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = { + .pdev_info = lenovo_yoga_tab2_830_1050_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_830_1050_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -800,8 +788,9 @@ const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = { + .pdev_info = lenovo_yoga_tab2_1380_pdevs, + .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs), + .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes, +- .swnode_group = lenovo_yoga_tab2_830_1050_swnodes, ++ .swnode_group = generic_lipo_hv_4v35_battery_swnodes, + .modules = lenovo_yoga_tab2_modules, ++ .has_crystalcove = true, + .gpiochip_type = X86_GPIOCHIP_BAYTRAIL, + .init = lenovo_yoga_tab2_1380_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +@@ -985,13 +974,15 @@ static struct arizona_pdata lenovo_yt3_wm5102_pdata = { + }, + }; + ++static const struct software_node lenovo_yt3_wm5102; ++ + static const struct property_entry lenovo_yt3_wm1502_props[] = { + PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios", + &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios", + &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH), + PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH), +- PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH), ++ PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &lenovo_yt3_wm5102, 2, GPIO_ACTIVE_HIGH), + { } + }; + +@@ -1000,11 +991,6 @@ static const struct software_node lenovo_yt3_wm5102 = { + .name = "wm5102", + }; + +-static const struct software_node *lenovo_yt3_swnodes[] = { +- &arizona_gpiochip_node, +- &lenovo_yt3_wm5102, +- NULL +-}; + + static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = { + { +@@ -1072,7 +1058,6 @@ const struct x86_dev_info lenovo_yt3_info __initconst = { + .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients), + .spi_dev_info = lenovo_yt3_spi_devs, + .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs), +- .swnode_group = lenovo_yt3_swnodes, + .modules = lenovo_yt3_modules, + .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW, + .init = lenovo_yt3_init, +diff --git a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +--- a/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h ++++ b/drivers/platform/x86/x86-android-tablets/x86-android-tablets.h +@@ -96,6 +96,7 @@ struct x86_dev_info { + int (*init)(struct device *dev); + void (*exit)(void); + bool use_pci; ++ bool has_crystalcove; + enum x86_gpiochip_type gpiochip_type; + }; + +@@ -107,6 +108,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data); + /* Software nodes representing GPIO chips used by various tablets */ + extern const struct software_node baytrail_gpiochip_nodes[]; + extern const struct software_node cherryview_gpiochip_nodes[]; ++extern const struct software_node crystalcove_gpiochip_node; + + /* + * Extern declarations of x86_dev_info structs so there can be a single diff --git a/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch.sig new file mode 100644 index 0000000..f2ddd7a Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0802-platform-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch b/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch new file mode 100644 index 0000000..c3246f0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch @@ -0,0 +1,412 @@ +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -736,7 +736,7 @@ static LIST_HEAD(acpi_battery_list); + static LIST_HEAD(battery_hook_list); + static DEFINE_MUTEX(hook_mutex); + +-static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -753,7 +753,7 @@ static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) + pr_info("hook unregistered: %s\n", hook->name); + } + +-void battery_hook_unregister(struct acpi_battery_hook *hook) ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) + { + mutex_lock(&hook_mutex); + /* +@@ -762,13 +762,13 @@ void battery_hook_unregister(struct acpi_battery_hook *hook) + * adding a new battery. + */ + if (!list_empty(&hook->list)) +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_unregister); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); + +-void battery_hook_register(struct acpi_battery_hook *hook) ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) + { + struct acpi_battery *battery; + +@@ -789,7 +789,7 @@ void battery_hook_register(struct acpi_battery_hook *hook) + * hooks. + */ + pr_err("hook failed to load: %s", hook->name); +- battery_hook_unregister_unlocked(hook); ++ acpi_battery_hook_unregister_unlocked(hook); + goto end; + } + +@@ -799,22 +799,22 @@ void battery_hook_register(struct acpi_battery_hook *hook) + end: + mutex_unlock(&hook_mutex); + } +-EXPORT_SYMBOL_GPL(battery_hook_register); ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); + +-static void devm_battery_hook_unregister(void *data) ++static void devm_acpi_battery_hook_unregister(void *data) + { + struct acpi_battery_hook *hook = data; + +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) ++int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) + { +- battery_hook_register(hook); ++ acpi_battery_hook_register(hook); + +- return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook); ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); + } +-EXPORT_SYMBOL_GPL(devm_battery_hook_register); ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); + + /* + * This function gets called right after the battery sysfs +@@ -843,7 +843,7 @@ static void battery_hook_add_battery(struct acpi_battery *battery) + */ + pr_err("error in hook, unloading: %s", + hook_node->name); +- battery_hook_unregister_unlocked(hook_node); ++ acpi_battery_hook_unregister_unlocked(hook_node); + } + } + mutex_unlock(&hook_mutex); +@@ -876,7 +876,7 @@ static void __exit battery_hook_exit(void) + * need to remove the hooks. + */ + list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- battery_hook_unregister(hook); ++ acpi_battery_hook_unregister(hook); + } + mutex_destroy(&hook_mutex); + } +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -1681,14 +1681,14 @@ static void asus_wmi_battery_init(struct asus_wmi *asus) + asus->battery_rsoc_available = false; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) { + asus->battery_rsoc_available = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + } + } + + static void asus_wmi_battery_exit(struct asus_wmi *asus) + { + if (asus->battery_rsoc_available) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* LEDs ***********************************************************************/ +diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c +--- a/drivers/platform/x86/ayaneo-ec.c ++++ b/drivers/platform/x86/ayaneo-ec.c +@@ -493,7 +493,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev) + data->battery_hook.add_battery = ayaneo_add_battery; + data->battery_hook.remove_battery = ayaneo_remove_battery; + data->battery_hook.name = "Ayaneo Battery"; +- ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook); ++ ret = devm_acpi_battery_hook_register(&pdev->dev, &data->battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c +--- a/drivers/platform/x86/dell/dell-laptop.c ++++ b/drivers/platform/x86/dell/dell-laptop.c +@@ -2446,13 +2446,13 @@ static void __init dell_battery_init(struct device *dev) + battery_supported_modes = battery_get_supported_modes(); + + if (battery_supported_modes != 0) +- battery_hook_register(&dell_battery_hook); ++ acpi_battery_hook_register(&dell_battery_hook); + } + + static void dell_battery_exit(void) + { + if (battery_supported_modes != 0) +- battery_hook_unregister(&dell_battery_hook); ++ acpi_battery_hook_unregister(&dell_battery_hook); + } + + static int __init dell_init(void) +diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c +--- a/drivers/platform/x86/dell/dell-wmi-ddv.c ++++ b/drivers/platform/x86/dell/dell-wmi-ddv.c +@@ -972,7 +972,7 @@ static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) + data->eppid_attr.attr.mode = 0444; + data->eppid_attr.show = eppid_show; + +- return devm_battery_hook_register(&data->wdev->dev, &data->hook); ++ return devm_acpi_battery_hook_register(&data->wdev->dev, &data->hook); + } + + static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method) +diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c +--- a/drivers/platform/x86/fujitsu-laptop.c ++++ b/drivers/platform/x86/fujitsu-laptop.c +@@ -267,7 +267,7 @@ static int fujitsu_battery_charge_control_add(struct device *dev) + return -ENODEV; + + priv->charge_control_supported = true; +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + } +@@ -277,7 +277,7 @@ static void fujitsu_battery_charge_control_remove(struct device *dev) + struct fujitsu_laptop *priv = dev_get_drvdata(dev); + + if (priv->charge_control_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + /* Hardware access for LCD brightness control */ +diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c +--- a/drivers/platform/x86/huawei-wmi.c ++++ b/drivers/platform/x86/huawei-wmi.c +@@ -512,7 +512,7 @@ static void huawei_wmi_battery_setup(struct device *dev) + return; + } + +- battery_hook_register(&huawei_wmi_battery_hook); ++ acpi_battery_hook_register(&huawei_wmi_battery_hook); + device_create_file(dev, &dev_attr_charge_control_thresholds); + } + +@@ -521,7 +521,7 @@ static void huawei_wmi_battery_exit(struct device *dev) + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + if (huawei->battery_available) { +- battery_hook_unregister(&huawei_wmi_battery_hook); ++ acpi_battery_hook_unregister(&huawei_wmi_battery_hook); + device_remove_file(dev, &dev_attr_charge_control_thresholds); + } + } +diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c +--- a/drivers/platform/x86/lenovo/ideapad-laptop.c ++++ b/drivers/platform/x86/lenovo/ideapad-laptop.c +@@ -2197,8 +2197,8 @@ static int ideapad_check_features(struct ideapad_private *priv) + priv->battery_hook.remove_battery = ideapad_battery_remove; + priv->battery_hook.name = "Ideapad Battery Extension"; + +- err = devm_battery_hook_register(&priv->platform_device->dev, +- &priv->battery_hook); ++ err = devm_acpi_battery_hook_register(&priv->platform_device->dev, ++ &priv->battery_hook); + if (err) + return err; + } +diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c +--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c ++++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c +@@ -10021,13 +10021,13 @@ static int __init tpacpi_battery_init(struct ibm_init_struct *ibm) + battery_quirk_table, + ARRAY_SIZE(battery_quirk_table)); + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void tpacpi_battery_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + static struct ibm_struct battery_driver_data = { +diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c +--- a/drivers/platform/x86/lenovo/wmi-other.c ++++ b/drivers/platform/x86/lenovo/wmi-other.c +@@ -946,7 +946,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv) + priv->battery_hook.name = "Lenovo WMI Other Battery Extension"; + priv->bh_registered = true; + +- battery_hook_register(&priv->battery_hook); ++ acpi_battery_hook_register(&priv->battery_hook); + } + + /** +@@ -960,7 +960,7 @@ static void lwmi_om_psy_remove(struct lwmi_om_priv *priv) + if (!priv->bh_registered) + return; + +- battery_hook_unregister(&priv->battery_hook); ++ acpi_battery_hook_unregister(&priv->battery_hook); + priv->bh_registered = false; + } + +diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c +--- a/drivers/platform/x86/lg-laptop.c ++++ b/drivers/platform/x86/lg-laptop.c +@@ -1053,7 +1053,7 @@ static int acpi_probe(struct platform_device *pdev) + if (ret) + goto out_platform_device; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +@@ -1068,7 +1068,7 @@ static void acpi_remove(struct platform_device *pdev) + { + sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group); + +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + platform_device_unregister(pf_device); + pf_device = NULL; + platform_driver_unregister(&pf_driver); +diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c +--- a/drivers/platform/x86/msi-ec.c ++++ b/drivers/platform/x86/msi-ec.c +@@ -1428,13 +1428,13 @@ static int __init msi_ec_init(void) + if (result < 0) + return result; + +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + return 0; + } + + static void __exit msi_ec_exit(void) + { +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + } + + MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c +--- a/drivers/platform/x86/oxpec.c ++++ b/drivers/platform/x86/oxpec.c +@@ -956,7 +956,7 @@ static int oxp_platform_probe(struct platform_device *pdev) + return PTR_ERR(hwdev); + + if (oxp_psy_ext_supported()) { +- ret = devm_battery_hook_register(dev, &battery_hook); ++ ret = devm_acpi_battery_hook_register(dev, &battery_hook); + if (ret) + return ret; + } +diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c +--- a/drivers/platform/x86/samsung-galaxybook.c ++++ b/drivers/platform/x86/samsung-galaxybook.c +@@ -549,7 +549,8 @@ static int galaxybook_battery_threshold_init(struct samsung_galaxybook *galaxybo + galaxybook->battery_hook.remove_battery = galaxybook_battery_remove; + galaxybook->battery_hook.name = "Samsung Galaxy Book Battery Extension"; + +- return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook); ++ return devm_acpi_battery_hook_register(&galaxybook->platform->dev, ++ &galaxybook->battery_hook); + } + + /* +diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c +--- a/drivers/platform/x86/samsung-laptop.c ++++ b/drivers/platform/x86/samsung-laptop.c +@@ -1142,8 +1142,8 @@ static int __init samsung_battery_hook_init(struct samsung_laptop *samsung) + samsung->battery_hook.add_battery = samsung_battery_add; + samsung->battery_hook.remove_battery = samsung_battery_remove; + samsung->battery_hook.name = "Samsung Battery Extension"; +- retval = devm_battery_hook_register(&samsung->platform_device->dev, +- &samsung->battery_hook); ++ retval = devm_acpi_battery_hook_register(&samsung->platform_device->dev, ++ &samsung->battery_hook); + } + + return retval; +diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c +--- a/drivers/platform/x86/system76_acpi.c ++++ b/drivers/platform/x86/system76_acpi.c +@@ -288,12 +288,12 @@ static struct acpi_battery_hook system76_battery_hook = { + + static void system76_battery_init(void) + { +- battery_hook_register(&system76_battery_hook); ++ acpi_battery_hook_register(&system76_battery_hook); + } + + static void system76_battery_exit(void) + { +- battery_hook_unregister(&system76_battery_hook); ++ acpi_battery_hook_unregister(&system76_battery_hook); + } + + // Get the airplane mode LED brightness +diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c +--- a/drivers/platform/x86/toshiba_acpi.c ++++ b/drivers/platform/x86/toshiba_acpi.c +@@ -3301,7 +3301,7 @@ static void toshiba_acpi_remove(struct platform_device *pdev) + } + + if (dev->battery_charge_mode_supported) +- battery_hook_unregister(&battery_hook); ++ acpi_battery_hook_unregister(&battery_hook); + + if (toshiba_acpi) + toshiba_acpi = NULL; +@@ -3566,7 +3566,7 @@ static int toshiba_acpi_probe(struct platform_device *pdev) + * set, this must be done after toshiba_acpi is assigned. + */ + if (dev->battery_charge_mode_supported) +- battery_hook_register(&battery_hook); ++ acpi_battery_hook_register(&battery_hook); + + return 0; + +diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c +--- a/drivers/platform/x86/uniwill/uniwill-acpi.c ++++ b/drivers/platform/x86/uniwill/uniwill-acpi.c +@@ -2165,7 +2165,7 @@ static int uniwill_battery_init(struct uniwill_data *data) + data->hook.add_battery = uniwill_add_battery; + data->hook.remove_battery = uniwill_remove_battery; + +- return devm_battery_hook_register(data->dev, &data->hook); ++ return devm_acpi_battery_hook_register(data->dev, &data->hook); + } + + static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy) +diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c +--- a/drivers/power/supply/cros_charge-control.c ++++ b/drivers/power/supply/cros_charge-control.c +@@ -315,7 +315,7 @@ static int cros_chctl_probe(struct platform_device *pdev) + if (ret < 0) + return ret; + +- return devm_battery_hook_register(dev, &priv->battery_hook); ++ return devm_acpi_battery_hook_register(dev, &priv->battery_hook); + } + + static const struct platform_device_id cros_chctl_id[] = { +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,8 +18,9 @@ struct acpi_battery_hook { + struct list_head list; + }; + +-void battery_hook_register(struct acpi_battery_hook *hook); +-void battery_hook_unregister(struct acpi_battery_hook *hook); +-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook); ++void acpi_battery_hook_register(struct acpi_battery_hook *hook); ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook); + + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch.sig b/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch.sig new file mode 100644 index 0000000..84b7206 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0803-acpi-battery-hook-api-acpi-prefix.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch b/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch new file mode 100644 index 0000000..a50095e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch @@ -0,0 +1,472 @@ +diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig +--- a/drivers/acpi/Kconfig ++++ b/drivers/acpi/Kconfig +@@ -171,8 +171,12 @@ config ACPI_AC + To compile this driver as a module, choose M here: + the module will be called ac. + ++config ACPI_BATTERY_HOOKS ++ tristate ++ + config ACPI_BATTERY + tristate "Battery" ++ select ACPI_BATTERY_HOOKS + select POWER_SUPPLY + default y + help +@@ -445,6 +449,7 @@ config ACPI_HOTPLUG_IOAPIC + config ACPI_SBS + tristate "Smart Battery System" + depends on X86 && ACPI_EC ++ select ACPI_BATTERY_HOOKS + select POWER_SUPPLY + help + This driver supports the Smart Battery System, another +diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile +--- a/drivers/acpi/Makefile ++++ b/drivers/acpi/Makefile +@@ -97,6 +97,7 @@ obj-$(CONFIG_ACPI_NHLT) += nhlt.o + obj-$(CONFIG_ACPI_NUMA) += numa/ + obj-$(CONFIG_ACPI) += acpi_memhotplug.o + obj-$(CONFIG_ACPI_HOTPLUG_IOAPIC) += ioapic.o ++obj-$(CONFIG_ACPI_BATTERY_HOOKS) += battery_hooks.o + obj-$(CONFIG_ACPI_BATTERY) += battery.o + obj-$(CONFIG_ACPI_SBS) += sbshc.o + obj-$(CONFIG_ACPI_SBS) += sbs.o +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -48,6 +48,7 @@ MODULE_AUTHOR("Paul Diefenbaugh"); + MODULE_AUTHOR("Alexey Starikovskiy "); + MODULE_DESCRIPTION("ACPI Battery Driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static int battery_bix_broken_package; + static int battery_notification_delay_ms; +@@ -97,7 +98,7 @@ struct acpi_battery { + struct acpi_device *device; + struct device *phys_dev; + struct notifier_block pm_nb; +- struct list_head list; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long flags; + + struct mutex property_lock; /* Protects properties below. */ +@@ -723,164 +724,6 @@ static struct attribute *acpi_battery_attrs[] = { + }; + ATTRIBUTE_GROUPS(acpi_battery); + +-/* +- * The Battery Hooking API +- * +- * This API is used inside other drivers that need to expose +- * platform-specific behaviour within the generic driver in a +- * generic way. +- * +- */ +- +-static LIST_HEAD(acpi_battery_list); +-static LIST_HEAD(battery_hook_list); +-static DEFINE_MUTEX(hook_mutex); +- +-static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- /* +- * In order to remove a hook, we first need to +- * de-register all the batteries that are registered. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (!hook->remove_battery(battery->bat, hook)) +- power_supply_changed(battery->bat); +- } +- list_del_init(&hook->list); +- +- pr_info("hook unregistered: %s\n", hook->name); +-} +- +-void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) +-{ +- mutex_lock(&hook_mutex); +- /* +- * Ignore already unregistered battery hooks. This might happen +- * if a battery hook was previously unloaded due to an error when +- * adding a new battery. +- */ +- if (!list_empty(&hook->list)) +- acpi_battery_hook_unregister_unlocked(hook); +- +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); +- +-void acpi_battery_hook_register(struct acpi_battery_hook *hook) +-{ +- struct acpi_battery *battery; +- +- mutex_lock(&hook_mutex); +- list_add(&hook->list, &battery_hook_list); +- /* +- * Now that the driver is registered, we need +- * to notify the hook that a battery is available +- * for each battery, so that the driver may add +- * its attributes. +- */ +- list_for_each_entry(battery, &acpi_battery_list, list) { +- if (hook->add_battery(battery->bat, hook)) { +- /* +- * If a add-battery returns non-zero, +- * the registration of the hook has failed, +- * and we will not add it to the list of loaded +- * hooks. +- */ +- pr_err("hook failed to load: %s", hook->name); +- acpi_battery_hook_unregister_unlocked(hook); +- goto end; +- } +- +- power_supply_changed(battery->bat); +- } +- pr_info("new hook: %s\n", hook->name); +-end: +- mutex_unlock(&hook_mutex); +-} +-EXPORT_SYMBOL_GPL(acpi_battery_hook_register); +- +-static void devm_acpi_battery_hook_unregister(void *data) +-{ +- struct acpi_battery_hook *hook = data; +- +- acpi_battery_hook_unregister(hook); +-} +- +-int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook) +-{ +- acpi_battery_hook_register(hook); +- +- return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); +-} +-EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); +- +-/* +- * This function gets called right after the battery sysfs +- * attributes have been added, so that the drivers that +- * define custom sysfs attributes can add their own. +- */ +-static void battery_hook_add_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook_node, *tmp; +- +- mutex_lock(&hook_mutex); +- INIT_LIST_HEAD(&battery->list); +- list_add(&battery->list, &acpi_battery_list); +- /* +- * Since we added a new battery to the list, we need to +- * iterate over the hooks and call add_battery for each +- * hook that was registered. This usually happens +- * when a battery gets hotplugged or initialized +- * during the battery module initialization. +- */ +- list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { +- if (hook_node->add_battery(battery->bat, hook_node)) { +- /* +- * The notification of the hook has failed, to +- * prevent further errors we will unload the hook. +- */ +- pr_err("error in hook, unloading: %s", +- hook_node->name); +- acpi_battery_hook_unregister_unlocked(hook_node); +- } +- } +- mutex_unlock(&hook_mutex); +-} +- +-static void battery_hook_remove_battery(struct acpi_battery *battery) +-{ +- struct acpi_battery_hook *hook; +- +- mutex_lock(&hook_mutex); +- /* +- * Before removing the hook, we need to remove all +- * custom attributes from the battery. +- */ +- list_for_each_entry(hook, &battery_hook_list, list) { +- hook->remove_battery(battery->bat, hook); +- } +- /* Then, just remove the battery from the list */ +- list_del(&battery->list); +- mutex_unlock(&hook_mutex); +-} +- +-static void __exit battery_hook_exit(void) +-{ +- struct acpi_battery_hook *hook; +- struct acpi_battery_hook *ptr; +- /* +- * At this point, the acpi_bus_unregister_driver() +- * has called remove for all batteries. We just +- * need to remove the hooks. +- */ +- list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) { +- acpi_battery_hook_unregister(hook); +- } +- mutex_destroy(&hook_mutex); +-} +- + static int sysfs_add_battery(struct acpi_battery *battery) + { + struct power_supply_config psy_cfg = { +@@ -936,7 +779,7 @@ static int sysfs_add_battery(struct acpi_battery *battery) + battery->bat = NULL; + return result; + } +- battery_hook_add_battery(battery); ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); + return 0; + } + +@@ -945,7 +788,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery) + if (!battery->bat) + return; + +- battery_hook_remove_battery(battery); ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); + battery->bat = NULL; + } +@@ -1391,7 +1234,6 @@ static int __init acpi_battery_init(void) + static void __exit acpi_battery_exit(void) + { + platform_driver_unregister(&acpi_battery_driver); +- battery_hook_exit(); + } + + module_init(acpi_battery_init); +diff --git a/drivers/acpi/battery_hooks.c b/drivers/acpi/battery_hooks.c +new file mode 100644 +--- /dev/null ++++ b/drivers/acpi/battery_hooks.c +@@ -0,0 +1,159 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * ACPI Battery Hooks ++ * ++ * Provides helpers for registering and unregistering battery hooks for ++ * drivers that use platform-specific extensions to ACPI-enumerated ++ * batteries (both ACPI Control Method batteries and Smart Battery System ++ * batteries). ++ */ ++ ++#define pr_fmt(fmt) "ACPI: battery: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static LIST_HEAD(acpi_battery_list); ++static LIST_HEAD(battery_hook_list); ++static DEFINE_MUTEX(hook_mutex); ++ ++static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ /* ++ * In order to remove a hook, we first need to ++ * de-register all the batteries that are registered. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (!hook->remove_battery(entry->battery, hook)) ++ power_supply_changed(entry->battery); ++ } ++ list_del_init(&hook->list); ++ ++ pr_info("hook unregistered: %s\n", hook->name); ++} ++ ++void acpi_battery_hook_unregister(struct acpi_battery_hook *hook) ++{ ++ guard(mutex)(&hook_mutex); ++ ++ /* ++ * Ignore already unregistered battery hooks. This might happen ++ * if a battery hook was previously unloaded due to an error when ++ * adding a new battery. ++ */ ++ if (!list_empty(&hook->list)) ++ acpi_battery_hook_unregister_unlocked(hook); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister); ++ ++void acpi_battery_hook_register(struct acpi_battery_hook *hook) ++{ ++ struct acpi_battery_hooks_list_entry *entry; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&hook->list, &battery_hook_list); ++ /* ++ * Now that the driver is registered, we need ++ * to notify the hook that a battery is available ++ * for each battery, so that the driver may add ++ * its attributes. ++ */ ++ list_for_each_entry(entry, &acpi_battery_list, list_entry) { ++ if (hook->add_battery(entry->battery, hook)) { ++ /* ++ * If a add-battery returns non-zero, ++ * the registration of the hook has failed, ++ * and we will not add it to the list of loaded ++ * hooks. ++ */ ++ pr_err("hook failed to load: %s\n", hook->name); ++ acpi_battery_hook_unregister_unlocked(hook); ++ return; ++ } ++ ++ power_supply_changed(entry->battery); ++ } ++ pr_info("new hook: %s\n", hook->name); ++} ++EXPORT_SYMBOL_GPL(acpi_battery_hook_register); ++ ++static void devm_acpi_battery_hook_unregister(void *data) ++{ ++ struct acpi_battery_hook *hook = data; ++ ++ acpi_battery_hook_unregister(hook); ++} ++ ++int devm_acpi_battery_hook_register(struct device *dev, ++ struct acpi_battery_hook *hook) ++{ ++ acpi_battery_hook_register(hook); ++ ++ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook); ++} ++EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register); ++ ++/* ++ * This function gets called right after the battery sysfs ++ * attributes have been added, so that the drivers that ++ * define custom sysfs attributes can add their own. ++ */ ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery) ++{ ++ struct acpi_battery_hook *hook_node, *tmp; ++ ++ entry->battery = battery; ++ ++ guard(mutex)(&hook_mutex); ++ ++ list_add(&entry->list_entry, &acpi_battery_list); ++ /* ++ * Since we added a new battery to the list, we need to ++ * iterate over the hooks and call add_battery for each ++ * hook that was registered. This usually happens ++ * when a battery gets hotplugged or initialized ++ * during the battery module initialization. ++ */ ++ list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) { ++ if (hook_node->add_battery(entry->battery, hook_node)) { ++ /* ++ * The notification of the hook has failed, to ++ * prevent further errors we will unload the hook. ++ */ ++ pr_err("error in hook, unloading: %s\n", hook_node->name); ++ acpi_battery_hook_unregister_unlocked(hook_node); ++ } ++ } ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_add_hooks, "ACPI_BATTERY_HOOKS"); ++ ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry) ++{ ++ struct acpi_battery_hook *hook; ++ ++ guard(mutex)(&hook_mutex); ++ /* ++ * Before removing the hook, we need to remove all ++ * custom attributes from the battery. ++ */ ++ list_for_each_entry(hook, &battery_hook_list, list) ++ hook->remove_battery(entry->battery, hook); ++ ++ /* Then, just remove the battery from the list */ ++ list_del(&entry->list_entry); ++ entry->battery = NULL; ++} ++EXPORT_SYMBOL_NS_GPL(acpi_battery_remove_hooks, "ACPI_BATTERY_HOOKS"); ++ ++MODULE_DESCRIPTION("ACPI battery hooks"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c +--- a/drivers/acpi/sbs.c ++++ b/drivers/acpi/sbs.c +@@ -36,6 +36,7 @@ + MODULE_AUTHOR("Alexey Starikovskiy "); + MODULE_DESCRIPTION("Smart Battery System ACPI interface driver"); + MODULE_LICENSE("GPL"); ++MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS"); + + static unsigned int cache_time = 1000; + module_param(cache_time, uint, 0644); +@@ -54,6 +55,7 @@ struct acpi_battery { + struct power_supply *bat; + struct power_supply_desc bat_desc; + struct acpi_sbs *sbs; ++ struct acpi_battery_hooks_list_entry hooks_list_entry; + unsigned long update_time; + char name[8]; + char manufacturer_name[ACPI_SBS_BLOCK_MAX]; +@@ -555,6 +557,8 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id) + goto end; + } + ++ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat); ++ + end: + pr_info("%s [%s]: Battery Slot [%s] (battery %s)\n", + ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device), +@@ -566,8 +570,10 @@ static void acpi_battery_remove(struct acpi_sbs *sbs, int id) + { + struct acpi_battery *battery = &sbs->battery[id]; + +- if (battery->bat) ++ if (battery->bat) { ++ acpi_battery_remove_hooks(&battery->hooks_list_entry); + power_supply_unregister(battery->bat); ++ } + } + + static int acpi_charger_add(struct acpi_sbs *sbs) +diff --git a/include/acpi/battery.h b/include/acpi/battery.h +--- a/include/acpi/battery.h ++++ b/include/acpi/battery.h +@@ -18,9 +18,18 @@ struct acpi_battery_hook { + struct list_head list; + }; + ++struct acpi_battery_hooks_list_entry { ++ struct list_head list_entry; ++ struct power_supply *battery; ++}; ++ + void acpi_battery_hook_register(struct acpi_battery_hook *hook); + void acpi_battery_hook_unregister(struct acpi_battery_hook *hook); + int devm_acpi_battery_hook_register(struct device *dev, + struct acpi_battery_hook *hook); + ++void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry, ++ struct power_supply *battery); ++void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig b/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig new file mode 100644 index 0000000..3d8754f Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0804-acpi-battery-unified-hooks-acpi-sbs.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch b/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch new file mode 100644 index 0000000..9b45c66 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch @@ -0,0 +1,209 @@ +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -380,6 +380,7 @@ config SENSORS_FAM15H_POWER + config SENSORS_APPLESMC + tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" + depends on INPUT && X86 ++ depends on POWER_SUPPLY || POWER_SUPPLY=n + select NEW_LEDS + select LEDS_CLASS + help +diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c +--- a/drivers/hwmon/applesmc.c ++++ b/drivers/hwmon/applesmc.c +@@ -33,6 +33,8 @@ + #include + #include + #include ++#include ++#include + #include + + /* data port used by Apple SMC */ +@@ -76,6 +78,20 @@ + + #define TEMP_SENSOR_TYPE "sp78" + ++/* ++ * BCLM caps charging at a percentage. BFCL only sets when the charging LED ++ * switches from orange to green. ++ */ ++#define BATTERY_CHARGE_LIMIT_KEY "BCLM" /* r/w ui8 */ ++#define BATTERY_CHARGE_LIMIT_LED_KEY "BFCL" /* r/w ui8 */ ++ ++/* ++ * Points kept between BCLM and BFCL so the LED turns green before charging ++ * stops. Measured on a MacBookAir6,2, where a margin of 1 only just ties; ++ * 3 leaves headroom for other models. ++ */ ++#define APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN 3 ++ + /* List of keys used to read/write fan speeds */ + static const char *const fan_speed_fmt[] = { + "F%dAc", /* actual speed */ +@@ -131,6 +147,8 @@ static struct applesmc_registers { + int num_light_sensors; /* number of light sensors */ + bool has_accelerometer; /* has motion sensor */ + bool has_key_backlight; /* has keyboard backlight */ ++ bool has_battery_charge_limit; /* has BCLM battery charge limit */ ++ bool has_battery_charge_limit_led; /* has BFCL MagSafe LED charge limit */ + bool init_complete; /* true when fully initialized */ + struct applesmc_entry *cache; /* cached key entries */ + const char **index; /* temperature key index */ +@@ -633,6 +651,12 @@ static int applesmc_init_smcreg_try(void) + if (ret) + return ret; + ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_KEY, &s->has_battery_charge_limit); ++ if (ret) ++ return ret; ++ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_LED_KEY, &s->has_battery_charge_limit_led); + if (ret) + return ret; + +@@ -724,6 +748,120 @@ static struct platform_driver applesmc_driver = { + }, + }; + ++static DEFINE_MUTEX(applesmc_bat_lock); ++ ++static int applesmc_bat_get_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ u8 limit; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ scoped_guard(mutex, &applesmc_bat_lock) ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ val->intval = limit; ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_set_property(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp, ++ const union power_supply_propval *val) ++{ ++ u8 limit, led_limit, readback; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: ++ /* 20 is known to work and 10 is not accepted; the floor is unknown. */ ++ if (val->intval < 20 || val->intval > 100) ++ return -EINVAL; ++ ++ limit = val->intval; ++ /* At 100% charging is never cut short, so no margin is needed. */ ++ led_limit = limit < 100 ? ++ limit - APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN : limit; ++ ++ scoped_guard(mutex, &applesmc_bat_lock) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1); ++ if (ret) ++ return ret; ++ ++ /* ++ * The SMC silently ignores values it does not accept and ++ * still reports success, so read the limit back. ++ */ ++ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &readback, 1); ++ if (ret) ++ return ret; ++ if (readback != limit) ++ return -EINVAL; ++ ++ if (smcreg.has_battery_charge_limit_led) { ++ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_LED_KEY, ++ &led_limit, 1); ++ /* BCLM is already applied, so this is not an error. */ ++ if (ret) ++ pr_warn("failed to update MagSafe LED threshold (BFCL): %d\n", ++ ret); ++ } ++ } ++ ++ power_supply_changed(psy); ++ return 0; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int applesmc_bat_property_is_writeable(struct power_supply *psy, ++ const struct power_supply_ext *ext, ++ void *data, ++ enum power_supply_property psp) ++{ ++ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD; ++} ++ ++static const enum power_supply_property applesmc_bat_props[] = { ++ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, ++}; ++ ++static const struct power_supply_ext applesmc_bat_ext = { ++ .name = "applesmc-charge-control", ++ .properties = applesmc_bat_props, ++ .num_properties = ARRAY_SIZE(applesmc_bat_props), ++ .get_property = applesmc_bat_get_property, ++ .set_property = applesmc_bat_set_property, ++ .property_is_writeable = applesmc_bat_property_is_writeable, ++}; ++ ++static int applesmc_bat_add(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ return power_supply_register_extension(battery, &applesmc_bat_ext, &pdev->dev, NULL); ++} ++ ++static int applesmc_bat_remove(struct power_supply *battery, struct acpi_battery_hook *hook) ++{ ++ power_supply_unregister_extension(battery, &applesmc_bat_ext); ++ return 0; ++} ++ ++static struct acpi_battery_hook applesmc_bat_hook = { ++ .name = "Apple SMC Battery Charge Control", ++ .add_battery = applesmc_bat_add, ++ .remove_battery = applesmc_bat_remove, ++}; ++ + /* + * applesmc_calibrate - Set our "resting" values. Callers must + * hold applesmc_lock. +@@ -1563,6 +1701,10 @@ static int __init applesmc_init(void) + goto out_light_ledclass; + } + ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_register(&applesmc_bat_hook); ++ + return 0; + + out_light_ledclass: +@@ -1589,6 +1731,10 @@ static int __init applesmc_init(void) + + static void __exit applesmc_exit(void) + { ++ if (smcreg.has_battery_charge_limit && ++ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS)) ++ acpi_battery_hook_unregister(&applesmc_bat_hook); ++ + hwmon_device_unregister(hwmon_dev); + applesmc_release_key_backlight(); + applesmc_release_light_sensor(); diff --git a/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig b/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig new file mode 100644 index 0000000..992c843 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0805-hwmon-applesmc-charge-control-end-threshold.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch b/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch new file mode 100644 index 0000000..d4ab07f --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch @@ -0,0 +1,142 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -22,6 +22,7 @@ + #define FUTEX_WAIT_REQUEUE_PI 11 + #define FUTEX_CMP_REQUEUE_PI 12 + #define FUTEX_LOCK_PI2 13 ++#define FUTEX_WAIT_MULTIPLE 31 + + #define FUTEX_PRIVATE_FLAG 128 + #define FUTEX_CLOCK_REALTIME 256 +@@ -126,6 +127,18 @@ struct futex_waitv { + __u32 __reserved; + }; + ++/** ++ * struct futex_wait_block - Block of futexes to be waited for ++ * @uaddr: User address of the futex ++ * @val: Futex value expected by userspace ++ * @bitset: Bitset for the optional bitmasked wakeup ++ */ ++struct futex_wait_block { ++ __u32 __user *uaddr; ++ __u32 val; ++ __u32 bitset; ++}; ++ + /* + * Support for robust futexes: the kernel cleans up held futexes at + * thread exit time. +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -169,6 +169,7 @@ static __always_inline bool futex_cmd_has_timeout(u32 cmd) + case FUTEX_LOCK_PI2: + case FUTEX_WAIT_BITSET: + case FUTEX_WAIT_REQUEUE_PI: ++ case FUTEX_WAIT_MULTIPLE: + return true; + } + return false; +@@ -181,13 +182,79 @@ futex_init_timeout(u32 cmd, u32 op, struct timespec64 *ts, ktime_t *t) + return -EINVAL; + + *t = timespec64_to_ktime(*ts); +- if (cmd == FUTEX_WAIT) ++ if (cmd == FUTEX_WAIT || cmd == FUTEX_WAIT_MULTIPLE) + *t = ktime_add_safe(ktime_get(), *t); + else if (cmd != FUTEX_LOCK_PI && !(op & FUTEX_CLOCK_REALTIME)) + *t = timens_ktime_to_host(CLOCK_MONOTONIC, *t); + return 0; + } + ++/** ++ * futex_read_wait_block - Read an array of futex_wait_block from userspace ++ * @uaddr: Userspace address of the block ++ * @count: Number of blocks to be read ++ * ++ * This function creates and allocate an array of futex_q (we zero it to ++ * initialize the fields) and then, for each futex_wait_block element from ++ * userspace, fill a futex_q element with proper values. ++ */ ++inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++{ ++ unsigned int i; ++ struct futex_vector *futexv; ++ struct futex_wait_block fwb; ++ struct futex_wait_block __user *entry = ++ (struct futex_wait_block __user *)uaddr; ++ ++ if (!count || count > FUTEX_WAITV_MAX) ++ return ERR_PTR(-EINVAL); ++ ++ futexv = kcalloc(count, sizeof(*futexv), GFP_KERNEL); ++ if (!futexv) ++ return ERR_PTR(-ENOMEM); ++ ++ for (i = 0; i < count; i++) { ++ if (copy_from_user(&fwb, &entry[i], sizeof(fwb))) { ++ kfree(futexv); ++ return ERR_PTR(-EFAULT); ++ } ++ ++ futexv[i].w.flags = FUTEX_32; ++ futexv[i].w.val = fwb.val; ++ futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].q = futex_q_init; ++ } ++ ++ return futexv; ++} ++ ++int futex_wait_multiple(struct futex_vector *vs, unsigned int count, ++ struct hrtimer_sleeper *to); ++ ++int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++{ ++ int ret; ++ struct futex_vector *vs; ++ struct hrtimer_sleeper *to = NULL, timeout; ++ ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ ++ vs = futex_read_wait_block(uaddr, count); ++ ++ if (IS_ERR(vs)) ++ return PTR_ERR(vs); ++ ++ ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); ++ kfree(vs); ++ ++ if (to) { ++ hrtimer_cancel(&to->timer); ++ destroy_hrtimer_on_stack(&to->timer); ++ } ++ ++ return ret; ++} ++ + SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + const struct __kernel_timespec __user *, utime, + u32 __user *, uaddr2, u32, val3) +@@ -207,6 +274,9 @@ SYSCALL_DEFINE6(futex, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + +@@ -521,6 +591,9 @@ SYSCALL_DEFINE6(futex_time32, u32 __user *, uaddr, int, op, u32, val, + tp = &t; + } + ++ if (cmd == FUTEX_WAIT_MULTIPLE) ++ return futex_opcode_31(tp, uaddr, val); ++ + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } + #endif /* CONFIG_COMPAT_32BIT_TIME */ diff --git a/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch.sig b/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch.sig new file mode 100644 index 0000000..c9e1f4e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0850-futex-wait-multiple.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch new file mode 100644 index 0000000..671934a --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch @@ -0,0 +1,35 @@ +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,7 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) + { + unsigned int i; + struct futex_vector *futexv; +@@ -231,19 +231,18 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); +- + vs = futex_read_wait_block(uaddr, count); +- + if (IS_ERR(vs)) + return PTR_ERR(vs); + ++ to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); + diff --git a/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch.sig new file mode 100644 index 0000000..e3fb191 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0851-futex-wait-multiple-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch b/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch new file mode 100644 index 0000000..b0d6ba3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch @@ -0,0 +1,88 @@ +diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h +--- a/include/uapi/linux/futex.h ++++ b/include/uapi/linux/futex.h +@@ -134,7 +134,7 @@ + * @bitset: Bitset for the optional bitmasked wakeup + */ + struct futex_wait_block { +- __u32 __user *uaddr; ++ __aligned_u64 uaddr; + __u32 val; + __u32 bitset; + }; +diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c +--- a/kernel/futex/syscalls.c ++++ b/kernel/futex/syscalls.c +@@ -198,7 +198,8 @@ + * initialize the fields) and then, for each futex_wait_block element from + * userspace, fill a futex_q element with proper values. + */ +-static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count) ++static inline struct futex_vector *futex_read_wait_block(u32 __user *uaddr, u32 count, ++ unsigned int flags) + { + unsigned int i; + struct futex_vector *futexv; +@@ -219,10 +220,17 @@ + return ERR_PTR(-EFAULT); + } + +- futexv[i].w.flags = FUTEX_32; ++ if (!fwb.bitset) { ++ kfree(futexv); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ futexv[i].w.flags = flags; + futexv[i].w.val = fwb.val; +- futexv[i].w.uaddr = (uintptr_t) (fwb.uaddr); ++ futexv[i].w.uaddr = fwb.uaddr; + futexv[i].q = futex_q_init; ++ futexv[i].q.bitset = fwb.bitset; ++ futexv[i].q.wake = futex_wake_mark; + } + + return futexv; +@@ -231,17 +239,21 @@ + int futex_wait_multiple(struct futex_vector *vs, unsigned int count, + struct hrtimer_sleeper *to); + +-static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count) ++static int futex_opcode_31(ktime_t *abs_time, u32 __user *uaddr, int count, int op) + { + int ret; + struct futex_vector *vs; + struct hrtimer_sleeper *to = NULL, timeout; ++ unsigned int flags = futex_to_flags(op); ++ ++ if (flags & FLAGS_CLOCKRT) ++ return -ENOSYS; + +- vs = futex_read_wait_block(uaddr, count); ++ vs = futex_read_wait_block(uaddr, count, flags); + if (IS_ERR(vs)) + return PTR_ERR(vs); + +- to = futex_setup_timer(abs_time, &timeout, 0, 0); ++ to = futex_setup_timer(abs_time, &timeout, flags, current->timer_slack_ns); + + ret = futex_wait_multiple(vs, count, abs_time ? to : NULL); + kfree(vs); +@@ -274,7 +286,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } +@@ -591,7 +603,7 @@ + } + + if (cmd == FUTEX_WAIT_MULTIPLE) +- return futex_opcode_31(tp, uaddr, val); ++ return futex_opcode_31(tp, uaddr, val, op); + + return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); + } diff --git a/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch.sig b/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch.sig new file mode 100644 index 0000000..d3db2d8 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0852-futex-wait-multiple-abi-fixes.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch b/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch new file mode 100644 index 0000000..ebd6e0d --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch @@ -0,0 +1,95 @@ +diff --git a/include/linux/module.h b/include/linux/module.h +--- a/include/linux/module.h ++++ b/include/linux/module.h +@@ -975,6 +975,10 @@ unsigned long module_kallsyms_lookup_name(const char *name); + + unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name); + ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data); ++ + #else /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + static inline int module_kallsyms_on_each_symbol(const char *modname, +@@ -1018,6 +1022,12 @@ static inline unsigned long find_kallsyms_symbol_value(struct module *mod, + return 0; + } + ++static inline void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++} ++ + #endif /* CONFIG_MODULES && CONFIG_KALLSYMS */ + + /* Define __free(module_put) macro for struct module *. */ +diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c +--- a/kernel/module/kallsyms.c ++++ b/kernel/module/kallsyms.c +@@ -246,6 +246,18 @@ static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms, unsigned + return kallsyms->strtab + kallsyms->symtab[symnum].st_name; + } + ++/* ++ * Whether find_kallsyms_symbol() may resolve an address to symbol @symnum. ++ * Unnamed symbols are ignored: they're uninformative and inserted at a whim. ++ */ ++static bool is_lookup_symbol(struct mod_kallsyms *kallsyms, unsigned int symnum) ++{ ++ const char *name = kallsyms_symbol_name(kallsyms, symnum); ++ ++ return kallsyms->symtab[symnum].st_shndx != SHN_UNDEF && ++ *name != '\0' && !is_mapping_symbol(name); ++} ++ + /* + * Given a module and address, find the corresponding symbol and return its name + * while providing its size and offset if needed. +@@ -286,15 +298,7 @@ static const char *find_kallsyms_symbol(struct module *mod, + const Elf_Sym *sym = &kallsyms->symtab[i]; + unsigned long thisval = kallsyms_symbol_value(sym); + +- if (sym->st_shndx == SHN_UNDEF) +- continue; +- +- /* +- * We ignore unnamed symbols: they're uninformative +- * and inserted at a whim. +- */ +- if (*kallsyms_symbol_name(kallsyms, i) == '\0' || +- is_mapping_symbol(kallsyms_symbol_name(kallsyms, i))) ++ if (!is_lookup_symbol(kallsyms, i)) + continue; + + if (thisval <= addr && thisval > bestval) { +@@ -458,6 +462,28 @@ unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name) + return __find_kallsyms_symbol_value(mod, name); + } + ++/* ++ * Call @fn with the address of each symbol of @mod that find_kallsyms_symbol() ++ * may resolve an address to. Unlike module_kallsyms_on_each_symbol(), this ++ * also works while @mod is still being loaded. ++ */ ++void module_kallsyms_on_each_addr(struct module *mod, ++ void (*fn)(void *, unsigned long), ++ void *data) ++{ ++ struct mod_kallsyms *kallsyms; ++ unsigned int i; ++ ++ guard(rcu)(); ++ kallsyms = rcu_dereference(mod->kallsyms); ++ ++ /* ELF starts real symbols at 1. */ ++ for (i = 1; i < kallsyms->num_symtab; i++) { ++ if (is_lookup_symbol(kallsyms, i)) ++ fn(data, kallsyms_symbol_value(&kallsyms->symtab[i])); ++ } ++} ++ + int module_kallsyms_on_each_symbol(const char *modname, + int (*fn)(void *, const char *, unsigned long), + void *data) diff --git a/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch.sig b/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch.sig new file mode 100644 index 0000000..bf22caf Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0860-module-add-module-kallsyms-on-each-addr.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch b/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch new file mode 100644 index 0000000..82b00bb --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch @@ -0,0 +1,113 @@ +diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c +--- a/kernel/trace/ftrace.c ++++ b/kernel/trace/ftrace.c +@@ -4445,11 +4445,6 @@ static int print_rec(struct seq_file *m, unsigned long ip) + return ret == NULL ? -1 : 0; + } + #else +-static inline int test_for_valid_rec(struct dyn_ftrace *rec) +-{ +- return 1; +-} +- + static inline int print_rec(struct seq_file *m, unsigned long ip) + { + seq_printf(m, "%ps", (void *)ip); +@@ -7611,6 +7606,73 @@ static void test_is_sorted(unsigned long *start, unsigned long count) + } + #endif + ++#ifdef FTRACE_MCOUNT_MAX_OFFSET ++struct ftrace_mod_locs { ++ unsigned long *start; ++ unsigned long count; ++ unsigned long *valid; ++}; ++ ++/* Mark the locations that lie at most FTRACE_MCOUNT_MAX_OFFSET after @addr. */ ++static void ftrace_mark_valid_locs(void *data, unsigned long addr) ++{ ++ struct ftrace_mod_locs *locs = data; ++ unsigned long lo = 0, hi = locs->count, mid, ip; ++ ++ /* ++ * ftrace_call_adjust() moves a location forward by at most ++ * FTRACE_MCOUNT_MAX_OFFSET, so start looking that far before @addr. ++ */ ++ while (lo < hi) { ++ mid = lo + (hi - lo) / 2; ++ if (locs->start[mid] + FTRACE_MCOUNT_MAX_OFFSET < addr) ++ lo = mid + 1; ++ else ++ hi = mid; ++ } ++ ++ for (; lo < locs->count; lo++) { ++ if (locs->start[lo] > addr + FTRACE_MCOUNT_MAX_OFFSET) ++ break; ++ ip = ftrace_call_adjust(locs->start[lo]); ++ if (ip >= addr && ip - addr <= FTRACE_MCOUNT_MAX_OFFSET) ++ __set_bit(lo, locs->valid); ++ } ++} ++ ++/* ++ * A weak function overridden within its module keeps its mcount location but ++ * has no symbol. Zero such locations before they become records, as sorttable ++ * does for vmlinux: keep only those with a symbol at most ++ * FTRACE_MCOUNT_MAX_OFFSET before them. ++ */ ++static int ftrace_zero_weak_locs(struct module *mod, unsigned long *start, ++ unsigned long count) ++{ ++ struct ftrace_mod_locs locs = { .start = start, .count = count }; ++ unsigned long i; ++ ++ locs.valid = bitmap_zalloc(count, GFP_KERNEL); ++ if (!locs.valid) ++ return -ENOMEM; ++ ++ module_kallsyms_on_each_addr(mod, ftrace_mark_valid_locs, &locs); ++ ++ for_each_clear_bit(i, locs.valid, count) ++ start[i] = 0; ++ ++ bitmap_free(locs.valid); ++ return 0; ++} ++#else ++static inline int ftrace_zero_weak_locs(struct module *mod, ++ unsigned long *start, ++ unsigned long count) ++{ ++ return 0; ++} ++#endif ++ + static int ftrace_process_locs(struct module *mod, + unsigned long *start, + unsigned long *end) +@@ -7644,6 +7706,9 @@ static int ftrace_process_locs(struct module *mod, + test_is_sorted(start, count); + } + ++ if (mod && ftrace_zero_weak_locs(mod, start, count)) ++ return -ENOMEM; ++ + start_pg = ftrace_allocate_pages(count, &pages); + if (!start_pg) + return -ENOMEM; +@@ -8049,13 +8114,6 @@ void ftrace_module_enable(struct module *mod) + + cond_resched(); + +- /* Weak functions should still be ignored */ +- if (!test_for_valid_rec(rec)) { +- /* Clear all other flags. Should not be enabled anyway */ +- rec->flags = FTRACE_FL_DISABLED; +- continue; +- } +- + cnt = 0; + + /* diff --git a/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig b/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig new file mode 100644 index 0000000..ba105f0 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/0861-ftrace-drop-weak-function-locations-of-modules.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch b/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch new file mode 100644 index 0000000..45590a2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch @@ -0,0 +1,381 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1468,6 +1468,39 @@ struct memory_failure_stats { + }; + #endif + ++/* ++ * Per-pgdat state machine for the kswapd "opportunistic compaction" hint. ++ * ++ * wakeup_kswapd() collapses the gfp flags of all wakers that arrive between ++ * two kswapd runs into a single tri-state, which kswapd then forwards to the ++ * shrinkers via shrink_control::opportunistic_compaction: ++ * ++ * KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION ++ * Initial state after kswapd consumes the previous value. No waker has ++ * been observed yet for the upcoming run. ++ * ++ * KSWAPD_NO_OPPORTUNISTIC_COMPACTION ++ * At least one waker is an order-0 allocation, or a high-order ++ * allocation that cannot tolerate failure (i.e., not eligible for ++ * opportunistic behaviour). Shrinkers must do their normal best-effort ++ * work; the hint is cleared. ++ * ++ * KSWAPD_OPPORTUNISTIC_COMPACTION ++ * All wakers seen so far are high-order allocations that may fail ++ * (__GFP_NORETRY or __GFP_RETRY_MAYFAIL, without __GFP_NOFAIL). Shrinkers ++ * may skip work that is unlikely to produce a contiguous high-order ++ * block (e.g., evicting working-set pages). ++ * ++ * The state is sticky in the "NO" direction within a single kswapd run: once ++ * any non-eligible waker is observed, subsequent eligible wakers cannot ++ * upgrade it back to KSWAPD_OPPORTUNISTIC_COMPACTION. ++ */ ++enum kswapd_opportunistic_compaction_type { ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION = 0, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION, ++}; ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1532,6 +1565,13 @@ typedef struct pglist_data { + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ + int kswapd_order; ++ /* ++ * Aggregated opportunistic-compaction hint for the next kswapd run. ++ * Updated by wakeup_kswapd() based on the gfp flags / order of each ++ * waker, and consumed (and reset) by kswapd before balance_pgdat(). ++ * See enum kswapd_opportunistic_compaction_type for the state machine. ++ */ ++ atomic_t kswapd_opportunistic_compaction; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h +--- a/include/linux/shrinker.h ++++ b/include/linux/shrinker.h +@@ -37,6 +37,26 @@ struct shrink_control { + /* current node being shrunk (for NUMA aware shrinkers) */ + int nid; + ++ /* ++ * Opportunistic compaction hint. ++ * ++ * Set by the reclaim path to tell shrinkers that this pass is ++ * driven by an order > 0 allocation that the caller is willing to ++ * have fail (e.g., __GFP_NORETRY / __GFP_RETRY_MAYFAIL without ++ * __GFP_NOFAIL). Such allocations only really benefit from ++ * shrinking when doing so frees up a contiguous, high-order block; ++ * thrashing working sets in the hope of producing one is typically ++ * counter-productive. ++ * ++ * Shrinkers that can produce naturally-aligned high-order folios ++ * (see shrink_control::order) should treat this as a hint to skip ++ * costly work that is unlikely to help compaction (for example, ++ * evicting hot/working-set pages just to free single pages). ++ * ++ * Only meaningful when @order > 0; ignored otherwise. ++ */ ++ bool opportunistic_compaction; ++ + /* + * How many objects scan_objects should scan and try to reclaim. + * This is reset before every call, so it is safe for callees +diff --git a/mm/internal.h b/mm/internal.h +--- a/mm/internal.h ++++ b/mm/internal.h +@@ -1767,7 +1767,7 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + + /* shrinker related functions */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority); ++ int priority, bool opportunistic_compaction); + + int shmem_add_to_page_cache(struct folio *folio, + struct address_space *mapping, +diff --git a/mm/shrinker.c b/mm/shrinker.c +--- a/mm/shrinker.c ++++ b/mm/shrinker.c +@@ -476,7 +476,7 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl, + + #ifdef CONFIG_MEMCG + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, bool opportunistic_compaction) + { + struct shrinker_info *info; + unsigned long ret, freed = 0; +@@ -538,6 +538,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + struct shrinker *shrinker; + int shrinker_id = calc_shrinker_id(index, offset); +@@ -597,7 +598,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + } + #else /* !CONFIG_MEMCG */ + static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, +- struct mem_cgroup *memcg, int priority) ++ struct mem_cgroup *memcg, int priority, ++ bool opportunistic_compaction) + { + return 0; + } +@@ -609,6 +611,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * @nid: node whose slab caches to target + * @memcg: memory cgroup whose slab caches to target + * @priority: the reclaim priority ++ * @opportunistic_compaction: do compaction opportunistically (e.g., do not swap working sets) + * + * Call the shrink functions to age shrinkable caches. + * +@@ -624,7 +627,7 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid, + * Returns the number of reclaimed slab objects. + */ + unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, +- int priority) ++ int priority, bool opportunistic_compaction) + { + unsigned long ret, freed = 0; + struct shrinker *shrinker; +@@ -637,7 +640,8 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + * oom. + */ + if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg)) +- return shrink_slab_memcg(gfp_mask, nid, memcg, priority); ++ return shrink_slab_memcg(gfp_mask, nid, memcg, priority, ++ opportunistic_compaction); + + /* + * lockless algorithm of global shrink. +@@ -666,6 +670,7 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg, + .gfp_mask = gfp_mask, + .nid = nid, + .memcg = memcg, ++ .opportunistic_compaction = opportunistic_compaction, + }; + + if (!shrinker_try_get(shrinker)) +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -96,6 +96,14 @@ struct scan_control { + /* Swappiness value for proactive reclaim. Always use sc_swappiness()! */ + int *proactive_swappiness; + ++ /* ++ * Opportunistic compaction hint snapshotted from the pgdat at the ++ * start of this reclaim pass. Forwarded to shrinkers through ++ * shrink_control::opportunistic_compaction so they can skip ++ * non-productive work for failable high-order allocations. ++ */ ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ + /* Can active folios be deactivated as part of reclaim? */ + #define DEACTIVATE_ANON 1 + #define DEACTIVATE_FILE 2 +@@ -198,6 +206,29 @@ struct scan_control { + */ + int vm_swappiness = 60; + ++/* ++ * Is @gfp_flags a high-order allocation that is eligible for the ++ * "opportunistic compaction" treatment in kswapd / shrinkers? ++ * ++ * The caller must be willing to tolerate failure (__GFP_NORETRY or ++ * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such ++ * allocations there is little value in burning working-set pages just to ++ * scrape together a single high-order block: if compaction can't easily ++ * succeed, the caller would rather see the allocation fail. ++ */ ++static bool gfp_opportunistic_compaction(gfp_t gfp_flags) ++{ ++ return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && ++ !(gfp_flags & __GFP_NOFAIL); ++} ++ ++static bool sc_opportunistic_compaction(struct scan_control *sc) ++{ ++ return sc->order && (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && ++ gfp_opportunistic_compaction(sc->gfp_mask))); ++} ++ + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) + { + if (sc->proactive && sc->proactive_swappiness) +@@ -411,7 +442,7 @@ static unsigned long drop_slab_node(int nid) + + memcg = mem_cgroup_iter(NULL, NULL, NULL); + do { +- freed += shrink_slab(GFP_KERNEL, nid, memcg, 0); ++ freed += shrink_slab(GFP_KERNEL, nid, memcg, 0, false); + } while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL); + + return freed; +@@ -5081,6 +5112,7 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + unsigned long reclaimed = sc->nr_reclaimed; + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct pglist_data *pgdat = lruvec_pgdat(lruvec); ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* lru_gen_age_node() called mem_cgroup_calculate_protection() */ + if (mem_cgroup_below_min(NULL, memcg)) +@@ -5096,7 +5128,8 @@ static int shrink_one(struct lruvec *lruvec, struct scan_control *sc) + + need_rotate = try_to_shrink_lruvec(lruvec, sc); + +- shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority); ++ shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, sc->priority, ++ opportunistic_compaction); + + if (!sc->proactive) + vmpressure(sc->gfp_mask, sc->order, memcg, false, +@@ -6163,6 +6196,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat); + unsigned long reclaimed; + unsigned long scanned; ++ bool opportunistic_compaction = sc_opportunistic_compaction(sc); + + /* + * This loop can become CPU-bound when target memcgs +@@ -6200,7 +6234,7 @@ static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc) + shrink_lruvec(lruvec, sc); + + shrink_slab(sc->gfp_mask, pgdat->node_id, memcg, +- sc->priority); ++ sc->priority, opportunistic_compaction); + + /* Record the group's reclaim efficiency */ + if (!sc->proactive) +@@ -7133,8 +7167,14 @@ clear_reclaim_active(pg_data_t *pgdat, int highest_zoneidx) + * found to have free_pages <= high_wmark_pages(zone), any page in that zone + * or lower is eligible for reclaim until at least one usable zone is + * balanced. ++ * ++ * @kswapd_opportunistic_compaction is the aggregated hint produced by ++ * wakeup_kswapd() for this run; it is propagated into scan_control so that ++ * shrinkers can skip costly work that is unlikely to help compaction when ++ * all wakers are failable high-order allocations. + */ +-static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) ++static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + int i; + unsigned long nr_soft_reclaimed; +@@ -7148,6 +7188,7 @@ static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx) + .gfp_mask = GFP_KERNEL, + .order = order, + .may_unmap = 1, ++ .kswapd_opportunistic_compaction = kswapd_opportunistic_compaction, + }; + + trace_mm_vmscan_balance_pgdat_begin(pgdat->node_id, order, +@@ -7372,8 +7413,10 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + +-static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, +- unsigned int highest_zoneidx) ++static void ++kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, ++ unsigned int highest_zoneidx, ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction) + { + long remaining = 0; + DEFINE_WAIT(wait); +@@ -7419,6 +7462,11 @@ static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_o + + if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) + WRITE_ONCE(pgdat->kswapd_order, reclaim_order); ++ ++ if (kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7475,6 +7523,7 @@ static int kswapd(void *p) + unsigned int highest_zoneidx = MAX_NR_ZONES - 1; + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; ++ enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7492,6 +7541,8 @@ static int kswapd(void *p) + set_freezable(); + + WRITE_ONCE(pgdat->kswapd_order, 0); ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { +@@ -7500,13 +7551,18 @@ static int kswapd(void *p) + alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); ++ kswapd_opportunistic_compaction = ++ atomic_read(&pgdat->kswapd_opportunistic_compaction); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, +- highest_zoneidx); ++ highest_zoneidx, kswapd_opportunistic_compaction); + + /* Read the new order and highest_zoneidx */ + alloc_order = READ_ONCE(pgdat->kswapd_order); ++ kswapd_opportunistic_compaction = ++ atomic_xchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); + WRITE_ONCE(pgdat->kswapd_order, 0); +@@ -7533,7 +7589,8 @@ static int kswapd(void *p) + trace_mm_vmscan_kswapd_wake(pgdat->node_id, highest_zoneidx, + alloc_order); + reclaim_order = balance_pgdat(pgdat, alloc_order, +- highest_zoneidx); ++ highest_zoneidx, ++ kswapd_opportunistic_compaction); + if (reclaim_order < alloc_order) + goto kswapd_try_sleep; + } +@@ -7571,6 +7628,28 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (READ_ONCE(pgdat->kswapd_order) < order) + WRITE_ONCE(pgdat->kswapd_order, order); + ++ /* ++ * Fold this waker into the per-pgdat opportunistic-compaction hint ++ * that kswapd will pick up at the start of its next run. ++ * ++ * The state is sticky in the "NO" direction: once any waker in this ++ * batch is order-0 or a non-failable high-order allocation, the hint ++ * stays cleared until kswapd consumes it. Only when every waker so ++ * far is a failable high-order allocation do we set ++ * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work ++ * that won't realistically help compaction. ++ */ ++ if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { ++ if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ atomic_set(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, ++ KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, ++ KSWAPD_OPPORTUNISTIC_COMPACTION); ++ } ++ + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; + diff --git a/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch.sig b/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch.sig new file mode 100644 index 0000000..46b6d94 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8204-mm-opportunistic-compaction.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch b/pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch new file mode 100644 index 0000000..258f9b0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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 +@@ -1683,7 +1683,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 +@@ -3423,7 +3423,7 @@ struct page *rmqueue(struct zone *preferred_zone, + if ((alloc_flags & ALLOC_KSWAPD) && + unlikely(test_bit(ZONE_BOOSTED_WATERMARK, &zone->flags))) { + clear_bit(ZONE_BOOSTED_WATERMARK, &zone->flags); +- wakeup_kswapd(zone, 0, 0, zone_idx(zone)); ++ wakeup_kswapd(zone, 0, 0, false, zone_idx(zone)); + } + + VM_BUG_ON_PAGE(page && bad_range(zone, page), page); +@@ -4442,6 +4442,7 @@ static unsigned int check_retry_zonelist(unsigned int seq) + /* Perform direct synchronous page reclaim */ + static unsigned long + __perform_reclaim(gfp_t gfp_mask, unsigned int order, ++ unsigned int alloc_order, + const struct alloc_context *ac) + { + unsigned int noreclaim_flag; +@@ -4454,7 +4455,7 @@ __perform_reclaim(gfp_t gfp_mask, unsigned int order, + fs_reclaim_acquire(gfp_mask); + noreclaim_flag = memalloc_noreclaim_save(); + +- progress = try_to_free_pages(ac->zonelist, order, gfp_mask, ++ progress = try_to_free_pages(ac->zonelist, order, alloc_order, gfp_mask, + ac->nodemask); + + memalloc_noreclaim_restore(noreclaim_flag); +@@ -4481,7 +4482,7 @@ __alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order, + reclaim_order = max(order, pageblock_order); + + psi_memstall_enter(&pflags); +- *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, ac); ++ *did_some_progress = __perform_reclaim(gfp_mask, reclaim_order, order, ac); + if (unlikely(!(*did_some_progress))) + goto out; + +@@ -4513,19 +4514,24 @@ static void wake_all_kswapds(unsigned int order, gfp_t gfp_mask, + pg_data_t *last_pgdat = NULL; + enum zone_type highest_zoneidx = ac->highest_zoneidx; + unsigned int reclaim_order; ++ bool opportunistic; + + if (defrag_mode) + reclaim_order = max(order, pageblock_order); + else + reclaim_order = order; + ++ /* Classify what the caller asked for, not what reclaim was raised to. */ ++ opportunistic = gfp_opportunistic_compaction(gfp_mask, order); ++ + for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, highest_zoneidx, + ac->nodemask) { + if (!managed_zone(zone)) + continue; + if (last_pgdat == zone->zone_pgdat) + continue; +- wakeup_kswapd(zone, gfp_mask, reclaim_order, highest_zoneidx); ++ wakeup_kswapd(zone, gfp_mask, reclaim_order, opportunistic, ++ highest_zoneidx); + last_pgdat = zone->zone_pgdat; + } + } +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -160,6 +160,12 @@ struct scan_control { + /* Allocation order */ + s8 order; + ++ /* ++ * The order the allocation asked for. @order above may have been ++ * raised to pageblock_order by defrag_mode. ++ */ ++ s8 alloc_order; ++ + /* Scan (total_size >> priority) pages at once */ + s8 priority; + +@@ -206,27 +212,19 @@ struct scan_control { + */ + int vm_swappiness = 60; + +-/* +- * Is @gfp_flags a high-order allocation that is eligible for the +- * "opportunistic compaction" treatment in kswapd / shrinkers? +- * +- * The caller must be willing to tolerate failure (__GFP_NORETRY or +- * __GFP_RETRY_MAYFAIL) and must not have set __GFP_NOFAIL. For such +- * allocations there is little value in burning working-set pages just to +- * scrape together a single high-order block: if compaction can't easily +- * succeed, the caller would rather see the allocation fail. +- */ +-static bool gfp_opportunistic_compaction(gfp_t gfp_flags) +-{ +- return (gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)) && +- !(gfp_flags & __GFP_NOFAIL); +-} +- + static bool sc_opportunistic_compaction(struct scan_control *sc) + { +- return sc->order && (sc->kswapd_opportunistic_compaction == +- KSWAPD_OPPORTUNISTIC_COMPACTION || (!current_is_kswapd() && +- gfp_opportunistic_compaction(sc->gfp_mask))); ++ if (!sc->order) ++ return false; ++ ++ if (sc->kswapd_opportunistic_compaction == ++ KSWAPD_OPPORTUNISTIC_COMPACTION) ++ return true; ++ ++ if (current_is_kswapd()) ++ return false; ++ ++ return gfp_opportunistic_compaction(sc->gfp_mask, sc->alloc_order); + } + + static int sc_swappiness(struct scan_control *sc, struct mem_cgroup *memcg) +@@ -6781,7 +6779,8 @@ static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist, + } + + unsigned long try_to_free_pages(struct zonelist *zonelist, int order, +- gfp_t gfp_mask, nodemask_t *nodemask) ++ int alloc_order, gfp_t gfp_mask, ++ nodemask_t *nodemask) + { + unsigned long nr_reclaimed; + struct scan_control sc = { +@@ -6789,6 +6788,7 @@ unsigned long try_to_free_pages(struct zonelist *zonelist, int order, + .gfp_mask = current_gfp_context(gfp_mask), + .reclaim_idx = gfp_zone(gfp_mask), + .order = order, ++ .alloc_order = alloc_order, + .nodemask = nodemask, + .priority = DEF_PRIORITY, + .may_writepage = 1, +@@ -7608,7 +7608,7 @@ static int kswapd(void *p) + * needed. + */ + void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, +- enum zone_type highest_zoneidx) ++ bool opportunistic, enum zone_type highest_zoneidx) + { + pg_data_t *pgdat; + enum zone_type curr_idx; +@@ -7641,10 +7641,10 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + */ + if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != + KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!order || !gfp_opportunistic_compaction(gfp_flags)) ++ if (!opportunistic) + atomic_set(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else if (order && gfp_opportunistic_compaction(gfp_flags)) ++ else + atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, + KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, + KSWAPD_OPPORTUNISTIC_COMPACTION); +@@ -7942,6 +7942,7 @@ int node_reclaim(struct pglist_data *pgdat, gfp_t gfp_mask, unsigned int order) + .nr_to_reclaim = max(nr_pages, SWAP_CLUSTER_MAX), + .gfp_mask = current_gfp_context(gfp_mask), + .order = order, ++ .alloc_order = order, + .priority = NODE_RECLAIM_PRIORITY, + .may_writepage = !!(node_reclaim_mode & RECLAIM_WRITE), + .may_unmap = !!(node_reclaim_mode & RECLAIM_UNMAP), diff --git a/pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch.sig b/pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch.sig new file mode 100644 index 0000000..b1703dd Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8205-mm-hint-uses-allocation-order.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch b/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch new file mode 100644 index 0000000..2a82df3 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch @@ -0,0 +1,232 @@ +diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h +--- a/include/linux/mmzone.h ++++ b/include/linux/mmzone.h +@@ -1501,6 +1501,32 @@ enum kswapd_opportunistic_compaction_type { + KSWAPD_OPPORTUNISTIC_COMPACTION, + }; + ++/* ++ * pgdat->kswapd_request holds the order and the hint above in one word. A ++ * waker folds both in with a single cmpxchg and kswapd takes both with a ++ * single exchange, so a run cannot serve one waker's order under another ++ * waker's hint. ++ */ ++#define KSWAPD_REQUEST_HINT_MASK 3 ++#define KSWAPD_REQUEST_ORDER_SHIFT 2 ++ ++static inline int kswapd_request(int order, ++ enum kswapd_opportunistic_compaction_type hint) ++{ ++ return (order << KSWAPD_REQUEST_ORDER_SHIFT) | hint; ++} ++ ++static inline int kswapd_request_order(int request) ++{ ++ return request >> KSWAPD_REQUEST_ORDER_SHIFT; ++} ++ ++static inline enum kswapd_opportunistic_compaction_type ++kswapd_request_hint(int request) ++{ ++ return request & KSWAPD_REQUEST_HINT_MASK; ++} ++ + /* + * On NUMA machines, each NUMA node would have a pg_data_t to describe + * it's memory layout. On UMA machines there is a single pglist_data which +@@ -1564,14 +1590,15 @@ typedef struct pglist_data { + struct mutex kswapd_lock; + #endif + struct task_struct *kswapd; /* Protected by kswapd_lock */ +- int kswapd_order; + /* +- * Aggregated opportunistic-compaction hint for the next kswapd run. +- * Updated by wakeup_kswapd() based on the gfp flags / order of each +- * waker, and consumed (and reset) by kswapd before balance_pgdat(). +- * See enum kswapd_opportunistic_compaction_type for the state machine. ++ * The next kswapd run's request: the highest order asked for since ++ * kswapd last consumed one, and the aggregated opportunistic- ++ * compaction hint, in one word. Updated by wakeup_kswapd() from the ++ * gfp flags and order of each waker, and consumed by kswapd before ++ * balance_pgdat(). See kswapd_request() and ++ * enum kswapd_opportunistic_compaction_type. + */ +- atomic_t kswapd_opportunistic_compaction; ++ atomic_t kswapd_request; + enum zone_type kswapd_highest_zoneidx; + + atomic_t kswapd_failures; /* Number of 'reclaimed == 0' runs */ +diff --git a/mm/mm_init.c b/mm/mm_init.c +--- a/mm/mm_init.c ++++ b/mm/mm_init.c +@@ -1558,7 +1558,8 @@ int __ref free_area_init_core_hotplug(struct pglist_data *pgdat) + * when it starts in the near future. + */ + pgdat->nr_zones = 0; +- pgdat->kswapd_order = 0; ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + pgdat->kswapd_highest_zoneidx = 0; + pgdat->node_start_pfn = 0; + pgdat->node_present_pages = 0; +diff --git a/mm/vmscan.c b/mm/vmscan.c +--- a/mm/vmscan.c ++++ b/mm/vmscan.c +@@ -7413,6 +7413,49 @@ static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat, + return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx; + } + ++/* ++ * How a caller votes on the opportunistic-compaction hint when it folds a ++ * request into pgdat->kswapd_request. ++ */ ++enum kswapd_vote { ++ KSWAPD_VOTE_NONE, ++ KSWAPD_VOTE_NO_OPPORTUNISTIC, ++ KSWAPD_VOTE_OPPORTUNISTIC, ++}; ++ ++/* ++ * Raise the requested order to @order and fold in @vote, in one step. ++ * ++ * The hint is sticky in the "NO" direction: once any waker votes against ++ * opportunistic treatment, the hint stays cleared until kswapd consumes the ++ * request. KSWAPD_VOTE_NONE raises the order and leaves the hint alone. ++ * ++ * Order and hint live in the same word, so a waker whose order kswapd ++ * observes always contributed its vote to the hint kswapd observes with it. ++ */ ++static void kswapd_request_fold(pg_data_t *pgdat, int order, ++ enum kswapd_vote vote) ++{ ++ int old = atomic_read(&pgdat->kswapd_request); ++ int new; ++ ++ do { ++ enum kswapd_opportunistic_compaction_type hint = ++ kswapd_request_hint(old); ++ int req_order = max(kswapd_request_order(old), order); ++ ++ if (vote == KSWAPD_VOTE_NO_OPPORTUNISTIC) ++ hint = KSWAPD_NO_OPPORTUNISTIC_COMPACTION; ++ else if (vote == KSWAPD_VOTE_OPPORTUNISTIC && ++ hint == KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION) ++ hint = KSWAPD_OPPORTUNISTIC_COMPACTION; ++ ++ new = kswapd_request(req_order, hint); ++ if (new == old) ++ return; ++ } while (!atomic_try_cmpxchg(&pgdat->kswapd_request, &old, new)); ++} ++ + static void + kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + unsigned int highest_zoneidx, +@@ -7460,13 +7503,11 @@ kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order, + kswapd_highest_zoneidx(pgdat, + highest_zoneidx)); + +- if (READ_ONCE(pgdat->kswapd_order) < reclaim_order) +- WRITE_ONCE(pgdat->kswapd_order, reclaim_order); +- +- if (kswapd_opportunistic_compaction == +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); ++ kswapd_request_fold(pgdat, reclaim_order, ++ kswapd_opportunistic_compaction == ++ KSWAPD_NO_OPPORTUNISTIC_COMPACTION ? ++ KSWAPD_VOTE_NO_OPPORTUNISTIC : ++ KSWAPD_VOTE_NONE); + } + + finish_wait(&pgdat->kswapd_wait, &wait); +@@ -7524,6 +7565,7 @@ static int kswapd(void *p) + pg_data_t *pgdat = (pg_data_t *)p; + struct task_struct *tsk = current; + enum kswapd_opportunistic_compaction_type kswapd_opportunistic_compaction; ++ int request; + + /* + * Tell the memory management that we're a "memory allocator", +@@ -7540,32 +7582,30 @@ static int kswapd(void *p) + tsk->flags |= PF_MEMALLOC | PF_KSWAPD; + set_freezable(); + +- WRITE_ONCE(pgdat->kswapd_order, 0); +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ atomic_set(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + atomic_set(&pgdat->nr_writeback_throttled, 0); + for ( ; ; ) { + bool was_frozen; + +- alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order); ++ request = atomic_read(&pgdat->kswapd_request); ++ alloc_order = reclaim_order = kswapd_request_order(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- kswapd_opportunistic_compaction = +- atomic_read(&pgdat->kswapd_opportunistic_compaction); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + + kswapd_try_sleep: + kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order, + highest_zoneidx, kswapd_opportunistic_compaction); + +- /* Read the new order and highest_zoneidx */ +- alloc_order = READ_ONCE(pgdat->kswapd_order); +- kswapd_opportunistic_compaction = +- atomic_xchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION); ++ /* Take the new request and highest_zoneidx */ ++ request = atomic_xchg(&pgdat->kswapd_request, ++ kswapd_request(0, KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION)); ++ alloc_order = kswapd_request_order(request); ++ kswapd_opportunistic_compaction = kswapd_request_hint(request); + highest_zoneidx = kswapd_highest_zoneidx(pgdat, + highest_zoneidx); +- WRITE_ONCE(pgdat->kswapd_order, 0); + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES); + + if (kthread_freezable_should_stop(&was_frozen)) +@@ -7625,30 +7665,15 @@ void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order, + if (curr_idx == MAX_NR_ZONES || curr_idx < highest_zoneidx) + WRITE_ONCE(pgdat->kswapd_highest_zoneidx, highest_zoneidx); + +- if (READ_ONCE(pgdat->kswapd_order) < order) +- WRITE_ONCE(pgdat->kswapd_order, order); +- + /* +- * Fold this waker into the per-pgdat opportunistic-compaction hint +- * that kswapd will pick up at the start of its next run. +- * +- * The state is sticky in the "NO" direction: once any waker in this +- * batch is order-0 or a non-failable high-order allocation, the hint +- * stays cleared until kswapd consumes it. Only when every waker so +- * far is a failable high-order allocation do we set +- * KSWAPD_OPPORTUNISTIC_COMPACTION, asking shrinkers to skip work +- * that won't realistically help compaction. ++ * Fold this waker's order and its vote on the opportunistic-compaction ++ * hint into the request kswapd will pick up at the start of its next ++ * run. Both change together, so the run that serves this order also ++ * carries this waker's vote. + */ +- if (atomic_read(&pgdat->kswapd_opportunistic_compaction) != +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION) { +- if (!opportunistic) +- atomic_set(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_NO_OPPORTUNISTIC_COMPACTION); +- else +- atomic_cmpxchg(&pgdat->kswapd_opportunistic_compaction, +- KSWAPD_UNSET_OPPORTUNISTIC_COMPACTION, +- KSWAPD_OPPORTUNISTIC_COMPACTION); +- } ++ kswapd_request_fold(pgdat, order, ++ opportunistic ? KSWAPD_VOTE_OPPORTUNISTIC : ++ KSWAPD_VOTE_NO_OPPORTUNISTIC); + + if (!waitqueue_active(&pgdat->kswapd_wait)) + return; diff --git a/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch.sig b/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch.sig new file mode 100644 index 0000000..6f15263 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8206-mm-carry-order-and-hint-in-one-word.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch b/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch new file mode 100644 index 0000000..bfa7d1e --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch @@ -0,0 +1,28 @@ +diff --git c/mm/internal.h i/mm/internal.h +--- c/mm/internal.h ++++ i/mm/internal.h +@@ -1778,12 +1778,24 @@ void __meminit __init_page_from_nid(unsigned long pfn, int nid); + * @order is the order the caller asked for, not the order reclaim was + * promoted to. defrag_mode raises the reclaim order to pageblock_order, + * which says nothing about what the caller can tolerate. ++ * ++ * A transparent huge page allocation sets neither flag, yet a failed ++ * collapse or fault falls back to small pages. Recognise it by its mask. ++ * In this tree the complete GFP_TRANSHUGE_LIGHT set is carried by the ++ * huge page fault path, khugepaged and the large folio migration ++ * targets. The huge zero folio clears __GFP_MOVABLE and so keeps normal ++ * shrinker behaviour. The three other sites that combine __GFP_COMP ++ * with __GFP_NOMEMALLOC do not carry the movable, highmem and ++ * filesystem bits, so they do not match. + */ + static inline bool gfp_opportunistic_compaction(gfp_t gfp_flags, int order) + { + if (!order || (gfp_flags & __GFP_NOFAIL)) + return false; + ++ if ((gfp_flags & GFP_TRANSHUGE_LIGHT) == GFP_TRANSHUGE_LIGHT) ++ return true; ++ + return !!(gfp_flags & (__GFP_NORETRY | __GFP_RETRY_MAYFAIL)); + } + diff --git a/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch.sig b/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch.sig new file mode 100644 index 0000000..e60df1b Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8207-mm-classify-huge-page-allocations-as-failable.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch b/pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch new file mode 100644 index 0000000..0552be0 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/8209-xe-shrinker-use-opportunistic-hint.patch.sig b/pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch.sig new file mode 100644 index 0000000..7928b4e Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8209-xe-shrinker-use-opportunistic-hint.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch b/pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch new file mode 100644 index 0000000..9daf161 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/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-muqss/8210-xe-shrinker-single-backup-decision.patch.sig b/pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch.sig new file mode 100644 index 0000000..a823434 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8210-xe-shrinker-single-backup-decision.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch b/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch new file mode 100644 index 0000000..76470c1 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch @@ -0,0 +1,116 @@ +diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c +--- a/drivers/gpu/drm/xe/xe_bo.c ++++ b/drivers/gpu/drm/xe/xe_bo.c +@@ -28,6 +28,7 @@ + #include "xe_ggtt.h" + #include "xe_map.h" + #include "xe_migrate.h" ++#include "xe_module.h" + #include "xe_pat.h" + #include "xe_pm.h" + #include "xe_preempt_fence.h" +@@ -1244,6 +1245,26 @@ xe_bo_eviction_valuable(struct ttm_buffer_object *bo, const struct ttm_place *pl + return true; + } + ++static bool xe_bo_bound_to_active_vm(struct xe_bo *bo) ++{ ++ u64 window = (u64)READ_ONCE(xe_modparam.shrink_active_ms) * NSEC_PER_MSEC; ++ u64 now = ktime_get_ns(); ++ struct drm_gpuvm_bo *vm_bo; ++ ++ if (!window) ++ return false; ++ ++ drm_gem_for_each_gpuvm_bo(vm_bo, &bo->ttm.base) { ++ struct xe_vm *vm = gpuvm_to_vm(vm_bo->vm); ++ u64 last = READ_ONCE(vm->last_exec_ns); ++ ++ if (!xe_vm_in_fault_mode(vm) && last && now - last < window) ++ return true; ++ } ++ ++ return false; ++} ++ + /** + * xe_bo_shrink() - Try to shrink an xe bo. + * @ctx: The struct ttm_operation_ctx used for shrinking. +@@ -1295,6 +1316,10 @@ long xe_bo_shrink(struct ttm_operation_ctx *ctx, struct ttm_buffer_object *bo, + goto out_unref; + } + ++ if (xe_bo_bound_to_active_vm(xe_bo) && ++ try_wait_for_completion(&xe->pm_block)) ++ goto out_unref; ++ + /* System CCS needs gpu copy when moving PL_TT -> PL_SYSTEM */ + needs_rpm = (!IS_DGFX(xe) && bo->resource->mem_type != XE_PL_SYSTEM && + xe_bo_needs_ccs_pages(xe_bo)); +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -22,5 +22,6 @@ + #define XE_DEFAULT_WEDGED_MODE XE_WEDGED_MODE_UPON_CRITICAL_ERROR + #define XE_DEFAULT_WEDGED_MODE_STR "upon-critical-error" + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 ++#define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 + + #endif +diff --git a/drivers/gpu/drm/xe/xe_exec.c b/drivers/gpu/drm/xe/xe_exec.c +--- a/drivers/gpu/drm/xe/xe_exec.c ++++ b/drivers/gpu/drm/xe/xe_exec.c +@@ -344,6 +344,7 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file) + if (!xe_vm_in_lr_mode(vm)) + xe_exec_queue_last_fence_set(q, vm, &job->drm.s_fence->finished); + xe_sched_job_push(job); ++ WRITE_ONCE(vm->last_exec_ns, ktime_get_ns()); + xe_vm_reactivate_rebind(vm); + + if (!err && !xe_vm_in_lr_mode(vm)) { +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -30,6 +30,7 @@ struct xe_modparam xe_modparam = { + #endif + .wedged_mode = XE_DEFAULT_WEDGED_MODE, + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, ++ .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, + /* the rest are 0 by default */ + }; + +@@ -37,6 +38,11 @@ module_param_named(svm_notifier_size, xe_modparam.svm_notifier_size, uint, 0600) + MODULE_PARM_DESC(svm_notifier_size, "Set the svm notifier size in MiB, must be power of 2 " + "[default=" __stringify(XE_DEFAULT_SVM_NOTIFIER_SIZE) "]"); + ++module_param_named(shrink_active_ms, xe_modparam.shrink_active_ms, uint, 0600); ++MODULE_PARM_DESC(shrink_active_ms, "Do not back up a buffer object bound to a VM that submitted work " ++ "within this many ms, 0=disabled " ++ "[default=" __stringify(XE_DEFAULT_SHRINK_ACTIVE_MS) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -25,6 +25,7 @@ struct xe_modparam { + #endif + unsigned int wedged_mode; + u32 svm_notifier_size; ++ unsigned int shrink_active_ms; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_vm_types.h b/drivers/gpu/drm/xe/xe_vm_types.h +--- a/drivers/gpu/drm/xe/xe_vm_types.h ++++ b/drivers/gpu/drm/xe/xe_vm_types.h +@@ -245,6 +245,8 @@ struct xe_vm { + /** @lru_bulk_move: Bulk LRU move list for this VM's BOs */ + struct ttm_lru_bulk_move lru_bulk_move; + ++ u64 last_exec_ns; ++ + u64 size; + + struct xe_pt *pt_root[XE_MAX_TILES_PER_DEVICE]; diff --git a/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch.sig b/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch.sig new file mode 100644 index 0000000..7467cb6 Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8211-xe-shrinker-skip-active-vm.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch b/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch new file mode 100644 index 0000000..f77d2f2 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch @@ -0,0 +1,95 @@ +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -23,5 +23,6 @@ + #define XE_DEFAULT_WEDGED_MODE_STR "upon-critical-error" + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 + #define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 ++#define XE_DEFAULT_SHRINK_DIRECT_BACKUP false + + #endif +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -31,6 +31,7 @@ struct xe_modparam xe_modparam = { + .wedged_mode = XE_DEFAULT_WEDGED_MODE, + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, + .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, ++ .shrink_direct_backup = XE_DEFAULT_SHRINK_DIRECT_BACKUP, + /* the rest are 0 by default */ + }; + +@@ -43,6 +44,10 @@ MODULE_PARM_DESC(shrink_active_ms, "Do not back up a buffer object bound to a VM + "within this many ms, 0=disabled " + "[default=" __stringify(XE_DEFAULT_SHRINK_ACTIVE_MS) "]"); + ++module_param_named(shrink_direct_backup, xe_modparam.shrink_direct_backup, bool, 0600); ++MODULE_PARM_DESC(shrink_direct_backup, "Back up buffer objects in direct reclaim too, not only in kswapd " ++ "[default=" __stringify(XE_DEFAULT_SHRINK_DIRECT_BACKUP) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -26,6 +26,7 @@ struct xe_modparam { + unsigned int wedged_mode; + u32 svm_notifier_size; + unsigned int shrink_active_ms; ++ bool shrink_direct_backup; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c +--- a/drivers/gpu/drm/xe/xe_shrinker.c ++++ b/drivers/gpu/drm/xe/xe_shrinker.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + + #include + #include +@@ -11,6 +12,7 @@ + #include + + #include "xe_bo.h" ++#include "xe_module.h" + #include "xe_pm.h" + #include "xe_shrinker.h" + +@@ -170,9 +172,12 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + +-static inline bool xe_shrinker_can_backup(const struct shrink_control *sc) ++static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, ++ const struct shrink_control *sc) + { +- return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction; ++ return (sc->gfp_mask & __GFP_FS) && !sc->opportunistic_compaction && ++ (current_is_kswapd() || READ_ONCE(xe_modparam.shrink_direct_backup) || ++ !try_wait_for_completion(&shrinker->xe->pm_block)); + } + + static unsigned long +@@ -188,7 +193,7 @@ xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc) + * wants, and it needs __GFP_FS. + */ + read_lock(&shrinker->lock); +- if (xe_shrinker_can_backup(sc)) ++ if (xe_shrinker_can_backup(shrinker, sc)) + num_pages = min_t(unsigned long, + ttm_backup_bytes_avail() >> PAGE_SHIFT, + shrinker->shrinkable_pages); +@@ -241,7 +246,7 @@ static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_con + }; + bool runtime_pm; + bool purgeable; +- bool can_backup = xe_shrinker_can_backup(sc); ++ bool can_backup = xe_shrinker_can_backup(shrinker, sc); + + nr_to_scan = sc->nr_to_scan; + diff --git a/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch.sig b/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch.sig new file mode 100644 index 0000000..ae4ebed Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8212-xe-shrinker-backup-in-kswapd-only.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch b/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch new file mode 100644 index 0000000..b639c28 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch @@ -0,0 +1,112 @@ +diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h +--- a/drivers/gpu/drm/xe/xe_defaults.h ++++ b/drivers/gpu/drm/xe/xe_defaults.h +@@ -24,5 +24,6 @@ + #define XE_DEFAULT_SVM_NOTIFIER_SIZE 512 + #define XE_DEFAULT_SHRINK_ACTIVE_MS 1000 + #define XE_DEFAULT_SHRINK_DIRECT_BACKUP false ++#define XE_DEFAULT_HIBERNATE_WRITEBACK false + + #endif +diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c +--- a/drivers/gpu/drm/xe/xe_module.c ++++ b/drivers/gpu/drm/xe/xe_module.c +@@ -32,6 +32,7 @@ struct xe_modparam xe_modparam = { + .svm_notifier_size = XE_DEFAULT_SVM_NOTIFIER_SIZE, + .shrink_active_ms = XE_DEFAULT_SHRINK_ACTIVE_MS, + .shrink_direct_backup = XE_DEFAULT_SHRINK_DIRECT_BACKUP, ++ .hibernate_writeback = XE_DEFAULT_HIBERNATE_WRITEBACK, + /* the rest are 0 by default */ + }; + +@@ -48,6 +49,10 @@ module_param_named(shrink_direct_backup, xe_modparam.shrink_direct_backup, bool, + MODULE_PARM_DESC(shrink_direct_backup, "Back up buffer objects in direct reclaim too, not only in kswapd " + "[default=" __stringify(XE_DEFAULT_SHRINK_DIRECT_BACKUP) "]"); + ++module_param_named(hibernate_writeback, xe_modparam.hibernate_writeback, bool, 0600); ++MODULE_PARM_DESC(hibernate_writeback, "Write buffer objects to swap before hibernation, " ++ "integrated GPUs only [default=" __stringify(XE_DEFAULT_HIBERNATE_WRITEBACK) "]"); ++ + module_param_named_unsafe(force_execlist, xe_modparam.force_execlist, bool, 0444); + MODULE_PARM_DESC(force_execlist, "Force Execlist submission"); + +diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h +--- a/drivers/gpu/drm/xe/xe_module.h ++++ b/drivers/gpu/drm/xe/xe_module.h +@@ -27,6 +27,7 @@ struct xe_modparam { + u32 svm_notifier_size; + unsigned int shrink_active_ms; + bool shrink_direct_backup; ++ bool hibernate_writeback; + }; + + extern struct xe_modparam xe_modparam; +diff --git a/drivers/gpu/drm/xe/xe_pm.c b/drivers/gpu/drm/xe/xe_pm.c +--- a/drivers/gpu/drm/xe/xe_pm.c ++++ b/drivers/gpu/drm/xe/xe_pm.c +@@ -26,6 +26,7 @@ + #include "xe_pcode.h" + #include "xe_printk.h" + #include "xe_pxp.h" ++#include "xe_shrinker.h" + #include "xe_sriov_vf_ccs.h" + #include "xe_sysctrl.h" + #include "xe_trace.h" +@@ -450,6 +451,9 @@ static int xe_pm_notifier_callback(struct notifier_block *nb, + if (err) + drm_dbg(&xe->drm, "Notifier evict user failed (%d)\n", err); + ++ if (action == PM_HIBERNATION_PREPARE) ++ xe_shrinker_backup_for_hibernation(xe->mem.shrinker); ++ + err = xe_bo_notifier_prepare_all_pinned(xe); + if (err) + drm_dbg(&xe->drm, "Notifier prepare pin failed (%d)\n", err); +diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c +--- a/drivers/gpu/drm/xe/xe_shrinker.c ++++ b/drivers/gpu/drm/xe/xe_shrinker.c +@@ -172,6 +172,34 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker, + return ret; + } + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker) ++{ ++ struct ttm_operation_ctx ctx = { ++ .interruptible = false, ++ .no_wait_gpu = false, ++ }; ++ struct xe_bo_shrink_flags flags = { ++ .purge = false, ++ .writeback = true, ++ }; ++ unsigned long to_scan, scanned = 0, freed = 0; ++ ktime_t start = ktime_get(); ++ int ret; ++ ++ if (!READ_ONCE(xe_modparam.hibernate_writeback) || IS_DGFX(shrinker->xe) || ++ !ttm_backup_bytes_avail()) ++ return; ++ ++ read_lock(&shrinker->lock); ++ to_scan = shrinker->shrinkable_pages; ++ read_unlock(&shrinker->lock); ++ ++ ret = __xe_shrinker_walk(shrinker, &ctx, flags, to_scan, &scanned, &freed); ++ drm_info(&shrinker->xe->drm, ++ "Hibernation writeback: %lu of %lu pages scanned, %lu freed in %lld ms (%d)\n", ++ scanned, to_scan, freed, ktime_ms_delta(ktime_get(), start), ret); ++} ++ + static inline bool xe_shrinker_can_backup(struct xe_shrinker *shrinker, + const struct shrink_control *sc) + { +diff --git a/drivers/gpu/drm/xe/xe_shrinker.h b/drivers/gpu/drm/xe/xe_shrinker.h +--- a/drivers/gpu/drm/xe/xe_shrinker.h ++++ b/drivers/gpu/drm/xe/xe_shrinker.h +@@ -13,4 +13,6 @@ void xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long p + + int xe_shrinker_create(struct xe_device *xe); + ++void xe_shrinker_backup_for_hibernation(struct xe_shrinker *shrinker); ++ + #endif diff --git a/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch.sig b/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch.sig new file mode 100644 index 0000000..eb7552a Binary files /dev/null and b/pkgbuilds/linux-omarchy-muqss/8213-xe-hibernation-writeback-gated.patch.sig differ diff --git a/pkgbuilds/linux-omarchy-muqss/PKGBUILD b/pkgbuilds/linux-omarchy-muqss/PKGBUILD new file mode 100644 index 0000000..0cc8e80 --- /dev/null +++ b/pkgbuilds/linux-omarchy-muqss/PKGBUILD @@ -0,0 +1,1009 @@ +# Maintainer: The Omarchy Authors +# Adopted from the Arch Linux linux package. + +pkgbase=linux-omarchy-muqss +pkgver=7.2.8 +pkgrel=5 +pkgdesc='Omarchy Linux (MuQSS CPU 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%%.*} +_srcname=linux-7.2.8 +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-muqss-0.31.patch{,.sig} + 0120-tlbpull.patch{,.sig} + 0121-smp-preempt.patch{,.sig} + 0130-sched-detach-tasks.patch{,.sig} + 0140-sched-always-inline.patch{,.sig} + 0141-sched-urgent-fixes.patch{,.sig} + 0143-sched-hybrid-cluster-balancing.patch{,.sig} + 0144-sched-nohz-idle-core.patch{,.sig} + 0210-pm-updates.patch{,.sig} + 0211-pm-fixes-1.patch{,.sig} + 0212-pm-fixes-2.patch{,.sig} + 0213-pm-fixes-3.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} + 0281-mm-no-deferred-kernel-page-table-free-at-boot.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} + 0300-btrfs.patch{,.sig} + 0301-btrfs-fixes-1.patch{,.sig} + 0302-btrfs-zstd-decompress-direct-to-page.patch{,.sig} + 0303-btrfs-fixes-2.patch{,.sig} + 0304-btrfs-fixes-3.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} + 0320-vfs-open-consume-path-reference.patch{,.sig} + 0350-drm-edid-populate-monitor-range-from-displayid-adaptive-sync.patch{,.sig} + 0360-gpu-mem-cgroup.patch{,.sig} + 0400-drm-i915-alpm-limit-pr-alpm-to-panel-replay.patch{,.sig} + 0401-psr2-early-transport-panels.patch{,.sig} + 0402-drm-i915-quirk-panel-replay-exit-on-flush.patch{,.sig} + 0403-drm-i915-quirk-dell-xps14-ptl-panel-replay-exit-on-flush.patch{,.sig} + 0404-drm-i915-quirk-dell-xps16-ptl-panel-replay-exit-on-flush.patch{,.sig} + 0405-drm-i915-quirk-dell-xps13-dx13260-ptl-psr1-only.patch{,.sig} + 0406-drm-i915-quirk-dell-xps13-dx13260-wcl-panel-replay-exit-on-flush.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} + 0442-drm-i915-psr-clear-stale-sel-fetch-bits.patch{,.sig} + 0443-drm-i915-dsb-delayed-vblank-scanline-shift.patch{,.sig} + 0444-drm-i915-vrr-dc-balance-off-by-default.patch{,.sig} + 0450-amd-hdmi-vrr-allm.patch{,.sig} + 0451-amd-vtem-tmds-links.patch{,.sig} + 0452-amd-hdmi-frl-default.patch{,.sig} + 0453-amd-vtem-null-sink.patch{,.sig} + 0454-amd-hdmi-frl-status-polling.patch{,.sig} + 0455-amd-hdmi-frl-cap-on-nondestructive-verify.patch{,.sig} + 0456-amd-dml-frl-compressed-cap-check.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} + 0474-amd-display-oled-vesa-backlight.patch{,.sig} + 0475-revert-drm-i915-dp-as-sdp-vrr-or-pr.patch{,.sig} + 0476-amd-display-ycbcr420-only-modes.patch{,.sig} + 0477-amd-display-irq-atomic-rmw.patch{,.sig} + 0478-amd-freesync-vcp-code-check-hdmi-pcon.patch{,.sig} + 0479-amd-hdmi-allm-hf-vsif-in-vsp-infopacket.patch{,.sig} + 0510-sound-updates.patch{,.sig} + 0511-asoc-updates.patch{,.sig} + 0512-sound-fixes-1.patch{,.sig} + 0513-asoc-fixes-1.patch{,.sig} + 0514-sound-fixes-2.patch{,.sig} + 0515-xps13-sof-quirk.patch{,.sig} + 0516-rt766-stream-config-type.patch{,.sig} + 0517-hda-realtek-rog-strix-g733zw-speakers.patch{,.sig} + 0518-asoc-amd-yc-acer-aspire-a314-23p.patch{,.sig} + 0522-sound-fixes-3.patch{,.sig} + 0523-asoc-fixes-2.patch{,.sig} + 0524-sound-fixes-4.patch{,.sig} + 0525-asoc-fixes-3.patch{,.sig} + 0526-sof-amd-acp7x-suspend-unknown-revision.patch{,.sig} + 0527-sound-fixes-5.patch{,.sig} + 0528-asoc-fixes-4.patch{,.sig} + 0541-cvs-nova-lake-acpi-id.patch{,.sig} + 0542-media-cvs-wake-irq-without-claiming-gpio.patch{,.sig} + 0543-media-laptop-cameras.patch{,.sig} + 0544-ipu-bridge-lenovo-x9-upside-down.patch{,.sig} + 0545-ipu-bridge-dell-16-premium-da16250-upside-down.patch{,.sig} + 0546-ipu-bridge-samsung-galaxy-book5-pro-360-upside-down.patch{,.sig} + 0547-int3472-imx471-vana-power-enable.patch{,.sig} + 0548-media-cvs-sensor-node-in-bound-callback.patch{,.sig} + 0549-media-imx471-analog-gain-max-960.patch{,.sig} + 0560-input-updates.patch{,.sig} + 0561-input-fixes.patch{,.sig} + 0562-hid-fixes.patch{,.sig} + 0563-input-fixes-2.patch{,.sig} + 0565-i2c-asue140d-touchpad-100khz.patch{,.sig} + 0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch{,.sig} + 0601-thunderbolt-updates.patch{,.sig} + 0602-thunderbolt-fixes.patch{,.sig} + 0603-usb-updates.patch{,.sig} + 0604-xhci-fixes.patch{,.sig} + 0605-thunderbolt-pcie-rescan-after-tunnel.patch{,.sig} + 0606-thunderbolt-fixes-2.patch{,.sig} + 0607-usb-fixes.patch{,.sig} + 0620-usb-string-sanitize.patch{,.sig} + 0650-wireguard-tstamp-type.patch{,.sig} + 0660-btusb-mediatek-mt7922-13d3-3625.patch{,.sig} + 0662-iwlwifi-mld-skip-tx-when-firmware-dead.patch{,.sig} + 0663-btintel-pcie-serialize-reset-type.patch{,.sig} + 0664-btintel-pcie-stale-cache-in-dxstate-fallback.patch{,.sig} + 0665-btintel-pcie-pm-flow-s0ix-s3-s4.patch{,.sig} + 0666-btintel-pcie-verify-serialize-dstate.patch{,.sig} + 0667-btintel-pcie-gp0-cause-w1c-write.patch{,.sig} + 0668-btintel-pcie-dstate-races.patch{,.sig} + 0669-btintel-version-tlv-parse-errors.patch{,.sig} + 0670-bluetooth-fixes.patch{,.sig} + 0700-pci-target-speed-quirk.patch{,.sig} + 0701-pci-amd-enhanced-atomics.patch{,.sig} + 0702-ahci-jmb585-32bit-dma-revert.patch{,.sig} + 0703-pci-vmd-scan-bios-enumerated-devices.patch{,.sig} + 0704-pci-vmd-meteor-lake-mtl016-flush.patch{,.sig} + 0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch{,.sig} + 0711-iommu-amd-perfopt-support.patch{,.sig} + 0712-drm-amdgpu-perfopt-in-identity-domain.patch{,.sig} + 0740-hwmon-updates.patch{,.sig} + 0741-hwmon-fixes-1.patch{,.sig} + 0742-hwmon-fixes-2.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} + 0802-platform-fixes.patch{,.sig} + 0803-acpi-battery-hook-api-acpi-prefix.patch{,.sig} + 0804-acpi-battery-unified-hooks-acpi-sbs.patch{,.sig} + 0805-hwmon-applesmc-charge-control-end-threshold.patch{,.sig} + 0850-futex-wait-multiple.patch{,.sig} + 0851-futex-wait-multiple-fixes.patch{,.sig} + 0852-futex-wait-multiple-abi-fixes.patch{,.sig} + 0860-module-add-module-kallsyms-on-each-addr.patch{,.sig} + 0861-ftrace-drop-weak-function-locations-of-modules.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} + 8209-xe-shrinker-use-opportunistic-hint.patch{,.sig} + 8210-xe-shrinker-single-backup-decision.patch{,.sig} + 8211-xe-shrinker-skip-active-vm.patch{,.sig} + 8212-xe-shrinker-backup-in-kswapd-only.patch{,.sig} + 8213-xe-hibernation-writeback-gated.patch{,.sig} +) +source_x86_64=(config.x86_64) +validpgpkeys=( + ABAF11C65A2970B130ABE3C479BE3E4300411886 # Linus Torvalds + 647F28654894E3BD457199BE38DBBDC86092693E # Greg Kroah-Hartman + 12D27D5D8C8E9BF1AABC7C7C7C64768D3DE334E7 # Krzysztof Wilczynski +) +b2sums=('5326dde778eb945f282740ef0d8765b46fbd198f1209cdfd7b91e5dac1312fc01f8bf0b2bc67b16a76de736edd8ea21b38b6c91e696ec0c4b39746cd21a15214' + 'SKIP' + '60eac409f1ba50fe319f6b3aa5b0af81bc7ce5628ca41c1af346a6d5ef0fc9c577527742d1f70ece7e07ea410fcd112b32d64f0a05af5b532f27b62e5e7d837a' + 'SKIP' + 'cc2a66a097b5567e80c59b7d6c492fef5e667fc71a0390908712c69512c4136dbaf3d150b62d04eff960c796f7a94e6d0915d3b92cb336318b49f5f516a3bba6' + 'SKIP' + 'ad3918eb5273405716f7c6ac8ab8c8a14887ef271226eb3121309fefb1bc5216d1364baf24c40e7eeb87f76ed19d7f6108d60dcf4f6d8fc580aab69f0b3b3ba5' + 'SKIP' + 'c5a06c5632ba1eeb592f19b7f1581acfb7adf603b098d1d966359a1bb266a330a5707b64aa1f0e0346003e0b3008f46d8af669b621ccacd9f219838fb5e122bc' + 'SKIP' + 'b4d6c1f0d6cb4eb6664f5cb8d8eeb4e78e1c19b20f875d28d1652424fe1064c93f511cd897de88c7997bd645fd40ac2e1259fb8e0a3dc98ca0a4e481d40b0c69' + 'SKIP' + '91e65754432c2d33fd44d81f9c8664b8c233dece280ed27800d9a319f035db18764ec6b8c90617238c6a54ab98fef9f524ca65facbe7434c00891a2dcde1eea9' + 'SKIP' + '0115177a24cdc454aeab5bc494c6a1d355b76750d8d7c4c23ea720ea9f82307e70b4162da7dbc88b5a91f743d08b9adc700a6d024b6a34e53b3c8a88f8b26b6e' + 'SKIP' + '9f0d647d9cbe6f758f2d260920c3fda78ed91ed52f8c3108ff9a1a263505b18430ea21700311e5e162dfcdadc8feef120b999d622db803363785c8beca1f50ec' + 'SKIP' + 'c94778de1f106d30c7f2628be8b99f5bed8864e09dc3298e5c85181804150df49f3b151eaa94f801f14b1d6c3b132184d133840c4903192b565799cae226a4e9' + 'SKIP' + '3d5a9ed6d78d5f82b43da9fb43cb57b5621faa7601ff04bba71b5c3ead05bd4fd916323ce68e50cf178e2a9c14bc826ea85ab203c878de26446618f180a5fb4c' + 'SKIP' + '921a4b6ca25fb1a10588d7a3df65277cf6acf1115e618741486d230bd8eee16daef3f712518f98c8a49222aed468e706015eb238ba289769a464f5f33a21b21e' + 'SKIP' + '486987a57ff24ae8c58953c01db6c8e4f4eca4093a8484b28b9a647790a6757a381631e13d64605bb009613985b686a0f98ef58b2e2f9c9ddc5b4d0de1c62de1' + 'SKIP' + 'e2d21e10be9239260aaf41ec5584885555d3d5150a817fdc2b04001444ae07cebefd140b20e841c0988843749e850ad52f93b98d005d5b158e158454c2020691' + 'SKIP' + 'cd6d3232202e8e6a44e80783becc1b527336027420e308f354b32b6de1f83de210aac78ee0c639ba23030b4a0811cf7728bcaeed08e9dbe11caf3edff380ed6c' + 'SKIP' + 'e59c4da548b2ea4fd6144886e3e2e765b3e73972121e9e73dbc05c066834b2b35e6ef4c17617e77efc90260566bf7c0910d2138d8d5c1c3b6094eeac9de64cfe' + 'SKIP' + '9809e258eb684b80d508fa2f2fa270683b62bd1412ed05a970114aaa7d84c37c0beaea51f9ff8c6ee1bc7318c4aee3725e9a1131fefb51ba700801de6ac414c5' + 'SKIP' + 'f115e8c80d723f7c24e6b1638b801cd664bef20acb1d2f6e6f5612744b916278693011782228cd3f4d0518654b8d53309bc5b06bbe8ed722fab9995f369bf548' + 'SKIP' + '31abdd15ae3f9709d6912c90319a0b62d8380e1ae1169575c0e8e419f8ce264a7cf7c72558a85719cd5a1cae7341007adb3e3936268999ebd5215c2ed288f22c' + 'SKIP' + 'f95af54bcfaf17e80d9c0f60243d946aff2bbb796ae47c92a97a353f598483f03ca87fccefbaa47639b1018b82e7879dae4b8759e8fd9206b33d219560d682a7' + 'SKIP' + 'ff9a3e5df8457880b25caef9de90a7eb513ad8de99296ed503268749a32d8fa79da11f598a5fd80d28231abdd65a247e2aa92ce7c8ccc9f497fa483eee93aa82' + 'SKIP' + '55951c59286df43f069302b0b1036424812f80b45c5ea13219fce1bfc3e831731432c216288d722e44493c736cbf89a3e44801b5a4f1f242c17b5a89b236a5ef' + 'SKIP' + '737db39c2f7f46550de4d6d80659bd6c1fc2bf1ca42bd7bd6fc345fc90af995acd056051e12175ac6c957333629a017051d76d5359c871158c93f0a294fb8de5' + 'SKIP' + '4d0646c8ad139add6abb62d75308d2d7b5b07afbc74a0f0297ffc2148f0db38abaea16b048704187da49fe3ddee8370b10a7a41d6e62b665650cd79e8c392b4e' + 'SKIP' + 'bd7bce1bf0ee24ce201fe0e5f0b8a6c829a84d224800547cf97a893326381e2e6eaed9c70529a6864b0c338276b3a307b14e26fba221e62263b90c02eb5e5f34' + 'SKIP' + 'ad2cfdc3d8de2193110325395e11015577f26ef1081307bb236ed73fbd109822dd44ea026a57c2e17233e1fb7982e78aa986b71c043f42887e6c5d7f51b84ea9' + 'SKIP' + '92cadbb0f7c288987838534f523f85a67d07db19ce27fd6c8717f73c02f7ec7b7b47276fea8b2a86593178dbc4f44e774727050f2b46baafdb7296028fb24a0f' + 'SKIP' + '0b281a5780efd064a6c98a08a41eadb1286a2aa5a7f1bab33d3adaa82713f4676bd3acf07556f3617ab0649f95fa5ce3a0418da1b6ae48cb580f0d7d5d9752b3' + 'SKIP' + 'a51e5f2a5e37c350fb4d715ece644c565a508970c3aeb1d28cb6cd03a0c7316a1935e028b64f20a03818926c611b50ae0ffd77d04c676d604d303beed0f96e1f' + 'SKIP' + '71e8a5d91d285519367ba73241d01adc3b61a2b7b21f8061ecc3749fbf9d87f5ca414809343fd37530bfd5226f919cc3d87dc3b4034cc5b8be9c720f8b4676b2' + 'SKIP' + '64ca5b3c45b579c51c04f1177c8e6eb25db4c76f17f7b253c5ed959c9688bafaa97da701257435b34338432e88071f04a8d4049ba66895dd3ba804173cb08b28' + 'SKIP' + '4dfbcdb23d7117b75202944547f7ce956dd8fa7405bc5ca4856135e2becc21d950eb6e1aaec082da4290ac6cffa402686c2c9137d4dc23952796ce3334b95ff7' + 'SKIP' + '51a7a4f87d53e4b3d25041a0536b238d4550fcfdef0e7de5aa75c26a7a9217f6f3e260670154b9fa6674470f72e5e09c76571e6224d8be4eb5dd4383fef64a55' + 'SKIP' + 'fd487cb02212ffdae5d8beb7d895819377686085b95658dcf597a534b017adcb65dfdf06e0276e7322a46dc28118803b05c246c22e7d3c361f1778ed9e12a3c6' + 'SKIP' + '2c58614d716943cc1883dc513c2206e82f49574eca79f3e7dde21f8dd0f02f18611b2b2a444745df820c30c887d8263bdf9786a4a90b7979efee8d86633cbb88' + 'SKIP' + '218d01002d5487e2cb29f479f9ef9a2beb8226d2adccd1a9f932dc162a8502003db19b1bf74395b0239a2a15888131bcdc969b737cf56358dac6ab14d1b4501d' + 'SKIP' + '1b340a502dcf5a1680caf5320b3aa72aa4317e4cf1d877c1f6425e281a5668d5f092417e1f282371c0a211344f5c187fb3bdaf3f97c7e122951edf7e4c08ca58' + 'SKIP' + '1124c5c3fd5104c886c841ead1ae9afb42683388b640b1a86523dad9919c6bd59a1f78b07ee5a213e0dca77c636a29676af0592d4ed1735ea35f282f6f23e46f' + 'SKIP' + '505bf01a9d6b0926b557b673bb686d1a4ff73ee70539991e52272fbf58aad3ea25cf5ffc5c2d094a2d5e78b18a1a134f2ab530f595073a2d3eba9ff2c1418e1e' + 'SKIP' + 'b4bf4c4c0672bad91d7f8d309e741104918dd8ee8688a00d600379eba5828d1b8157953377c73d0b58050b4ff888c43013ede4e9c959be8d9c028a6143bf58ed' + 'SKIP' + 'a5ae2f6c29ba850292fcd1cd39d7af6041169ad9d73151d8fcc07ec2e1da473f3ac801eda892082b615d7f19c2a3ac17bceeb1d100dc1e2ff4b30287bbd8fc8b' + 'SKIP' + '3b8e01822f9f97842c5f7d7211df876d46d8477321546bd5b8cb74484550a2c520ffe351ce0ebb7df5659d2745187081883c091574c855ae7361315d153bc557' + 'SKIP' + '90e189032c6a04ca809607a5c9a8e798ff2f3dfa7d82bb510eb608da961196bb4b53f35907bfa9ce4006fe182102900bfa20f4566b146aaddaace4fc19adc305' + 'SKIP' + 'b9f60ea91694fb859543cf895ac9d654edd0aad92a3bce6ba9cfcfc450f413e49bc8cdb08adc6fba1a46ef319c10cdc501e3ffe6b37de222b11cd1c6cf138db9' + 'SKIP' + 'f80573494f330b31af70f1a079100082e00e3c7e185bbc51928d09d2ad3ccabb3134c72a0edcef994d6cc517015190d88c41301cf73c73419c594977f86a2906' + 'SKIP' + 'e403bdc300284c4f0f4302e501b8ccfe5e9af04e73a6070eb6615322f5f40ed6edad53af2ed278741f61a4e3811b92faf1af02f113dbcc2a19bde335a55930d7' + 'SKIP' + '5de29d0eb5264e24ab9228bea2ea4b549b3fe37ea67c045df9f83064f11c2f557b9641e772d28db59f8d258418c77435b66ff471c3695043f0f8275d5e7b1685' + 'SKIP' + '25a4ec87d1c789cab55e8ee6357276eb2e87b5e70eba2cd978b2c898e7b79de3fbc8cf5997fcd660998223b6173c98e440c853ad7c3e116f52a48d38622dfb46' + 'SKIP' + '2b33b11e48d8dd8d6743f26170fb99eae38541e3665e2448d91cedf729cca7c0278bfba1078ecbe88201eac4407ec5bd0292a97dbab9cfc67e42646712befc1f' + 'SKIP' + '5ef0d9561a570941752fd1976a2929eb25fb4dc16ff7fad09ce71e0d6881924f1ba8839574f7f877bd8089d116d689e33a54c14e2f177fe797dc86b305a74c0f' + 'SKIP' + '0415549ab1bf3ef0f2af4e62130b66e80d31dd3a1d1acda1cfcc2e4c926f25cf9449153736216a564cbd0710c0f0f1294607bf27c26790bad16e0f8956127e32' + 'SKIP' + 'a0a17aa48cdbf60db7a6d3677fae8a3388757b0f1de71043d8eb93028710f5a26aa57eb2011199b84e9689f7e4457843b74a5731c7dc14e4d1fca44b957bf33e' + 'SKIP' + 'ff1f07b3ad829d444b92c93fa63ab2c08277fa512483806d119a4cc75efbbcfc197dc982d9a146b7e9b94a9ca5bac95eed54735f607d7715cd52444ff0039e30' + 'SKIP' + 'a5670a422ae3d084f20800a5ca2c5615efaef4cdc58e466ceb25a8c56597e592cbf3eea7f4b8b28f1e7c79e9ecc7ff29c284d7be41de26fc6291df020fdd3d33' + 'SKIP' + '353b7e89a7044b7426db730d5b900eb347907aa0c20e307d2317925d6021a969a343649ede7a32e698dad60dd4472eb197c9578fb0e36fc354a60266fd680cd7' + 'SKIP' + 'd70e2f90fd0630f53b9680a1a935294d0a9e86e52ef9a712905a7799827ced2ff5acfa27a51980cf6f8157d60d9831cc0d62c623f98cf577810a4cab02b334fe' + 'SKIP' + '88a071e8e20be14db31253152064a26961b74c329793b7fd0c81b629973bd39040e9a66189e3dfda31ecc189f82e65781b310603f498e13456b8090662f80b0a' + 'SKIP' + '0cdfeb8a469295f0d7afc27800f5109f5c88c4afda93056d4c4fc9601988334c32d287e1490966ae21bbe80c5e4f8b8da2cdb6a019ec72682521972840247b8c' + 'SKIP' + 'acc6611ef0605b18b3326e2170f32c3daad02b6472b4fd8fd2a8ba4f99a35b319f2d67b6f1f017e9beb6e8cdb8edc081e97382f3cbe7999d8ad57bd5e929a9bb' + 'SKIP' + '29f44ba6256ca05c8f40f0f1b0e813a5b3928f23e37a23cef9e7e148832f41d8ab4ec43b398ef21a06cc8588e4feee24fbf1f3ec4428ef1b22b1d43a776022c8' + 'SKIP' + 'f3437588231ac856c45eee210b6d1e4f31019480665ae31f62e0fcc4b9931cea4bdcd2bb95345cb9dea26dba43931ccc73e9c9ed209ab3281a1618378658ec0d' + 'SKIP' + '824089f9f45e2ff9e788d874bb9294b9208b4486fd22c56cb59d4d88cb996ff5a81ac22f8ff136ddffa1e40330f02e53ab759ffea7e4535c11d4f8195069966b' + 'SKIP' + '22e8b6835a86ad485e5b23ba73a4e1515242b06f42063c99d0dda1f9248ce222e003acd43a517dced198a28bbd1812ce5faf66fa589717734558783fa385bb13' + 'SKIP' + '7228e249d25a99f5106040b8ddb5705979b3a5af21ca60d0d43a9ecaf6eaccef6bde8ea69c896cffbf2b5963ec4f68987e69b184a8885c7f4c98d2b05fd59fbb' + 'SKIP' + '4d5c5e2fbb5d8fe50a899bcfc629645d1bc9ef995c76611ecb38d25070552487d53ea59cb48bb3922d9d9965cc1f7d37ceea4f2ec789ed1bf37d69b831dea6bf' + 'SKIP' + '47015b9baebb1d6dd2a68321980c1c97da097825966f480c8569bcc9df5bef0257c51d5f7398960f28016258eaf1fff25bf142a64a01b829e882b4c1dda10612' + 'SKIP' + '4f3df2be1b52573ef4299934896dd5b4a5b9ebfa1f69c92f125edee1aa2db2f99bd2e6cf67734e1c953e9413bc6936067e8ac619cad66a5fe8dbcfcfc7906b83' + 'SKIP' + 'ee01ba98e614695154ba62789874843e48ce2a4715b3e6ca0304da0526f705672bb29253e0c52dc8f8fd3148c072067232ae6f4e86315dfc27b1563073cb92eb' + 'SKIP' + '0963ee814b6f64903d67757c5c55a93ec1f5b16374af16aeff2e7e494254d19031d4e1422428f4f7a8167592b7750a1db87476871eeb5cba851b377e59911149' + 'SKIP' + 'dcb350ef2987b801e481b5cf32cdfbe47018939f6b43e45b6dcff87eb75cc1a48f25e679caa823f3fe6391048f216ed9d95ae79b9cac237041857458f56e7f7a' + 'SKIP' + '0e4156949673d16caa6ee1405bf33d432ce2632e433d0abea2f4e9bc5b413500573ca06c10b8dfb477d2a6c7b4201f66ebe81ffb05302e3771dd2f0c515fc5da' + 'SKIP' + '9576c977d2c95cc474e84ac8dd7683a71df38d1d6e1ad80065cfa78db8185c7a52bb408af9881a71767a1c7e3f011424a180d0cba574b14ea53f65a567784181' + 'SKIP' + '4834e1cc74f3902e8e88eb28f9fb1f2bc3cdcda6745083092e7fc39c131bb0b1a8b06d74e6112e5d4c97d14e8b56302096691005474e6e5d2720da02463246a9' + 'SKIP' + '5d82f32f7c04b084530722b9848254aac289c2a754a10e16821083a871ec14065dd9ee83e72b8e9e8ba2915f3fbaf17b0874df724002eb08a5f4f84d1b6aecd8' + 'SKIP' + '4928cc008dc91fddb89b154b89b1ce2bbff14d8186c0ec1f81971fb9d3b622475e93e42015916699c3442e2268680779f545d183112b57dedae96fc48a75844c' + 'SKIP' + 'f93494f5f3196cc35d017ff9719d146bf3e5e7bfe32a7f5e75d96468ce30ef367428ba83b1c99b665064d3edaec84cf3bf18211d8f7b4f118b3bdee495eb8ef9' + 'SKIP' + 'eb79554ef0d014b94bc72d4d559c2cc5419df96486acb3b6a09d5e8e9dc48db626ce0f90338c67fe873b2cc3c45c3f308c8080532b60f74a07c9e39e01d34197' + 'SKIP' + '89482f70ea83fde5a88554f54c866d11c98baab1cbcb7fa6fe0079187821736b7ad9896246cb3976ec9d471fc0ebcec6dab32b95b4928704e23e3f4ff935f221' + 'SKIP' + '31202d94636ee8a62214952d0d54eb12b8e1e264edd20c374678dd442d5fc9f8b3ffa2e94cdb6d27c58004a660823d62ffe11aa81decb5af166cc6912486b9cc' + 'SKIP' + 'c0551a31a3c63b70966c9f87eb852cbbf43a13f649bb14b6fd2e8ca23f7fb311ba55d94ddb589a78a432eb225cd2336e553d399787071cee9933f2078fe8b5ad' + 'SKIP' + 'e6f6460dd43c5a7e4d943e6545479438286712237c9feec357317b1431aa778684132b734c5c113f1e57180de366411542c29f62507095e7db62e2d9aa6ad9a6' + 'SKIP' + '58899f7c2bb14ee442c6cfedb9a354f729c03cefd325cfe71da23f33d5a548f6083d5ad922231108fb58ee00f7ef2a2d45ce58c5767079e3ae3efaff306924d8' + 'SKIP' + 'acfcf80889e146bde867b12a7c1b4a7b88a71c09603d58974b647d8066cc281b300b7a43c0e1820f22fafb5f7056522091b9c6c0072ae6a4835e603578c5f95f' + 'SKIP' + 'c446acd1c5e498f0972febf76bf6198b4ee72c6e3e4ef285862564f2467d7ad7ad74eee88191fe1fd887a90a19ad61a3945ed9ee22a2ff6902fc5a806788b151' + 'SKIP' + '8bf8f181ffbd249b0af3a8fcc8463d84daa3c4aeb567595065c311f29b2dec2344fdd240d30314eae2c3d4a4daac53dad34e8452df11e386ce0a028267b18c0d' + 'SKIP' + 'bb06066e90132f0172333d46be54ca816eb7d3230a193a6cae765d8edc50df4a6ba9fb7c0647cd6befc8c3bbbe1d96c285ee202812477d7295c1130ea8cdd6f4' + 'SKIP' + '788488e828ac47a55ab78ad43251f7a2e5ad15bd5b5468bb31d4ee31e5de01a79b5680d5b048cb123eb3b7bde75e6b3d1e366e745009ed4129c8a6585d92b6b1' + 'SKIP' + '3bdf369bc73fad9bcf5b866043ff007365302318f42de7107fee47a7e8404c02c9c8fb5762d07ef10aebd2eb815c5483fc4960b7936e30ea5aa969a55f392474' + 'SKIP' + 'bdc66e50a9ef697e3baeee768f0f2dc56485afeb8a4e9f9ffd45f183d9614c33e0d0e72a8babca87963d1d9d701148eb41990730f2215a1c40e0965cc7f05e78' + 'SKIP' + 'a22e13b9bf0eac5d4190fd42bfde72ad0830cdbde8c0d309b2dfe9b0151e4c85f9f14f595ba3f36af479173ba1274f7700293c3d61dd913f711196a2dae8d6dd' + 'SKIP' + 'ebf13eb83afc051e3b9c84bfff7b326d73de6bac03f5b4d5996485d06a9cc87aca795b5c029646f804d62314a97e4a2b5da0b5f72f71556f33b62fd26529797e' + 'SKIP' + '8c6b5b96a3992c7cfbe50384fc0a4504e6d522c6918739f822b5bab29066eaeee7d1cc525b41a9b5b354ce020b1837e88d9bb940f3d2ba55a034aa9a845d489e' + 'SKIP' + 'aa24ab789db9badbd38db52f1e01fbc9d5abc09d812e2426ff281c869bbc0923cd903842d0c10ed8cf0e1a4695d3508b8ee4650f6a3eac3655247830aebb2c0b' + 'SKIP' + '1cf95a50e985ac37abd0faf86f4138e510cfb33af0ec172a2ca63f9252e6e4cba2f6c43a0babb11a96973bef3b7bf309a0f22ede26dc437673fcc77afef7fc31' + 'SKIP' + 'd5ab07d8a1db9191fc3b0d1f44c0237e93d662fb8bf04dd55385d8cded9589f66c8212a4d37c651c505fcfcb4a26da072c0be71d97fa0774b4724ba925a7af8e' + 'SKIP' + '7da8c7cd405efee8b37aff9916215b2d7052850f734b693656d2b66bae485661484efadc7207fa00dca5aa02d5e1bbaec2df9abc3e583f280097a939fc32096b' + 'SKIP' + '36fa6403c6519e064972afa0905cbff55f88fb89cad7582a89079f657537458da3d93deae474e33e018928d03090e1a373c6246b180d79f43366e16ccee533f7' + 'SKIP' + '44dcaad2ab326ebee62d3e63207a161dcb1b63bdb21412b5624ca7875bb6e99997528683a633d7dd526dc5ae9408e73bd066e2a1e5b34ba5c829162df2265346' + 'SKIP' + '69c1d61ffec58e2d3058d9e9e5fda57a089397d5ad451224f75f1cc2816c7f3fbd650b31bfb5911157fd364f77cb6f720e0825ae3deed8fe9f03576b8f7f1b31' + 'SKIP' + 'e3d60abe9fbfa046409598588dd23a9b16439474595136526e3169ff47982e011ede263d403211f5f5692ac43cf88af5bbf0d11b8e7e959478305c67687c525d' + 'SKIP' + '1fb895cb1deb1b227515087e12fb0f7fab70b0db25a1a535fa3c294a5455af4d987d02c7c8ff24aed41359055d1383ea90bf9709999382610ed156dbc4069086' + 'SKIP' + 'a2fb510d537b9afe8719334e5578a79a8ce7b021ff76c59830c749dbd963c38b7dfb60a61422b88f76886ccffb5ad5cb456b307a14ed68a5c86981eefd4fe3d0' + 'SKIP' + '5ecc37fe83f8dad00a82aa40a0c75d7518d6458b2f22d77820a28a34d772f1255cdb2befa5e525344d5deabac8f5dd092434a9ee2a9c27ccca5c5c231790c017' + 'SKIP' + 'bf138fd2ba2618ae9ff64838706c2212b478513e04b54ca3bdd8bf97292a8a310d73e0cd96533be62f139b645d7ac5de22a4c3bc1fd9f67e807bb862e965ea08' + 'SKIP' + '825397852f1c25b18c37d25bc53a7706af1eedd97cb9cf85ac590d1044e2254d06c4639bd54cac06ad42534399081b4ca17b0b10605c152ed41a22674c0ae42d' + 'SKIP' + '42a4c3debfa847f46c26db0d3e7cb1a540d3d15deae359b582eb2c71514c2cdb36c28c8c57b22dc32d0cee4d00ad7519da01c66127446692c81ea7a9178d722e' + 'SKIP' + 'ab28a1f4f1756b8451840566d8100cb3d0b498b13d006593c72829b9783e94900bae78ab47c576d156e1d08c56602274b3f7d5bd885de17336623b23c3e4f255' + 'SKIP' + '8481f23c33a84d8efb798f4b83f78011f74cd5a3dec428b177d7e600922836c1eec61c4238724038221d8ca2e2cdf20462c5911085da3461aa315f7dd19d96d8' + 'SKIP' + 'eca2778279d88085b6bf7af742773ad533906f98b146ba2526a9e00136e154913f233be728105765b0d2fc2774068f670a4ab1b49d8a7486d20e04d116f71c0b' + 'SKIP' + 'fea498b5c5ada6d7f57caf652b68f687081aae8552d2d04cd9e96764efd1b896208671f4f501210bb557710cca3ceb0c02ac2b5da6d4c04c210e87ec2d715384' + 'SKIP' + '39650b6ac7f62174d4b24e5267f9c6042c22452f16c649e032570eacf428e83ef8e10959fe54adf8573ac1c84d6082eb713ae5f173165af7d1f687e5d26fe6bc' + 'SKIP' + '3acc8aeadbac6ea2dd5a4fba4c10165dc1e6a4a55b838bff184fa46e827df67ea4e9a8535e17ed8cc80cc5338252306a76049af5956dc767bd591729b2aceb4c' + 'SKIP' + 'd442bf561a4b5dd1058c45a8181157feb268a84da94ec7f260dc6d10f7e0ddb71f1097bef945509f3d212431975ad3197f778bcc1c93ce1829ed2070bcf6bf9c' + 'SKIP' + 'c966ed5918d71acb91e988f3776d84db0b072f42e3e3f05cc178fa92a0d902fa3e34beb790c963042b52bc500d6ab9a2802b84a8411ca02cc1e28d361603bb2d' + 'SKIP' + '60fde203ea68744ea9bb8b81a18aedc8a4de8a985dc65bfd6b2f6b43210f87c2875e53eb61f4498c9b236892abde92da32755b69ee5bdb23f079079f22b529ac' + 'SKIP' + '56420f41da60382143b4df2d1fd58384180ca3a179b9f1b5082b2e4b74976ed13a29725c622e61c698631d923384082b43c9f703ce417d09e99556e4600f981f' + 'SKIP' + '5d880f3cdb32ed6fb7ce26ffbd1774fda56641d9304b5372806b6e559dab31dcac233cd6765a0b64bb6afe99fd9602402fb5e3b8c791cd1a9eaf9af28ca01bee' + 'SKIP' + 'a6098df3b653702e2689ec25934839c8a986b0e2f113b216ff4f86c8c5d530584f61d33bf4a027d1b304deb989406a3b342f13e11a66cc91ec69457c5d2e6f14' + 'SKIP' + '4a4af88fc8a8c37ae6d20b35d19e60a17672569d5489be120edbcc62ab3ba4bbc43b78cd0799b9fd73c84b089364320bcbad6fcb7128c2cb06d15ed984d6fc8c' + 'SKIP' + '411fd61157830b68da00e19be0d0db7ed61de97fd99b46a31c3958c5f66885265521e9e099c4f3404975db9de9b178cb698dbbe1399d81bf13ecff79b9ebd8b8' + 'SKIP' + '49adc1fc4c68da5c39d96746007d19107ae2d293518cfb36f3742aeba7723db31660fb1f7b3579e8fa88bb53206dbd1e718934f9b37e87925c6c0b06759e77fd' + 'SKIP' + 'f8a67f01fca5614f0eb3bfef59658913fb9b88b043f57c8bfedf04ad79912ff04d5d356dce8ff453ca001c151ab1bc017dcd7daffd5024e2bc20b087d08ab8ce' + 'SKIP' + '561a635f88fb3729937e1632d736a8981002cffd57cf16edad538381de8bb6a583f48b05e4d2bf1c8b6c03aece2495ac2850c1d66c4b540af2b88e1a9a0d9c2b' + 'SKIP' + 'da96dbbe3b6b4e189cf7bdce14647bba07bbc5599ce28ed385a1c34d237ee7ae3ac595d2552444615f2b15c2b7eda42f40743d5bdebd443e3ec31e5743f59056' + 'SKIP' + 'cb2494c930b2c2c4cd6755494339c5f95ace87940b2b2e51928d98ea63262e4bf7a4245f629b8bb06e7592f45a0c379e5583c8695563011009fdb07f965d7c1a' + 'SKIP' + '6083db5b92aa46bf54144b4d2fab368916974ef01334fbe934bded3818c5dfb4fc8d5d0e896bf1baddc29aac1c559f80dabc727082a757301117843def8089c8' + 'SKIP' + 'c25bff6e5be62aca887ae0781899e37a39e1aec90f0eb934ec5394abf560f4d02ad1fb3973a45d00058b46555bbc8d9febfcc8026e210f53a8864a91b3b80a9d' + 'SKIP' + 'e70e0077249bf008e069041a0239c6bb63075044cd0df3218649ff92a6a2d43467026886f2a6a70c70552268f6936764459d7cc0817a103b6a0f74c37c52c72d' + 'SKIP' + 'f1f853a0ef095a53080629a00598b7b1e0023141fb1890622ffd8efc4688afdeb87be4f1a096b76868840d45c10b3c88bb33f44677ff70378d63ec81eb3110e3' + 'SKIP' + '1b63569b589e994d1869e2f8eaf5d4ee225fe68862ac5c848e46544b827ca5fddb4bf060f5f39001608fd9bb284eec005f5f9b095307d3a008c9ea8957c2fad1' + 'SKIP' + 'f6430036af77a31a9ddd16200bd3c2c7b1b7e46475c7917dee91c835766be046a31736123ba8429ca2f0f8d3b93ef60522873bbec45d50b954cbb8319e6f25ba' + 'SKIP' + 'd7dffb68fd22db7ef41d1ac193dd297fb6150f034f257a44e6cb0a4848dc4a94e328c2e26d5296a72993e2db75d09e3ac46456d337a5bae767fb266082d3cc66' + 'SKIP' + '4e4792acaf0455f41cb1541d20d37e7a39104aa2dfb505e9be29dc3e7fecf328557780982b32cd8f67f77594542f5b8104766a4e8b5ce2270c9c601728363b3f' + 'SKIP' + '00417f024d2cc0b0fad28dec7f11c3054f5da7bf8c353d06fd1086be1926eacbe07ba924380157f270aac910d771f909f34beed0f60106f6ae60c96f58423b4a' + 'SKIP' + '2c254a2df00e79f03b51d13846f232073e9276e022c7bf46bfe810cc98456d481535152afa9d915e0726e7701594453f9363b6190b8e616db203d176a08fd31f' + 'SKIP' + 'cbfa20431371f3332f28013bfa21beef21a5a6f616e09fa91e9adb42899cb2e1880e27cf4b90bba980bce26710af3ac3c1e49054df4e48051bcc4612732d3e9e' + 'SKIP' + 'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835' + 'SKIP' + '803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67' + 'SKIP' + '4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d' + 'SKIP' + '335c0c1164e9a2b9a9a252ddf7418ca0e6493f2f0fddcf3f01ae20666b6cb6713b8bf7a2a165d37485d2107b6c00f42b201974154c41c15c4456615b3bb0d78f' + 'SKIP' + '01a28159b9c42e6b52147d1b061e1a2cb40ee381f8b6f7e2db3f94adc160365bc0dee602713cf42745fd2275b822b273df25d739d7b77f03273922edc746e50f' + 'SKIP' + '504b7545cbbd42170d2797abe033539cbd93d1d8881ffa8d589486544932574ab9863a50edcc5da3e35a684d0a3b09948dd50fa77507d4c5deee766d712632ad' + 'SKIP' + 'e0790aa7fdf26f920bce07a2ab9dbee1fc72bc7aaa2f30df62d529913f265793492feb0415e2649d13f8feacdca31685d1510867aed338dcbf538b8b548b99d9' + 'SKIP' + '88c166e0f9b401a47b2e7aab41e23ac35879b8b60b9b593493f5254be8b4890c3a4496c82f9cc553ac100a3dc9132748ed66a38405e78e1542faa2c2e27a40a4' + 'SKIP' + '9ac15b3542629c5333cc7d7f53b4f2995d807a31331b9e04d3da051be0ddde7e8c3cbc79837ffb49a4d364db1680cdc6d763465fa56bd4d26b16b765985babe1' + 'SKIP' + '70e35154f9385156a11280294cc254cb314fd89653daedb46546a6bd64c550e3fc7e7a8df3e653c516687f0317bcc710fce82b4d649472333c501ed85fcd6cf6' + 'SKIP' + '576fd826f28d945782840865f3e5b7c87d7f6f91f08e473ff0d06a37caa2f89cdd9cb4a7aafef5551363aab9c60e887ee794d5b6292874c12baf88c23ea5668e' + 'SKIP' + '01125c624a4971686d02a23918e3da697645a322e5a7638159636d9a16c82f815efdad1d8e22c507518dc0b92b6842277637cb2d0940f4eaab40c0faa77d8da6' + 'SKIP' + '322e4e255a2b900ad224ff376b00199c40e26fc5407e15dd36c1ca4e51c7ca0431397a69696959d1c1910b02a4bdac8efbaa20f6f6f6dd5424b711c149ea367b' + 'SKIP' + 'ae93d961d0eef41ed39c473fe9478b62fab13ba395333b3c11b34b7787904a3d2684520ec76dbe2603dd96a146a68bcbe98da1b7e4cc1e659246127384bce22c' + 'SKIP') +b2sums_x86_64=('ac5d224895c0e9b1bb9e556d0de6ff19a64e8fe40839c7f64edf0095cbaa98b6f755e19d454b016eb958dc91208eb92d201a44c34ecda1ad4498cb9a49f62f56') + +# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc +sha256sums=('12e8d5a973d1ad7c5a5c69882e4022b131ed715db7003fdcd760ddf8c3e51941' + 'SKIP' + '777e4bbfe44531c6504fa646ee74e95e8ff11f264a3fa281b528e25960e741ad' + 'SKIP' + '6cf74fc3f28a751354187b6cf610c3e43cf9088b5f730b68dbb575828c3a7478' + 'SKIP' + '2c7ae6d2cfc8a0a070b395259eec15808c514932edc6f0b1dbe9a2c2545415bb' + 'SKIP' + '58f21f8c48dbbb1295d488815aae50003155ec9ea5633ffbca4f0e17b18e8803' + 'SKIP' + '66fa4915efe471770358fb0af4969b7ea9daa59d08370c3caea1eec02cfbd79a' + 'SKIP' + '83d51eee049d9898a217d3846edeef317365f4b74b277988582668fa71cb4a28' + 'SKIP' + '042258cf2958a3965fc37cb06798b7dad8860b967d2a7e21ea4c69bf7d7518ba' + 'SKIP' + 'c89cb6da5e007551dfc6437a485adb664e9ac68e6bb80763dea757ef66c989da' + 'SKIP' + '01319b540a2cb4772eb4cccdc8c85ba4c96b150be0dcc88e3ac1290b4fdf6cdc' + 'SKIP' + '4fc90cc8e9d9979d7c839fd1761e42221577667ceb7e4502c354081b8d40d3f5' + 'SKIP' + 'df870910371645a95140a3f92424db43c4e1a92bd493393e598d61c6266f1c7b' + 'SKIP' + '3d5550935087a7d82b527227e8f08ed98c2ffdb830b526c09e30b93137b3e607' + 'SKIP' + '2351136c78ad2dfe68e34bf9a70d377724257f6239e68bf8f501637b607af6b2' + 'SKIP' + '6c93475fc28d211ad7c15c691ea882deedeea8c0defe153cdde0a2a74488c7a0' + 'SKIP' + '718b40e15350049e03c0ed281b9257fca5432b8dec2e0e145e8b26507ee8892d' + 'SKIP' + 'b7ba949eea77e169aceb2c401d4eb83a7413aab6f15ee2a7ab1a96352f0aa12d' + 'SKIP' + '341424b925d506869793686e22ea8f095a34048595ebebec409c0a7bbd346ca2' + 'SKIP' + '7c25d0a53a115bc1f072ff0cfdd9aa88858e754ae8b15026a2582656e8bd9c43' + 'SKIP' + '336d72ab830bb33ef2a23bda1869e1fc071af64b24779281e197073ab70c851a' + 'SKIP' + '829300e576f418e64101a20d87f30f7c90798cd6e179bfb72768a83d99fa711e' + 'SKIP' + '5e3b455024fab2855b590091c9d14b0d12d5363f3d4bf025b31b9aa88b65eba5' + 'SKIP' + 'abd8c9326cecc9bb85db4b3dc98ddcb66306b34a6d84fb05d0805aed537e2dc4' + 'SKIP' + '60a9df54821cd98581e7476f8a7bb6cb0819762bdbed4d356332e41d164a8182' + 'SKIP' + '929108ae0a8eb4782cafdd89e3d4426d02d86f2c2102ed99e760e31f0bc3794a' + 'SKIP' + 'e1226c836a9c2fb6daff109e56ba21c13fd198e109cbb181de920af04fe153e6' + 'SKIP' + '2cdd81f5520b935e1d99629fbd6e1cc9d1351b301396316f37946b95084f1a26' + 'SKIP' + 'e65c2645b0d01301a91874183faed44a390b7a402d2dffd45171d210034dcc93' + 'SKIP' + 'd8e64b9da3beab7c33832c019da50d0977b39dc32ec1f649a7c3bc93a2fdb9a5' + 'SKIP' + '5e2515879d58eba10c861f73318f4c4b478b8dd592680eb58961aa4065d4c3df' + 'SKIP' + '874efac3eb32970f3e06275adc072a384b89ff69e2e5b7452d6146a6e89049b0' + 'SKIP' + '43aa4a11d9d48f5094b4c0e807d1d31598e861a50695bdfa52f19819b5ff5285' + 'SKIP' + '2b49e4c98c5d704bee0a0e2b38051d59177161bca7b0188e6c0feb555a95fbf1' + 'SKIP' + 'e6b770d37e80509ed7ec5d8b59b4d2e5d6dba54ba659d7392de3b567f1eb6e54' + 'SKIP' + 'a9b3ef8aa79488d1cd813507e040e0762d4c8c5845b7f4ecc29c6cea1ade8056' + 'SKIP' + '264d49d74eb4006c8eded08e39409045032920d76df035aebe8a1d0939d4b7b7' + 'SKIP' + '5cc215215fed4247c2d6b9732deff586331a07d54c3b00538a81921093a3a1a1' + 'SKIP' + 'e303da14a3c8a15fd1cb45394138f03dd16e66d2ac0d1ebcd933977df937d771' + 'SKIP' + '1f0c958b64ba48b8dc8b5548e1ad1934b8d4903fc16fec15014759e3681ea275' + 'SKIP' + 'd9eb3717ba98e270b0a998f8dd90cbda072f63b6a6ea8f66763bde3ea64e591d' + 'SKIP' + 'ce634014dc0131a44c3c95a8e9cb0a9de433f59d73fd99d3e3297adde65fef71' + 'SKIP' + 'd069e594d170adb016c5000d9cb7eac12c3c267a6b60c27afde7f046dba0509b' + 'SKIP' + '78feb2e2fabccaf954fdae5b776a22cec4a3f4135fbd24f79596a5274fc0129b' + 'SKIP' + 'f864c7daeaac0bad482bbf3853583d8a6cbbf2bf264562e397fc26355ffd5504' + 'SKIP' + 'c3a709af1589bd0c467565dc66ec4c01bed9c2b94314dde30c839084e594557b' + 'SKIP' + '1e2fddb2e0c5d184a2e8c774886cf9be2f2f292e4f05832b50e5b49c48a7bacc' + 'SKIP' + 'b9b8771b6a8bd7128f4344ad189e32c155abf72abd3ff646dcf5c78b08eb94c4' + 'SKIP' + '34991cf6cec6c22bc1a55a76bc5f72b405165a2f14ef977a0446df7c18bdfbb4' + 'SKIP' + 'cae59282e3d1afd69f4f5dc00a180c712ef464c98b8fd7ccc9b03de944dc79b9' + 'SKIP' + '3b06b5285923c251b0ebaf69911875f796cf7f1d7580ec33bb218509fea313a7' + 'SKIP' + '15609bef72571868fec6b8971f53b74441e5e93fbdd3c677ede963d0719cbc40' + 'SKIP' + '1231a6e23557936b811ce5dc184c23a7fbd268a20d582b6820c67125df8f07c0' + 'SKIP' + 'ec12a77671afb85f8297c86fc97f8314b4f00a1630acb199af12e3550a9d4ec8' + 'SKIP' + '1b1660064a3dadf2bb3006b140d1c7016a1ad051f27e7a4d6d9f3f40d491fe9c' + 'SKIP' + '40ac31789399a5964c4fd820d4cdc7214add91109bbcf4a614619e5d8b367441' + 'SKIP' + 'ab3b968d8bc2073e8c53478aa5d5afbadc5329a22b1486a014e5d0d84bfcf752' + 'SKIP' + '2a2a81e647aefda80192d230cd1dbae2aeb611f66b7d6c83694b9f9f8e2befdb' + 'SKIP' + '2772f0733267d12f36ae2a339c9aeaf0a133a20381a6b73e0c26f2ef92996ff3' + 'SKIP' + '3998c774884fdfc94be3da3c4c3641bd4b5aeb1878d8ef13bf0e27cd9f2e9994' + 'SKIP' + '0d5afb79681a948a65943f557d143c15126e2497e71c5ff50ac13ed8619a67e5' + 'SKIP' + 'e95f8fb6d116b30c2b86d9fb51747a9f9b4e5f980c1a3b627a38940db9d0e521' + 'SKIP' + 'dab92dbc6eac02771c72801d94e6a53aabcf629a9cd79e85e0ce2ba44a65b06b' + 'SKIP' + 'f55bc6366f8b9ffef744b289788a19e8604239e84c1af8b929a6922cf07d223d' + 'SKIP' + '0057dff07a3ce9c7b7085edd1f912958e01ce8f7400151d55dfb74c0c6a084e2' + 'SKIP' + '9c4c16b9593449ce556a4d6325e5e2205cb1698f9000a967ba7df118395b1ef0' + 'SKIP' + '00afdc277c53dff0898ac86fd44ead020e007373134d7c7c333285a9f1dbf786' + 'SKIP' + 'bed39a5e6bbb7cdac3f56a883a4684b2a63f3e8fe2f8cdcdd0b89b374b8d2a21' + 'SKIP' + '75d7fc371ba9b00a78af2e4b0fdb7cf79bf25a6620c983662d6b2e90cf5ce8c0' + 'SKIP' + '0fe32924142868879ac9417c67b0233b6ef8ddb0163183352479f5163bf7b5df' + 'SKIP' + '7444186ad4f6d8e2b52a3729064f649b035af6eae4f4ad46b9768c006419fdbc' + 'SKIP' + '94750a925a726ad6631d0486e7f3cb3796b3941715b188e790fa135d0aa5734d' + 'SKIP' + '143193a175084d65035bcfdcc81e9dfef2e794dd56d637556db2c73b117f708f' + 'SKIP' + 'f18821fa9527c7ee07a49b9c1c983a3584d5058f9734b520b5d51cb485c93f17' + 'SKIP' + 'f7ff13ea46359217d696c6ccd49bc3d74c441ac5893d5151d95138017b0d10aa' + 'SKIP' + 'd17493b3579c730d27dc723447175b70d991f50253c9fd419df5e923bdf7a244' + 'SKIP' + '92971bb4dbde209cf170838cfcd75d8ac9978239821d3493c28cc12623c2bb60' + 'SKIP' + '7245c6e2cb5c0b0ddf800894f087770cbf8ccc6f8b3f53b9a21c704e9dfa42c2' + 'SKIP' + 'd49629fd003900f13418c05d8c2fc37c8dea2fd8a15debb5b863008c9c46021f' + 'SKIP' + '411fe33ab7021dcf73b02cdc80a64094a511333c52dd754e54d382fe1145de08' + 'SKIP' + '5104ee45fc11994b1cb44130811435c84fe4db532a2fa9a722c7b03dbbdc8bf1' + 'SKIP' + 'f7617e3c4248e015f6f174e7c8659299d831035d0fb3c89acd1a858a202f91b9' + 'SKIP' + 'e7dd17ccd4b417a1813c21285c738a3fdede1326d8a1445086ea1af294c3710b' + 'SKIP' + '058aa32a18723035b18a770e1bd7f4b4f101bf5618bf0114e1874a398de42e06' + 'SKIP' + '833c5ede23f4a014f428c64b7da550cdcd7ecc939dfeb45dce172b18417888a5' + 'SKIP' + '21a9196887fe8453218e4805e171b19958005f38e5d21886c58a55f8d9b86795' + 'SKIP' + '29c61ec984bd342113b3c4310a3d5fddfd6d7db83847cfb26b84cc730385320a' + 'SKIP' + '4b5abcc24f557e83e4d385c8304c781d6ab78fc0a3935db97e01e7d27fc617ea' + 'SKIP' + '0dc5f0f91640715ee1744fb8cfe680de6ac1370f4df8e17d31016744f31d3638' + 'SKIP' + 'cd068c7e199f458356886eb321f759a91894bca374c71ccac242407d8b22a1f5' + 'SKIP' + '618369a173be9a5a2f91fb6044931d897bbf4911586bd94a10b43a25838872a2' + 'SKIP' + 'bd3535f78183d56b133986b814ec31a8c1aa933d71b3a611937e6377ee04af10' + 'SKIP' + 'bb1e2ab62b109e434bd2c2635b5912602b8a8cc0ba24743b7bb5f691dcde2353' + 'SKIP' + '55ddb7c072f3933eef42ac48f3b32f90a5298f5cf90b4ba5b2128a197250494a' + 'SKIP' + '62e446b250e96fb65a3068bac424e1e5fe4482ad6d7ac473fa94cb8ac047c0ab' + 'SKIP' + '8b55dbedf730b0dc675793418c21b909aed5db4311248bd277d3184839d261f0' + 'SKIP' + '5a09e7b8e10fdc61839ada49e9a33a64b240fa54847c1c0e0044518297196f0b' + 'SKIP' + 'b857f4c98ff5998adab1765a06ad307f9334a8d7d9644a3de556f2b0e9cb2b4b' + 'SKIP' + 'c28fca9ba015e96599627acf42e52737a6a846bdcbfd3d4af4147eda3109e12d' + 'SKIP' + 'd15cdfc9b86c40a119a4f21f4fbaec72efebc67677a7386df46a61816d4c6e02' + 'SKIP' + '1b070eef78ccffb3763b464f5a1b0636936f4c33b0be9b44ab116c5dcbadbebe' + 'SKIP' + 'eb0923de01175707151930d28b1a1a912d24bbc171248fa9f45f57a2125d077a' + 'SKIP' + 'c076ed3752bcc2033e435043668fe007eb1ea1760597416fbb5d90bed4947bec' + 'SKIP' + '0fa9fdc6d6c85435d7b3cb38b51823f458ab273c527fcbe21b945dbd89686304' + 'SKIP' + 'ef66f6c32ac98a311b7437265b21a8c70746f1ea4dfbe28aff655e778d09c28b' + 'SKIP' + 'b45303b559ab8990ecb4e0281e5d529625311e795d6c3a9a52c2c27498141e18' + 'SKIP' + '1728e516153acca3c343f5184c9894fe6f1a1fbc95abe1594f544ef601db39ed' + 'SKIP' + '534e68a989d36823f14a10299803f294eef39391619b4c329d78c20539d9fcd5' + 'SKIP' + '516e6ec51ca0330989128a4eb2a8f6785e5d45d930899cbbbf37b8147ddfd854' + 'SKIP' + '757ccc291d53030b89088802615ecf727df6f18ab2515cc0075008ebbd564e36' + 'SKIP' + '3a271bc6b0219152047fb2b78d9e695eb791b796ea6d8233096912c77d62a5c0' + 'SKIP' + 'fd6bf3ed3d16c788330630fdd15604836ec2a0b6a7db2b04216f756da9117bd2' + 'SKIP' + '4d598b9e00edf7c17d915eec7c8d369fc0ea40badb7f4576cb3dedd92a685d4b' + 'SKIP' + '98ff0d6196470c582b3e3841ef35bf82a63b57ebaef952885bd1905bcb8e627b' + 'SKIP' + 'de2682af1db7f5ae8cd74da4f9020bfb7d2167f030eb15f0afb7bd46ffe49e84' + 'SKIP' + '73dc22b7f97a6cc5a5f35d6b30b9b7b12be2566160a024769550290a0e7e4029' + 'SKIP' + 'f06834dfaa9e976a5778fa4fd07117a85bbff3b788c91e61d8cbc628e1b8bfb3' + 'SKIP' + '11884198740c5ac572cbe87086b1744ce19f0e1f856775e6edccce843ab097a3' + 'SKIP' + 'e6d848172228e98dde3a7ec679e7ea6bb0a42cca0b0105c58c8f9d6d44e33943' + 'SKIP' + '4e80f2d05591175d4ed0a1a144509e97260e5593472aae586c07956215d99162' + 'SKIP' + '1b442ad6f28c684068844ce93616690b84651e35870802b02937351bea078b4a' + 'SKIP' + '9be4f91e6a4be87791d700f44c228fe599468f06a4f627b79e75718ac020db4d' + 'SKIP' + 'ed635ed3b63cdb61614b8805aa5a9515d5b538522165be625dca89bdb7d6e801' + 'SKIP' + 'e28c29b9f359db85a60bfd49d2f28ba51f4d2272eb79b7d3e19ba8b3d5acdac9' + 'SKIP' + '3175c56fd2935848ce99fe10be1bc348231909f8479c3d3af911c5c7ab7f381b' + 'SKIP' + '30a12a616afa8fe95b969e90b42aeb240962b9fab8b8bd357ac00706abdd68c7' + 'SKIP' + 'eb0ebb8c55332dab8931c667ec84cd32c91c657d21af4504327ae05643108e1d' + 'SKIP' + '945c90d99f9a9fa47d61e471f76d17c0229ffa0fa8cda22de990a2ccc4194bb2' + 'SKIP' + '70ff41b2341836c27c5545a7948258f137d991c2879cbeb29f7af740c11d20e6' + 'SKIP' + 'b4efd997d3f09cbf258fd0f78aca44e4c96694dc4efc5c8c9a677ea67ebf5a54' + 'SKIP' + '2e1e2a0eb039ce61c1612bf6c7ab5227b99df053bcd5592da7758715dd333fda' + 'SKIP' + '6800dd82115ebe71d80dde0bb368fbd8563caa6c29127ffde0d6b5df81b502b0' + 'SKIP' + 'd90cc6570a47c8754e4c5d45834e0434c0a1fcabda9b8933b1fba84e3dabc083' + 'SKIP' + 'a008c052ca657efcd4e27b8f7b819adbc9606e261aac37d41a40cf67c094cff9' + 'SKIP' + '57169ef893dff957cbf33f7c0dc22f2fdfcfcb3befcd62ad59e2456484423bac' + 'SKIP' + '01ecc1adbe715bb31153a2350ca46e1cb8a6e3fdc6899b2debeff015af2412ed' + 'SKIP' + 'a4331d3968fc5002534063b42bd219bddfe4a7d6b08e4cb30ed60f4366ca3c41' + 'SKIP' + '028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8' + 'SKIP' + 'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311' + 'SKIP' + '44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a' + 'SKIP' + '6f01b5357af7deed3f4bccea6678b4d9e8afc83e598978f42f244241667f975d' + 'SKIP' + '0da7247035a359e2491933f9effff43ea7b29748f21a0e3080bef0018c2488e3' + 'SKIP' + '1b716bda3af93420536ab5b67aec8a064d69b5328b5bd982d55a0bc2ea251948' + 'SKIP' + 'c51cd96d1fdb2b5c09eaf582ecc6c83469d70006550bd27564911d05b0764227' + 'SKIP' + 'daa84f9ef576c5e95c5479eef713d6bb2e6c16c9b1cad07f69c734704f6b5d26' + 'SKIP' + '07683e522d6dea3d3ce658ee80bf4317f6c3caf248259da960bc4610f4d1e807' + 'SKIP' + 'c5279468d93e562f509a6b599a2b7ca7ee0032d3fe35314c5769fc6c9a9234ab' + 'SKIP' + 'c0111614ec44014cbf621eedf5a4ef302a855423bc1633ee692392950e971d0e' + 'SKIP' + 'c2f916fa04d91edf926abf8fed086302fe538ba62d92450af0ddb6d3a33942f1' + 'SKIP' + 'd17774053ab07de92e18abdb0ff4d00352a02b6a97c48e28d8c875954c8eec36' + 'SKIP' + '774f337663b215c5aa8877c06ffce95be44403fe77516c1be9ef9789e2d049e5' + '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 $(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 deleted file mode 100644 index a9f0fa6..0000000 Binary files a/pkgbuilds/linux-omarchy/0131-sched-avg-idle.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch.sig b/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch.sig deleted file mode 100644 index 2e7bcd9..0000000 Binary files a/pkgbuilds/linux-omarchy/0140-sched-always-inline.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch.sig b/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch.sig deleted file mode 100644 index 4983403..0000000 Binary files a/pkgbuilds/linux-omarchy/0141-sched-urgent-fixes.patch.sig and /dev/null 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 deleted file mode 100644 index a0f39ea..0000000 --- a/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index d96b7f9..0000000 Binary files a/pkgbuilds/linux-omarchy/0142-sched-itmt-no-debugfs-dependency.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch.sig b/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch.sig deleted file mode 100644 index b191450..0000000 Binary files a/pkgbuilds/linux-omarchy/0143-sched-hybrid-cluster-balancing.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch.sig b/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch.sig deleted file mode 100644 index 0a059ef..0000000 Binary files a/pkgbuilds/linux-omarchy/0144-sched-nohz-idle-core.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0200-idle.patch b/pkgbuilds/linux-omarchy/0200-idle.patch deleted file mode 100644 index dc499e5..0000000 --- a/pkgbuilds/linux-omarchy/0200-idle.patch +++ /dev/null @@ -1,61 +0,0 @@ -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 deleted file mode 100644 index 5998b39..0000000 Binary files a/pkgbuilds/linux-omarchy/0200-idle.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0210-pstate.patch b/pkgbuilds/linux-omarchy/0210-pstate.patch deleted file mode 100644 index 3d4eefc..0000000 --- a/pkgbuilds/linux-omarchy/0210-pstate.patch +++ /dev/null @@ -1,480 +0,0 @@ -diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c ---- a/drivers/cpufreq/amd-pstate.c -+++ b/drivers/cpufreq/amd-pstate.c -@@ -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 deleted file mode 100644 index 1d30612..0000000 Binary files a/pkgbuilds/linux-omarchy/0210-pstate.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch b/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch deleted file mode 100644 index 67a6cf4..0000000 --- a/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch +++ /dev/null @@ -1,127 +0,0 @@ -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 deleted file mode 100644 index 7f218b5..0000000 Binary files a/pkgbuilds/linux-omarchy/0211-amd-pstate-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch b/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch deleted file mode 100644 index 61ea14c..0000000 --- a/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c ---- a/drivers/cpufreq/amd-pstate.c -+++ b/drivers/cpufreq/amd-pstate.c -@@ -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 deleted file mode 100644 index 7371ce6..0000000 Binary files a/pkgbuilds/linux-omarchy/0212-amd-pstate-epp-cache.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0280-mm-updates.patch.sig b/pkgbuilds/linux-omarchy/0280-mm-updates.patch.sig deleted file mode 100644 index 4e1b338..0000000 Binary files a/pkgbuilds/linux-omarchy/0280-mm-updates.patch.sig and /dev/null 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 deleted file mode 100644 index 602ef86..0000000 --- a/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index b01f81d..0000000 Binary files a/pkgbuilds/linux-omarchy/0296-x86-mm-pmd-modify-keep-dirty-bit.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0300-btrfs.patch.sig b/pkgbuilds/linux-omarchy/0300-btrfs.patch.sig deleted file mode 100644 index 60dbc9e..0000000 Binary files a/pkgbuilds/linux-omarchy/0300-btrfs.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch b/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch deleted file mode 100644 index c5a4318..0000000 --- a/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch +++ /dev/null @@ -1,386 +0,0 @@ -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 deleted file mode 100644 index 53ac4a1..0000000 Binary files a/pkgbuilds/linux-omarchy/0301-btrfs-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch.sig b/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch.sig deleted file mode 100644 index c65855e..0000000 Binary files a/pkgbuilds/linux-omarchy/0310-fuse-eof-zeroing.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0311-fuse-perf.patch.sig b/pkgbuilds/linux-omarchy/0311-fuse-perf.patch.sig deleted file mode 100644 index ce13d31..0000000 Binary files a/pkgbuilds/linux-omarchy/0311-fuse-perf.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch.sig b/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch.sig deleted file mode 100644 index e161277..0000000 Binary files a/pkgbuilds/linux-omarchy/0313-fuse-background-wakeup.patch.sig and /dev/null 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 deleted file mode 100644 index 6977a84..0000000 --- a/pkgbuilds/linux-omarchy/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 8ca1431..0000000 Binary files a/pkgbuilds/linux-omarchy/0361-drm-ttm-swapped-out-resource-leaves-bulk-move.patch.sig and /dev/null differ 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 deleted file mode 100644 index 4c3c1a0..0000000 Binary files a/pkgbuilds/linux-omarchy/0401-drm-i915-psr-exit-panel-replay-for-alpm-lag.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0430-fbc.patch.sig b/pkgbuilds/linux-omarchy/0430-fbc.patch.sig deleted file mode 100644 index f0db1f3..0000000 Binary files a/pkgbuilds/linux-omarchy/0430-fbc.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch.sig b/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch.sig deleted file mode 100644 index ee35537..0000000 Binary files a/pkgbuilds/linux-omarchy/0450-amd-hdmi-vrr-allm.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch.sig b/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch.sig deleted file mode 100644 index 7077a0e..0000000 Binary files a/pkgbuilds/linux-omarchy/0451-amd-vtem-tmds-links.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch.sig b/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch.sig deleted file mode 100644 index f76e652..0000000 Binary files a/pkgbuilds/linux-omarchy/0460-vesa-displayid-dsc-bpp.patch.sig and /dev/null differ 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 deleted file mode 100644 index d303e52..0000000 Binary files a/pkgbuilds/linux-omarchy/0461-vesa-dsc-passthru-mode-match-fix.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch b/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch deleted file mode 100644 index 08d39ec..0000000 --- a/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 4e8c059..0000000 Binary files a/pkgbuilds/linux-omarchy/0473-i915-ptl-cdclk-sanitize.patch.sig and /dev/null differ 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 deleted file mode 100644 index 14aa526..0000000 Binary files a/pkgbuilds/linux-omarchy/0474-amd-display-oled-vesa-backlight.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0510-sound-updates.patch.sig b/pkgbuilds/linux-omarchy/0510-sound-updates.patch.sig deleted file mode 100644 index cfb31fc..0000000 Binary files a/pkgbuilds/linux-omarchy/0510-sound-updates.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch.sig b/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch.sig deleted file mode 100644 index d84e156..0000000 Binary files a/pkgbuilds/linux-omarchy/0511-sound-updates-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0512-sound-fixes.patch.sig b/pkgbuilds/linux-omarchy/0512-sound-fixes.patch.sig deleted file mode 100644 index c02c1df..0000000 Binary files a/pkgbuilds/linux-omarchy/0512-sound-fixes.patch.sig and /dev/null 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 deleted file mode 100644 index 3762b2e..0000000 --- a/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 5f2c204..0000000 Binary files a/pkgbuilds/linux-omarchy/0540-media-ipu-bridge-ivsc-no-cvs-lookup.patch.sig and /dev/null differ 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 deleted file mode 100644 index 6ea6cfa..0000000 Binary files a/pkgbuilds/linux-omarchy/0541-cvs-nova-lake-acpi-id.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0560-input.patch b/pkgbuilds/linux-omarchy/0560-input.patch deleted file mode 100644 index 0d58569..0000000 --- a/pkgbuilds/linux-omarchy/0560-input.patch +++ /dev/null @@ -1,76 +0,0 @@ -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 deleted file mode 100644 index 643da0a..0000000 Binary files a/pkgbuilds/linux-omarchy/0560-input.patch.sig and /dev/null differ 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 deleted file mode 100644 index dbe7b90..0000000 Binary files a/pkgbuilds/linux-omarchy/0566-hid-asus-no-keyboard-init-reports-to-touchpads.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch b/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch deleted file mode 100644 index 5fe836e..0000000 --- a/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 753b735..0000000 Binary files a/pkgbuilds/linux-omarchy/0600-usb4stream-fixes.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch b/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch deleted file mode 100644 index 0cb7d2a..0000000 --- a/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch +++ /dev/null @@ -1,477 +0,0 @@ -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 deleted file mode 100644 index 45711f6..0000000 Binary files a/pkgbuilds/linux-omarchy/0601-usb4stream-busy-poll.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch b/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch deleted file mode 100644 index 3db7549..0000000 --- a/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch +++ /dev/null @@ -1,132 +0,0 @@ -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 deleted file mode 100644 index 939524b..0000000 Binary files a/pkgbuilds/linux-omarchy/0610-typec-cable-altmode-check.patch.sig and /dev/null differ 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 deleted file mode 100644 index ade8cc2..0000000 Binary files a/pkgbuilds/linux-omarchy/0660-btusb-mediatek-mt7922-13d3-3625.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch b/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch deleted file mode 100644 index 539fe9a..0000000 --- a/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index 0459681..0000000 Binary files a/pkgbuilds/linux-omarchy/0661-rtw89-command-offload-source.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch b/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch deleted file mode 100644 index bd8dd7c..0000000 --- a/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch +++ /dev/null @@ -1,31 +0,0 @@ -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 deleted file mode 100644 index d3799b1..0000000 Binary files a/pkgbuilds/linux-omarchy/0750-applesmc-cache-race.patch.sig and /dev/null 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 deleted file mode 100644 index 6eda26b..0000000 --- a/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index f0da512..0000000 Binary files a/pkgbuilds/linux-omarchy/0751-applesmc-key-backlight-workqueue-leak.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/0800-platform-updates.patch.sig b/pkgbuilds/linux-omarchy/0800-platform-updates.patch.sig deleted file mode 100644 index 1afc8bb..0000000 Binary files a/pkgbuilds/linux-omarchy/0800-platform-updates.patch.sig and /dev/null 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 deleted file mode 100644 index f340536..0000000 --- a/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch +++ /dev/null @@ -1,132 +0,0 @@ -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 deleted file mode 100644 index bbe1ce3..0000000 Binary files a/pkgbuilds/linux-omarchy/8201-xe-shrinker-return-freed-page-count.patch.sig and /dev/null 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 deleted file mode 100644 index 29ccecf..0000000 --- a/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch +++ /dev/null @@ -1,149 +0,0 @@ -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 deleted file mode 100644 index 0707c41..0000000 Binary files a/pkgbuilds/linux-omarchy/8202-xe-shrinker-runtime-pm-for-non-system-memory.patch.sig and /dev/null 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 deleted file mode 100644 index 6af456f..0000000 --- a/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index ddd9329..0000000 Binary files a/pkgbuilds/linux-omarchy/8203-xe-shrinker-release-through-the-put-helper.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch.sig b/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch.sig deleted file mode 100644 index 1b113b3..0000000 Binary files a/pkgbuilds/linux-omarchy/8204-mm-opportunistic-compaction.patch.sig and /dev/null differ 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 deleted file mode 100644 index de65251..0000000 Binary files a/pkgbuilds/linux-omarchy/8206-mm-carry-order-and-hint-in-one-word.patch.sig and /dev/null 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 deleted file mode 100644 index 8de9983..0000000 --- a/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index 88c17d4..0000000 Binary files a/pkgbuilds/linux-omarchy/8208-mm-thp-deferred-split-uses-hint.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch b/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch deleted file mode 100644 index ff35b72..0000000 --- a/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 303bf7f..0000000 Binary files a/pkgbuilds/linux-omarchy/9999-bpftool-strip-wformat-bootstrap.patch.sig and /dev/null differ diff --git a/pkgbuilds/linux-omarchy/PKGBUILD b/pkgbuilds/linux-omarchy/PKGBUILD index cc70049..40c2605 100644 --- a/pkgbuilds/linux-omarchy/PKGBUILD +++ b/pkgbuilds/linux-omarchy/PKGBUILD @@ -1,741 +1,29 @@ # Maintainer: The Omarchy Authors -# Adopted from the Arch Linux linux package. pkgbase=linux-omarchy -pkgver=7.2.5 -pkgrel=6 -pkgdesc='Omarchy Linux' +pkgname=( + linux-omarchy + linux-omarchy-headers +) +pkgver=7.2.8 +pkgrel=5 +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} - 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 $( pkgname=mise-bin -pkgver=2026.10.0 -pkgrel=1 +pkgver=2026.10.3 +pkgrel=2 pkgdesc="dev tools, env vars, task runner" arch=('x86_64' 'aarch64') url="https://github.com/jdx/mise" @@ -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=('0f9533cd255f6a7ff2347d6c1a2f5beb198347d3bd9c1d36601320713222c234') -sha256sums_aarch64=('4800d2ded125cdddeac658a0cefa50fa62671f095e7285df4c3681f21fa2b8be') +sha256sums_x86_64=('ff0870ddad7f8c5ba673ceb3e7659f0353da8263eabe8b82220f5816a772c786') +sha256sums_aarch64=('a5141f834d883239c2542b9a2136e75428779b2040206537399714d8c534d91f') package() { install -Dm755 "${srcdir}/mise/bin/mise" "${pkgdir}/usr/bin/mise" diff --git a/pkgbuilds/monologue/PKGBUILD b/pkgbuilds/monologue/PKGBUILD index 5684379..528b2ec 100644 --- a/pkgbuilds/monologue/PKGBUILD +++ b/pkgbuilds/monologue/PKGBUILD @@ -2,7 +2,7 @@ pkgname=monologue pkgver=0.3.0 -pkgrel=1 +pkgrel=2 pkgdesc='A simple, theme-synced webcam recorder for Omarchy' arch=('x86_64' 'aarch64') url='https://github.com/omacom/monologue' diff --git a/pkgbuilds/nautilus-dropbox/PKGBUILD b/pkgbuilds/nautilus-dropbox/PKGBUILD index 2b150ad..32e045f 100644 --- a/pkgbuilds/nautilus-dropbox/PKGBUILD +++ b/pkgbuilds/nautilus-dropbox/PKGBUILD @@ -12,7 +12,7 @@ pkgname=nautilus-dropbox pkgdesc="Dropbox Nautilus Extension" pkgver=2026.09.28 pkgrel=1 -arch=(x86_64) +arch=(x86_64 aarch64) url="https://www.dropbox.com/" license=('CC-BY-ND-3.0 AND GPL-3.0-or-later') depends=(nautilus libnautilus-extension dropbox) diff --git a/pkgbuilds/obsidian/PKGBUILD b/pkgbuilds/obsidian/PKGBUILD index 25a0ea5..157af51 100644 --- a/pkgbuilds/obsidian/PKGBUILD +++ b/pkgbuilds/obsidian/PKGBUILD @@ -2,8 +2,8 @@ # Extracted from omacom/omarchy-pkgs PR #240, revision dcef132b2df11bb762e233ea8c6af3110f079b67. pkgname=obsidian -pkgver=1.13.7 -pkgrel=2 +pkgver=1.14.4 +pkgrel=1 pkgdesc='Knowledge base on top of a local folder of plain-text Markdown files' arch=('aarch64') url='https://obsidian.md/' @@ -49,7 +49,7 @@ source_aarch64=( sha256sums=('42ecb332c900a01b2a6db0a70b4cc8c0553e863aee72894b23ed20a6289b131c' 'd2933bdc97d9ba55986d710114512e0eb105e1cc549cef4aa7416cb333e0a9b8' '7213e4e691c53e184b0f037478ac6758fe44adad7a984fc1daef7fd17c31d5d8') -sha256sums_aarch64=('98aac34d1f132a35cf506fc3fa196d595dcdeefdebd44b0cc5faaa7a1a210de2') +sha256sums_aarch64=('fc212804515713ed85fe5ef5bc4b009872bcc01847c4be4fcc14971deee2a42d') check() { python test-upstream.py diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 368bbdd..f0620dd 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,6 +1,6 @@ pkgname=omakade pkgver=1.15.0 -pkgrel=1 +pkgrel=2 pkgdesc='A beautiful, local-first game library for Omarchy' arch=('x86_64' 'aarch64') url='https://btsouth.github.io/omakade/' diff --git a/pkgbuilds/omakeyboard/.omarchy/package.json b/pkgbuilds/omakeyboard/.omarchy/package.json new file mode 100644 index 0000000..15a9e44 --- /dev/null +++ b/pkgbuilds/omakeyboard/.omarchy/package.json @@ -0,0 +1,9 @@ +{ + "source": "local", + "upstream": { + "watch": { + "github": "gabamnml/omakeyboard", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + } +} diff --git a/pkgbuilds/omakeyboard/PKGBUILD b/pkgbuilds/omakeyboard/PKGBUILD new file mode 100644 index 0000000..524b382 --- /dev/null +++ b/pkgbuilds/omakeyboard/PKGBUILD @@ -0,0 +1,63 @@ +# Maintainer: Gabriel Aramburu + +pkgname=omakeyboard +pkgver=0.1.0 +pkgrel=1 +pkgdesc='Keyboard remapping and shortcuts for Omarchy, built with Qt Quick' +arch=('x86_64' 'aarch64') +url='https://github.com/gabamnml/omakeyboard' +license=('MIT') +install='omakeyboard.install' +options=('!debug') + +# hyprland and lua are runtime tools, not linked: hyprctl applies the +# changes, and lua reads the bindings already in the configuration. +depends=( + 'hicolor-icon-theme' + 'hyprland' + 'libxkbcommon' + 'lua' + 'qt6-base' + 'qt6-declarative' + 'qt6-wayland' +) +makedepends=( + 'gcc' + 'make' + 'qt6-base' + 'qt6-declarative' +) +checkdepends=( + 'qt6-base' + 'qt6-declarative' +) +optdepends=('xdg-desktop-portal: follow the desktop dark mode and text scale, and use the desktop file chooser when exporting or importing') + +source=("$pkgname-$pkgver.tar.gz::https://github.com/gabamnml/omakeyboard/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('3cc272c47cd0670b6d475e058fafbfbe264555780e7442c357428bbf1b7282a6') + +build() { + cd "$srcdir/$pkgname-$pkgver" + ./bin/build +} + +check() { + cd "$srcdir/$pkgname-$pkgver" + ./bin/test +} + +package() { + cd "$srcdir/$pkgname-$pkgver" + + install -Dm755 build/omakeyboard "$pkgdir/usr/bin/omakeyboard" + install -Dm644 packaging/omakeyboard.desktop \ + "$pkgdir/usr/share/applications/omakeyboard.desktop" + for size in 16 22 24 32 48 64 96 128 256 512; do + install -Dm644 "resources/icons/${size}.png" \ + "$pkgdir/usr/share/icons/hicolor/${size}x${size}/apps/omakeyboard.png" + done + install -Dm644 resources/wordmark.svg \ + "$pkgdir/usr/share/omakeyboard/wordmark.svg" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" +} diff --git a/pkgbuilds/omakeyboard/omakeyboard.install b/pkgbuilds/omakeyboard/omakeyboard.install new file mode 100644 index 0000000..c4be03c --- /dev/null +++ b/pkgbuilds/omakeyboard/omakeyboard.install @@ -0,0 +1,24 @@ +post_install() { + gtk-update-icon-cache -q -t -f usr/share/icons/hicolor >/dev/null 2>&1 || true + update-desktop-database -q >/dev/null 2>&1 || true +} + +post_upgrade() { + post_install +} + +post_remove() { + post_install + + cat <<'MESSAGE' +OmaKeyboard has been removed, but the configuration it generated is still in place: + + ~/.config/omakeyboard/ your rules and the generated data + ~/.config/hypr/omakeyboard.lua the loader + ~/.local/state/omakeyboard/ generated keymaps and backups + +and hyprland.lua still requires the loader. To undo all of that, run +"Restore defaults" in the app before uninstalling, or remove the block +marked OmaKeyboard from ~/.config/hypr/hyprland.lua by hand. +MESSAGE +} diff --git a/pkgbuilds/omarchy-billboard-generator/PKGBUILD b/pkgbuilds/omarchy-billboard-generator/PKGBUILD index b714a61..9cafc05 100644 --- a/pkgbuilds/omarchy-billboard-generator/PKGBUILD +++ b/pkgbuilds/omarchy-billboard-generator/PKGBUILD @@ -6,7 +6,7 @@ pkgname=omarchy-billboard-generator pkgver=0.2.0 -pkgrel=1 +pkgrel=2 pkgdesc='Desktop app and CLI that renders animated OMARCHY domain videos' arch=('any') url='https://github.com/llstrk/omarchy-billboard-generator' diff --git a/pkgbuilds/omarchy-dev/PKGBUILD b/pkgbuilds/omarchy-dev/PKGBUILD index 299c470..cf4bf5d 100644 --- a/pkgbuilds/omarchy-dev/PKGBUILD +++ b/pkgbuilds/omarchy-dev/PKGBUILD @@ -1,13 +1,13 @@ # Maintainer: Ryan Hughes pkgname='omarchy-dev' -pkgver=4.0.0.r6720.g8e02fc8 +pkgver=4.0.0.r6815.g50d687a pkgrel=1 # Pinned by the upstream watch in .omarchy/package.json (bin/sync-upstream): # every quattro tip becomes a commit pin here, so the package is versioned, # checksummed and built exactly like a release, just more often. The r-number # is the branch's total commit count, not the distance from the last tag: the # published history used the total, and pacman must never see it go down. -_commit=8e02fc84f5bdc511ed102e2a14f8935bba4f92bd +_commit=50d687a1f27063513cf42e6f33c3a4fcb17c24ed pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH (quattro branch tip)' # The payload is architecture-independent, but the dependency set is not: the # boot stack differs per architecture (see depends_x86_64 / depends_aarch64), @@ -81,7 +81,7 @@ makedepends=( # build from a local source tree instead (mirrors omarchy-zsh/omarchy-fish DX); # the arrays are emptied below so nothing is downloaded in that case. source=("omarchy::git+https://github.com/basecamp/omarchy.git#commit=${_commit}") -sha256sums=('f19f9baad1cca793d3f8b95faec014d2af6570d40800e5d9dd4102e3f5257710') +sha256sums=('8f26a967de236f5c0d2b34e9e7119ad13fe7a9db67028f0ad1a4bf2ddf8c40cf') if [[ -n "${OMARCHY_SRC:-}" ]]; then source=() sha256sums=() diff --git a/pkgbuilds/omarchy-hyprland-titlebars/.omarchy/package.json b/pkgbuilds/omarchy-hyprland-titlebars/.omarchy/package.json new file mode 100644 index 0000000..fed2378 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/.omarchy/package.json @@ -0,0 +1,6 @@ +{ + "source": "local", + "rebuild_on": [ + "hyprland" + ] +} diff --git a/pkgbuilds/omarchy-hyprland-titlebars/BarPassElement.cpp b/pkgbuilds/omarchy-hyprland-titlebars/BarPassElement.cpp new file mode 100644 index 0000000..df2a44c --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/BarPassElement.cpp @@ -0,0 +1,30 @@ +#include "BarPassElement.hpp" +#include +#include +#include "barDeco.hpp" + +using namespace Render::GL; + +COmarchyFloatingBarPassElement::COmarchyFloatingBarPassElement(const COmarchyFloatingBarPassElement::SBarData &data_) : data(data_) { ; } + +std::vector> COmarchyFloatingBarPassElement::draw() { + data.deco->renderPass(g_pHyprRenderer->m_renderData.pMonitor.lock(), data.a); + return {}; +} + +bool COmarchyFloatingBarPassElement::needsLiveBlur() { + static auto PENABLEBLURGLOBAL = CConfigValue("decoration:blur:enabled"); + + CHyprColor color = data.deco->m_bForcedBarColor.value_or(CHyprColor{static_cast(g_pOmarchyFloatingGlobalState->config.barColor->value())}); + color.a *= data.a; + const bool SHOULDBLUR = g_pOmarchyFloatingGlobalState->config.barBlur->value() && *PENABLEBLURGLOBAL && color.a < 1.F; + + return SHOULDBLUR; +} + +std::optional COmarchyFloatingBarPassElement::boundingBox() { + // Temporary fix: expand the bar bb a bit, otherwise occlusion gets too aggressive. + return data.deco->assignedBoxGlobal().translate(-g_pHyprRenderer->m_renderData.pMonitor->m_position).expand(10); +} + +bool COmarchyFloatingBarPassElement::needsPrecomputeBlur() { return false; } diff --git a/pkgbuilds/omarchy-hyprland-titlebars/BarPassElement.hpp b/pkgbuilds/omarchy-hyprland-titlebars/BarPassElement.hpp new file mode 100644 index 0000000..9cf19f9 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/BarPassElement.hpp @@ -0,0 +1,27 @@ +#pragma once +#include + +class COmarchyFloatingHyprBar; + +class COmarchyFloatingBarPassElement : public IPassElement { +public: + struct SBarData { + COmarchyFloatingHyprBar *deco = nullptr; + float a = 1.F; + }; + + COmarchyFloatingBarPassElement(const SBarData &data_); + virtual ~COmarchyFloatingBarPassElement() = default; + + virtual std::vector> draw() override; + virtual bool needsLiveBlur() override; + virtual bool needsPrecomputeBlur() override; + virtual std::optional boundingBox() override; + + virtual const char *passName() override { return "COmarchyFloatingBarPassElement"; } + + virtual ePassElementType type() override { return EK_CUSTOM; } + +private: + SBarData data; +}; \ No newline at end of file diff --git a/pkgbuilds/omarchy-hyprland-titlebars/CMakeLists.txt b/pkgbuilds/omarchy-hyprland-titlebars/CMakeLists.txt new file mode 100644 index 0000000..c0f27cf --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.27) + +project(omarchy-hyprland-titlebars + DESCRIPTION "Titlebars and edge snapping for Omarchy's floating workspace mode" + VERSION 0.1.0 + LANGUAGES CXX +) + +set(CMAKE_CXX_STANDARD 23) + +set(SRC main.cpp barDeco.cpp BarPassElement.cpp edgeSnap.cpp) + +# Built as titlebars.so (no lib prefix). Omarchy loads it from the fixed path +# /usr/lib/omarchy-hyprland-titlebars/titlebars.so. The Hyprland-facing plugin +# name stays "hyprbars" so the hl.plugin.hyprbars Lua namespace is unchanged. +add_library(titlebars SHARED ${SRC}) +set_target_properties(titlebars PROPERTIES PREFIX "") +# Hyprland exposes inline globals through its headers, so whole-library hidden +# visibility breaks the host API. Our own types/globals have unique OmarchyFloating names. +target_compile_options(titlebars PRIVATE -fno-gnu-unique) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET + hyprland + libdrm + libinput + libudev + pixman-1 + wayland-server + xkbcommon +) +target_link_libraries(titlebars PRIVATE rt PkgConfig::deps) + +install(TARGETS titlebars LIBRARY DESTINATION lib/omarchy-hyprland-titlebars) diff --git a/pkgbuilds/omarchy-hyprland-titlebars/LICENSE b/pkgbuilds/omarchy-hyprland-titlebars/LICENSE new file mode 100644 index 0000000..eca5561 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, Hypr Development + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkgbuilds/omarchy-hyprland-titlebars/LICENSE.MIT b/pkgbuilds/omarchy-hyprland-titlebars/LICENSE.MIT new file mode 100644 index 0000000..f12cfa7 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/LICENSE.MIT @@ -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-hyprland-titlebars/PKGBUILD b/pkgbuilds/omarchy-hyprland-titlebars/PKGBUILD new file mode 100644 index 0000000..7e5307e --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/PKGBUILD @@ -0,0 +1,49 @@ +# Maintainer: Ryan Hughes + +pkgname=omarchy-hyprland-titlebars +pkgver=0.1.0 +pkgrel=1 +pkgdesc="Titlebars and edge snapping for Omarchy's floating workspaces" +arch=('x86_64' 'aarch64') +url='https://github.com/omacom/omarchy' +license=('BSD-3-Clause' 'MIT') +# A Hyprland plugin only loads into the exact Hyprland build it was compiled +# against. rebuild_on in .omarchy/package.json rebuilds it with every Hyprland. +depends=('hyprland=0.56.2' 'glibc' 'libgcc' 'libstdc++' 'hyprlang' 'hyprutils') +makedepends=('cmake' 'pkgconf') +options=('!lto' '!debug') +source=('CMakeLists.txt' + 'main.cpp' + 'barDeco.cpp' + 'barDeco.hpp' + 'BarPassElement.cpp' + 'BarPassElement.hpp' + 'edgeSnap.cpp' + 'edgeSnap.hpp' + 'globals.hpp' + 'LICENSE' + 'LICENSE.MIT') +sha256sums=('27a292b1b037210774d785a8db17d0e77aceba529a9946b7b555bd6333b9b8e8' + 'aa6e0c8cb22dc4d26516fa64aac81d1e493cf7a9b99d7a8bd78b82d61b2f2e89' + '2d9ab463efa263881c23110f9550b10694366e508418bfe06700dc9e18b31579' + 'dbdd56902558b36f7e7d01f7b9884530a93f7dda411a2b360acf69255a42c8f2' + 'e0074f71d8bbb3ae0a24a91a4647c4d0da6ab8c5effa0dce2b5a1bb8c85d085c' + 'dffbf8ddc768fd111b2a41697fe6547e954c46edc16750913c789d8daa8690cc' + 'a2b70dd8fc9974a9fca2f4a7db575d14c2c6802c154e4606a3c9e06c1b25a4e6' + '2ebf96f7e7508e4485d706c99c186d6fabf14ba5ceb7349fbc3d72529904424e' + '831147435d58b3071ad26fd756f9755eaf461793d664ea3cb049270b38228993' + '3a1d080d85ce1d722b3bd84d87b74dd2332ba7b68a40a28a7126a86cfca6502a' + '717ba1949502290f8e47688ae2e323acd06c8ca47aec9f7596b15f678c1af4a2') + +build() { + cmake -S "$srcdir" -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr + cmake --build build +} + +package() { + DESTDIR="$pkgdir" cmake --install build + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 LICENSE.MIT "$pkgdir/usr/share/licenses/$pkgname/LICENSE.MIT" +} diff --git a/pkgbuilds/omarchy-hyprland-titlebars/README.md b/pkgbuilds/omarchy-hyprland-titlebars/README.md new file mode 100644 index 0000000..07e71ed --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/README.md @@ -0,0 +1,91 @@ +# omarchy-hyprland-titlebars + +A native Hyprland plugin that gives Omarchy's floating workspace mode its window titlebars and edge snapping. It draws theme-neutral titlebars on floating windows and snaps a dragged window to the left or right half of the monitor, or maximizes it at the top edge. + +Omarchy core has no build step, so the plugin ships as its own package, built here alongside Hyprland. Omarchy loads it from its Lua config only while a workspace floats, from a fixed path: + +```lua +hl.plugin.load("/usr/lib/omarchy-hyprland-titlebars/titlebars.so") +``` + +That path is a contract with Omarchy core. The plugin still registers itself with Hyprland as `hyprbars` and keeps the `hl.plugin.hyprbars.add_button()` Lua interface and the `plugin.hyprbars.*` options, because Omarchy's config is written against those names. + +## Origin and license + +This is a fork of Hypr Development's [BSD-licensed hyprbars](https://github.com/hyprwm/hyprland-plugins/tree/main/hyprbars). The source was adapted from the Omarchy Windows 98 theme's `vendor/hyprbars` fork ([theme project](https://github.com/omacom/omarchy-98-theme)), which supplies Hyprland 0.56 Lua API support and safe coexistence with the earlier Windows XP fork. Colors, font, rounding, and actions are supplied by Omarchy's configuration; no theme styling is embedded in the renderer. + +The original hyprbars license is in `LICENSE` (BSD-3-Clause). Omarchy's changes are under the MIT license in `LICENSE.MIT`. The package installs both under `/usr/share/licenses/omarchy-hyprland-titlebars/`. + +## Added options + +These are in addition to the upstream `plugin.hyprbars` titlebar settings. + +| Option | Default | Behavior | +| --- | --- | --- | +| `edge_snap` | `false` | Enables edge snapping for moving floating windows. | +| `edge_threshold` | `24` | Edge activation distance in logical pixels. | +| `snap_gap` | `8` | Space around and between half-screen windows, in logical pixels. | +| `bar_color_inactive` | transparent | Inactive titlebar color; transparent falls back to `bar_color`. | +| `workspace_tag` | empty | When set, titlebars and edge snapping apply only to floating windows with this tag. Removing the tag or tiling the window immediately releases titlebar space. | + +## Window targeting with `%WINDOW%` + +Button `action` commands and `on_double_click` may contain `%WINDOW%`. Every occurrence is replaced synchronously with the clicked titlebar's window address, such as `0x1234abcd`, before the command is spawned. Use it to keep asynchronous actions targeted at the clicked window when focus changes before the command runs: + +```lua +hl.plugin.hyprbars.add_button({ + bg_color = "rgb(f7768e)", fg_color = "rgb(1a1b26)", size = 18, icon = "×", + action = [[hyprctl dispatch 'hl.dsp.window.close({ window = "address:%WINDOW%" })']], +}) +``` + +The replacement contains only `0x` and hexadecimal digits; no window title or other application-controlled text enters the command. + +## Edge snapping + +Left/right snapping divides the usable monitor area into equal halves, preserving the configured gap and accounting for the titlebar, window borders, monitor scale, and reserved panels. Windows whose minimum size cannot fit a half stay freely placed. The top edge uses Hyprland's native maximized state so decorations remain reachable and normal fullscreen behavior stays available. Dragging a snapped or maximized titlebar restores its floating size under the pointer. + +## `omarchy_snap_preview` event + +The plugin emits socket2 events only when the snap preview changes: + +```text +omarchy_snap_preview>>monitorName,left,x,y,width,height +omarchy_snap_preview>>monitorName,right,x,y,width,height +omarchy_snap_preview>>monitorName,maximize,x,y,width,height +omarchy_snap_preview>>,none,0,0,0,0 +``` + +Geometry is global and logical, including titlebar and borders. Omarchy's shell renders the preview. Leaving an edge, releasing the pointer, closing the dragged window, reloading configuration, or unloading the plugin clears it. + +## Hyprland ABI + +A Hyprland plugin is only compatible with the exact Hyprland build whose headers it was compiled against, and Hyprland refuses to load a mismatched build. `rebuild_on` in `.omarchy/package.json` rebuilds this package whenever `hyprland` changes. A new Hyprland version also needs the `hyprland=` dependency in `PKGBUILD` moved to it. + +Plugin-owned C++ symbols have an `OmarchyFloating` prefix and the library is built with `-fno-gnu-unique`, so it can be unloaded and reloaded cleanly alongside other hyprbars forks such as the Windows XP theme's. Do not use whole-library hidden visibility: Hyprland shares inline state through its headers. + +## Layout + +The plugin source sits beside `PKGBUILD` as local sources, since makepkg finds those by file name. `tools/verify.py` is a nested-compositor verifier, with `pointer.c` and the wlr virtual pointer protocol it uses to drive real pointer input; it is not part of the build. + +## Building + +```bash +bin/repo release --package omarchy-hyprland-titlebars +``` + +Or locally, with the Hyprland headers from the `hyprland` package installed: + +```bash +makepkg -f +``` + +## Verifying + +```bash +python3 tools/verify.py src/build/titlebars.so +``` + +The verifier needs a running Hyprland session with exactly one Hyprland instance, plus `foot`, `grim`, `wayland-scanner`, and a C compiler. It starts a disposable nested Hyprland with its own runtime directory and never loads the candidate library into the parent compositor. It checks repeated load/reload/unload, interleaved with the Windows XP fork when `/usr/lib/omarchy-windows-xp/hyprbars.so` is installed, then exercises real pointer titlebar dragging, both half-screen snaps, top-edge maximize, drag-down restoration, double-click maximize, and preview events. A two-window regression check deliberately delays button and double-click commands and changes focus before they run, to verify that actions keep the clicked window as their target. A final check confirms that `workspace_tag`-scoped titlebars release their space immediately when a window is tiled or untagged. + +Screenshots and logs go to `/tmp/omarchy-hyprland-titlebars-verification` by default; pass `--artifacts DIR` to change it. diff --git a/pkgbuilds/omarchy-hyprland-titlebars/barDeco.cpp b/pkgbuilds/omarchy-hyprland-titlebars/barDeco.cpp new file mode 100644 index 0000000..8c0d532 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/barDeco.cpp @@ -0,0 +1,744 @@ +#include "barDeco.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "globals.hpp" +#include "BarPassElement.hpp" +#include "edgeSnap.hpp" +#include + +#include +#include + +using namespace Render::GL; + +static CHyprColor configColor(Config::INTEGER color) { return CHyprColor{static_cast(color)}; } + +static std::string windowCommand(std::string command, const PHLWINDOW &window) { + // Resolve the decoration owner synchronously. An asynchronously spawned + // command must not choose its target from whatever happens to be focused. + constexpr std::string_view token = "%WINDOW%"; + const auto address = std::format("0x{:x}", reinterpret_cast(window.get())); + size_t offset = 0; + while ((offset = command.find(token, offset)) != std::string::npos) { + command.replace(offset, token.size(), address); + offset += address.size(); + } + return command; +} + +COmarchyFloatingHyprBar::COmarchyFloatingHyprBar(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow) { + m_pWindow = pWindow; + + const auto PMONITOR = pWindow->m_monitor.lock(); + PMONITOR->m_scheduledRecalc = true; + + // button events + m_pMouseButtonCallback = + Event::bus()->m_events.input.mouse.button.listen([&](IPointer::SButtonEvent e, Event::SCallbackInfo &info) { onMouseButton(info, e); }); + m_pTouchDownCallback = Event::bus()->m_events.input.touch.down.listen([&](ITouch::SDownEvent e, Event::SCallbackInfo &info) { onTouchDown(info, e); }); + m_pTouchUpCallback = Event::bus()->m_events.input.touch.up.listen([&](ITouch::SUpEvent e, Event::SCallbackInfo &info) { onTouchUp(info, e); }); + + // move events + m_pTouchMoveCallback = Event::bus()->m_events.input.touch.motion.listen([&](ITouch::SMotionEvent e, Event::SCallbackInfo &info) { onTouchMove(info, e); }); + m_pMouseMoveCallback = Event::bus()->m_events.input.mouse.move.listen([&](Vector2D c, Event::SCallbackInfo &info) { onMouseMove(c); }); + + Animation::mgr()->createAnimation(configColor(g_pOmarchyFloatingGlobalState->config.barColor->value()), m_cRealBarColor, + Config::animationTree()->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_NONE); + m_cRealBarColor->setUpdateCallback([&](auto) { damageEntire(); }); +} + +COmarchyFloatingHyprBar::~COmarchyFloatingHyprBar() { std::erase(g_pOmarchyFloatingGlobalState->bars, m_self); } + +SDecorationPositioningInfo COmarchyFloatingHyprBar::getPositioningInfo() { + const auto HEIGHT = g_pOmarchyFloatingGlobalState->config.barHeight->value(); + const auto ENABLED = g_pOmarchyFloatingGlobalState->config.enabled->value(); + const auto PRECEDENCE = g_pOmarchyFloatingGlobalState->config.barPrecedenceOverBorder->value(); + + SDecorationPositioningInfo info; + info.policy = m_hidden ? DECORATION_POSITION_ABSOLUTE : DECORATION_POSITION_STICKY; + info.edges = DECORATION_EDGE_TOP; + info.priority = PRECEDENCE ? 10005 : 5000; + info.reserved = true; + info.desiredExtents = {{0, m_hidden || !ENABLED ? 0 : HEIGHT}, {0, 0}}; + return info; +} + +void COmarchyFloatingHyprBar::onPositioningReply(const SDecorationPositioningReply &reply) { + if (reply.assignedGeometry.size() != m_bAssignedBox.size()) + m_bWindowSizeChanged = true; + + m_bAssignedBox = reply.assignedGeometry; +} + +std::string COmarchyFloatingHyprBar::getDisplayName() { return "Hyprbar"; } + +bool COmarchyFloatingHyprBar::inputIsValid() { + if (m_hidden || !g_pOmarchyFloatingGlobalState->config.enabled->value()) + return false; + + if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(m_pWindow->wlSurface()->resource())) + return false; + + const auto MOUSE = g_pInputManager->getMouseCoordsInternal(); + auto PMONITOR = Desktop::focusState()->monitor(); + + if (!PMONITOR) + return false; + + Desktop::CViewHitTester hitTester{*Desktop::viewState()}; + + const auto WINDOWATCURSOR = hitTester.windowAt(MOUSE, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); + + auto focusState = Desktop::focusState(); + auto window = focusState->window(); + + if (WINDOWATCURSOR != m_pWindow && m_pWindow != window) + return false; + + PHLLS foundSurface = nullptr; + Vector2D surfaceCoords; + + // Check Top Layer + hitTester.layerSurfaceAt(MOUSE, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &surfaceCoords, &foundSurface); + if (foundSurface) + return false; + + // Check Overlay Layer + hitTester.layerSurfaceAt(MOUSE, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &surfaceCoords, &foundSurface); + if (foundSurface) + return false; + + return true; +} + +void COmarchyFloatingHyprBar::onMouseButton(Event::SCallbackInfo &info, IPointer::SButtonEvent e) { + if (e.button != BTN_LEFT) + return; + + if (e.state != WL_POINTER_BUTTON_STATE_PRESSED) { + // A titlebar drag can finish over a panel or another surface. + if (m_bDraggingThis || m_bCancelledDown || m_bDragPending) + handleUpEvent(info); + return; + } + + if (!inputIsValid()) + return; + + handleDownEvent(info, std::nullopt); +} + +void COmarchyFloatingHyprBar::onTouchDown(Event::SCallbackInfo &info, ITouch::SDownEvent e) { + // Don't do anything if you're already grabbed a window with another finger + if (!inputIsValid() || e.touchID != 0) + return; + + handleDownEvent(info, e); +} + +void COmarchyFloatingHyprBar::onTouchUp(Event::SCallbackInfo &info, ITouch::SUpEvent e) { + if (!m_bDragPending || !m_bTouchEv || e.touchID != m_touchId) + return; + + handleUpEvent(info); +} + +void COmarchyFloatingHyprBar::onMouseMove(Vector2D coords) { + // ensure proper redraws of button icons on hover when using hardware cursors + if (g_pOmarchyFloatingGlobalState->config.iconOnHover->value()) + damageOnButtonHover(); + + if (!m_bDragPending || m_bTouchEv || !validMapped(m_pWindow) || m_touchId != 0) + return; + + m_bDragPending = false; + handleMovement(); +} + +void COmarchyFloatingHyprBar::onTouchMove(Event::SCallbackInfo &info, ITouch::SMotionEvent e) { + if (!m_bDragPending || !m_bTouchEv || !validMapped(m_pWindow) || e.touchID != m_touchId) + return; + + auto PMONITOR = m_pWindow->m_monitor.lock(); + PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); + const auto COORDS = Vector2D(PMONITOR->m_position.x + e.pos.x * PMONITOR->m_size.x, PMONITOR->m_position.y + e.pos.y * PMONITOR->m_size.y); + + if (!m_bDraggingThis) { + // Initial setup for dragging a window. + g_pKeybindManager->m_dispatchers["setfloating"]("activewindow"); + g_pKeybindManager->m_dispatchers["resizewindowpixel"]("exact 50% 50%,activewindow"); + // pin it so you can change workspaces while dragging a window + g_pKeybindManager->m_dispatchers["pin"]("activewindow"); + } + g_pKeybindManager->m_dispatchers["movewindowpixel"](std::format("exact {} {},activewindow", (int)(COORDS.x - (assignedBoxGlobal().w / 2)), (int)COORDS.y)); + m_bDraggingThis = true; +} + +void COmarchyFloatingHyprBar::handleDownEvent(Event::SCallbackInfo &info, std::optional touchEvent) { + m_bTouchEv = touchEvent.has_value(); + if (m_bTouchEv) + m_touchId = touchEvent.value().touchID; + + const auto PWINDOW = m_pWindow.lock(); + + auto COORDS = cursorRelativeToBar(); + if (m_bTouchEv) { + ITouch::SDownEvent e = touchEvent.value(); + PHLMONITOR PMONITOR = nullptr; + for (auto &m : State::monitorState()->monitors()) { + if (m->m_name == (!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : "")) { + PMONITOR = m; + break; + } + } + PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); + COORDS = Vector2D(PMONITOR->m_position.x + e.pos.x * PMONITOR->m_size.x, PMONITOR->m_position.y + e.pos.y * PMONITOR->m_size.y) - assignedBoxGlobal().pos(); + } + + const auto HEIGHT = g_pOmarchyFloatingGlobalState->config.barHeight->value(); + const auto BARBUTTONPADDING = g_pOmarchyFloatingGlobalState->config.barButtonPadding->value(); + const auto BARPADDING = g_pOmarchyFloatingGlobalState->config.barPadding->value(); + const auto ALIGNBUTTONS = g_pOmarchyFloatingGlobalState->config.barButtonsAlignment->value(); + const auto ON_DOUBLE_CLICK = g_pOmarchyFloatingGlobalState->config.onDoubleClick->value(); + + const bool BUTTONSRIGHT = ALIGNBUTTONS != "left"; + + if (!VECINRECT(COORDS, 0, 0, assignedBoxGlobal().w, HEIGHT - 1)) { + + if (m_bDraggingThis) { + if (m_bTouchEv) + g_pKeybindManager->m_dispatchers["settiled"]("activewindow"); + g_pKeybindManager->m_dispatchers["mouse"]("0movewindow"); + Log::logger->log(Log::DEBUG, "[hyprbars] Dragging ended on {:x}", (uintptr_t)PWINDOW.get()); + } + + m_bDraggingThis = false; + m_bDragPending = false; + m_bTouchEv = false; + return; + } + + if (Desktop::focusState()->window() != PWINDOW) + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_CLICK); + + if (PWINDOW->m_isFloating) + Desktop::windowState()->raise(PWINDOW); + + info.cancelled = true; + m_bCancelledDown = true; + + if (doButtonPress(BARPADDING, BARBUTTONPADDING, HEIGHT, COORDS, BUTTONSRIGHT)) + return; + + if (!ON_DOUBLE_CLICK.empty() && + std::chrono::duration_cast(Time::steadyNow() - m_lastMouseDown).count() < 400 /* Arbitrary delay I found suitable */) { + Config::Supplementary::executor()->spawn(windowCommand(ON_DOUBLE_CLICK, PWINDOW)); + m_bDragPending = false; + } else { + m_lastMouseDown = Time::steadyNow(); + m_bDragPending = true; + } +} + +void COmarchyFloatingHyprBar::handleUpEvent(Event::SCallbackInfo &info) { + if (m_pWindow.lock() != Desktop::focusState()->window()) + return; + + if (m_bCancelledDown) + info.cancelled = true; + + m_bCancelledDown = false; + + if (m_bDraggingThis) { + g_pKeybindManager->changeMouseBindMode(MBIND_INVALID); + m_bDraggingThis = false; + if (m_bTouchEv) + (void)Config::Actions::floatWindow(Config::Actions::eTogglableAction::TOGGLE_ACTION_DISABLE); + + Log::logger->log(Log::DEBUG, "[hyprbars] Dragging ended on {:x}", (uintptr_t)m_pWindow.lock().get()); + } + + m_bDragPending = false; + m_bTouchEv = false; + m_touchId = 0; +} + +void COmarchyFloatingHyprBar::handleMovement() { + const auto window = m_pWindow.lock(); + const auto cursor = g_pInputManager->getMouseCoordsInternal(); + const auto bar = assignedBoxGlobal(); + const auto anchor = std::clamp((cursor.x - bar.x) / std::max(1.0, bar.w), 0.0, 1.0); + const auto offset = std::clamp(cursor.y - bar.y, 0.0, bar.h); + const bool maximized = Fullscreen::controller()->isFullscreen(window, Fullscreen::FSMODE_MAXIMIZED); + if (maximized) + Fullscreen::controller()->setFullscreenMode(window, Fullscreen::FSMODE_NONE); + + const bool restored = omarchyFloatingRestoreSnap(window); + if (maximized || restored) { + auto box = window->layoutTarget()->position(); + const auto reserved = window->getFullWindowReservedArea(); + box.x = cursor.x - box.w * anchor; + box.y = cursor.y + reserved.topLeft.y - offset; + g_layoutManager->setTargetGeom(box, window->layoutTarget()); + window->layoutTarget()->warpPositionSize(); + } + g_pKeybindManager->changeMouseBindMode(MBIND_MOVE); + m_bDraggingThis = true; + Log::logger->log(Log::DEBUG, "[hyprbars] Dragging initiated on {:x}", (uintptr_t)m_pWindow.lock().get()); + return; +} + +bool COmarchyFloatingHyprBar::doButtonPress(Config::INTEGER barPadding, Config::INTEGER barButtonPadding, Config::INTEGER barHeight, Vector2D COORDS, + const bool BUTTONSRIGHT) { + // check if on a button + float offset = barPadding; + + for (auto &b : g_pOmarchyFloatingGlobalState->buttons) { + const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, barHeight}; + Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - barButtonPadding - b.size - offset : offset), (BARBUF.y - b.size) / 2.0}.floor(); + + if (VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + b.size + barButtonPadding, currentPos.y + b.size)) { + // hit on close + g_pKeybindManager->m_dispatchers["exec"](windowCommand(b.cmd, m_pWindow.lock())); + return true; + } + + offset += barButtonPadding + b.size; + } + return false; +} + +void COmarchyFloatingHyprBar::renderBarTitle(const Vector2D &bufferSize, const float scale) { + const auto COLORVAL = g_pOmarchyFloatingGlobalState->config.textColor->value(); + const auto SIZE = g_pOmarchyFloatingGlobalState->config.barTextSize->value(); + const auto WEIGHT = g_pOmarchyFloatingGlobalState->config.barTextWeight->value(); + const auto FONT = g_pOmarchyFloatingGlobalState->config.barTextFont->value(); + const auto ALIGN = g_pOmarchyFloatingGlobalState->config.barTextAlign->value(); + const auto BARPADDING = g_pOmarchyFloatingGlobalState->config.barPadding->value(); + const auto BARBUTTONPADDING = g_pOmarchyFloatingGlobalState->config.barButtonPadding->value(); + + float buttonSizes = BARBUTTONPADDING; + for (auto &b : g_pOmarchyFloatingGlobalState->buttons) { + buttonSizes += b.size + BARBUTTONPADDING; + } + + const int scaledSize = std::round(SIZE * scale); + const auto scaledButtonsSize = buttonSizes * scale; + const auto scaledBarPadding = BARPADDING * scale; + const int paddingTotal = scaledBarPadding * 2 + scaledButtonsSize + (ALIGN != "left" ? scaledButtonsSize : 0); + const int maxWidth = std::clamp(static_cast(bufferSize.x - paddingTotal), 0, INT_MAX); + + if (m_szLastTitle.empty() || maxWidth < 1) { + m_pTextTex = nullptr; + return; + } + + const CHyprColor COLOR = m_bForcedTitleColor.value_or(configColor(COLORVAL)); + m_pTextTex = g_pHyprRenderer->renderText(m_szLastTitle, COLOR, scaledSize, false, FONT, maxWidth, WEIGHT.m_value); +} + +size_t COmarchyFloatingHyprBar::getVisibleButtonCount(Config::INTEGER barButtonPadding, Config::INTEGER barPadding, const Vector2D &bufferSize, + const float scale) { + float availableSpace = bufferSize.x - barPadding * scale * 2; + size_t count = 0; + + for (const auto &button : g_pOmarchyFloatingGlobalState->buttons) { + const float buttonSpace = (button.size + barButtonPadding) * scale; + if (availableSpace >= buttonSpace) { + count++; + availableSpace -= buttonSpace; + } else + break; + } + + return count; +} + +void COmarchyFloatingHyprBar::renderBarButtons(CBox *barBox, const float scale, const float a) { + const auto BARBUTTONPADDING = g_pOmarchyFloatingGlobalState->config.barButtonPadding->value(); + const auto BARPADDING = g_pOmarchyFloatingGlobalState->config.barPadding->value(); + const auto ALIGNBUTTONS = g_pOmarchyFloatingGlobalState->config.barButtonsAlignment->value(); + const auto INACTIVECOLOR = g_pOmarchyFloatingGlobalState->config.inactiveButtonColor->value(); + const auto BUTTONROUNDING = g_pOmarchyFloatingGlobalState->config.buttonRounding->value(); + const auto BUTTONBORDERSIZE = g_pOmarchyFloatingGlobalState->config.buttonBorderSize->value(); + const auto BUTTONBORDERCOLOR = g_pOmarchyFloatingGlobalState->config.buttonBorderColor->value(); + + const bool BUTTONSRIGHT = ALIGNBUTTONS != "left"; + const auto visibleCount = getVisibleButtonCount(BARBUTTONPADDING, BARPADDING, Vector2D{barBox->w, barBox->h}, scale); + const bool INVALIDATEICONS = m_bButtonsDirty || m_bWindowSizeChanged; + + int offset = BARPADDING * scale; + for (size_t i = 0; i < visibleCount; ++i) { + auto &button = g_pOmarchyFloatingGlobalState->buttons[i]; + const auto scaledButtonSize = button.size * scale; + const auto scaledButtonsPad = BARBUTTONPADDING * scale; + + auto color = button.bgcol; + + if (INACTIVECOLOR > 0) { + color = m_bWindowHasFocus ? color : configColor(INACTIVECOLOR); + if (INVALIDATEICONS && button.userfg && button.iconTex) + button.iconTex = nullptr; + } + + color.a *= a; + + CBox buttonBox = {barBox->x + (BUTTONSRIGHT ? barBox->w - offset - scaledButtonSize : offset), barBox->y + (barBox->h - scaledButtonSize) / 2.0, + scaledButtonSize, scaledButtonSize}; + buttonBox.round(); + + const int maxRounding = static_cast(std::round(scaledButtonSize / 2.0)); + const int rounding = BUTTONROUNDING < 0 ? maxRounding : std::clamp(static_cast(std::round(BUTTONROUNDING * scale)), 0, maxRounding); + const int borderSize = std::clamp(static_cast(std::round(BUTTONBORDERSIZE * scale)), 0, maxRounding); + + if (borderSize > 0) { + auto borderColor = configColor(BUTTONBORDERCOLOR); + borderColor.a *= a; + g_pHyprOpenGL->renderRect(buttonBox, borderColor, {.round = rounding}); + auto face = buttonBox.copy().expand(-borderSize); + g_pHyprOpenGL->renderRect(face, color, {.round = std::max(0, rounding - borderSize)}); + } else + g_pHyprOpenGL->renderRect(buttonBox, color, {.round = rounding}); + + offset += scaledButtonsPad + scaledButtonSize; + } +} + +void COmarchyFloatingHyprBar::renderBarButtonsText(CBox *barBox, const float scale, const float a) { + const auto HEIGHT = g_pOmarchyFloatingGlobalState->config.barHeight->value(); + const auto BARBUTTONPADDING = g_pOmarchyFloatingGlobalState->config.barButtonPadding->value(); + const auto BARPADDING = g_pOmarchyFloatingGlobalState->config.barPadding->value(); + const auto ALIGNBUTTONS = g_pOmarchyFloatingGlobalState->config.barButtonsAlignment->value(); + const auto ICONONHOVER = g_pOmarchyFloatingGlobalState->config.iconOnHover->value(); + + const bool BUTTONSRIGHT = ALIGNBUTTONS != "left"; + const auto visibleCount = getVisibleButtonCount(BARBUTTONPADDING, BARPADDING, Vector2D{barBox->w, barBox->h}, scale); + const auto COORDS = cursorRelativeToBar(); + + int offset = BARPADDING * scale; + float noScaleOffset = BARPADDING; + + for (size_t i = 0; i < visibleCount; ++i) { + auto &button = g_pOmarchyFloatingGlobalState->buttons[i]; + const auto scaledButtonSize = button.size * scale; + const auto scaledButtonsPad = BARBUTTONPADDING * scale; + + // check if hovering here + const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, HEIGHT}; + Vector2D currentPos = + Vector2D{(BUTTONSRIGHT ? BARBUF.x - BARBUTTONPADDING - button.size - noScaleOffset : noScaleOffset), (BARBUF.y - button.size) / 2.0}.floor(); + bool hovering = VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + button.size + BARBUTTONPADDING, currentPos.y + button.size); + noScaleOffset += BARBUTTONPADDING + button.size; + + if ((!button.iconTex || button.iconTex->m_texID == 0) && !button.icon.empty()) { + // render icon + auto fgcol = button.userfg ? button.fgcol : (button.bgcol.r + button.bgcol.g + button.bgcol.b < 1) ? CHyprColor(0xFFFFFFFF) : CHyprColor(0xFF000000); + + button.iconTex = g_pHyprRenderer->renderText(button.icon, fgcol, std::round(button.size * 0.62 * scale), false, "sans", scaledButtonSize); + } + + if (!button.iconTex || button.iconTex->m_texID == 0) + continue; + + const auto iconX = + barBox->x + (BUTTONSRIGHT ? barBox->width - offset - scaledButtonSize / 2.0 : offset + scaledButtonSize / 2.0) - button.iconTex->m_size.x / 2.0; + const auto iconY = barBox->y + barBox->height / 2.0 - button.iconTex->m_size.y / 2.0; + CBox pos = {iconX, iconY, button.iconTex->m_size.x, button.iconTex->m_size.y}; + + if (!ICONONHOVER || (ICONONHOVER && m_iButtonHoverState > 0)) + g_pHyprOpenGL->renderTexture(button.iconTex, pos, {.a = a}); + offset += scaledButtonsPad + scaledButtonSize; + + bool currentBit = (m_iButtonHoverState & (1 << i)) != 0; + if (hovering != currentBit) { + m_iButtonHoverState ^= (1 << i); + // damage to get rid of some artifacts when icons are "hidden" + damageEntire(); + } + } +} + +void COmarchyFloatingHyprBar::draw(PHLMONITOR pMonitor, const float &a) { + const auto ENABLED = g_pOmarchyFloatingGlobalState->config.enabled->value(); + + if (m_bLastEnabledState != ENABLED) { + m_bLastEnabledState = ENABLED; + g_pDecorationPositioner->repositionDeco(this); + } + + if (m_hidden || !validMapped(m_pWindow) || !ENABLED) + return; + + const auto PWINDOW = m_pWindow.lock(); + + if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) + return; + + auto data = COmarchyFloatingBarPassElement::SBarData{this, a}; + g_pHyprRenderer->m_renderPass.add(makeUnique(data)); +} + +void COmarchyFloatingHyprBar::renderPass(PHLMONITOR pMonitor, const float &a) { + const auto PWINDOW = m_pWindow.lock(); + + static auto PENABLEBLURGLOBAL = CConfigValue("decoration:blur:enabled"); + const auto BARCOLOR = g_pOmarchyFloatingGlobalState->config.barColor->value(); + const auto HEIGHT = g_pOmarchyFloatingGlobalState->config.barHeight->value(); + const auto PRECEDENCE = g_pOmarchyFloatingGlobalState->config.barPrecedenceOverBorder->value(); + const auto ALIGNBUTTONS = g_pOmarchyFloatingGlobalState->config.barButtonsAlignment->value(); + const auto ENABLETITLE = g_pOmarchyFloatingGlobalState->config.barTitleEnabled->value(); + const auto ENABLEBLUR = g_pOmarchyFloatingGlobalState->config.barBlur->value(); + const auto INACTIVECOLOR = g_pOmarchyFloatingGlobalState->config.inactiveButtonColor->value(); + + if (INACTIVECOLOR > 0) { + bool currentWindowFocus = PWINDOW == Desktop::focusState()->window(); + if (currentWindowFocus != m_bWindowHasFocus) { + m_bWindowHasFocus = currentWindowFocus; + m_bButtonsDirty = true; + } + } + + const auto INACTIVEBAR = g_pOmarchyFloatingGlobalState->config.inactiveBarColor->value(); + const auto baseColor = PWINDOW != Desktop::focusState()->window() && INACTIVEBAR != 0 ? INACTIVEBAR : BARCOLOR; + const CHyprColor DEST_COLOR = m_bForcedBarColor.value_or(configColor(baseColor)); + if (DEST_COLOR != m_cRealBarColor->goal()) + *m_cRealBarColor = DEST_COLOR; + + CHyprColor color = m_cRealBarColor->value(); + + color.a *= a; + const bool BUTTONSRIGHT = ALIGNBUTTONS != "left"; + const bool SHOULDBLUR = ENABLEBLUR && *PENABLEBLURGLOBAL && color.a < 1.F; + + if (HEIGHT < 1) { + m_iLastHeight = HEIGHT; + return; + } + + const auto PWORKSPACE = PWINDOW->m_workspace; + const auto WORKSPACEOFFSET = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); + + const auto ROUNDING = PWINDOW->rounding() + (PRECEDENCE ? 0 : PWINDOW->getRealBorderSize()); + + const auto scaledRounding = ROUNDING > 0 ? ROUNDING * pMonitor->m_scale - 2 /* idk why but otherwise it looks bad due to the gaps */ : 0; + + m_seExtents = {{0, HEIGHT}, {}}; + + const auto DECOBOX = assignedBoxGlobal(); + + const auto BARBUF = DECOBOX.size() * pMonitor->m_scale; + + CBox titleBarBox = {DECOBOX.x - pMonitor->m_position.x, DECOBOX.y - pMonitor->m_position.y, DECOBOX.w, + DECOBOX.h + ROUNDING * 3 /* to fill the bottom cuz we can't disable rounding there */}; + + titleBarBox.translate(PWINDOW->m_floatingOffset).scale(pMonitor->m_scale).round(); + + if (titleBarBox.w < 1 || titleBarBox.h < 1) + return; + + g_pHyprOpenGL->scissor(titleBarBox); + + if (ROUNDING) { + // the +1 is a shit garbage temp fix until renderRect supports an alpha matte + CBox windowBox = {PWINDOW->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x + PWINDOW->m_floatingOffset.x - pMonitor->m_position.x + 1, + PWINDOW->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y + PWINDOW->m_floatingOffset.y - pMonitor->m_position.y + 1, + PWINDOW->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x - 2, PWINDOW->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y - 2}; + + if (windowBox.w < 1 || windowBox.h < 1) + return; + + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + + g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, true); + + glStencilFunc(GL_ALWAYS, 1, -1); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + windowBox.translate(WORKSPACEOFFSET).scale(pMonitor->m_scale).round(); + g_pHyprOpenGL->renderRect(windowBox, CHyprColor(0, 0, 0, 0), {.round = scaledRounding, .roundingPower = m_pWindow->roundingPower()}); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glStencilFunc(GL_NOTEQUAL, 1, -1); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + } + + g_pHyprOpenGL->renderRect(titleBarBox, color, {.round = static_cast(scaledRounding), .roundingPower = PWINDOW->roundingPower()}); + + // render title + if (ENABLETITLE && (m_szLastTitle != PWINDOW->m_title || m_bWindowSizeChanged || !m_pTextTex || m_pTextTex->m_texID == 0 || m_bTitleColorChanged)) { + m_szLastTitle = PWINDOW->m_title; + renderBarTitle(BARBUF, pMonitor->m_scale); + } + + if (ROUNDING) { + // cleanup stencil + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, false); + glStencilMask(-1); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + } + + CBox textBox = {titleBarBox.x, titleBarBox.y, (int)BARBUF.x, (int)BARBUF.y}; + if (ENABLETITLE && m_pTextTex) { + const auto BARPADDING = g_pOmarchyFloatingGlobalState->config.barPadding->value(); + const auto BARBUTTONPADDING = g_pOmarchyFloatingGlobalState->config.barButtonPadding->value(); + const auto ALIGN = g_pOmarchyFloatingGlobalState->config.barTextAlign->value(); + + float buttonSizes = BARBUTTONPADDING; + for (auto &b : g_pOmarchyFloatingGlobalState->buttons) { + buttonSizes += b.size + BARBUTTONPADDING; + } + + const auto scaledBorderSize = PWINDOW->getRealBorderSize() * pMonitor->m_scale; + const auto scaledButtonsSize = buttonSizes * pMonitor->m_scale; + const auto scaledBarPadding = BARPADDING * pMonitor->m_scale; + const auto xOffset = ALIGN == "left" ? std::round(scaledBarPadding + (BUTTONSRIGHT ? 0 : scaledButtonsSize)) + : std::round(((BARBUF.x - scaledBorderSize) / 2.0 - m_pTextTex->m_size.x / 2.0)); + const auto yOffset = std::round((BARBUF.y - m_pTextTex->m_size.y) / 2.0); + CBox titleBox = {textBox.x + xOffset, textBox.y + yOffset, m_pTextTex->m_size.x, m_pTextTex->m_size.y}; + + g_pHyprOpenGL->renderTexture(m_pTextTex, titleBox, {.a = a}); + } + + renderBarButtons(&textBox, pMonitor->m_scale, a); + m_bButtonsDirty = false; + + g_pHyprOpenGL->scissor(nullptr); + + renderBarButtonsText(&textBox, pMonitor->m_scale, a); + + m_bWindowSizeChanged = false; + m_bTitleColorChanged = false; + + // dynamic updates change the extents + if (m_iLastHeight != HEIGHT) { + PWINDOW->layoutTarget()->recalc(); + m_iLastHeight = HEIGHT; + } +} + +eDecorationType COmarchyFloatingHyprBar::getDecorationType() { return DECORATION_CUSTOM; } + +void COmarchyFloatingHyprBar::updateWindow(PHLWINDOW pWindow) { + updateRules(); + damageEntire(); +} + +void COmarchyFloatingHyprBar::onConfigReloaded() { + m_bButtonsDirty = true; + m_bTitleColorChanged = true; + m_pTextTex = nullptr; + + g_pDecorationPositioner->repositionDeco(this); + damageEntire(); +} + +void COmarchyFloatingHyprBar::damageEntire() { g_pHyprRenderer->damageBox(assignedBoxGlobal()); } + +Vector2D COmarchyFloatingHyprBar::cursorRelativeToBar() { return g_pInputManager->getMouseCoordsInternal() - assignedBoxGlobal().pos(); } + +eDecorationLayer COmarchyFloatingHyprBar::getDecorationLayer() { return DECORATION_LAYER_UNDER; } + +uint64_t COmarchyFloatingHyprBar::getDecorationFlags() { + return DECORATION_ALLOWS_MOUSE_INPUT | (g_pOmarchyFloatingGlobalState->config.barPartOfWindow->value() ? DECORATION_PART_OF_MAIN_WINDOW : 0); +} + +CBox COmarchyFloatingHyprBar::assignedBoxGlobal() { + if (!validMapped(m_pWindow)) + return {}; + + CBox box = m_bAssignedBox; + box.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_TOP, m_pWindow.lock())); + + const auto PWORKSPACE = m_pWindow->m_workspace; + const auto WORKSPACEOFFSET = PWORKSPACE && !m_pWindow->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); + + return box.translate(WORKSPACEOFFSET); +} + +PHLWINDOW COmarchyFloatingHyprBar::getOwner() { return m_pWindow.lock(); } + +void COmarchyFloatingHyprBar::updateRules() { + const auto PWINDOW = m_pWindow.lock(); + if (!PWINDOW) + return; + auto prevHidden = m_hidden; + auto prevForcedTitleColor = m_bForcedTitleColor; + + m_bForcedBarColor = std::nullopt; + m_bForcedTitleColor = std::nullopt; + m_hidden = false; + + if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pOmarchyFloatingGlobalState->nobarRuleIdx)) + m_hidden = truthy(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pOmarchyFloatingGlobalState->nobarRuleIdx)->effect); + const auto workspaceTag = g_pOmarchyFloatingGlobalState->config.workspaceTag->value(); + if (!workspaceTag.empty()) + m_hidden = m_hidden || !PWINDOW->m_isFloating || !PWINDOW->m_ruleApplicator->m_tagKeeper.isTagged(workspaceTag); + if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pOmarchyFloatingGlobalState->barColorRuleIdx)) + m_bForcedBarColor = CHyprColor( + Config::ParserUtils::parseColor(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pOmarchyFloatingGlobalState->barColorRuleIdx)->effect).value_or(0)); + if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pOmarchyFloatingGlobalState->titleColorRuleIdx)) + m_bForcedTitleColor = + CHyprColor(Config::ParserUtils::parseColor(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pOmarchyFloatingGlobalState->titleColorRuleIdx)->effect) + .value_or(0)); + + if (prevHidden != m_hidden) { + g_pDecorationPositioner->repositionDeco(this); + if (const auto monitor = PWINDOW->m_monitor.lock()) + monitor->m_scheduledRecalc = true; + } + if (prevForcedTitleColor != m_bForcedTitleColor) + m_bTitleColorChanged = true; +} + +void COmarchyFloatingHyprBar::damageOnButtonHover() { + const auto BARPADDING = g_pOmarchyFloatingGlobalState->config.barPadding->value(); + const auto BARBUTTONPADDING = g_pOmarchyFloatingGlobalState->config.barButtonPadding->value(); + const auto HEIGHT = g_pOmarchyFloatingGlobalState->config.barHeight->value(); + const auto ALIGNBUTTONS = g_pOmarchyFloatingGlobalState->config.barButtonsAlignment->value(); + const bool BUTTONSRIGHT = ALIGNBUTTONS != "left"; + + float offset = BARPADDING; + + const auto COORDS = cursorRelativeToBar(); + + for (auto &b : g_pOmarchyFloatingGlobalState->buttons) { + const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, HEIGHT}; + Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - BARBUTTONPADDING - b.size - offset : offset), (BARBUF.y - b.size) / 2.0}.floor(); + + bool hover = VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + b.size + BARBUTTONPADDING, currentPos.y + b.size); + + if (hover != m_bButtonHovered) { + m_bButtonHovered = hover; + damageEntire(); + } + + offset += BARBUTTONPADDING + b.size; + } +} diff --git a/pkgbuilds/omarchy-hyprland-titlebars/barDeco.hpp b/pkgbuilds/omarchy-hyprland-titlebars/barDeco.hpp new file mode 100644 index 0000000..bf19e85 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/barDeco.hpp @@ -0,0 +1,124 @@ +#pragma once + +#define WLR_USE_UNSTABLE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "globals.hpp" + +#define private public +#include +#undef private + +namespace Event { +struct SCallbackInfo; +} + +class COmarchyFloatingHyprBar : public IHyprWindowDecoration { +public: + COmarchyFloatingHyprBar(PHLWINDOW); + virtual ~COmarchyFloatingHyprBar(); + + virtual SDecorationPositioningInfo getPositioningInfo(); + + virtual void onPositioningReply(const SDecorationPositioningReply &reply); + + virtual void draw(PHLMONITOR, float const &a); + + virtual eDecorationType getDecorationType(); + + virtual void updateWindow(PHLWINDOW); + + virtual void damageEntire(); + + virtual eDecorationLayer getDecorationLayer(); + + virtual uint64_t getDecorationFlags(); + + bool m_bButtonsDirty = true; + + virtual std::string getDisplayName(); + + PHLWINDOW getOwner(); + + void updateRules(); + void onConfigReloaded(); + + WP m_self; + +private: + SBoxExtents m_seExtents; + + PHLWINDOWREF m_pWindow; + + CBox m_bAssignedBox; + + SP m_pTextTex; + + bool m_bWindowSizeChanged = false; + bool m_hidden = false; + bool m_bTitleColorChanged = false; + bool m_bButtonHovered = false; + bool m_bLastEnabledState = false; + bool m_bWindowHasFocus = false; + std::optional m_bForcedBarColor; + std::optional m_bForcedTitleColor; + + Time::steady_tp m_lastMouseDown{}; + + PHLANIMVAR m_cRealBarColor; + + Vector2D cursorRelativeToBar(); + + void renderPass(PHLMONITOR, float const &a); + void renderBarTitle(const Vector2D &bufferSize, const float scale); + void renderBarButtons(CBox *barBox, const float scale, const float a); + void renderBarButtonsText(CBox *barBox, const float scale, const float a); + void damageOnButtonHover(); + + bool inputIsValid(); + void onMouseButton(Event::SCallbackInfo &info, IPointer::SButtonEvent e); + void onTouchDown(Event::SCallbackInfo &info, ITouch::SDownEvent e); + void onTouchUp(Event::SCallbackInfo &info, ITouch::SUpEvent e); + void onMouseMove(Vector2D coords); + void onTouchMove(Event::SCallbackInfo &info, ITouch::SMotionEvent e); + + void handleDownEvent(Event::SCallbackInfo &info, std::optional touchEvent); + void handleUpEvent(Event::SCallbackInfo &info); + void handleMovement(); + bool doButtonPress(Config::INTEGER barPadding, Config::INTEGER barButtonPadding, Config::INTEGER barHeight, Vector2D COORDS, bool BUTTONSRIGHT); + + CBox assignedBoxGlobal(); + + CHyprSignalListener m_pMouseButtonCallback; + CHyprSignalListener m_pTouchDownCallback; + CHyprSignalListener m_pTouchUpCallback; + + CHyprSignalListener m_pTouchMoveCallback; + CHyprSignalListener m_pMouseMoveCallback; + + std::string m_szLastTitle; + + bool m_bDraggingThis = false; + bool m_bTouchEv = false; + bool m_bDragPending = false; + bool m_bCancelledDown = false; + int m_touchId = 0; + + // store hover state for buttons as a bitfield + unsigned int m_iButtonHoverState = 0; + + // for dynamic updates + int m_iLastHeight = 0; + + size_t getVisibleButtonCount(Config::INTEGER barButtonPadding, Config::INTEGER barPadding, const Vector2D &bufferSize, const float scale); + + friend class COmarchyFloatingBarPassElement; +}; diff --git a/pkgbuilds/omarchy-hyprland-titlebars/edgeSnap.cpp b/pkgbuilds/omarchy-hyprland-titlebars/edgeSnap.cpp new file mode 100644 index 0000000..e1dc1f6 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/edgeSnap.cpp @@ -0,0 +1,160 @@ +#include "edgeSnap.hpp" +#include "globals.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { +PHLWINDOWREF dragged; +CBox originalBox; +CBox snapBox; +std::string zone; +std::string preview; +std::unordered_map restoreBoxes; +std::vector listeners; + +void hidePreview() { + if (!preview.empty()) + g_pEventManager->postEvent({"omarchy_snap_preview", ",none,0,0,0,0"}); + preview.clear(); + zone.clear(); +} + +bool enabled() { return g_pOmarchyFloatingGlobalState->config.enabled->value() && g_pOmarchyFloatingGlobalState->config.edgeSnap->value(); } + +void updatePreview(const Vector2D &cursor) { + const auto &drag = g_layoutManager->dragController(); + const auto target = drag->target(); + if (!enabled() || drag->mode() != MBIND_MOVE || !target || !target->floating() || !validMapped(target->window())) { + omarchyFloatingSnapReset(); + return; + } + + const auto window = target->window(); + const auto workspaceTag = g_pOmarchyFloatingGlobalState->config.workspaceTag->value(); + if (!workspaceTag.empty() && !window->m_ruleApplicator->m_tagKeeper.isTagged(workspaceTag)) { + omarchyFloatingSnapReset(); + return; + } + if (dragged != window) { + dragged = window; + originalBox = target->position(); + } + + const auto monitor = State::monitorState()->query().vec(cursor).run(); + if (!monitor) { + hidePreview(); + return; + } + + const auto work = monitor->logicalBoxMinusReserved(); + const auto threshold = std::clamp(g_pOmarchyFloatingGlobalState->config.edgeThreshold->value(), 1, 100); + const auto gap = std::clamp(g_pOmarchyFloatingGlobalState->config.snapGap->value(), 0, 100); + std::string nextZone; + if (cursor.y <= work.y + threshold) + nextZone = "maximize"; + else if (cursor.x <= monitor->m_position.x + threshold) + nextZone = "left"; + else if (cursor.x >= monitor->m_position.x + monitor->m_size.x - threshold) + nextZone = "right"; + + if (nextZone.empty()) { + hidePreview(); + return; + } + + auto box = nextZone == "maximize" ? work : work.copy().expand(-gap); + if (nextZone != "maximize") { + box.w = std::floor((work.w - 3 * gap) / 2.0); + if (nextZone == "right") + box.x = work.x + work.w - gap - box.w; + } + + const auto reserved = window->getFullWindowReservedArea(); + const auto min = target->minSize().value_or(Vector2D{1, 1}); + // Applications with a larger minimum cannot occupy a half without overlap. + // Keep their free placement instead of previewing geometry we cannot honor. + if (nextZone != "maximize" && (box.w - reserved.topLeft.x - reserved.bottomRight.x < min.x || box.h - reserved.topLeft.y - reserved.bottomRight.y < min.y)) { + hidePreview(); + return; + } + + zone = nextZone; + snapBox = box; + const auto next = std::format("{},{},{:.0f},{:.0f},{:.0f},{:.0f}", monitor->m_name, zone, box.x, box.y, box.w, box.h); + if (next != preview) { + preview = next; + g_pEventManager->postEvent({"omarchy_snap_preview", preview}); + } +} + +void release(IPointer::SButtonEvent event) { + if (event.button != BTN_LEFT || event.state != WL_POINTER_BUTTON_STATE_RELEASED) + return; + + const auto window = dragged.lock(); + const auto target = g_layoutManager->dragController()->target(); + const auto nextZone = zone; + const auto box = snapBox; + const auto before = originalBox; + const bool commit = + enabled() && validMapped(window) && target && target->window() == window && g_layoutManager->dragController()->mode() == MBIND_MOVE && !nextZone.empty(); + omarchyFloatingSnapReset(); + if (!commit) + return; + + // End the native drag before changing geometry, otherwise its final motion + // overwrites the snap. The titlebar's release handler is idempotent. + g_pKeybindManager->changeMouseBindMode(MBIND_INVALID); + if (nextZone == "maximize") { + restoreBoxes.erase(window); + Fullscreen::controller()->setFullscreenMode(window, Fullscreen::FSMODE_MAXIMIZED); + } else { + restoreBoxes.try_emplace(window, before); + Fullscreen::controller()->setFullscreenMode(window, Fullscreen::FSMODE_NONE); + const auto reserved = window->getFullWindowReservedArea(); + const CBox clientBox{box.pos() + reserved.topLeft, box.size() - reserved.topLeft - reserved.bottomRight}; + g_layoutManager->setTargetGeom(clientBox, window->layoutTarget()); + } +} +} // namespace + +void omarchyFloatingSnapReset() { + hidePreview(); + dragged.reset(); +} + +bool omarchyFloatingRestoreSnap(PHLWINDOW window) { + const auto found = restoreBoxes.find(window); + if (found == restoreBoxes.end()) + return false; + const auto box = found->second; + restoreBoxes.erase(found); + g_layoutManager->setTargetGeom(box, window->layoutTarget()); + return true; +} + +void omarchyFloatingSnapInit() { + listeners.emplace_back(Event::bus()->m_events.input.mouse.move.listen([](Vector2D cursor, Event::SCallbackInfo &) { updatePreview(cursor); })); + listeners.emplace_back(Event::bus()->m_events.input.mouse.button.listen([](IPointer::SButtonEvent event, Event::SCallbackInfo &) { release(event); })); + listeners.emplace_back(Event::bus()->m_events.window.close.listen([](PHLWINDOW window) { + restoreBoxes.erase(window); + if (dragged == window) + omarchyFloatingSnapReset(); + })); +} + +void omarchyFloatingSnapExit() { + omarchyFloatingSnapReset(); + listeners.clear(); + restoreBoxes.clear(); +} diff --git a/pkgbuilds/omarchy-hyprland-titlebars/edgeSnap.hpp b/pkgbuilds/omarchy-hyprland-titlebars/edgeSnap.hpp new file mode 100644 index 0000000..bbcc38a --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/edgeSnap.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +void omarchyFloatingSnapInit(); +void omarchyFloatingSnapReset(); +void omarchyFloatingSnapExit(); +bool omarchyFloatingRestoreSnap(PHLWINDOW window); diff --git a/pkgbuilds/omarchy-hyprland-titlebars/globals.hpp b/pkgbuilds/omarchy-hyprland-titlebars/globals.hpp new file mode 100644 index 0000000..3d35694 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/globals.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +inline HANDLE OMARCHY_FLOATING_PHANDLE = nullptr; + +struct SOmarchyFloatingHyprButton { + std::string cmd = ""; + bool userfg = false; + CHyprColor fgcol = CHyprColor(0, 0, 0, 0); + CHyprColor bgcol = CHyprColor(0, 0, 0, 0); + float size = 10; + std::string icon = ""; + SP iconTex; +}; + +class COmarchyFloatingHyprBar; + +struct SOmarchyFloatingGlobalState { + std::vector buttons; + std::vector> bars; + uint32_t nobarRuleIdx = 0; + uint32_t barColorRuleIdx = 0; + uint32_t titleColorRuleIdx = 0; + + struct { + SP barColor, textColor, inactiveBarColor, inactiveButtonColor, buttonBorderColor; + SP barHeight; + SP barTextSize; + SP barTextWeight; + SP barPadding; + SP barButtonPadding; + SP buttonRounding; + SP buttonBorderSize; + SP edgeThreshold, snapGap; + SP barBlur, barTitleEnabled, barPartOfWindow, barPrecedenceOverBorder, enabled, iconOnHover, edgeSnap; + SP barTextFont, barTextAlign, barButtonsAlignment, onDoubleClick, workspaceTag; + } config; +}; + +inline UP g_pOmarchyFloatingGlobalState; diff --git a/pkgbuilds/omarchy-hyprland-titlebars/main.cpp b/pkgbuilds/omarchy-hyprland-titlebars/main.cpp new file mode 100644 index 0000000..74df825 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/main.cpp @@ -0,0 +1,324 @@ +#define WLR_USE_UNSTABLE + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "barDeco.hpp" +#include "globals.hpp" +#include "edgeSnap.hpp" + +extern "C" { +#include +#include +} + +// Do NOT change this function. +APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } + +static void onNewWindow(PHLWINDOW window) { + if (!window->m_X11DoesntWantBorders) { + if (std::ranges::any_of(window->m_windowDecorations, [](const auto &d) { return d->getDisplayName() == "Hyprbar"; })) + return; + + auto bar = makeUnique(window); + g_pOmarchyFloatingGlobalState->bars.emplace_back(bar); + bar->m_self = bar; + HyprlandAPI::addWindowDecoration(OMARCHY_FLOATING_PHANDLE, window, std::move(bar)); + } +} + +static void onPreConfigReload() { + omarchyFloatingSnapReset(); + g_pOmarchyFloatingGlobalState->buttons.clear(); +} + +static void onConfigReloaded() { + for (auto &b : g_pOmarchyFloatingGlobalState->bars) { + if (!b) + continue; + + b->onConfigReloaded(); + } +} + +static void onUpdateWindowRules(PHLWINDOW window) { + const auto BARIT = std::find_if(g_pOmarchyFloatingGlobalState->bars.begin(), g_pOmarchyFloatingGlobalState->bars.end(), + [window](const auto &bar) { return bar->getOwner() == window; }); + + if (BARIT == g_pOmarchyFloatingGlobalState->bars.end()) + return; + + (*BARIT)->updateRules(); + window->updateWindowDecos(); +} + +Hyprlang::CParseResult onNewOmarchyFloatingButton(const char *K, const char *V) { + std::string v = V; + Hyprutils::String::CVarList vars(v); + + Hyprlang::CParseResult result; + + // hyprbars-button = bgcolor, size, icon, action, fgcolor + + if (vars[0].empty() || vars[1].empty()) { + result.setError("bgcolor and size cannot be empty"); + return result; + } + + float size = 10; + try { + size = std::stof(vars[1]); + } catch (std::exception &e) { + result.setError("failed to parse size"); + return result; + } + + bool userfg = false; + auto fgcolor = Config::ParserUtils::parseColor("rgb(ffffff)"); + auto bgcolor = Config::ParserUtils::parseColor(vars[0]); + + if (!bgcolor) { + result.setError("invalid bgcolor"); + return result; + } + + if (vars.size() == 5) { + userfg = true; + fgcolor = Config::ParserUtils::parseColor(vars[4]); + } + + if (!fgcolor) { + result.setError("invalid fgcolor"); + return result; + } + + g_pOmarchyFloatingGlobalState->buttons.push_back(SOmarchyFloatingHyprButton{vars[3], userfg, *fgcolor, *bgcolor, size, vars[2]}); + + for (auto &b : g_pOmarchyFloatingGlobalState->bars) { + b->m_bButtonsDirty = true; + } + + return result; +} + +int newOmarchyFloatingLuaButton(lua_State *L) { + if (!lua_istable(L, 1)) + return Config::Lua::Bindings::Internal::configError(L, "add_button: expected a table { bg_color, fg_color, size, icon, action }"); + + SOmarchyFloatingHyprButton button; + + { + Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 1, "bg_color"); + + Config::Lua::CLuaConfigColor parser(0); + auto err = parser.parse(L); + if (err.errorCode != Config::Lua::PARSE_ERROR_OK) + return Config::Lua::Bindings::Internal::configError(L, "add_button: failed to parse bg_color"); + + button.bgcol = parser.parsed(); + } + + { + Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 1, "fg_color"); + + Config::Lua::CLuaConfigColor parser(0); + auto err = parser.parse(L); + if (err.errorCode != Config::Lua::PARSE_ERROR_OK) + return Config::Lua::Bindings::Internal::configError(L, "add_button: failed to parse fg_color"); + + button.userfg = true; + button.fgcol = parser.parsed(); + } + + { + Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 1, "size"); + + if (!lua_isnumber(L, -1)) + return Config::Lua::Bindings::Internal::configError(L, "add_button: size must be an integer"); + + button.size = lua_tointeger(L, -1); + } + + { + Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 1, "icon"); + + if (!lua_isstring(L, -1)) + return Config::Lua::Bindings::Internal::configError(L, "add_button: icon must be a string"); + + button.icon = lua_tostring(L, -1); + } + + { + Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 1, "action"); + + if (!lua_isstring(L, -1)) + return Config::Lua::Bindings::Internal::configError(L, "add_button: action must be a string"); + + button.cmd = lua_tostring(L, -1); + } + + g_pOmarchyFloatingGlobalState->buttons.push_back(std::move(button)); + + for (auto &b : g_pOmarchyFloatingGlobalState->bars) { + b->m_bButtonsDirty = true; + } + + return 0; +} + +static PLUGIN_DESCRIPTION_INFO initializeOmarchyFloating(HANDLE handle) { + OMARCHY_FLOATING_PHANDLE = handle; + + const std::string HASH = __hyprland_api_get_hash(); + const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); + + if (HASH != CLIENT_HASH) { + HyprlandAPI::addNotification(OMARCHY_FLOATING_PHANDLE, + "[hyprbars] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", + CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); + throw std::runtime_error("[hb] Version mismatch"); + } + + g_pOmarchyFloatingGlobalState = makeUnique(); + g_pOmarchyFloatingGlobalState->nobarRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:no_bar"); + g_pOmarchyFloatingGlobalState->barColorRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:bar_color"); + g_pOmarchyFloatingGlobalState->titleColorRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:title_color"); + + static auto P = Event::bus()->m_events.window.open.listen([&](PHLWINDOW w) { onNewWindow(w); }); + static auto P3 = Event::bus()->m_events.window.updateRules.listen([&](PHLWINDOW w) { onUpdateWindowRules(w); }); + + g_pOmarchyFloatingGlobalState->config.barColor = makeShared("plugin:hyprbars:bar_color", "Change the bar color", 0x88333333); + g_pOmarchyFloatingGlobalState->config.textColor = makeShared("plugin:hyprbars:col.text", "Change the text color", 0xffffffff); + g_pOmarchyFloatingGlobalState->config.inactiveBarColor = + makeShared("plugin:hyprbars:bar_color_inactive", "Inactive title bar color; transparent means use bar_color", 0x00000000); + g_pOmarchyFloatingGlobalState->config.inactiveButtonColor = makeShared( + "plugin:hyprbars:inactive_button_color", "Change the inactive button's color. 0x00000000 means unset", 0x00000000); + g_pOmarchyFloatingGlobalState->config.barHeight = makeShared("plugin:hyprbars:bar_height", "Change the bar's height", 15); + g_pOmarchyFloatingGlobalState->config.barTextSize = makeShared("plugin:hyprbars:bar_text_size", "Change the bar's text size", 10); + g_pOmarchyFloatingGlobalState->config.barTextWeight = + makeShared("plugin:hyprbars:bar_text_weight", "Bar's title text weight (e.g. \"bold\" or an integer 100-1000)", 400); + g_pOmarchyFloatingGlobalState->config.barTitleEnabled = + makeShared("plugin:hyprbars:bar_title_enabled", "Whether to enable titles in the bar", true); + g_pOmarchyFloatingGlobalState->config.barBlur = + makeShared("plugin:hyprbars:bar_blur", "Whether to enable blur of the bar", false); + g_pOmarchyFloatingGlobalState->config.barTextFont = makeShared("plugin:hyprbars:bar_text_font", "Bar's text font", "Sans"); + g_pOmarchyFloatingGlobalState->config.barTextAlign = + makeShared("plugin:hyprbars:bar_text_align", "Bar's text alignment", "center"); + g_pOmarchyFloatingGlobalState->config.barPartOfWindow = + makeShared("plugin:hyprbars:bar_part_of_window", "Whether the bar is a part of the window (reserves space)", true); + g_pOmarchyFloatingGlobalState->config.barPrecedenceOverBorder = + makeShared("plugin:hyprbars:bar_precedence_over_border", "Whether the bar is before, or after the border", false); + g_pOmarchyFloatingGlobalState->config.barButtonsAlignment = + makeShared("plugin:hyprbars:bar_buttons_alignment", "Alignment of the bar buttons", "right"); + g_pOmarchyFloatingGlobalState->config.barPadding = makeShared("plugin:hyprbars:bar_padding", "Padding of the bar", 7); + g_pOmarchyFloatingGlobalState->config.barButtonPadding = + makeShared("plugin:hyprbars:bar_button_padding", "Padding of the bar buttons", 5); + g_pOmarchyFloatingGlobalState->config.buttonRounding = + makeShared("plugin:hyprbars:button_rounding", "Button corner radius. -1 preserves the upstream circular style", -1); + g_pOmarchyFloatingGlobalState->config.buttonBorderSize = + makeShared("plugin:hyprbars:button_border_size", "Width of the button border", 0); + g_pOmarchyFloatingGlobalState->config.buttonBorderColor = + makeShared("plugin:hyprbars:button_border_color", "Color of the button border", 0x00000000); + g_pOmarchyFloatingGlobalState->config.enabled = makeShared("plugin:hyprbars:enabled", "Whether bars are enabled", true); + g_pOmarchyFloatingGlobalState->config.iconOnHover = + makeShared("plugin:hyprbars:icon_on_hover", "Whether to use an icon on hover of the buttons", false); + g_pOmarchyFloatingGlobalState->config.onDoubleClick = + makeShared("plugin:hyprbars:on_double_click", "Action to execute on double click of the bar", ""); + g_pOmarchyFloatingGlobalState->config.workspaceTag = + makeShared("plugin:hyprbars:workspace_tag", "Restrict bars and snapping to floating windows with this tag", ""); + g_pOmarchyFloatingGlobalState->config.edgeSnap = + makeShared("plugin:hyprbars:edge_snap", "Snap dragged floating windows at monitor edges", false); + g_pOmarchyFloatingGlobalState->config.edgeThreshold = + makeShared("plugin:hyprbars:edge_threshold", "Edge activation distance in logical pixels", 24); + g_pOmarchyFloatingGlobalState->config.snapGap = + makeShared("plugin:hyprbars:snap_gap", "Outer and center spacing in logical pixels", 8); + + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barColor); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.textColor); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.inactiveBarColor); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.inactiveButtonColor); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barHeight); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barTextSize); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barTextWeight); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barTitleEnabled); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barBlur); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barTextFont); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barTextAlign); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barPartOfWindow); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barPrecedenceOverBorder); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barButtonsAlignment); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barPadding); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.barButtonPadding); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.buttonRounding); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.buttonBorderSize); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.buttonBorderColor); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.enabled); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.workspaceTag); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.iconOnHover); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.onDoubleClick); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.edgeSnap); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.edgeThreshold); + HyprlandAPI::addConfigValueV2(OMARCHY_FLOATING_PHANDLE, g_pOmarchyFloatingGlobalState->config.snapGap); + + omarchyFloatingSnapInit(); + + if (Config::mgr()->type() == Config::CONFIG_LEGACY) + HyprlandAPI::addConfigKeyword(OMARCHY_FLOATING_PHANDLE, "plugin:hyprbars:hyprbars-button", onNewOmarchyFloatingButton, Hyprlang::SHandlerOptions{}); + else + HyprlandAPI::addLuaFunction(OMARCHY_FLOATING_PHANDLE, "hyprbars", "add_button", ::newOmarchyFloatingLuaButton); + static auto P4 = Event::bus()->m_events.config.preReload.listen([&] { onPreConfigReload(); }); + static auto P5 = Event::bus()->m_events.config.reloaded.listen([&] { onConfigReloaded(); }); + + // add deco to existing windows + for (auto &w : Desktop::windowState()->windows()) { + if (w->isHidden() || !w->m_isMapped) + continue; + + onNewWindow(w); + } + + HyprlandAPI::reloadConfig(); + + return {"hyprbars", "A plugin to add title bars to windows.", "Vaxry", "1.0"}; +} + +static void exitOmarchyFloating() { + omarchyFloatingSnapExit(); + for (auto &m : State::monitorState()->monitors()) + m->m_scheduledRecalc = true; + + g_pHyprRenderer->m_renderPass.removeAllOfType("COmarchyFloatingBarPassElement"); + + Desktop::Rule::windowEffects()->unregisterEffect(g_pOmarchyFloatingGlobalState->barColorRuleIdx); + Desktop::Rule::windowEffects()->unregisterEffect(g_pOmarchyFloatingGlobalState->titleColorRuleIdx); + Desktop::Rule::windowEffects()->unregisterEffect(g_pOmarchyFloatingGlobalState->nobarRuleIdx); +} + +APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { return initializeOmarchyFloating(handle); } +APICALL EXPORT void PLUGIN_EXIT() { exitOmarchyFloating(); } diff --git a/pkgbuilds/omarchy-hyprland-titlebars/tools/pointer.c b/pkgbuilds/omarchy-hyprland-titlebars/tools/pointer.c new file mode 100644 index 0000000..35a932b --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/tools/pointer.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include +#include "pointer-protocol.h" + +static struct zwlr_virtual_pointer_manager_v1 *manager; + +static void global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + if (!strcmp(interface, "zwlr_virtual_pointer_manager_v1")) + manager = wl_registry_bind(registry, name, &zwlr_virtual_pointer_manager_v1_interface, 1); +} + +static void removed(void *data, struct wl_registry *registry, uint32_t name) {} + +int main(int argc, char **argv) { + if (argc != 3) + return 2; + struct wl_display *display = wl_display_connect(NULL); + if (!display) + return 1; + struct wl_registry *registry = wl_display_get_registry(display); + const struct wl_registry_listener listener = {global, removed}; + wl_registry_add_listener(registry, &listener, NULL); + wl_display_roundtrip(display); + if (!manager) + return 3; + struct zwlr_virtual_pointer_v1 *pointer = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, NULL); + char line[128], command[32]; + int x, y; + while (fgets(line, sizeof(line), stdin)) { + if (sscanf(line, "%31s %d %d", command, &x, &y) < 1) + continue; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint32_t now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + if (!strcmp(command, "move")) + zwlr_virtual_pointer_v1_motion_absolute(pointer, now, x, y, atoi(argv[1]), atoi(argv[2])); + else if (!strcmp(command, "down")) + zwlr_virtual_pointer_v1_button(pointer, now, 272, WL_POINTER_BUTTON_STATE_PRESSED); + else if (!strcmp(command, "up")) + zwlr_virtual_pointer_v1_button(pointer, now, 272, WL_POINTER_BUTTON_STATE_RELEASED); + else if (!strcmp(command, "wait")) + usleep(x * 1000); + zwlr_virtual_pointer_v1_frame(pointer); + wl_display_roundtrip(display); + } + zwlr_virtual_pointer_v1_destroy(pointer); + zwlr_virtual_pointer_manager_v1_destroy(manager); + wl_registry_destroy(registry); + wl_display_roundtrip(display); + wl_display_disconnect(display); + return 0; +} diff --git a/pkgbuilds/omarchy-hyprland-titlebars/tools/verify.py b/pkgbuilds/omarchy-hyprland-titlebars/tools/verify.py new file mode 100644 index 0000000..aa497a1 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/tools/verify.py @@ -0,0 +1,239 @@ +"""Verify omarchy-hyprland-titlebars lifecycle and pointer gestures in an isolated nested Hyprland. + +The candidate library is only ever loaded into the disposable nested compositor, +never into the parent session. +""" +import argparse +import json +import os +from pathlib import Path +import socket +import subprocess +import tempfile +import time + +source = Path(__file__).resolve().parent +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument('library', type=Path, help='built titlebars.so to test') +parser.add_argument('--artifacts', type=Path, default=Path('/tmp/omarchy-hyprland-titlebars-verification')) +args = parser.parse_args() +args.library = args.library.resolve() +if not args.library.is_file(): + parser.error(f'{args.library} does not exist') +args.artifacts.mkdir(parents=True, exist_ok=True) + +with tempfile.TemporaryDirectory(prefix='oht-') as scratch: + scratch = Path(scratch) + for kind, target in [('client-header', 'pointer-protocol.h'), ('private-code', 'pointer-protocol.c')]: + subprocess.run(['wayland-scanner', kind, str(source / 'wlr-virtual-pointer-unstable-v1.xml'), str(scratch / target)], check=True) + subprocess.run(['cc', str(source / 'pointer.c'), str(scratch / 'pointer-protocol.c'), '-I', str(scratch), '-lwayland-client', '-o', str(scratch / 'pointer')], check=True) + config = scratch / 'hyprland.lua' + config.write_text(''' +hl.monitor({ output = "", mode = "1280x800@60", position = "0x0", scale = 1 }) +hl.config({ general = { gaps_in = 8, gaps_out = 8, border_size = 2, resize_on_border = true }, animations = { enabled = false }, input = { follow_mouse = 0 }, misc = { disable_hyprland_logo = true, disable_splash_rendering = true } }) +hl.window_rule({ name = "test-float", match = { class = ".*" }, float = true, size = { 600, 400 }, center = true }) +if hl.plugin and hl.plugin.hyprbars then + hl.config({ plugin = { hyprbars = { + enabled = true, bar_height = 30, bar_color = "rgb(24283b)", bar_color_inactive = "rgb(1a1b26)", col = { text = "rgb(c0caf5)" }, + button_rounding = 4, bar_text_align = "left", bar_text_font = "Sans", bar_text_size = 12, bar_part_of_window = true, + edge_snap = true, edge_threshold = 24, snap_gap = 8, + on_double_click = [[hyprctl dispatch 'hl.dsp.window.fullscreen({ mode = "maximized", action = "toggle", window = "address:%WINDOW%" })']], + } } }) + hl.plugin.hyprbars.add_button({ bg_color = "rgb(f7768e)", fg_color = "rgb(1a1b26)", size = 18, icon = "×", action = [[hyprctl dispatch 'hl.dsp.window.close({ window = "address:%WINDOW%" })']] }) +end +''') + parent_instances = json.loads(subprocess.check_output(['hyprctl', 'instances', '-j'], text=True)) + if len(parent_instances) != 1: + raise RuntimeError('Verification expects exactly one parent compositor') + parent_signature = parent_instances[0]['instance'] + parent_display = str(Path(os.environ['XDG_RUNTIME_DIR']) / parent_instances[0]['wl_socket']) + env = dict(os.environ, XDG_RUNTIME_DIR=str(scratch), AQ_BACKEND='wayland', WAYLAND_DISPLAY=parent_display, HYPRLAND_NO_SD_NOTIFY='1', HYPRLAND_NO_SD_VARS='1') + env.pop('HYPRLAND_INSTANCE_SIGNATURE', None) + log = (args.artifacts / 'compositor.log').open('w') + compositor = subprocess.Popen(['Hyprland', '--config', str(config)], env=env, stdout=log, stderr=log) + terminal = None + secondary = None + + def ctl(*argv): + # Never let a plugin command reach the parent compositor. + if argv[0] == 'plugin': + signature = env.get('HYPRLAND_INSTANCE_SIGNATURE') + if not signature or signature == parent_signature or env['XDG_RUNTIME_DIR'] != str(scratch): + raise RuntimeError('Refusing to send a plugin command outside the nested compositor') + result = subprocess.run(['hyprctl', *argv], env=env, capture_output=True, text=True, timeout=5, check=True) + if result.stdout.startswith('Err') or "Couldn't" in result.stdout: + raise RuntimeError(result.stdout) + return result.stdout + + def client(): + return json.loads(ctl('clients', '-j'))[0] + + def pointer(commands): + subprocess.run([str(scratch / 'pointer'), '1280', '800'], input='\n'.join(commands) + '\n', text=True, env=env, check=True, timeout=10) + time.sleep(.15) + + def drag_to(x, y): + window = client() + start_x = window['at'][0] + min(window['size'][0] // 2, 220) + start_y = window['at'][1] - 15 + commands = [f'move {start_x} {start_y}', 'wait 100', 'down', 'wait 120'] + for i in range(1, 21): + commands += [f'move {round(start_x + (x - start_x) * i / 20)} {round(start_y + (y - start_y) * i / 20)}', 'wait 20'] + commands += ['wait 100', 'up'] + pointer(commands) + return client() + + def capture(name): + monitor = json.loads(ctl('monitors', '-j'))[0]['name'] + subprocess.run(['grim', '-o', monitor, str(args.artifacts / f'{name}.png')], env=env, check=True) + + try: + for _ in range(80): + if compositor.poll() is not None: + raise RuntimeError('Isolated compositor exited; inspect compositor.log') + try: + instances = json.loads(ctl('instances', '-j')) + except (ValueError, subprocess.SubprocessError): + instances = [] + if instances: + env['HYPRLAND_INSTANCE_SIGNATURE'] = instances[0]['instance'] + env['WAYLAND_DISPLAY'] = instances[0]['wl_socket'] + break + time.sleep(.1) + else: + raise RuntimeError('Isolated compositor did not start') + time.sleep(.2) + terminal = subprocess.Popen(['foot', '--app-id=omarchy-titlebars-isolated-test', '--title=Titlebars — isolated verification', 'sleep', '90'], env=env, stdout=log, stderr=log) + for _ in range(60): + if json.loads(ctl('clients', '-j')): + break + time.sleep(.1) + else: + raise RuntimeError('Test client did not map') + + xp = Path('/usr/lib/omarchy-windows-xp/hyprbars.so') + sequence = [args.library, args.library] + if xp.exists(): + sequence = [xp, args.library, xp, args.library] + for library in sequence: + ctl('plugin', 'load', str(library)) + time.sleep(.15) + for _ in range(3): + ctl('reload') + time.sleep(.1) + if library == args.library: + assert not ctl('configerrors').strip(), ctl('configerrors') + assert compositor.poll() is None + assert client() + ctl('plugin', 'unload', str(library)) + time.sleep(.15) + assert not json.loads(ctl('plugin', 'list', '-j')) + print('PASS: repeated load/reload/unload, interleaved with the XP fork', flush=True) + + ctl('plugin', 'load', str(args.library)) + ctl('reload') + time.sleep(.25) + assert not ctl('configerrors').strip(), ctl('configerrors') + original = client() + events = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + events.connect(str(scratch / 'hypr' / env['HYPRLAND_INSTANCE_SIGNATURE'] / '.socket2.sock')) + events.settimeout(.2) + capture('floating') + + left = drag_to(1, 360) + assert left['at'][0] <= 12 and 610 <= left['size'][0] <= 630, left + capture('snap-left') + right = drag_to(1278, 360) + assert 640 <= right['at'][0] <= 656 and 610 <= right['size'][0] <= 630, right + capture('snap-right') + free = drag_to(650, 220) + assert free['size'] == original['size'], (free, original) + top = drag_to(650, 1) + assert top['fullscreen'] == 1 and top['at'][1] >= 30, top + capture('maximized') + restored = drag_to(650, 220) + assert restored['fullscreen'] == 0 and restored['size'] == original['size'], restored + assert 205 <= restored['at'][1] <= 255, restored + print('PASS: real pointer titlebar drag left/right, restore size, top maximize, drag-down restore', flush=True) + + window = client() + x, y = window['at'][0] + 120, window['at'][1] - 15 + pointer([f'move {x} {y}', 'wait 450', 'down', 'up', 'wait 100', 'down', 'up']) + assert client()['fullscreen'] == 1, client() + print('PASS: titlebar double-click maximize', flush=True) + messages = b'' + try: + while chunk := events.recv(65536): + messages += chunk + except TimeoutError: + pass + events.close() + text = messages.decode() + for marker in [',left,', ',right,', ',maximize,', ',none,']: + assert f'omarchy_snap_preview>>' in text and marker in text, (marker, text) + (args.artifacts / 'events.log').write_text(text) + print('PASS: snap preview events and release cleanup', flush=True) + + # Make dispatch deliberately slow, then focus another window after clicking. + # Both double-click and button actions must keep the decoration owner. + config.write_text(config.read_text().replace('hyprctl dispatch', 'sleep 0.6; hyprctl dispatch')) + ctl('reload') + first_address = client()['address'] + ctl('dispatch', f'hl.dsp.window.fullscreen({{ mode = "maximized", action = "unset", window = "address:{first_address}" }})') + ctl('dispatch', f'hl.dsp.window.resize({{ x = 500, y = 350, window = "address:{first_address}" }})') + ctl('dispatch', f'hl.dsp.window.move({{ x = 60, y = 320, window = "address:{first_address}" }})') + secondary = subprocess.Popen(['foot', '--app-id=omarchy-titlebars-focus-race', '--title=Second window must stay unchanged', 'sleep', '90'], env=env, stdout=log, stderr=log) + for _ in range(60): + windows = json.loads(ctl('clients', '-j')) + if len(windows) == 2: + break + time.sleep(.1) + assert len(windows) == 2, windows + second_address = next(window['address'] for window in windows if window['address'] != first_address) + ctl('dispatch', f'hl.dsp.window.move({{ x = 690, y = 320, window = "address:{second_address}" }})') + pointer(['move 180 305', 'wait 450', 'down', 'up', 'wait 100', 'down', 'up']) + ctl('dispatch', f'hl.dsp.focus({{ window = "address:{second_address}" }})') + time.sleep(.7) + windows = {window['address']: window for window in json.loads(ctl('clients', '-j'))} + assert windows[first_address]['fullscreen'] == 1 and windows[second_address]['fullscreen'] == 0, windows + ctl('dispatch', f'hl.dsp.window.fullscreen({{ mode = "maximized", action = "unset", window = "address:{first_address}" }})') + ctl('dispatch', f'hl.dsp.window.move({{ x = 60, y = 320, window = "address:{first_address}" }})') + pointer(['move 540 305', 'wait 450', 'down', 'up']) + ctl('dispatch', f'hl.dsp.focus({{ window = "address:{second_address}" }})') + time.sleep(.7) + remaining = json.loads(ctl('clients', '-j')) + assert len(remaining) == 1 and remaining[0]['address'] == second_address, remaining + print('PASS: delayed double-click and close target the clicked window after focus changes', flush=True) + + # A workspace-scoped bar must release its reserved space as soon as a + # window joins tiling, without waiting for a compositor config reload. + ctl('eval', 'hl.config({ plugin = { hyprbars = { workspace_tag = "floating-test" } } })') + ctl('dispatch', f'hl.dsp.window.tag({{ tag = "+floating-test", window = "address:{second_address}" }})') + ctl('dispatch', f'hl.dsp.window.float({{ action = "disable", window = "address:{second_address}" }})') + time.sleep(.3) + tiled = client() + assert not tiled['floating'] and tiled['at'][1] < 30, tiled + capture('workspace-tiled-no-titlebar') + ctl('dispatch', f'hl.dsp.window.float({{ action = "enable", window = "address:{second_address}" }})') + ctl('dispatch', f'hl.dsp.window.fullscreen({{ mode = "maximized", action = "set", window = "address:{second_address}" }})') + time.sleep(.3) + assert client()['at'][1] >= 30, client() + ctl('dispatch', f'hl.dsp.window.tag({{ tag = "-floating-test", window = "address:{second_address}" }})') + time.sleep(.3) + assert client()['at'][1] < 30, client() + capture('workspace-untagged-no-titlebar') + print('PASS: workspace-scoped titlebars release space immediately when tiled or untagged', flush=True) + finally: + if secondary and secondary.poll() is None: + secondary.terminate() + secondary.wait(timeout=5) + if terminal and terminal.poll() is None: + terminal.terminate() + terminal.wait(timeout=5) + compositor.terminate() + try: + compositor.wait(timeout=5) + except subprocess.TimeoutExpired: + compositor.kill() + compositor.wait() + log.close() diff --git a/pkgbuilds/omarchy-hyprland-titlebars/tools/wlr-virtual-pointer-unstable-v1.xml b/pkgbuilds/omarchy-hyprland-titlebars/tools/wlr-virtual-pointer-unstable-v1.xml new file mode 100644 index 0000000..ea243e7 --- /dev/null +++ b/pkgbuilds/omarchy-hyprland-titlebars/tools/wlr-virtual-pointer-unstable-v1.xml @@ -0,0 +1,152 @@ + + + + Copyright © 2019 Josef Gajdusek + + 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 (including the next + paragraph) 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. + + + + + This protocol allows clients to emulate a physical pointer device. The + requests are mostly mirror opposites of those specified in wl_pointer. + + + + + + + + + + The pointer has moved by a relative amount to the previous request. + + Values are in the global compositor space. + + + + + + + + + The pointer has moved in an absolute coordinate frame. + + Value of x can range from 0 to x_extent, value of y can range from 0 + to y_extent. + + + + + + + + + + + A button was pressed or released. + + + + + + + + + Scroll and other axis requests. + + + + + + + + + Indicates the set of events that logically belong together. + + + + + + Source information for scroll and other axis. + + + + + + + Stop notification for scroll and other axes. + + + + + + + + Discrete step information for scroll and other axes. + + This event allows the client to extend data normally sent using the axis + event with discrete value. + + + + + + + + + + + + + + + This object allows clients to create individual virtual pointer objects. + + + + + Creates a new virtual pointer. The optional seat is a suggestion to the + compositor. + + + + + + + + + + + + + Creates a new virtual pointer. The seat and the output arguments are + optional. If the seat argument is set, the compositor should assign the + input device to the requested seat. If the output argument is set, the + compositor should map the input device to the requested output. + + + + + + + diff --git a/pkgbuilds/omarchy-mac-boot/PKGBUILD b/pkgbuilds/omarchy-mac-boot/PKGBUILD index e02f026..87c4a46 100644 --- a/pkgbuilds/omarchy-mac-boot/PKGBUILD +++ b/pkgbuilds/omarchy-mac-boot/PKGBUILD @@ -7,8 +7,8 @@ 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=20261004 -pkgrel=3 +pkgver=20261008 +pkgrel=1 pkgdesc='Apple Silicon boot support for Omarchy: initramfs, in-place encryption, first boot and Limine activation' arch=('aarch64') groups=('omarchy-platform-apple-silicon') @@ -22,9 +22,9 @@ conflicts=('omarchy-apple-boot' 'omarchy-first-boot') replaces=('omarchy-apple-boot' 'omarchy-first-boot') install=omarchy-mac-boot.install # An exact omarchy-mac-pkgs main commit, never a branch. -_commit=6bd123a6790e33e0ccb7ab09ac4eee7815c9a95b +_commit=162f3599ee1144d7cf44b1ae12a7eb14ba758b8a source=("omarchy-mac-pkgs::git+https://github.com/omacom/omarchy-mac-pkgs.git#commit=${_commit}") -sha256sums=('6af86c533129815653377731516cf5411fdd4f40e217687a8113ae33fc42c19f') +sha256sums=('31be04e5cd902403997756660bb50494c7f7978f1de1192edad426b543c15e01') prepare() { # Tests and staging must not be able to read the other package's tree. diff --git a/pkgbuilds/omarchy-mac-boot/README.md b/pkgbuilds/omarchy-mac-boot/README.md index 6b422a6..7fa3cbd 100644 --- a/pkgbuilds/omarchy-mac-boot/README.md +++ b/pkgbuilds/omarchy-mac-boot/README.md @@ -33,6 +33,8 @@ A new pin publishes on merge, so check what the pinned source needs first. The r - 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 dangling enable links of the replaced unit and of the removed migration verifier, 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. - **Device tree overlays (omacom/omarchy-mac-pkgs#3).** Other packages may drop overlays into `/usr/lib/omarchy-mac-boot/dtb-overlays/`, which this package owns; `/etc/default/update-m1n1` applies them and the boot check rebuilds `boot.bin` the same way. With none installed, `boot.bin` is unchanged. A Mac with its own copy of `/etc/default/update-m1n1` keeps it (the shipped one lands as `.pacnew`), and then update-m1n1 applies no overlays until it is merged; from `20261004-3` (omacom/omarchy-mac-pkgs#10) the boot check leaves them out too, with a warning, instead of failing that Mac's `boot.bin` and stopping `omarchy update`. The overlay hook does not run in the transaction that first installs it. +- **Factory reset keeps `/boot` writable (`20261008-1`, omacom/omarchy-mac-pkgs#20).** A reset reboot unlocks the disk with a key from the Boot partition, and systemd could mount that partition again just before the switch to the real root without unmounting it (systemd/systemd#28021), so `/boot` came up read-only and owner setup's re-key failed. An initramfs drop-in makes the unmount finish before the switch. +- **First-boot encryption progress on the splash (`20261008-1`, omacom/omarchy-mac-pkgs#18).** - `backup=` covers every `/etc` file 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`. The snapshot restore hooks in `/etc/boot/hooks` are symlinks to `/usr/bin/omarchy-mac-snapshot-check`, not files, so they stay out of it. ## Updates @@ -44,4 +46,4 @@ Updates are reviewed pins, never a branch: 3. Refresh `sha256sums` with `makepkg -g`. 4. Check the pin against [Publish order](#publish-order). `omarchy-mac` must be published at or past omacom/omarchy-mac#535, or re-pinned in the same pull request. -`20261004-3` sorts above edge's `20261004-2`, the first draft of that pin (`20261004-1`) and every lab candidate up to `20260928-3`. +`20261008-1` sorts above edge's `20261004-3` and every earlier pin and lab candidate. diff --git a/pkgbuilds/omarchy-meeting-recorder-bin/PKGBUILD b/pkgbuilds/omarchy-meeting-recorder-bin/PKGBUILD index 4ee40f7..c8b005d 100644 --- a/pkgbuilds/omarchy-meeting-recorder-bin/PKGBUILD +++ b/pkgbuilds/omarchy-meeting-recorder-bin/PKGBUILD @@ -2,7 +2,7 @@ pkgname=omarchy-meeting-recorder-bin _name=omarchy-meeting-recorder -pkgver=1.5.0 +pkgver=1.6.0 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') @@ -16,7 +16,7 @@ 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=('eb538fa7787ad42fd0074ab29a6a5a556d4e3c472934d7be5f2a61c4d4c1a8ab') +sha256sums_x86_64=('21794f9607fb2fcae73a8e8c0be9018b758477d0b4feb6305a7ca6dec7383510') package() { cd "$_name-$pkgver" diff --git a/pkgbuilds/omarchy-mlx/.omarchy/package.json b/pkgbuilds/omarchy-mlx/.omarchy/package.json index fe36a8c..9e46e41 100644 --- a/pkgbuilds/omarchy-mlx/.omarchy/package.json +++ b/pkgbuilds/omarchy-mlx/.omarchy/package.json @@ -1,6 +1,8 @@ { "source": "local", - "channels": ["edge"], + "channels": [ + "edge" + ], "min_release_age": "24h", "rebuild_on": [ "llvm" @@ -10,5 +12,10 @@ "github": "joshuaswarren/omarchy-mlx", "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" } + }, + "rebuilt_against": { + "aarch64": { + "llvm": "23.1.1-1" + } } } diff --git a/pkgbuilds/omarchy-mlx/PKGBUILD b/pkgbuilds/omarchy-mlx/PKGBUILD index 9b18705..c580563 100644 --- a/pkgbuilds/omarchy-mlx/PKGBUILD +++ b/pkgbuilds/omarchy-mlx/PKGBUILD @@ -3,7 +3,7 @@ pkgbase=omarchy-mlx pkgname=(omarchy-mlx omarchy-mlx-vulkan) pkgver=0.7.28 -pkgrel=1 +pkgrel=2 arch=('aarch64') url='https://github.com/joshuaswarren/omarchy-mlx' # The two packages publish together, so the tested wheel and the Vulkan driver diff --git a/pkgbuilds/omarchy-nvim/PKGBUILD b/pkgbuilds/omarchy-nvim/PKGBUILD index fb6d754..b5c6a60 100644 --- a/pkgbuilds/omarchy-nvim/PKGBUILD +++ b/pkgbuilds/omarchy-nvim/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Ryan Hughes pkgname=omarchy-nvim pkgver=2026.9.21 -pkgrel=2 +pkgrel=3 pkgdesc="Pre-built LazyVim configuration with cached plugins" arch=('any') url="https://github.com/LazyVim/LazyVim" diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 3a94b08..34eba93 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -1,13 +1,13 @@ # Maintainer: Ryan Hughes pkgname='omarchy-settings-dev' -pkgver=4.0.0.r6720.g8e02fc8 +pkgver=4.0.0.r6815.g50d687a pkgrel=1 # Pinned by the upstream watch in .omarchy/package.json (bin/sync-upstream): # every quattro tip becomes a commit pin here, so the package is versioned, # checksummed and built exactly like a release, just more often. The r-number # is the branch's total commit count, not the distance from the last tag: the # published history used the total, and pacman must never see it go down. -_commit=8e02fc84f5bdc511ed102e2a14f8935bba4f92bd +_commit=50d687a1f27063513cf42e6f33c3a4fcb17c24ed pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers (quattro branch tip)' # Arch-specific because the shipped /etc tree is not the same on every # architecture for older sources: their zram and oomd drop-ins belong to the @@ -126,7 +126,7 @@ _etc_override_paths=( # build from a local source tree instead (mirrors omarchy-zsh/omarchy-fish DX); # the arrays are emptied below so nothing is downloaded in that case. source=("omarchy::git+https://github.com/basecamp/omarchy.git#commit=${_commit}") -sha256sums=('f19f9baad1cca793d3f8b95faec014d2af6570d40800e5d9dd4102e3f5257710') +sha256sums=('8f26a967de236f5c0d2b34e9e7119ad13fe7a9db67028f0ad1a4bf2ddf8c40cf') if [[ -n "${OMARCHY_SRC:-}" ]]; then source=() sha256sums=() diff --git a/pkgbuilds/omarchy-settings/PKGBUILD b/pkgbuilds/omarchy-settings/PKGBUILD index 1a27d37..37fde6f 100644 --- a/pkgbuilds/omarchy-settings/PKGBUILD +++ b/pkgbuilds/omarchy-settings/PKGBUILD @@ -13,7 +13,7 @@ pkgname='omarchy-settings' _tag='v4.0.4' _commit='c668141e9c42b13c80c9ca4ea108e11708c5e8a5' pkgver=4.0.4 -pkgrel=5 +pkgrel=6 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 # architecture for older sources: their zram and oomd drop-ins belong to the diff --git a/pkgbuilds/omarchy-walker/PKGBUILD b/pkgbuilds/omarchy-walker/PKGBUILD deleted file mode 100644 index 923a7bb..0000000 --- a/pkgbuilds/omarchy-walker/PKGBUILD +++ /dev/null @@ -1,24 +0,0 @@ -pkgname=omarchy-walker -pkgver=1.0.0 -pkgrel=2 -pkgdesc='Meta package for walker with Elephant providers for Omarchy' -arch=('x86_64' 'aarch64') -url='https://omarchy.org' -license=('MIT') -options=('!debug') -depends=( - 'walker' - 'elephant' - 'elephant-bluetooth' - 'elephant-calc' - 'elephant-clipboard' - 'elephant-desktopapplications' - 'elephant-files' - 'elephant-menus' - 'elephant-providerlist' - 'elephant-runner' - 'elephant-symbols' - 'elephant-todo' - 'elephant-unicode' - 'elephant-websearch' -) diff --git a/pkgbuilds/omasnap-git/PKGBUILD b/pkgbuilds/omasnap-git/PKGBUILD index 93b673a..0129623 100644 --- a/pkgbuilds/omasnap-git/PKGBUILD +++ b/pkgbuilds/omasnap-git/PKGBUILD @@ -5,9 +5,9 @@ # tagged release it follows and below the next one. pkgname=omasnap-git -pkgver=1.21.0.r76.g614cdf5 -pkgrel=1 -_commit=614cdf55b42a43c1dcee2c85cd01e4764a52bdae +pkgver=1.21.0.r81.g6771b19 +pkgrel=2 +_commit=6771b19de8bcead2b17c16b49f1a8a97445dd8e3 pkgdesc="Native Wayland screenshot and annotation overlay for Hyprland (main branch)" arch=('x86_64' 'aarch64') url="https://github.com/omacom/omasnap" @@ -33,7 +33,7 @@ conflicts=('omasnap') options=('!debug') source=("omasnap::git+$url.git#commit=${_commit}") -sha256sums=('952539c68a81ac373c0f2d5fc084106d3419ee3e5ec2d544d02b510a3f416484') +sha256sums=('7721f7f1010e610c6435ce7a1e99d8da3b8ceb9ccb1674a28c94a96b8eb411b8') build() { cmake -S omasnap -B build -G Ninja \ @@ -53,7 +53,7 @@ check() { 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 + ./build/omasnap-smoke --output-dir "$srcdir/omasnap-smoke-output"; then status=0 else status=$? diff --git a/pkgbuilds/omasnap/PKGBUILD b/pkgbuilds/omasnap/PKGBUILD index c5cf340..b989de6 100644 --- a/pkgbuilds/omasnap/PKGBUILD +++ b/pkgbuilds/omasnap/PKGBUILD @@ -2,7 +2,7 @@ pkgname=omasnap pkgver=1.21.0 -pkgrel=1 +pkgrel=2 pkgdesc="Native Wayland screenshot and annotation overlay for Hyprland" arch=('x86_64' 'aarch64') url="https://github.com/omacom/omasnap" diff --git a/pkgbuilds/omatrack/PKGBUILD b/pkgbuilds/omatrack/PKGBUILD index 59fbbaa..35f62c3 100644 --- a/pkgbuilds/omatrack/PKGBUILD +++ b/pkgbuilds/omatrack/PKGBUILD @@ -2,7 +2,7 @@ pkgname=omatrack pkgver=1.8.6 -pkgrel=1 +pkgrel=2 pkgdesc="Cross-format motorsport telemetry analysis workstation" arch=('x86_64' 'aarch64') url="https://github.com/tobi/omatrack" @@ -12,8 +12,11 @@ makedepends=('cmake' 'ninja' 'rust') options=('!debug' '!lto') _commit=cd3fcdda3f8e15fda73288f6cf3803164138a7e4 +# tobi/omatrack is gone from GitHub. This copy serves the same commit; its +# archive matches the sha256 below byte for byte. Do not follow it for updates. +_source_copy=https://github.com/y4b9vb7nk9-debug/omatrack source=( - "omatrack-$_commit.tar.gz::$url/archive/$_commit.tar.gz" + "omatrack-$_commit.tar.gz::$_source_copy/archive/$_commit.tar.gz" 'lua-5.4.7.tar.gz::https://www.lua.org/ftp/lua-5.4.7.tar.gz' 'sol2-d805d027.tar.gz::https://github.com/ThePhD/sol2/archive/d805d027e0a0a7222e936926139f06e23828ce9f.tar.gz' ) diff --git a/pkgbuilds/omawrite/PKGBUILD b/pkgbuilds/omawrite/PKGBUILD index c1f52fa..d938415 100644 --- a/pkgbuilds/omawrite/PKGBUILD +++ b/pkgbuilds/omawrite/PKGBUILD @@ -2,7 +2,7 @@ pkgname=omawrite pkgver=0.6.0 -pkgrel=1 +pkgrel=2 pkgdesc='Dead-simple Markdown writing app built with Qt Quick' arch=('x86_64' 'aarch64') url='https://github.com/omacom/omawrite' diff --git a/pkgbuilds/omazed/PKGBUILD b/pkgbuilds/omazed/PKGBUILD index 5af9b4e..b6edef5 100644 --- a/pkgbuilds/omazed/PKGBUILD +++ b/pkgbuilds/omazed/PKGBUILD @@ -1,6 +1,6 @@ pkgname=omazed pkgver=2.2.0 -pkgrel=2 +pkgrel=3 pkgdesc="Live theme switching for Zed in Omarchy - automatically synchronize your Zed editor theme with your Omarchy system theme" arch=('any') url="https://github.com/aps6/omazed" diff --git a/pkgbuilds/openai-codex-bin/PKGBUILD b/pkgbuilds/openai-codex-bin/PKGBUILD index 2a82bb6..6327404 100644 --- a/pkgbuilds/openai-codex-bin/PKGBUILD +++ b/pkgbuilds/openai-codex-bin/PKGBUILD @@ -2,8 +2,8 @@ # shellcheck disable=SC2034 # Maintainer: Chmouel Boudjnah pkgname=openai-codex-bin -pkgver=0.160.0 -pkgrel=1 +pkgver=0.160.1 +pkgrel=2 pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated" arch=('x86_64' 'aarch64') url="https://github.com/openai/codex" @@ -21,8 +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=('306865417d4ee7a927785852910a527f41e1e159add390ac5ae3accb67d44a13' 'ac6cd6288f0e39f46a33eba1cfd37711651f14f33dbc73d6a1b82eb45e038cac') -sha256sums_aarch64=('883620139925f677e5a12c95ba87a1d7f421388ebb797b1c10aba42a04ede9d7' '94066fdf13ffecd2f58776ec5cb8fc3040283a642e3d6ff25c5d82048c41038e') +sha256sums_x86_64=('9226581be592d18f7e7f740a352fdb63aa61e45e39f7eb9b09d3888c84bba33f' '8a69207d97545ac753b6585974e1e67a4c51ae5deacf06517db512bb25e0e3c2') +sha256sums_aarch64=('f54dc5852042445bf41da3aa31156f3cb02f52c5a1a04074de73dc5598f7e1f7' 'e5e027e6689efda2e3570aa600179f0ebb18632803350e152ed6c9b97dcf9741') 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 7cf1aa3..071a09a 100644 --- a/pkgbuilds/openai-codex-desktop/PKGBUILD +++ b/pkgbuilds/openai-codex-desktop/PKGBUILD @@ -5,8 +5,8 @@ # the version and checksums below from that repository's package index. pkgname=openai-codex-desktop -pkgver=26.930.41038 -pkgrel=1 +pkgver=26.930.61225 +pkgrel=2 pkgdesc="Official ChatGPT desktop app with Codex" arch=('x86_64' 'aarch64') url="https://chatgpt.com/codex/" @@ -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=('ee7854145554718d7239d01ea37d44f6ba1e0ba4a93f47ac097d6e0f964da47c') -sha256sums_aarch64=('a4e76853b000efa9d6c32932ddd19973b4a2983c71d3bd144e5f2b9151cbb16b') +sha256sums_x86_64=('b90a80f9353bc12a5a5b8469502a8e5794a3c54a371c8880e094d500de695bb8') +sha256sums_aarch64=('541b4744a13a7972bddb0519887d4f626340ed3987817d35bcaf5fe88346d6bd') package() { cd "${srcdir}" diff --git a/pkgbuilds/openclaw/PKGBUILD b/pkgbuilds/openclaw/PKGBUILD index 02cd991..4fd9314 100644 --- a/pkgbuilds/openclaw/PKGBUILD +++ b/pkgbuilds/openclaw/PKGBUILD @@ -7,8 +7,8 @@ # upstream's release cadence outruns the AUR. pkgname=openclaw -pkgver=2026.9.6 -pkgrel=3 +pkgver=2026.9.8 +pkgrel=2 pkgdesc='Multi-channel AI gateway with extensible messaging integrations' arch=(x86_64 aarch64) url=https://github.com/openclaw/openclaw @@ -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=('1a7355691bc0e605222ba818f1f72c1787253c78dfeb0df6be2086ec73b71e63') +sha256sums=('317e0a58db32b386e01187fe9c5c4de541f4ce6d815657bf79a102609b81752a') options=(!debug !strip) install=$pkgname.install noextract=($pkgname-$pkgver.tgz) diff --git a/pkgbuilds/owe-lockfeed/PKGBUILD b/pkgbuilds/owe-lockfeed/PKGBUILD index a6af4b9..4a2a5bf 100644 --- a/pkgbuilds/owe-lockfeed/PKGBUILD +++ b/pkgbuilds/owe-lockfeed/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: owe contributors pkgname=owe-lockfeed -pkgver=0.2.9 -pkgrel=1 +pkgver=0.2.10 +pkgrel=2 pkgdesc="Lock screen video feed module for the OWE wallpaper engine" arch=('x86_64' 'aarch64') url="https://github.com/omacom/owe" @@ -9,7 +9,7 @@ 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=('cbf55f6bf4ca88621a2e59b3a62b2b078e1c82c06437e1f5137fdaeb8b5b560a') +sha256sums=('b171397c24c0ffd6096781b3f744f674055a57f660a74db07a3b71a111a6980b') build() { cmake -S "$srcdir/owe-$pkgver/qml-plugin" -B build \ diff --git a/pkgbuilds/owe/PKGBUILD b/pkgbuilds/owe/PKGBUILD index 99fbe84..3539163 100644 --- a/pkgbuilds/owe/PKGBUILD +++ b/pkgbuilds/owe/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: owe contributors pkgname=owe -pkgver=0.2.9 -pkgrel=1 +pkgver=0.2.10 +pkgrel=3 pkgdesc="High-performance wallpaper engine for Omarchy (mp4, gif, stills)" arch=('x86_64' 'aarch64') url="https://github.com/omacom/owe" @@ -11,8 +11,16 @@ 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=('cbf55f6bf4ca88621a2e59b3a62b2b078e1c82c06437e1f5137fdaeb8b5b560a') +source=("$pkgname-$pkgver.tar.gz::https://github.com/omacom/owe/archive/refs/tags/v$pkgver.tar.gz" + 'lock-policy-timestamp.patch') +sha256sums=('b171397c24c0ffd6096781b3f744f674055a57f660a74db07a3b71a111a6980b' + '3ebf413093922b0917bca60c42720c6424c77b1695317364bf73f9fd745376d8') + +prepare() { + cd "$srcdir/owe-$pkgver" + # Upstream 9876702: make the invalidation test independent of filesystem timestamps. + patch -p1 -i "$srcdir/lock-policy-timestamp.patch" +} build() { meson setup build "$srcdir/owe-$pkgver" -Dbuildtype=release -Dprefix=/usr @@ -30,7 +38,7 @@ check() { mapfile -t tests < <(meson test -C build --list | sed 's/^owe://' | grep -vx transition) (( ${#tests[@]} )) || return 1 fi - meson test -C build "${tests[@]}" + meson test -C build --print-errorlogs "${tests[@]}" } package() { diff --git a/pkgbuilds/owe/lock-policy-timestamp.patch b/pkgbuilds/owe/lock-policy-timestamp.patch new file mode 100644 index 0000000..457aad5 --- /dev/null +++ b/pkgbuilds/owe/lock-policy-timestamp.patch @@ -0,0 +1,18 @@ +diff --git i/test/lock_policy.c w/test/lock_policy.c +index 8561616..6c554b1 100644 +--- i/test/lock_policy.c ++++ w/test/lock_policy.c +@@ -576,6 +576,13 @@ int main(void) { + CHECK(!prepared_intro_matches(copy)); + a = open(prepared, O_WRONLY); + CHECK(a >= 0 && write(a, "clip-c", 6) == 6); ++ /* Fast writes can share a timestamp on the builder filesystem. ++ * Advance mtime explicitly to exercise prepared-file invalidation. */ ++ struct timespec changed_times[2] = { ++ { .tv_nsec = UTIME_OMIT }, ++ { .tv_sec = g_app.intro_prepared_mtime.tv_sec + 1 } ++ }; ++ CHECK(futimens(a, changed_times) == 0); + close(a); + CHECK(!prepared_intro_matches(prepared)); + clear_prepared_intro(); diff --git a/pkgbuilds/perplexity/PKGBUILD b/pkgbuilds/perplexity/PKGBUILD index 92e1d53..d7be71e 100644 --- a/pkgbuilds/perplexity/PKGBUILD +++ b/pkgbuilds/perplexity/PKGBUILD @@ -5,8 +5,8 @@ # from that repository's package index. pkgname=perplexity -pkgver=26.9.6+build95799 -pkgrel=1 +pkgver=26.10.0+build101817 +pkgrel=2 pkgdesc="Official Perplexity desktop app" arch=('x86_64' 'aarch64') url="https://www.perplexity.ai" @@ -81,8 +81,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64//+/%2B}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64//+/%2B}") noextract=("${_deb_x86_64}" "${_deb_aarch64}") sha256sums=('b180ca6fbd268712a277a34d4215f176001c0031a4f6360686458623f47fad65') -sha256sums_x86_64=('0e03afca70b7c09750f7758c72b5ff76a8fb95dc81a134eb8c97cdf25bbb4df5') -sha256sums_aarch64=('eb187761a65f8a7d83591ddbc381ad2e9c123fac8422cdbf51c0a0b5b5509715') +sha256sums_x86_64=('e58a95f5ec525834f06ba9870fd1ca898624b0ed7817a29c65dc246a95d843bb') +sha256sums_aarch64=('6469f70c759fb70ed145dc3f1560871bba65013f124c28a55b83b66b695ab772') package() { cd "${srcdir}" diff --git a/pkgbuilds/quickshell-git/.omarchy/package.json b/pkgbuilds/quickshell-git/.omarchy/package.json index 4b3a44f..4439131 100644 --- a/pkgbuilds/quickshell-git/.omarchy/package.json +++ b/pkgbuilds/quickshell-git/.omarchy/package.json @@ -8,6 +8,11 @@ ], "rebuilt_against": { "x86_64": { + "qt6-base": "6.12.0-2", + "qt6-declarative": "6.12.0-1", + "qt6-wayland": "6.12.0-1" + }, + "aarch64": { "qt6-base": "6.11.2-3", "qt6-declarative": "6.11.2-2", "qt6-wayland": "6.11.2-1" diff --git a/pkgbuilds/quickshell-git/PKGBUILD b/pkgbuilds/quickshell-git/PKGBUILD index 629cbf6..4bc514f 100644 --- a/pkgbuilds/quickshell-git/PKGBUILD +++ b/pkgbuilds/quickshell-git/PKGBUILD @@ -2,8 +2,8 @@ _pkgname=quickshell pkgname="$_pkgname-git" -pkgver=0.3.0.r20.g28771c7 -pkgrel=4 +pkgver=0.3.2.r0.g4f508be +pkgrel=2 pkgdesc='Flexible toolkit for making desktop shells with QtQuick' arch=(x86_64 aarch64) url='https://git.outfoxxed.me/quickshell/quickshell' @@ -37,7 +37,7 @@ provides=("$_pkgname") conflicts=("$_pkgname") _pkgsrc="$_pkgname" -source=("$_pkgsrc"::"git+$url.git#commit=28771c7c74b42e20afca0b1b63980cb46515537c" +source=("$_pkgsrc"::"git+$url.git#commit=4f508be500dea6e5732cc3d50382a0048b17e7b1" quickshell-check.hook) sha256sums=('SKIP' '8543e21aeaaa5441b73a679160e7601a957f16c433e8d6bd9257e80bd0e94083') diff --git a/pkgbuilds/quickshell/.omarchy/package.json b/pkgbuilds/quickshell/.omarchy/package.json new file mode 100644 index 0000000..6ec3d3d --- /dev/null +++ b/pkgbuilds/quickshell/.omarchy/package.json @@ -0,0 +1,26 @@ +{ + "source": "local", + "upstream": { + "watch": { + "github": "quickshell-mirror/quickshell", + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)" + } + }, + "rebuild_on": [ + "qt6-base", + "qt6-declarative", + "qt6-wayland" + ], + "rebuilt_against": { + "x86_64": { + "qt6-base": "6.12.0-2", + "qt6-declarative": "6.12.0-1", + "qt6-wayland": "6.12.0-1" + }, + "aarch64": { + "qt6-base": "6.11.2-3", + "qt6-declarative": "6.11.2-2", + "qt6-wayland": "6.11.2-1" + } + } +} diff --git a/pkgbuilds/quickshell/PKGBUILD b/pkgbuilds/quickshell/PKGBUILD new file mode 100644 index 0000000..31b830f --- /dev/null +++ b/pkgbuilds/quickshell/PKGBUILD @@ -0,0 +1,61 @@ +pkgname=quickshell +pkgver=0.3.2 +pkgrel=2 +pkgdesc='Flexible toolkit for making desktop shells with QtQuick' +arch=(x86_64 aarch64) +url='https://github.com/quickshell-mirror/quickshell' +license=(LGPL-3.0-only) +depends=( + cpptrace + glibc + hicolor-icon-theme + jemalloc + libdrm + libgcc + libglvnd + libpipewire + libstdc++ + libxcb + mesa + pam + polkit + qt6-base + qt6-declarative + qt6-svg + qt6-wayland + wayland +) +makedepends=( + cli11 + cmake + ninja + qt6-shadertools + spirv-tools + vulkan-headers + wayland-protocols +) +provides=("$pkgname") +conflicts=(quickshell-git) +_archive="$pkgname-$pkgver" +source=("$_archive.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz" + quickshell-check.hook) +sha256sums=('946a27e2a276917a03c1079e5c60971af84ece73088c3661dee455d92eb420f7' + '8543e21aeaaa5441b73a679160e7601a957f16c433e8d6bd9257e80bd0e94083') + +build() { + cd "$_archive" + cmake -G Ninja -B build -W no-dev \ + -D CMAKE_BUILD_TYPE=RelWithDebInfo \ + -D DISTRIBUTOR='Omarchy' \ + -D CRASH_REPORTER=Off \ + -D CMAKE_INSTALL_PREFIX=/usr \ + -D INSTALL_QML_PREFIX=lib/qt6/qml + cmake --build build +} + +package() { + install -Dm644 quickshell-check.hook -t "$pkgdir/usr/share/libalpm/hooks" + cd "$_archive" + DESTDIR="$pkgdir" cmake --install build + install -Dm644 LICENSE -t "$pkgdir/usr/share/licenses/$pkgname" +} diff --git a/pkgbuilds/quickshell/quickshell-check.hook b/pkgbuilds/quickshell/quickshell-check.hook new file mode 100644 index 0000000..a7a97f7 --- /dev/null +++ b/pkgbuilds/quickshell/quickshell-check.hook @@ -0,0 +1,10 @@ +[Trigger] +Operation = Upgrade +Operation = Install +Type = Package +Target = qt6-base +Target = qt6-wayland +[Action] +Description = Querying Quickshell compatibility after Qt6 update... +When = PostTransaction +Exec = /usr/bin/quickshell --private-check-compat diff --git a/pkgbuilds/rustdesk/PKGBUILD b/pkgbuilds/rustdesk/PKGBUILD index e817ffa..b1f0587 100644 --- a/pkgbuilds/rustdesk/PKGBUILD +++ b/pkgbuilds/rustdesk/PKGBUILD @@ -151,7 +151,7 @@ _pkgname='rustdesk' pkgname="${_pkgname}" pkgver=1.5.0 _pkgver=$pkgver -pkgrel=1 +pkgrel=2 _sfx='' #_sfx='-pr1-998b758' _hbb_commit=229b904508364c8997aad0fb5af57effac859f60 diff --git a/pkgbuilds/schist-bin/PKGBUILD b/pkgbuilds/schist-bin/PKGBUILD index 139dc47..f8ac0c5 100644 --- a/pkgbuilds/schist-bin/PKGBUILD +++ b/pkgbuilds/schist-bin/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Infrawrench LLC pkgname=schist-bin pkgver=0.15.0 -pkgrel=1 +pkgrel=2 # Upstream's own package release, embedded in the asset name. It is # packages.sh's "release=" and only moves when the packaging changes under # a version that has already shipped; pkgrel above is this package's own. diff --git a/pkgbuilds/slack-desktop/.omarchy/package.json b/pkgbuilds/slack-desktop/.omarchy/package.json index 0eca1ed..75e4c55 100644 --- a/pkgbuilds/slack-desktop/.omarchy/package.json +++ b/pkgbuilds/slack-desktop/.omarchy/package.json @@ -1,5 +1,6 @@ { "source": "local", + "release_ring": "fast", "upstream": { "watch": { "json": "https://slack.com/api/desktop.latestRelease?arch=x64&variant=deb", diff --git a/pkgbuilds/slack-desktop/PKGBUILD b/pkgbuilds/slack-desktop/PKGBUILD index 27fbc28..a7ea67d 100644 --- a/pkgbuilds/slack-desktop/PKGBUILD +++ b/pkgbuilds/slack-desktop/PKGBUILD @@ -2,7 +2,7 @@ pkgname=slack-desktop pkgver=4.52.171 -pkgrel=2 +pkgrel=3 # Slack publishes Linux builds for x86_64 only, which this package installs # unchanged. On AArch64 the same application code runs on the AArch64 build # of the Electron release Slack ships with; see README.md for the native diff --git a/pkgbuilds/omarchy-walker/.omarchy/package.json b/pkgbuilds/steam/.omarchy/package.json similarity index 100% rename from pkgbuilds/omarchy-walker/.omarchy/package.json rename to pkgbuilds/steam/.omarchy/package.json diff --git a/pkgbuilds/steam/PKGBUILD b/pkgbuilds/steam/PKGBUILD new file mode 100644 index 0000000..8abce39 --- /dev/null +++ b/pkgbuilds/steam/PKGBUILD @@ -0,0 +1,129 @@ +# Maintainer: Ryan Hughes +# Contributor: Levente Polyak +# Contributor: Giancarlo Razzolini +# Contributor: Christian Heusel +# +# Arch builds steam for x86_64 only; this is the aarch64 build. It installs +# the same Valve launcher files (desktop entry, icons, udev rules) but swaps +# the x86 bootstrap for Valve's native arm64 client, which then updates itself +# from Valve's linuxarm64 client channel. _client is the client build the +# bootstrap was taken from; it only matters for the first launch, so it moves +# with the launcher rather than with every client release. + +pkgbase=steam +pkgname=(steam steam-devices) +pkgver=1.0.0.87 +pkgrel=1 +_client=1788652215 +_client_zip=bins_linuxarm64_linuxarm64.zip.dc817d33f8308815bf4fde6a3cb61fd4529728c8 +pkgdesc="Valve's digital software delivery system" +url='https://steampowered.com/' +arch=('aarch64') +license=('LicenseRef-steam-subscriber-agreement') +# the beta directory is a superset of stable and also contains the stable releases +source=( + "https://repo.steampowered.com/$pkgbase/archive/beta/${pkgbase}_$pkgver.tar.gz" + "$pkgbase-client-$_client.zip::https://client-update.steamstatic.com/$_client_zip" + steam.sh +) +noextract=("$pkgbase-client-$_client.zip") +# The client checksum is the sha2 Valve's steam_client_linuxarm64 manifest +# publishes for that zip. +sha256sums=('649375d2f9377f8009aaf3e2ff09978041eb9114897d9c5a3886f1d82e27ba1f' + 'c2d7c0ecb8de7e209504cd657b785127cd1ac3d905668c296e87c360b2b54163' + '6f8d705e7d615b2debf8449898c51ad687136cd8f6605ef3c7573b4aa768e342') + +build() { + # The arm64 counterpart of Valve's bootstraplinux_ubuntu12_32.tar.xz. The + # client binary links only glibc and downloads the rest of Steam itself. + mkdir -p bootstrap + bsdtar -xf "$pkgbase-client-$_client.zip" -C bootstrap steamrtarm64/steam + chmod 755 bootstrap/steamrtarm64 bootstrap/steamrtarm64/steam + tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner \ + -cJf bootstraplinux_linuxarm64.tar.xz -C bootstrap steamrtarm64 +} + +package_steam() { + # Libraries the arm64 client links against from the host, plus lsof, which + # the client runs to find its own IPC ports. + depends=( + alsa-lib + at-spi2-core + bash + cairo + coreutils + dbus + fontconfig + freetype2 + gdk-pixbuf2 + glib2 + glibc + gtk2 + hicolor-icon-theme + libcups + libdrm + libgcc + libglvnd + libibus + libnm + libpipewire + libpulse + libsm + libstdc++ + libva + libvdpau + libx11 + libxcomposite + libxdamage + libxext + libxfixes + libxi + libxinerama + libxkbcommon + libxrandr + libxrender + libxtst + lsof + mesa + nss + openal + pango + sdl2-compat + systemd-libs + tar + ttf-font + vulkan-driver + vulkan-icd-loader + xdg-user-dirs + xz + ) + optdepends=( + 'ntsync-autoload: for NTSync support' + 'polkit: to setup SteamVR without root access' + 'xdg-desktop-portal-impl: file & folder picker' + 'xorg-fonts-misc: for non-latin locales' + ) + depends+=(steam-devices) + + cd "$pkgbase-launcher" + make DESTDIR="$pkgdir" install-docs install-icons install-desktop install-appdata + + install -Dm755 "$srcdir/steam.sh" "$pkgdir/usr/bin/steam" + install -Dm644 "$srcdir/bootstraplinux_linuxarm64.tar.xz" -t "$pkgdir/usr/lib/steam" + + install -d "$pkgdir/usr/share/licenses/$pkgname" + ln -s "../../doc/$pkgname/steam_subscriber_agreement.txt" "$pkgdir/usr/share/licenses/$pkgname/" + install -Dm644 debian/changelog -t "$pkgdir/usr/share/doc/$pkgname" +} + +package_steam-devices() { + pkgdesc="List of devices Steam and SteamVR will want read/write permissions on" + license=(MIT) + depends=(udev) + + cd "$srcdir/$pkgbase-launcher/subprojects/steam-devices" + + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 60-steam-input.rules "$pkgdir/usr/lib/udev/rules.d/60-steam-input.rules" + install -Dm644 60-steam-vr.rules "$pkgdir/usr/lib/udev/rules.d/60-steam-vr.rules" +} diff --git a/pkgbuilds/steam/steam.sh b/pkgbuilds/steam/steam.sh new file mode 100644 index 0000000..4115572 --- /dev/null +++ b/pkgbuilds/steam/steam.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Launches Valve's native arm64 Steam client. +# +# Valve's own launcher (bin_steam.sh) only knows the x86 bootstrap, so this +# does its job for linuxarm64: unpack the client on first run, keep the +# ~/.steam links the client refuses to start without, and start the client +# again whenever it exits asking to be restarted after updating itself. + +set -euo pipefail + +bootstrap=/usr/lib/steam/bootstraplinux_linuxarm64.tar.xz +# The exit status Steam uses to ask to be started again. +restart_status=42 + +if [[ $(id -u) == 0 ]]; then + echo "steam: cannot run as root" >&2 + exit 1 +fi + +# Like Valve's launcher, follow ~/.steam/steam when it points at a working +# install, so a library that was moved keeps working. +steamroot=$(readlink -e "$HOME/.steam/steam" || true) +if [[ -z $steamroot || ! -x $steamroot/steamrtarm64/steam ]]; then + steamroot=${XDG_DATA_HOME:-$HOME/.local/share}/Steam + if [[ ! -x $steamroot/steamrtarm64/steam ]]; then + (umask 077 && mkdir -p "$steamroot" && tar -xJf "$bootstrap" -C "$steamroot") + fi +fi + +mkdir -p "$HOME/.steam" +ln -sfn "$steamroot" "$HOME/.steam/steam" +ln -sfn "$steamroot" "$HOME/.steam/root" +# The arm64 counterpart of the sdk32/sdk64 links steam.sh makes for games +# that load the Steam API from ~/.steam. +ln -sfn "$steamroot/linuxarm64" "$HOME/.steam/sdkarm64" + +cd "$steamroot" +while :; do + status=0 + "$steamroot/steamrtarm64/steam" "$@" || status=$? + ((status == restart_status)) || exit "$status" +done diff --git a/pkgbuilds/strata/PKGBUILD b/pkgbuilds/strata/PKGBUILD index 017ef92..6039358 100644 --- a/pkgbuilds/strata/PKGBUILD +++ b/pkgbuilds/strata/PKGBUILD @@ -1,6 +1,6 @@ pkgname=strata -pkgver=0.20.1 -pkgrel=1 +pkgver=0.21.0 +pkgrel=2 pkgdesc='Fast, keyboard-first file manager for modern Linux desktops' arch=('x86_64' 'aarch64') url='https://github.com/lgse/strata' @@ -41,7 +41,7 @@ conflicts=('strata-git') options=('!debug' '!lto') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('cdbe7f196970cbaa1fa41c427833899e35c2dcaa8bb68a864c3afa6428827e2f') +sha256sums=('7d662afe5b2f797abb334d6a462df54d3a43ca6b88386b3e7954190812aeb6d0') prepare() { cd "$pkgname-$pkgver" @@ -117,6 +117,11 @@ check() { # bug in the example for users with UTF-8 locales; skip until upstream # logs names with errors="surrogateescape". skip+=(--skip adapters::local_jobs::tests::examples::checksum_example_handles_native_names_and_refuses_existing_files_and_links) + # reopening_resumes_where_the_preview_closed (new in 0.21.0) calls + # gtk::init().expect() itself, so on the headless builder it panics with + # "Failed to initialize GTK" where every other GTK test skips. Skip until + # upstream guards it like the rest. + skip+=(--skip ui::media::tests::reopening_resumes_where_the_preview_closed) # 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 diff --git a/pkgbuilds/sublime-text-4/PKGBUILD b/pkgbuilds/sublime-text-4/PKGBUILD index 31940a7..e8a864e 100644 --- a/pkgbuilds/sublime-text-4/PKGBUILD +++ b/pkgbuilds/sublime-text-4/PKGBUILD @@ -2,7 +2,7 @@ pkgname=sublime-text-4 pkgver=4.4215 -pkgrel=1 +pkgrel=2 pkgdesc='Sophisticated text editor for code, html and prose - stable build' arch=('x86_64' 'aarch64') url='https://www.sublimetext.com/download' diff --git a/pkgbuilds/supergfxctl/PKGBUILD b/pkgbuilds/supergfxctl/PKGBUILD index 46e46f2..f60267c 100644 --- a/pkgbuilds/supergfxctl/PKGBUILD +++ b/pkgbuilds/supergfxctl/PKGBUILD @@ -3,7 +3,7 @@ pkgname=supergfxctl pkgver=5.2.7 -pkgrel=3 +pkgrel=4 pkgdesc="A utility for Linux graphics switching on Intel/AMD iGPU + nVidia dGPU laptops" arch=('x86_64') url="https://gitlab.com/asus-linux/supergfxctl" diff --git a/pkgbuilds/superwhisper-bin/.omarchy/package.json b/pkgbuilds/superwhisper-bin/.omarchy/package.json new file mode 100644 index 0000000..1e480a5 --- /dev/null +++ b/pkgbuilds/superwhisper-bin/.omarchy/package.json @@ -0,0 +1,16 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h", + "upstream": { + "watch": { + "github": "superultrainc/superwhisper-linux-releases", + "allow_prerelease": true, + "pattern": "v(?P[0-9]+(?:\\.[0-9]+)*)-beta\\.(?P[0-9]+)", + "version": "{version}beta{beta}", + "variables": { + "_version": "{version}-beta.{beta}" + } + } + } +} diff --git a/pkgbuilds/superwhisper-bin/PKGBUILD b/pkgbuilds/superwhisper-bin/PKGBUILD new file mode 100644 index 0000000..6555eba --- /dev/null +++ b/pkgbuilds/superwhisper-bin/PKGBUILD @@ -0,0 +1,87 @@ +# Maintainer: Omarchy + +pkgname=superwhisper-bin +pkgver=0.1.0beta15 +_version=0.1.0-beta.15 +pkgrel=1 +pkgdesc='Local and cloud voice dictation with Superwhisper' +arch=('x86_64') +url='https://superwhisper.com' +license=('LicenseRef-proprietary' 'MIT') +provides=('superwhisper') +conflicts=('superwhisper') +depends=('glibc' 'libgcc' 'libstdc++' 'libgomp' 'xdg-utils' 'python' 'python-gobject' 'python-cairo' 'gtk4' + 'gtk4-layer-shell' 'at-spi2-core' 'pipewire-pulse' 'libnotify' + 'wl-clipboard' 'ffmpeg' 'vulkan-icd-loader' 'librsvg' 'fontconfig' + 'hicolor-icon-theme' 'foot') +optdepends=('fcitx5: insert dictation directly through the input method' + 'vulkan-intel: local processing on Intel GPUs' + 'vulkan-radeon: local processing on AMD GPUs') +makedepends=('minisign') +options=('!debug' '!strip') +_release="https://tux.superwhisper.com/x86_64/v$_version/superwhisper-omarchy-x86_64-$_version.tar.zst" +source=("superwhisper-$pkgver.tar.zst::$_release" + "superwhisper-$pkgver.tar.zst.minisig::$_release.minisig" + 'linux-release.pub' + 'superwhisper' + 'superwhisper.service' + 'superwhisper.desktop' + 'superwhisper-url.desktop' + 'setup-user') +noextract=("superwhisper-$pkgver.tar.zst") +sha256sums=('99e44cbeb60c44945d2713144f12d0a356b1dfb98ed5702b5468fb6b47ae49c0' + '4d933540a9299f5f7835d1f6494ce3facad866c477cdf0e82bf3e9ae5813325d' + '0672b5230ed6390f9168eaea009c6ce1e8838f0961a87888d9073d1e1db5267d' + '54a0fd574f2b123af24031e910fe0dc47be1d4c055ee5df775f1c681e6201c93' + '8b9a2cb81a6ea015d663c9981ba67edafe934eb0e56904fab5030fbbfdd8506b' + 'c65169c8432223f26b072c8defe9ff65ef82c26be34cd92a9617ed112cb22570' + 'e67978d239e12a386ec335346b14e10cc7e3bed8b9b1d4bcbed4377f10d4695a' + 'ffbf2f42e403fca85eb73810742397884fbf949b715ed33cb98f618f33556c16') + +prepare() { + minisign -Vm "superwhisper-$pkgver.tar.zst" -x "superwhisper-$pkgver.tar.zst.minisig" -p linux-release.pub + bsdtar -xf "superwhisper-$pkgver.tar.zst" + local bundle="superwhisper-omarchy-x86_64-$_version" + [[ $(< "$bundle/VERSION") == "$_version" ]] || return 1 + # Qt 6.12 adds a Color type that shadows Omarchy's palette singleton. + python - "superwhisper-omarchy-x86_64-$_version/app/assets/omarchy-plugin/superwhisper-panel" <<'PYTHON' +from pathlib import Path +import re +import sys + +for path in Path(sys.argv[1]).glob("*.qml"): + original = path.read_text() + updated = re.sub(r"(? "$pkgdir/usr/share/fcitx5/addon/superwhisper.conf" +} diff --git a/pkgbuilds/superwhisper-bin/README.md b/pkgbuilds/superwhisper-bin/README.md new file mode 100644 index 0000000..4ff42e1 --- /dev/null +++ b/pkgbuilds/superwhisper-bin/README.md @@ -0,0 +1,15 @@ +# Superwhisper + +Packages the vendor's signed portable x86_64 Linux archive under `/opt/superwhisper`, with a launcher, desktop entries, user service, fonts, icons and the fcitx5 text-commit addon. Local models are downloaded through Superwhisper's settings; they are not bundled in the package. + +`sync-upstream` follows beta releases from `superultrainc/superwhisper-linux-releases`, normalizing `0.1.0-beta.15` to the Arch version `0.1.0beta15` while retaining the vendor version in `_version`. The stable feed has not been published yet. The recipe verifies the archive's SHA256 and Minisign signature against the checked-in vendor release key. Packaging never runs the downloaded installer. + +Omarchy's Setup → Defaults → Dictation → Superwhisper invokes `/usr/share/superwhisper/setup-user`, then configures its shortcuts and saves Superwhisper as the default backend. The package does not add Omarchy menu extensions and omits the portable installer’s menu-writing helper. User setup enables the packaged service, links the vendor's panel and agent skill, and retires known portable-install wrappers and unit overrides with backups. Preferences, history and downloaded models are retained. Package updates come from OPR; the portable install's private update timer is disabled. + +Omarchy loads the selected backend's generated `~/.config/superwhisper/shortcuts.lua` bridge itself, so no Superwhisper block is required in the user's Hyprland files. The bridge retains cancellation and correction-based vocabulary learning alongside Omarchy's shared dictation commands. + +The launcher redirects the vendor’s generated Hyprland include to a private, unloaded file for both the daemon and CLI. User setup waits for daemon readiness. The portable updater is disabled and refused by the packaged launcher. The archive is verified before extraction, its version is checked, and the bundled panel uses explicit Omarchy palette references for Qt 6.12. + +Fresh profiles start with Alt+Space toggle, no native hold, and Escape cancellation before the daemon starts, avoiding a conflict with Omarchy’s Right Alt binding. Existing preferences remain unchanged. + +Panel discovery is asynchronous: user setup retries placement, leaves a pending marker if the shell is unavailable, and retries on the next setup without moving an already placed panel. Bundled component license notices are installed under `/usr/share/licenses/superwhisper-bin/`. diff --git a/pkgbuilds/superwhisper-bin/linux-release.pub b/pkgbuilds/superwhisper-bin/linux-release.pub new file mode 100644 index 0000000..bb2aa19 --- /dev/null +++ b/pkgbuilds/superwhisper-bin/linux-release.pub @@ -0,0 +1,2 @@ +untrusted comment: minisign public key BC524820AE37B966 +RWRmuTeuIEhSvCYQejUR+zzMNsfxAn7pr9iOZ2ZJ9U1/zJwfsqvhzATB diff --git a/pkgbuilds/superwhisper-bin/setup-user b/pkgbuilds/superwhisper-bin/setup-user new file mode 100644 index 0000000..2c219b9 --- /dev/null +++ b/pkgbuilds/superwhisper-bin/setup-user @@ -0,0 +1,99 @@ +#!/bin/bash + +set -euo pipefail + +config_home="${XDG_CONFIG_HOME:-$HOME/.config}" +data_home="${XDG_DATA_HOME:-$HOME/.local/share}" +backup_suffix=".before-opr.$(date +%s%N)" + +# Retire the portable install's wrappers and unit overrides. Keep its versioned +# binaries, preferences, history and models so the change is reversible. +launcher="$HOME/.local/bin/superwhisper" +mkdir -p "$HOME/.local/bin" +if [[ -f $launcher && ! -L $launcher ]] && grep -Fq 'superwhisper/app/current/run.sh' "$launcher"; then + mv "$launcher" "$launcher$backup_suffix" + ln -s /usr/bin/superwhisper "$launcher" +fi +if [[ ! -e $launcher && ! -L $launcher ]]; then + ln -s /usr/bin/superwhisper "$launcher" +fi +current="$data_home/superwhisper/app/current" +if [[ -L $current && $(readlink "$current") != "/opt/superwhisper" ]]; then + mv "$current" "$current$backup_suffix" + ln -s /opt/superwhisper "$current" +elif [[ ! -e $current && ! -L $current ]]; then + mkdir -p "${current%/*}" + ln -s /opt/superwhisper "$current" +fi +unit="$config_home/systemd/user/superwhisper.service" +if [[ -f $unit ]] && grep -Fq 'ExecStart=%h/.local/bin/superwhisper daemon' "$unit"; then + mv "$unit" "$unit$backup_suffix" +fi +systemctl --user disable --now superwhisper-update.timer 2>/dev/null || true +# Seed a fresh profile before the daemon starts. Its upstream Right Alt hold +# conflicts with Omarchy's own bindings and prevents sequential CLI changes. +preferences="$config_home/superwhisper/preferences.json" +if [[ ! -e $preferences ]]; then + mkdir -p "${preferences%/*}" + ( + umask 077 + printf '%s\n' '{"toggleRecordingShortcut":"Alt+Space","pushToTalkShortcut":"","cancelRecordingShortcut":"Escape"}' > "$preferences" + ) +fi + +systemctl --user daemon-reload +systemctl --user enable --now superwhisper.service +systemctl --user restart superwhisper.service + +ready=false +for (( attempt = 0; attempt < 50; attempt++ )); do + if superwhisper status >/dev/null 2>&1; then + ready=true + break + fi + sleep 0.1 +done +if [[ $ready != "true" ]]; then + echo "Superwhisper did not become ready. Check systemctl --user status superwhisper.service." >&2 + exit 1 +fi + +xdg-mime default superwhisper-url.desktop x-scheme-handler/superwhisper + +# Link the vendor's panel and agent guide to the packaged payload. They update +# with pacman; no copy of the application or private updater is installed. +plugins="$config_home/omarchy/plugins" +panel_pending="$config_home/superwhisper/omarchy-panel.pending" +if [[ ! -e $plugins/superwhisper-panel && ! -L $plugins/superwhisper-panel ]]; then + touch "$panel_pending" +fi +mkdir -p "$plugins" "$HOME/.agents/skills" +ln -sfn /opt/superwhisper/assets/omarchy-plugin/superwhisper-panel "$plugins/superwhisper-panel" +ln -sfn /opt/superwhisper/assets/agent-skill/superwhisper "$HOME/.agents/skills/superwhisper" +for agent in .claude .codex .pi/agent; do + if [[ -d $HOME/$agent ]]; then + mkdir -p "$HOME/$agent/skills" + ln -sfn /opt/superwhisper/assets/agent-skill/superwhisper "$HOME/$agent/skills/superwhisper" + fi +done +omarchy-shell -q shell rescanPlugins +if [[ -f $panel_pending ]]; then + for (( attempt = 0; attempt < 50; attempt++ )); do + placement=$(omarchy-shell shell putBarWidget superwhisper.panel '{"section":"right"}' 2>/dev/null) || placement="" + if [[ $placement == "ok" ]]; then + rm "$panel_pending" + break + elif [[ -z $placement ]]; then + break + fi + sleep 0.1 + done + if [[ -f $panel_pending ]]; then + echo "Superwhisper is ready; panel setup will retry next time setup-user runs." >&2 + fi +fi + +# A running input method discovers the packaged text-commit addon on restart. +if systemctl --user -q is-active omarchy-fcitx5.service; then + systemctl --user restart omarchy-fcitx5.service +fi diff --git a/pkgbuilds/superwhisper-bin/superwhisper b/pkgbuilds/superwhisper-bin/superwhisper new file mode 100644 index 0000000..55d9939 --- /dev/null +++ b/pkgbuilds/superwhisper-bin/superwhisper @@ -0,0 +1,15 @@ +#!/bin/bash + +# Omarchy loads shortcuts.lua from its selected backend, so the vendor's +# generated Hyprland block must live in a file Hyprland does not load. +export SUPERWHISPER_HYPR_BINDINGS="${XDG_CONFIG_HOME:-$HOME/.config}/superwhisper/hypr-block.lua" +mkdir -p "${SUPERWHISPER_HYPR_BINDINGS%/*}" +if [[ ! -e $SUPERWHISPER_HYPR_BINDINGS ]]; then + install -m600 /dev/null "$SUPERWHISPER_HYPR_BINDINGS" +fi + +if [[ ${1:-} == "update" ]]; then + echo "Superwhisper is updated through Omarchy's package repository." >&2 + exit 1 +fi +exec /opt/superwhisper/run.sh "$@" diff --git a/pkgbuilds/superwhisper-bin/superwhisper-url.desktop b/pkgbuilds/superwhisper-bin/superwhisper-url.desktop new file mode 100644 index 0000000..61a0b58 --- /dev/null +++ b/pkgbuilds/superwhisper-bin/superwhisper-url.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Superwhisper License Activation +Exec=superwhisper open-url %u +Icon=superwhisper +Terminal=false +NoDisplay=true +MimeType=x-scheme-handler/superwhisper; diff --git a/pkgbuilds/superwhisper-bin/superwhisper.desktop b/pkgbuilds/superwhisper-bin/superwhisper.desktop new file mode 100644 index 0000000..25ce29c --- /dev/null +++ b/pkgbuilds/superwhisper-bin/superwhisper.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Superwhisper +Comment=Local and cloud voice dictation +Exec=foot --app-id=com.superwhisper.settings --title=Superwhisper --font="JetBrainsMono Nerd Font,Superwhisper TUI:size=12" env SW_TUI_LOGO=font superwhisper settings +Icon=superwhisper +Terminal=false +Categories=Utility;Accessibility; diff --git a/pkgbuilds/superwhisper-bin/superwhisper.service b/pkgbuilds/superwhisper-bin/superwhisper.service new file mode 100644 index 0000000..b667698 --- /dev/null +++ b/pkgbuilds/superwhisper-bin/superwhisper.service @@ -0,0 +1,17 @@ +[Unit] +Description=Superwhisper dictation +After=graphical-session.target pipewire.service +PartOf=graphical-session.target + +[Service] +Type=simple +ExecStart=/usr/bin/superwhisper daemon +ExecStopPost=/usr/bin/superwhisper playback-restore +Restart=on-failure +RestartSec=2 +KillMode=control-group +TimeoutStopSec=15 +UMask=0077 + +[Install] +WantedBy=graphical-session.target diff --git a/pkgbuilds/symfony-cli/PKGBUILD b/pkgbuilds/symfony-cli/PKGBUILD index 5c09eb2..bfad8dc 100644 --- a/pkgbuilds/symfony-cli/PKGBUILD +++ b/pkgbuilds/symfony-cli/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: Florian Moser pkgname=symfony-cli -pkgrel=9 -pkgver=5.20.0 +pkgrel=1 +pkgver=5.21.0 pkgdesc="The Symfony client helps developers create and manage Symfony applications." url="https://symfony.com/" arch=('x86_64' 'aarch64') @@ -12,7 +12,7 @@ makedepends=('go') provides=('symfony-cli') conflicts=('symfony-cli') source=("$pkgname-$pkgver.tar.gz::https://github.com/symfony-cli/symfony-cli/releases/download/v${pkgver}/symfony-cli-${pkgver}.tar.gz") -sha256sums=('11354f7206740dc8b31410bf7fbcb20debccb4a92935da84729fd7d754b2082f') +sha256sums=('28fe034cd38223962db3a872b38a76d8c3f1d1a5c9b3e7dd030bd1d90078d425') noextract=("$pkgname-$pkgver.tar.gz") prepare() { diff --git a/pkgbuilds/t3code-bin/PKGBUILD b/pkgbuilds/t3code-bin/PKGBUILD index 9f62947..f2d0c63 100644 --- a/pkgbuilds/t3code-bin/PKGBUILD +++ b/pkgbuilds/t3code-bin/PKGBUILD @@ -8,7 +8,7 @@ pkgname=t3code-bin pkgver=0.0.45 -pkgrel=1 +pkgrel=2 pkgdesc="Open-source control plane for coding agents" arch=('x86_64' 'aarch64') url="https://t3.codes" @@ -85,10 +85,12 @@ package() { # The AppImage's usr/ tree is compatibility libraries for distributions that # lack them, and Arch is not one; its icons are the only part worth keeping. # A release that starts shipping something else there stops the build rather - # than having it deleted quietly on the way past. + # than having it deleted quietly on the way past. The same six libraries + # under their full versioned names (libXss.so.1.0.0) or their GTK 3 names + # (libappindicator3, libindicator3) are not something else. local unexpected unexpected=$(find usr \( -type f -o -type l \) | grep -vE \ - '^usr/share/icons/hicolor/[0-9]+x[0-9]+/apps/t3code\.png$|^usr/lib/lib(Xss\.so\.1|Xtst\.so\.6|appindicator\.so\.1|gconf-2\.so\.4|indicator\.so\.7|notify\.so\.4)$' || true) + '^usr/share/icons/hicolor/[0-9]+x[0-9]+/apps/t3code\.png$|^usr/lib/lib(Xss\.so\.1|Xtst\.so\.6|appindicator3?\.so\.1|gconf-2\.so\.4|indicator3?\.so\.7|notify\.so\.4)(\.[0-9]+)*$' || true) if [[ -n "${unexpected}" ]]; then echo "Unexpected files in the AppImage's usr/ tree:" >&2 echo "${unexpected}" >&2 diff --git a/pkgbuilds/t3code-nightly-bin/PKGBUILD b/pkgbuilds/t3code-nightly-bin/PKGBUILD index 3c3ecb8..35a4df4 100644 --- a/pkgbuilds/t3code-nightly-bin/PKGBUILD +++ b/pkgbuilds/t3code-nightly-bin/PKGBUILD @@ -7,9 +7,9 @@ # updates the version and architecture-specific checksums together. pkgname=t3code-nightly-bin -pkgver=0.0.46_nightly.20261004.2644 +pkgver=0.0.46_nightly.20261006.2735 _upstream_version=${pkgver/_/-} -pkgrel=1 +pkgrel=2 pkgdesc="Open-source control plane for coding agents (nightly)" arch=('x86_64' 'aarch64') url="https://t3.codes" @@ -65,8 +65,8 @@ noextract=("${_appimage_x86_64}" "${_appimage_aarch64}") sha256sums=('55e9aca82941ba21daff12e67f5b6c8a2ecbd30eb0ff9fc6c6a625dfc5aacd51' '85572a955d917fc92da30f6d0ea515370c97950aefc8e12eb25c3673aa4cca6e' '935d8f2af0c703f9c39517ee57cc4930b19d02d533be930b63f0e82f93614b43') -sha256sums_x86_64=('7521793d4dc87f2b985f88b48b070bedf33a1f590cca34a96c9e7ca0b1fe298b') -sha256sums_aarch64=('784939b3fc0aec185df9148893c22b0ae10abb6e32d03905474c5a43ba511508') +sha256sums_x86_64=('a1c312f6eded1d8384b204436ec2cd6cd42b6bf2fb3749a529589717a6288257') +sha256sums_aarch64=('8dfa943d5befdb4a8c18a7c05515f94bdb607050af0e48e5d9da32f3c1131caa') prepare() { local _appimage @@ -86,10 +86,12 @@ package() { # The AppImage's usr/ tree is compatibility libraries for distributions that # lack them, and Arch is not one; its icons are the only part worth keeping. # A release that starts shipping something else there stops the build rather - # than having it deleted quietly on the way past. + # than having it deleted quietly on the way past. The same six libraries + # under their full versioned names (libXss.so.1.0.0) or their GTK 3 names + # (libappindicator3, libindicator3) are not something else. local unexpected unexpected=$(find usr \( -type f -o -type l \) | grep -vE \ - '^usr/share/icons/hicolor/[0-9]+x[0-9]+/apps/t3code\.png$|^usr/lib/lib(Xss\.so\.1|Xtst\.so\.6|appindicator\.so\.1|gconf-2\.so\.4|indicator\.so\.7|notify\.so\.4)$' || true) + '^usr/share/icons/hicolor/[0-9]+x[0-9]+/apps/t3code\.png$|^usr/lib/lib(Xss\.so\.1|Xtst\.so\.6|appindicator3?\.so\.1|gconf-2\.so\.4|indicator3?\.so\.7|notify\.so\.4)(\.[0-9]+)*$' || true) if [[ -n "${unexpected}" ]]; then echo "Unexpected files in the AppImage's usr/ tree:" >&2 echo "${unexpected}" >&2 diff --git a/pkgbuilds/tmog-bin/PKGBUILD b/pkgbuilds/tmog-bin/PKGBUILD index 68c63fe..8a56632 100644 --- a/pkgbuilds/tmog-bin/PKGBUILD +++ b/pkgbuilds/tmog-bin/PKGBUILD @@ -11,7 +11,7 @@ pkgname=tmog-bin pkgver=1.0.0 -pkgrel=1 +pkgrel=2 pkgdesc="Native system monitor and task manager" arch=('x86_64') url="https://tmog.org/" diff --git a/pkgbuilds/tobi-try/PKGBUILD b/pkgbuilds/tobi-try/PKGBUILD index 17c0fcc..f1ab749 100644 --- a/pkgbuilds/tobi-try/PKGBUILD +++ b/pkgbuilds/tobi-try/PKGBUILD @@ -2,7 +2,7 @@ pkgname='tobi-try' pkgver=1.10.1 -pkgrel=1 +pkgrel=2 pkgdesc='Fresh directories for every vibe.' url='https://github.com/tobi/try' arch=('any') diff --git a/pkgbuilds/ttf-ia-writer/PKGBUILD b/pkgbuilds/ttf-ia-writer/PKGBUILD index f60c0d1..3c88639 100644 --- a/pkgbuilds/ttf-ia-writer/PKGBUILD +++ b/pkgbuilds/ttf-ia-writer/PKGBUILD @@ -2,7 +2,7 @@ pkgname=ttf-ia-writer pkgdesc='Subset of the fonts provided by iA Writer' pkgver=20261002.r63 -pkgrel=1 +pkgrel=2 url=https://github.com/iaolo/iA-Fonts conflicts=(ttf-ia-writer-duospace) provides=(${conflicts[@]}) diff --git a/pkgbuilds/ttfx/PKGBUILD b/pkgbuilds/ttfx/PKGBUILD index 13d3cc5..6b6e872 100644 --- a/pkgbuilds/ttfx/PKGBUILD +++ b/pkgbuilds/ttfx/PKGBUILD @@ -2,7 +2,7 @@ pkgname=ttfx pkgver=0.5.0 -pkgrel=1 +pkgrel=2 pkgdesc="Terminal text effects as a single static binary — Rust port of terminaltexteffects" arch=('x86_64' 'aarch64') url="https://github.com/omacom/ttfx" diff --git a/pkgbuilds/visual-studio-code-bin/PKGBUILD b/pkgbuilds/visual-studio-code-bin/PKGBUILD index 718b7ce..bbea2d1 100644 --- a/pkgbuilds/visual-studio-code-bin/PKGBUILD +++ b/pkgbuilds/visual-studio-code-bin/PKGBUILD @@ -3,7 +3,7 @@ pkgname=visual-studio-code-bin _pkgname=visual-studio-code pkgver=1.140.0 -pkgrel=1 +pkgrel=2 pkgdesc="Visual Studio Code (vscode): Editor for building and debugging modern web and cloud applications (official binary version)" arch=('x86_64' 'aarch64' 'armv7h') url="https://code.visualstudio.com/" diff --git a/pkgbuilds/walker/PKGBUILD b/pkgbuilds/walker/PKGBUILD index 8555361..ca313c2 100644 --- a/pkgbuilds/walker/PKGBUILD +++ b/pkgbuilds/walker/PKGBUILD @@ -2,7 +2,7 @@ pkgname=walker pkgver=2.17.1 -pkgrel=1 +pkgrel=2 pkgdesc='wayland application runner' url='https://github.com/abenz1267/walker' arch=('x86_64' 'aarch64') diff --git a/pkgbuilds/xdg-terminal-exec/.omarchy/package.json b/pkgbuilds/xdg-terminal-exec/.omarchy/package.json deleted file mode 100644 index 5e59cc2..0000000 --- a/pkgbuilds/xdg-terminal-exec/.omarchy/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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/xdg-terminal-exec/PKGBUILD b/pkgbuilds/xdg-terminal-exec/PKGBUILD deleted file mode 100644 index ecc60a5..0000000 --- a/pkgbuilds/xdg-terminal-exec/PKGBUILD +++ /dev/null @@ -1,26 +0,0 @@ -# Maintainer: Max Gautier -pkgname=xdg-terminal-exec -pkgver=0.14.3 -pkgrel=1 -pkgdesc="Proposed standard to launching desktop apps with Terminal=true" -arch=(any) -url="https://gitlab.freedesktop.org/Vladimir-csp/$pkgname" -makedepends=('scdoc') -checkdepends=('bats') -license=('GPL-3.0-or-later') -source=("${pkgname}-${pkgver}::$url/-/archive/v${pkgver}/${pkgname}-v${pkgver}.tar.gz") -sha256sums=('bfa291f6ad70fc61abd0b45510b799cb24c1c11f0b5cdc7b7538169688f1df79') -b2sums=('3ca3f4996fdbb11e98dfd84aceae3ac576c1652497e8a97627065de9b92df6f4b508afd4667e0a64b26d94850acbdbd10014cd60f5969cb3178a19682ca4ff51') - -check() { - cd "$pkgname-v$pkgver" - bats "test/" -} - -build() { - make -C "$pkgname-v$pkgver" -} - -package() { - make -C "$pkgname-v$pkgver" prefix="$pkgdir/usr" install -} diff --git a/pkgbuilds/zen-browser-bin/PKGBUILD b/pkgbuilds/zen-browser-bin/PKGBUILD index b827be7..67d9cc1 100644 --- a/pkgbuilds/zen-browser-bin/PKGBUILD +++ b/pkgbuilds/zen-browser-bin/PKGBUILD @@ -11,7 +11,7 @@ pkgname=zen-browser-bin _pkgname=zen-browser _desktopname=zen pkgver=1.23b -pkgrel=1 +pkgrel=2 pkgdesc="Official package for Zen, a privacy-focused, feature packed Firefox-based web browser" arch=('x86_64' 'aarch64') url="https://github.com/zen-browser/desktop" diff --git a/tests/controller.sh b/tests/controller.sh index d777fc5..cd21aa6 100755 --- a/tests/controller.sh +++ b/tests/controller.sh @@ -15,6 +15,7 @@ CONTROLLER_LIBRARY_ONLY=1 source "$ROOT/ci/controller.sh" CALLS_FILE=$(mktemp); trap 'rm -f "$CALLS_FILE"' EXIT NOW=$(date -u +%FT%TZ) OLD=$(date -u -d '5 hours ago' +%FT%TZ) +STUCK=$(date -u -d '15 minutes ago' +%FT%TZ) # Scenario state: DROPLETS is "id status created" lines, QUEUED a count, # BUSY a count. @@ -22,6 +23,7 @@ do_api() { local path=$1; shift echo "do $path $*" >>"$CALLS_FILE" case "$path" in + sizes\?*) echo '{"sizes":[{"slug":"g5-32vcpu-64gb-50gb","available":true,"regions":["ric1"]}]}' ;; 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 '{}' ;; @@ -31,8 +33,9 @@ gh_api() { local path=$1; shift echo "gh $path $*" >>"$CALLS_FILE" case "$path" in + */actions/runs\?status=in_progress*) echo '{"workflow_runs":[]}' ;; */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/runs/*/jobs\?*) echo '{"jobs":[{"id":'"$(echo "$path" | cut -d/ -f6)"',"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 @@ -52,15 +55,80 @@ DROPLETS="1 active $NOW" QUEUED=1 BUSY=1; run; check "one queued, one live but b 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 new $STUCK" QUEUED=1 BUSY=0; run; check "droplet stuck provisioning reaped and replaced" 1 1 +DROPLETS="1 new $NOW" QUEUED=1 BUSY=0; run; check "droplet still provisioning is left to boot" 0 0 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 +# A large in-progress matrix has no waiting jobs on page one. The queued +# workflow is on page two of the run listing; the same run can appear in +# both status queries while GitHub updates it, so count its jobs once. +gh_api() { + case "$1" in + *runs?status=queued*page=1) jq -nc '{workflow_runs: [range(100) | {id: .}]}' ;; + *runs?status=queued*page=2) echo '{"workflow_runs":[{"id":999}]}' ;; + *runs?status=in_progress*) echo '{"workflow_runs":[{"id":999},{"id":1000}]}' ;; + *runs/999/jobs*page=1) jq -nc '{jobs: [range(100) | {id: .,status:"completed",labels:["omarchy-builder"]}]}' ;; + *runs/999/jobs*page=2) echo '{"jobs":[{"id":9991,"status":"queued","labels":["omarchy-builder"]}]}' ;; + *runs/1000/jobs*) echo '{"jobs":[{"id":10001,"status":"queued","labels":["omarchy-builder"]},{"id":10002,"status":"queued","labels":["ubuntu-latest"]},{"id":10003,"status":"in_progress","labels":["omarchy-builder"]}]}' ;; + *jobs*) echo '{"jobs":[]}' ;; + *) echo "Unexpected API request: $1" >&2; return 1 ;; + esac +} +[[ $(queued_jobs) == 2 ]] || { echo "FAIL: full queue across pages and workflow states"; exit 1; } +echo "PASS: later run/job pages and in-progress workflows count each waiting builder once" + +gh_api() { + case "$1" in + *runs?status=queued*) echo '{"workflow_runs":[{"id":1}]}' ;; + *runs?status=in_progress*) echo '{"workflow_runs":[]}' ;; + *jobs*page=1) jq -nc '{jobs:[range(100)|{id:.,status:"completed",labels:[]}]}' ;; + *) return 1 ;; + esac +} +if queued_jobs >/dev/null; then echo "FAIL: a failed later page looks like an empty queue"; exit 1; fi +echo "PASS: API failures stop the queue query" + # 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; } +do_api() { + case "$1" in + droplets) printf '%s' "${*: -1}" >"$BODY_FILE"; echo '{"droplet":{"id":1}}' ;; + sizes*) echo '{"sizes":[{"slug":"g5-32vcpu-64gb-50gb","available":true,"regions":["ric1"]}]}' ;; + *) echo '{"droplets":[]}' ;; + esac +} gh_api() { echo '{"token":"TOK"}'; } -create_droplet >/dev/null +CANDIDATES=""; 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; } + +# Sizes are tried in SIZES order, each in the regions DigitalOcean lists it +# in stock (REGIONS first); a 422 falls through to the next pair with the +# refusal's message logged, and a refused pair is not retried in the tick. +# When every pair is refused, the create fails. +do_api() { + case "$1" in + sizes*) echo '{"sizes":[ + {"slug":"small","available":true,"regions":["r1","r2"]}, + {"slug":"gone","available":false,"regions":["r1"]}, + {"slug":"big","available":true,"regions":["r3"]}]}' ;; + droplets) + local pair; pair=$(jq -r '"\(.size)@\(.region)"' <<< "${*: -1}"); echo "$pair" >>"$CALLS_FILE" + [[ $pair == big@r3 ]] && { echo '{"droplet":{"id":2}}'; return 0; } + echo '{"id":"unprocessable_entity","message":"Size is not available in this region."}'; return 22 ;; + esac +} +: >"$CALLS_FILE"; CANDIDATES="" +out=$(SIZES="small gone big" REGIONS="r2"; create_droplet; create_droplet) +[[ $(paste -sd' ' "$CALLS_FILE") == "small@r2 small@r1 big@r3 big@r3" \ + && $out == *"small in r2 refused: Size is not available in this region."* && $out == *"created droplet 2"* ]] \ + && echo "PASS: a refused size falls back to the next region, then the next size, logging why" \ + || { echo "FAIL: size and region fallback"; echo "$out"; cat "$CALLS_FILE"; exit 1; } +CANDIDATES="" +if out=$(SIZES="small gone" create_droplet); then echo "FAIL: every pair refused looks like a create"; exit 1; fi +[[ $out == *"no size in 'small gone' can be created in any region"* ]] \ + && echo "PASS: every size refused everywhere fails the create" \ + || { echo "FAIL: all-refused message"; echo "$out"; exit 1; } diff --git a/tests/pr-workflow-approval.cjs b/tests/pr-workflow-approval.cjs index d704936..d72c16b 100644 --- a/tests/pr-workflow-approval.cjs +++ b/tests/pr-workflow-approval.cjs @@ -363,7 +363,7 @@ test('a labelled sync PR has its bot push released', async () => { assert.deepEqual(state.approved, [1, 2]); }); -test('an unlabelled sync PR stays held for a maintainer', async () => { +test('a sync PR whose label a maintainer removed stays held', async () => { const { state, push } = syncFixture(); state.pr.labels = []; await push(); @@ -435,7 +435,7 @@ test('scoped branch names reject anything that is not a package name', () => { }); test('sync workflows push scoped runs aside and keep actions: write out of the sync job', () => { - for (const file of ['sync-upstream.yml', 'sync-rebuilds.yml']) { + for (const file of ['sync-upstream.yml']) { const text = readFileSync(join(__dirname, '../.github/workflows', file), 'utf8'); const sync = text.slice(text.indexOf('\n sync:\n'), text.indexOf('\n approve:\n')); const approveJob = text.slice(text.indexOf('\n approve:\n')); @@ -443,6 +443,94 @@ test('sync workflows push scoped runs aside and keep actions: write out of the s assert.match(sync, /branch: \$\{\{ steps\.branch\.outputs\.branch \}\}/, file); assert.doesNotMatch(sync, /^ +actions: write$/m, file); assert.match(approveJob, /^ actions: write$/m, file); + // The sync PR is bot-authored and trusted: it labels itself so its own + // pushes build, including the push that opens the PR. + assert.match(sync, /labels: \|\n +automated\n +build-approved\n/, file); + assert.match(approveJob, /needs\.sync\.outputs\.operation == 'created'/, file); assert.match(approveJob, /needs\.sync\.outputs\.operation == 'updated'/, file); } }); + +test('rebuild sync opens its PR with the bot token and auto-merges it', () => { + const file = 'sync-rebuilds.yml'; + const text = readFileSync(join(__dirname, '../.github/workflows', file), 'utf8'); + assert.match(text, /sync-pr-branch\.sh auto\/sync-rebuilds "\$\{package_args\[@\]\}"/, file); + assert.match(text, /branch: \$\{\{ steps\.branch\.outputs\.branch \}\}/, file); + // A GITHUB_TOKEN merge would not start publish.yml, so the PR and the + // merge both go through the PAT. + assert.match(text, /token: \$\{\{ secrets\.PKGS_BOT_TOKEN \}\}/, file); + assert.doesNotMatch(text, /secrets\.GITHUB_TOKEN/, file); + assert.match(text, /gh pr merge --auto --merge "\$PR"/, file); + assert.doesNotMatch(text, /^ +actions: write$/m, file); + assert.doesNotMatch(text, /\n approve:\n/, file); +}); + +const { decide } = require('../.github/scripts/auto-merge-pr.cjs'); +const repository = 'omacom/omarchy-pkgs'; +const mergeable = (overrides = {}) => ({ + state: 'open', draft: false, labels: [], + head: { ref: 'superwhisper-bin', repo: { full_name: repository } }, ...overrides, +}); +const packageFiles = [{ filename: 'pkgbuilds/superwhisper-bin/PKGBUILD' }, + { filename: 'pkgbuilds/superwhisper-bin/.omarchy/package.json' }]; + +test('auto-merge follows the build gate: trusted authors, or build-approved for unknown ones', () => { + for (const vouchStatus of ['collaborator', 'vouched', 'bot']) { + assert.equal(decide({ pr: mergeable(), files: packageFiles, vouchStatus, repository }).enable, true, vouchStatus); + } + assert.equal(decide({ pr: mergeable(), files: packageFiles, vouchStatus: 'unknown', repository }).enable, false); + const labelled = mergeable({ labels: [{ name: 'build-approved' }] }); + assert.equal(decide({ pr: labelled, files: packageFiles, vouchStatus: 'unknown', repository }).enable, true); + // A denouncement is absolute, and a failed lookup is not approval. + for (const vouchStatus of ['denounced', '', undefined, 'error']) { + assert.equal(decide({ pr: labelled, files: packageFiles, vouchStatus, repository }).enable, false, String(vouchStatus)); + } +}); + +test('auto-merge only lands PRs that change nothing outside pkgbuilds/', () => { + const check = files => decide({ pr: mergeable(), files, vouchStatus: 'collaborator', repository }).enable; + assert.equal(check(packageFiles), true); + assert.equal(check([...packageFiles, { filename: '.github/workflows/publish.yml' }]), false); + assert.equal(check([{ filename: 'bin/build' }]), false); + assert.equal(check([{ filename: 'pkgbuilds/x/PKGBUILD', previous_filename: 'helpers/x.sh' }]), false); + assert.equal(check([]), false); +}); + +test('auto-merge accepts the test a package PR brings, and nothing else outside pkgbuilds/', () => { + const check = files => decide({ pr: mergeable(), files, vouchStatus: 'collaborator', repository }).enable; + const wiring = patch => ({ filename: '.github/workflows/test.yml', status: 'modified', patch }); + const newTest = { filename: 'tests/superwhisper-bin-install.sh', status: 'added' }; + // #854's shape: package files, a new test, and one line in test.yml running it. + const adds = '@@ -67,6 +67,7 @@ jobs:\n ./tests/voxtype-bin-install.sh\n+ ./tests/superwhisper-bin-install.sh\n pacman -S --noconfirm --quiet rclone >/dev/null'; + assert.equal(check([...packageFiles, newTest, wiring(adds)]), true); + // An existing test can run on master with write access; editing it is not a package change. + assert.equal(check([...packageFiles, { filename: 'tests/build-isolation.sh', status: 'modified' }]), false); + // test.yml changes that do anything but add a test call. + assert.equal(check([...packageFiles, wiring('@@ -1 +1 @@\n-on:\n+on: [push]')]), false); + assert.equal(check([...packageFiles, wiring('@@ -67 +67,2 @@\n+ ./tests/x.sh\n+ curl evil | sh')]), false); + assert.equal(check([...packageFiles, wiring('@@ -67 +67 @@\n- ./tests/a.sh\n+ ./tests/b.sh')]), false); + assert.equal(check([...packageFiles, wiring(undefined)]), false); +}); + +test('auto-merge skips drafts, closed PRs and the reviewed upstream sync', () => { + const check = pr => decide({ pr, files: packageFiles, vouchStatus: 'bot', repository }).enable; + assert.equal(check(mergeable({ draft: true })), false); + assert.equal(check(mergeable({ state: 'closed' })), false); + for (const ref of ['auto/sync-upstream', 'auto/sync-upstream/walker']) { + assert.equal(check(mergeable({ head: { ref, repo: { full_name: repository } } })), false, ref); + } + // The unattended lanes already enable their own auto-merge; agreeing is harmless. + assert.equal(check(mergeable({ head: { ref: 'auto/sync-rebuilds', repo: { full_name: repository } } })), true); + // A fork's branch named like ours is just a contributor branch. + assert.equal(check(mergeable({ head: { ref: 'auto/sync-upstream', repo: { full_name: 'someone/omarchy-pkgs' } } })), true); +}); + +test('auto-merge workflow enables with the bot token and never checks out the PR', () => { + const text = readFileSync(join(__dirname, '../.github/workflows/auto-merge-pr.yml'), 'utf8'); + assert.match(text, /pull_request_target:/); + assert.match(text, /ref: \$\{\{ github\.event\.repository\.default_branch \}\}/); + assert.doesNotMatch(text, /head\.sha \}\}|head\.ref \}\}|refs\/pull\//); + assert.match(text, /GH_TOKEN: \$\{\{ secrets\.PKGS_BOT_TOKEN \}\}/); + assert.match(text, /gh pr merge --auto --squash --match-head-commit "\$HEAD_SHA"/); + assert.doesNotMatch(text, /^ +(contents|pull-requests|actions): write$/m); +}); diff --git a/tests/superwhisper-bin-install.sh b/tests/superwhisper-bin-install.sh new file mode 100755 index 0000000..41a6e51 --- /dev/null +++ b/tests/superwhisper-bin-install.sh @@ -0,0 +1,154 @@ +#!/bin/bash + +set -euo pipefail + +ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT +export HOME="$test_tmp/home" XDG_CONFIG_HOME="$test_tmp/config" XDG_DATA_HOME="$test_tmp/data" +export SUPERWHISPER_TEST_LOG="$test_tmp/calls" +mkdir -p "$test_tmp/bin" "$HOME/.local/bin" "$XDG_CONFIG_HOME/systemd/user" "$XDG_DATA_HOME/superwhisper/app/old" +export PATH="$test_tmp/bin:$PATH" +for command in systemctl xdg-mime superwhisper; do + cat > "$test_tmp/bin/$command" <<'SH' +#!/bin/bash +printf '%s %s\n' "${0##*/}" "$*" >> "$SUPERWHISPER_TEST_LOG" +[[ $* != *is-active* ]] +SH +done +cat > "$test_tmp/bin/omarchy-shell" <<'SH' +#!/bin/bash +printf '%s %s\n' "${0##*/}" "$*" >> "$SUPERWHISPER_TEST_LOG" +[[ $1 != "-q" ]] || exit 0 +[[ ${SUPERWHISPER_SHELL_ABSENT:-0} != 1 ]] || exit 1 +if [[ $* == *putBarWidget* ]]; then + count=0 + [[ ! -f $SUPERWHISPER_PANEL_COUNT ]] || read -r count < "$SUPERWHISPER_PANEL_COUNT" + printf '%s\n' "$((count + 1))" > "$SUPERWHISPER_PANEL_COUNT" + if (( count < 2 )); then + echo 'not ready' + else + echo ok + fi +fi +SH +export SUPERWHISPER_PANEL_COUNT="$test_tmp/panel-count" +chmod +x "$test_tmp/bin/"* +cat > "$HOME/.local/bin/superwhisper" <<'SH' +#!/bin/bash +exec "${XDG_DATA_HOME:-$HOME/.local/share}/superwhisper/app/current/run.sh" "$@" +SH +printf '%s\n' 'ExecStart=%h/.local/bin/superwhisper daemon' > "$XDG_CONFIG_HOME/systemd/user/superwhisper.service" +ln -s "$XDG_DATA_HOME/superwhisper/app/old" "$XDG_DATA_HOME/superwhisper/app/current" +printf '%s\n' 'retained model' > "$XDG_DATA_HOME/superwhisper/app/old/model" +mkdir -p "$XDG_CONFIG_HOME/superwhisper" +printf '%s\n' 'retained preferences' > "$XDG_CONFIG_HOME/superwhisper/preferences.json" + +bash "$ROOT/pkgbuilds/superwhisper-bin/setup-user" +[[ $(readlink "$HOME/.local/bin/superwhisper") == "/usr/bin/superwhisper" ]] +[[ $(readlink "$XDG_DATA_HOME/superwhisper/app/current") == "/opt/superwhisper" ]] +[[ ! -e $XDG_CONFIG_HOME/systemd/user/superwhisper.service ]] +compgen -G "$XDG_CONFIG_HOME/systemd/user/superwhisper.service.before-opr.*" >/dev/null +compgen -G "$HOME/.local/bin/superwhisper.before-opr.*" >/dev/null +[[ $(cat "$XDG_DATA_HOME/superwhisper/app/old/model") == "retained model" ]] +[[ $(cat "$XDG_CONFIG_HOME/superwhisper/preferences.json") == "retained preferences" ]] +grep -Fxq 'systemctl --user disable --now superwhisper-update.timer' "$SUPERWHISPER_TEST_LOG" +grep -Fxq 'systemctl --user restart superwhisper.service' "$SUPERWHISPER_TEST_LOG" +[[ $(readlink "$XDG_CONFIG_HOME/omarchy/plugins/superwhisper-panel") == "/opt/superwhisper/assets/omarchy-plugin/superwhisper-panel" ]] +[[ $(readlink "$HOME/.agents/skills/superwhisper") == "/opt/superwhisper/assets/agent-skill/superwhisper" ]] +bash "$ROOT/pkgbuilds/superwhisper-bin/setup-user" +(( $(grep -c '^omarchy-shell shell putBarWidget ' "$SUPERWHISPER_TEST_LOG") == 3 )) +[[ ! -e $XDG_CONFIG_HOME/superwhisper/omarchy-panel.pending ]] +printf '%s\n' 'PASS: Superwhisper setup migrates portable wrappers with backups, preserves data, and keeps the panel placement' + +# A fresh install supplies the user launcher and current link the vendor panel +# expects, without installing a private updater or touching Hyprland config. +export HOME="$test_tmp/fresh" XDG_CONFIG_HOME="$test_tmp/fresh-config" XDG_DATA_HOME="$test_tmp/fresh-data" +bash "$ROOT/pkgbuilds/superwhisper-bin/setup-user" +[[ $(readlink "$HOME/.local/bin/superwhisper") == "/usr/bin/superwhisper" ]] +[[ $(readlink "$XDG_DATA_HOME/superwhisper/app/current") == "/opt/superwhisper" ]] +[[ ! -e $XDG_CONFIG_HOME/hypr ]] +python3 - "$XDG_CONFIG_HOME/superwhisper/preferences.json" <<'PY_TEST' +import json +import os +import sys +with open(sys.argv[1]) as profile: + preferences = json.load(profile) +assert preferences["toggleRecordingShortcut"] == "Alt+Space" +assert preferences["pushToTalkShortcut"] == "" +assert preferences["cancelRecordingShortcut"] == "Escape" +assert os.stat(sys.argv[1]).st_mode & 0o777 == 0o600 +PY_TEST +printf '%s\n' 'PASS: fresh Superwhisper setup uses the package and needs no personal Hyprland config' + +# Missing shell cannot fail backend setup; placement retries next time. +export HOME="$test_tmp/deferred" XDG_CONFIG_HOME="$test_tmp/deferred-config" XDG_DATA_HOME="$test_tmp/deferred-data" +SUPERWHISPER_SHELL_ABSENT=1 bash "$ROOT/pkgbuilds/superwhisper-bin/setup-user" 2> "$test_tmp/panel-warning" +[[ -f $XDG_CONFIG_HOME/superwhisper/omarchy-panel.pending ]] +bash "$ROOT/pkgbuilds/superwhisper-bin/setup-user" +[[ ! -e $XDG_CONFIG_HOME/superwhisper/omarchy-panel.pending ]] +printf '%s\n' 'PASS: asynchronous panel discovery retries and unavailable shell does not abort setup' + + +# The packaged launcher must put the native include in a file Hyprland never +# loads, for the daemon as well as clients. Model the verified vendor contract. +cat > "$test_tmp/vendor-run" <<'SH' +#!/bin/bash +printf '%s\n' "$*" >> "$SUPERWHISPER_TEST_LOG" +printf '%s\n' '-- Generated native include' > "$SUPERWHISPER_HYPR_BINDINGS" +SH +chmod +x "$test_tmp/vendor-run" +sed "s|/opt/superwhisper/run.sh|$test_tmp/vendor-run|" "$ROOT/pkgbuilds/superwhisper-bin/superwhisper" > "$test_tmp/launcher" +mkdir -p "$XDG_CONFIG_HOME/hypr" +printf '%s\n' 'personal bindings' > "$XDG_CONFIG_HOME/hypr/bindings.lua" +for action in daemon 'shortcuts apply' 'shortcuts set hold none'; do + bash "$test_tmp/launcher" $action +done +[[ $(cat "$XDG_CONFIG_HOME/hypr/bindings.lua") == "personal bindings" ]] +[[ $(cat "$XDG_CONFIG_HOME/superwhisper/hypr-block.lua") == "-- Generated native include" ]] +if bash "$test_tmp/launcher" update apply; then + echo 'FAIL: the portable updater must not replace a packaged installation' >&2 + exit 1 +fi +printf '%s\n' 'PASS: packaged daemon and clients redirect native includes and refuse the private updater' + +# A daemon that never becomes ready must stop user setup with a useful error. +cat > "$test_tmp/bin/superwhisper" <<'SH' +#!/bin/bash +exit 1 +SH +cat > "$test_tmp/bin/sleep" <<'SH' +#!/bin/bash +exit 0 +SH +chmod +x "$test_tmp/bin/superwhisper" "$test_tmp/bin/sleep" +if bash "$ROOT/pkgbuilds/superwhisper-bin/setup-user" 2> "$test_tmp/startup-error"; then + echo 'FAIL: user setup must report a daemon that failed to start' >&2 + exit 1 +fi +grep -Fq 'Superwhisper did not become ready' "$test_tmp/startup-error" +printf '%s\n' 'PASS: user setup fails clearly when the daemon never becomes ready' + +# Exercise the actual packaging rewrite with vendor import variations. +python3 - "$ROOT/pkgbuilds/superwhisper-bin/PKGBUILD" "$test_tmp/palette" <<'PY_TEST' +from pathlib import Path +import subprocess +import sys + +recipe = Path(sys.argv[1]).read_text() +patch = recipe.split("<<'PYTHON'\n", 1)[1].split("\nPYTHON\n", 1)[0] +folder = Path(sys.argv[2]) +folder.mkdir() +qml = folder / "Panel.qml" +for imports in ("import qs.Commons", "import qs.Commons 1.0", "import qs.Commons\nimport qs.Commons as Commons"): + qml.write_text(imports + "\nItem { property color ink: Color.foreground }\n") + subprocess.run([sys.executable, "-", str(folder)], input=patch, text=True, check=True) + result = qml.read_text() + assert result.count("import qs.Commons as Commons") == 1 + assert "Commons.Color.foreground" in result +for imports in ("import QtQuick", "import qs.Commons\nimport qs.Commons as Commons\nimport qs.Commons as Commons"): + qml.write_text(imports + "\nItem { property color ink: Color.foreground }\n") + result = subprocess.run([sys.executable, "-", str(folder)], input=patch, text=True, capture_output=True) + assert result.returncode != 0, "invalid imports must fail packaging" +PY_TEST +printf '%s\n' 'PASS: palette rewrite handles existing/versioned imports and fails on missing or duplicate aliases'